From 884aa0743096b4bfe946679aa48ad3f5727575df Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 23 Jun 2023 13:04:19 -0500 Subject: [PATCH 001/875] Update version tag for development of next release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 719b13b23b..06d7063240 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dspace-angular", - "version": "7.6.0", + "version": "7.6.1-next", "scripts": { "ng": "ng", "config:watch": "nodemon", From ae6b183faec9cda0d2932143a52e14ef94bd945c Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 14 Jun 2023 18:51:42 +0200 Subject: [PATCH 002/875] 103236: fix issue where setStaleByHrefSubtring wouldn't emit after all requests were stale --- src/app/core/data/request.service.ts | 36 +++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 2d5acb2cb3..3b7ee80ffb 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -2,8 +2,8 @@ import { Injectable } from '@angular/core'; import { HttpHeaders } from '@angular/common/http'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; -import { filter, map, take, tap } from 'rxjs/operators'; +import { Observable, from as observableFrom } from 'rxjs'; +import { filter, find, map, mergeMap, switchMap, take, tap, toArray } from 'rxjs/operators'; import { cloneDeep } from 'lodash'; import { hasValue, isEmpty, isNotEmpty, hasNoValue } from '../../shared/empty.util'; import { ObjectCacheEntry } from '../cache/object-cache.reducer'; @@ -292,22 +292,42 @@ export class RequestService { * Set all requests that match (part of) the href to stale * * @param href A substring of the request(s) href - * @return Returns an observable emitting whether or not the cache is removed + * @return Returns an observable emitting when those requests are all stale */ setStaleByHrefSubstring(href: string): Observable { - this.store.pipe( + const requestUUIDs$ = this.store.pipe( select(uuidsFromHrefSubstringSelector(requestIndexSelector, href)), take(1) - ).subscribe((uuids: string[]) => { + ); + requestUUIDs$.subscribe((uuids: string[]) => { for (const uuid of uuids) { this.store.dispatch(new RequestStaleAction(uuid)); } }); this.requestsOnTheirWayToTheStore = this.requestsOnTheirWayToTheStore.filter((reqHref: string) => reqHref.indexOf(href) < 0); - return this.store.pipe( - select(uuidsFromHrefSubstringSelector(requestIndexSelector, href)), - map((uuids) => isEmpty(uuids)) + // emit true after all requests are stale + return requestUUIDs$.pipe( + switchMap((uuids: string[]) => { + if (isEmpty(uuids)) { + // if there were no matching requests, emit true immediately + return [true]; + } else { + // otherwise emit all request uuids in order + return observableFrom(uuids).pipe( + // retrieve the RequestEntry for each uuid + mergeMap((uuid: string) => this.getByUUID(uuid)), + // check whether it is undefined or stale + map((request: RequestEntry) => hasNoValue(request) || isStale(request.state)), + // if it is, complete + find((stale: boolean) => stale === true), + // after all observables above are completed, emit them as a single array + toArray(), + // when the array comes in, emit true + map(() => true) + ); + } + }) ); } From 02a20c8862ca4d93919ca07e900d70a722ebbb5d Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 30 Jun 2023 16:56:37 +0200 Subject: [PATCH 003/875] 103236: Added tests for setStaleByHrefSubstring --- src/app/core/data/request.service.spec.ts | 46 ++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/app/core/data/request.service.spec.ts b/src/app/core/data/request.service.spec.ts index fe35d840d7..4493c61a69 100644 --- a/src/app/core/data/request.service.spec.ts +++ b/src/app/core/data/request.service.spec.ts @@ -1,6 +1,6 @@ import { Store, StoreModule } from '@ngrx/store'; import { cold, getTestScheduler } from 'jasmine-marbles'; -import { EMPTY, of as observableOf } from 'rxjs'; +import { EMPTY, Observable, of as observableOf } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; import { getMockObjectCacheService } from '../../shared/mocks/object-cache.service.mock'; @@ -625,4 +625,48 @@ describe('RequestService', () => { expect(done$).toBeObservable(cold('-----(t|)', { t: true })); })); }); + + describe('setStaleByHrefSubstring', () => { + let dispatchSpy: jasmine.Spy; + let getByUUIDSpy: jasmine.Spy; + + beforeEach(() => { + dispatchSpy = spyOn(store, 'dispatch'); + getByUUIDSpy = spyOn(service, 'getByUUID').and.callThrough(); + }); + + describe('with an empty/no matching requests in the state', () => { + it('should return true', () => { + const done$: Observable = service.setStaleByHrefSubstring('https://rest.api/endpoint/selfLink'); + expect(done$).toBeObservable(cold('(a|)', { a: true })); + }); + }); + + describe('with a matching request in the state', () => { + beforeEach(() => { + const state = Object.assign({}, initialState, { + core: Object.assign({}, initialState.core, { + 'index': { + 'get-request/href-to-uuid': { + 'https://rest.api/endpoint/selfLink': '5f2a0d2a-effa-4d54-bd54-5663b960f9eb' + } + } + }) + }); + mockStore.setState(state); + }); + + it('should return an Observable that emits true as soon as the request is stale', () => { + dispatchSpy.and.callFake(() => { /* empty */ }); // don't actually set as stale + getByUUIDSpy.and.returnValue(cold('a-b--c--d-', { // but fake the state in the cache + a: { state: RequestEntryState.ResponsePending }, + b: { state: RequestEntryState.Success }, + c: { state: RequestEntryState.SuccessStale }, + d: { state: RequestEntryState.Error }, + })); + const done$: Observable = service.setStaleByHrefSubstring('https://rest.api/endpoint/selfLink'); + expect(done$).toBeObservable(cold('-----(a|)', { a: true })); + }); + }); + }); }); From b2b1782cd8505cf079782811bab4238e5cc0a359 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 30 Jun 2023 20:23:51 +0200 Subject: [PATCH 004/875] Hide entity field in collection form when entities aren't initialized --- .../collection-form/collection-form.component.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/collection-page/collection-form/collection-form.component.ts b/src/app/collection-page/collection-form/collection-form.component.ts index 23698de84e..b52d282f63 100644 --- a/src/app/collection-page/collection-form/collection-form.component.ts +++ b/src/app/collection-page/collection-form/collection-form.component.ts @@ -79,9 +79,8 @@ export class CollectionFormComponent extends ComColFormComponent imp // retrieve all entity types to populate the dropdowns selection entities$.subscribe((entityTypes: ItemType[]) => { - entityTypes - .filter((type: ItemType) => type.label !== NONE_ENTITY_TYPE) - .forEach((type: ItemType, index: number) => { + entityTypes = entityTypes.filter((type: ItemType) => type.label !== NONE_ENTITY_TYPE); + entityTypes.forEach((type: ItemType, index: number) => { this.entityTypeSelection.add({ disabled: false, label: type.label, @@ -93,7 +92,7 @@ export class CollectionFormComponent extends ComColFormComponent imp } }); - this.formModel = [...collectionFormModels, this.entityTypeSelection]; + this.formModel = entityTypes.length === 0 ? collectionFormModels : [...collectionFormModels, this.entityTypeSelection]; super.ngOnInit(); }); From cf777268666587d5980408d7bc3872260606ae71 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 30 Jun 2023 22:41:07 +0200 Subject: [PATCH 005/875] Fix enter not submitting collection form correctly Fixed it for communities, collections, ePersons & groups --- .../eperson-form/eperson-form.component.html | 10 +++++----- .../group-form/group-form.component.html | 4 ++-- .../comcol-form/comcol-form.component.html | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html index e9cc48aee3..156f2e776d 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html @@ -15,23 +15,23 @@ [displayCancel]="false" (submitForm)="onSubmit()">
-
-
- -
- diff --git a/src/app/access-control/group-registry/group-form/group-form.component.html b/src/app/access-control/group-registry/group-form/group-form.component.html index 0fc5a574b7..e50a479090 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.html +++ b/src/app/access-control/group-registry/group-form/group-form.component.html @@ -25,12 +25,12 @@ [displayCancel]="false" (submitForm)="onSubmit()">
-
diff --git a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.html b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.html index 5d7b092f74..b7b3d344b1 100644 --- a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.html +++ b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.html @@ -42,7 +42,7 @@ [formModel]="formModel" [displayCancel]="false" (submitForm)="onSubmit()"> - From 3a48ed390b832c1a0a953284f6449678b2e8d361 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 5 Jul 2023 18:03:25 +0300 Subject: [PATCH 006/875] src/app: fix path to deny-request-copy component The themed-deny-request-copy.component erroneously includes the cus- tom theme's deny-request-copy component instead of its own. Closes: https://github.com/DSpace/dspace-angular/issues/2351 --- .../deny-request-copy/themed-deny-request-copy.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/request-copy/deny-request-copy/themed-deny-request-copy.component.ts b/src/app/request-copy/deny-request-copy/themed-deny-request-copy.component.ts index 664e4c541b..1539d49622 100644 --- a/src/app/request-copy/deny-request-copy/themed-deny-request-copy.component.ts +++ b/src/app/request-copy/deny-request-copy/themed-deny-request-copy.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { ThemedComponent } from 'src/app/shared/theme-support/themed.component'; -import { DenyRequestCopyComponent } from 'src/themes/custom/app/request-copy/deny-request-copy/deny-request-copy.component'; +import { DenyRequestCopyComponent } from './deny-request-copy.component'; /** * Themed wrapper for deny-request-copy.component From 7bf4da55cf8cb7017ce5c1b096e14df3d69eeff8 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 7 Jul 2023 11:56:47 -0500 Subject: [PATCH 007/875] Enable Pull Request Opened action to assign PRs to their creator --- .../pull_request_opened.yml | 26 ------------------- .github/workflows/pull_request_opened.yml | 24 +++++++++++++++++ 2 files changed, 24 insertions(+), 26 deletions(-) delete mode 100644 .github/disabled-workflows/pull_request_opened.yml create mode 100644 .github/workflows/pull_request_opened.yml diff --git a/.github/disabled-workflows/pull_request_opened.yml b/.github/disabled-workflows/pull_request_opened.yml deleted file mode 100644 index 0dc718c0b9..0000000000 --- a/.github/disabled-workflows/pull_request_opened.yml +++ /dev/null @@ -1,26 +0,0 @@ -# This workflow runs whenever a new pull request is created -# TEMPORARILY DISABLED. Unfortunately this doesn't work for PRs created from forked repositories (which is how we tend to create PRs). -# There is no known workaround yet. See https://github.community/t/how-to-use-github-token-for-prs-from-forks/16818 -name: Pull Request opened - -# Only run for newly opened PRs against the "main" branch -on: - pull_request: - types: [opened] - branches: - - main - -jobs: - automation: - runs-on: ubuntu-latest - steps: - # Assign the PR to whomever created it. This is useful for visualizing assignments on project boards - # See https://github.com/marketplace/actions/pull-request-assigner - - name: Assign PR to creator - uses: thomaseizinger/assign-pr-creator-action@v1.0.0 - # Note, this authentication token is created automatically - # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - # Ignore errors. It is possible the PR was created by someone who cannot be assigned - continue-on-error: true diff --git a/.github/workflows/pull_request_opened.yml b/.github/workflows/pull_request_opened.yml new file mode 100644 index 0000000000..9b61af72d1 --- /dev/null +++ b/.github/workflows/pull_request_opened.yml @@ -0,0 +1,24 @@ +# This workflow runs whenever a new pull request is created +name: Pull Request opened + +# Only run for newly opened PRs against the "main" or maintenance branches +# We allow this to run for `pull_request_target` so that github secrets are available +# (This is required to assign a PR back to the creator when the PR comes from a forked repo) +on: + pull_request_target: + types: [ opened ] + branches: + - main + - 'dspace-**' + +permissions: + pull-requests: write + +jobs: + automation: + runs-on: ubuntu-latest + steps: + # Assign the PR to whomever created it. This is useful for visualizing assignments on project boards + # See https://github.com/toshimaru/auto-author-assign + - name: Assign PR to creator + uses: toshimaru/auto-author-assign@v1.6.2 From a484379f69af39a6b1b83aaa310f95b47671c1f9 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 7 Jul 2023 11:56:54 -0500 Subject: [PATCH 008/875] Ensure codescan and label_merge_conflicts run on maintenance branches --- .github/workflows/codescan.yml | 10 +++++++--- .github/workflows/label_merge_conflicts.yml | 7 ++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml index 35a2e2d24a..8b415296c7 100644 --- a/.github/workflows/codescan.yml +++ b/.github/workflows/codescan.yml @@ -5,12 +5,16 @@ # because CodeQL requires a fresh build with all tests *disabled*. name: "Code Scanning" -# Run this code scan for all pushes / PRs to main branch. Also run once a week. +# Run this code scan for all pushes / PRs to main or maintenance branches. Also run once a week. on: push: - branches: [ main ] + branches: + - main + - 'dspace-**' pull_request: - branches: [ main ] + branches: + - main + - 'dspace-**' # Don't run if PR is only updating static documentation paths-ignore: - '**/*.md' diff --git a/.github/workflows/label_merge_conflicts.yml b/.github/workflows/label_merge_conflicts.yml index c1396b6f45..7ea3327741 100644 --- a/.github/workflows/label_merge_conflicts.yml +++ b/.github/workflows/label_merge_conflicts.yml @@ -1,11 +1,12 @@ # This workflow checks open PRs for merge conflicts and labels them when conflicts are found name: Check for merge conflicts -# Run whenever the "main" branch is updated -# NOTE: This means merge conflicts are only checked for when a PR is merged to main. +# Run this for all pushes (i.e. merges) to 'main' or maintenance branches on: push: - branches: [ main ] + branches: + - main + - 'dspace-**' # So that the `conflict_label_name` is removed if conflicts are resolved, # we allow this to run for `pull_request_target` so that github secrets are available. pull_request_target: From 1809f0585c833631f5f36d0e45c47fc9e01000cd Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 7 Jul 2023 12:05:56 -0500 Subject: [PATCH 009/875] Split docker images into separate jobs to run in parallel. Ensure 'main' codebase is tagged as 'latest' --- .github/workflows/docker.yml | 84 ++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9a2c838d83..0c36d5af98 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -15,29 +15,35 @@ on: permissions: contents: read # to fetch code (actions/checkout) + +env: + # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) + # For a new commit on default branch (main), use the literal tag 'latest' on Docker image. + # For a new commit on other branches, use the branch name as the tag for Docker image. + # For a new tag, copy that tag name as the tag for Docker image. + IMAGE_TAGS: | + type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} + type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }} + type=ref,event=tag + # Define default tag "flavor" for docker/metadata-action per + # https://github.com/docker/metadata-action#flavor-input + # We manage the 'latest' tag ourselves to the 'main' branch (see settings above) + TAGS_FLAVOR: | + latest=false + # Architectures / Platforms for which we will build Docker images + # If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work. + # If this is NOT a PR (e.g. a tag or merge commit), also build for ARM64. + PLATFORMS: linux/amd64${{ github.event_name != 'pull_request' && ', linux/arm64' || '' }} + + jobs: - docker: + ############################################### + # Build/Push the 'dspace/dspace-angular' image + ############################################### + dspace-angular: # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular' if: github.repository == 'dspace/dspace-angular' runs-on: ubuntu-latest - env: - # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) - # For a new commit on default branch (main), use the literal tag 'dspace-7_x' on Docker image. - # For a new commit on other branches, use the branch name as the tag for Docker image. - # For a new tag, copy that tag name as the tag for Docker image. - IMAGE_TAGS: | - type=raw,value=dspace-7_x,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }} - type=ref,event=tag - # Define default tag "flavor" for docker/metadata-action per - # https://github.com/docker/metadata-action#flavor-input - # We turn off 'latest' tag by default. - TAGS_FLAVOR: | - latest=false - # Architectures / Platforms for which we will build Docker images - # If this is a PR, we ONLY build for AMD64. For PRs we only do a sanity check test to ensure Docker builds work. - # If this is NOT a PR (e.g. a tag or merge commit), also build for ARM64. - PLATFORMS: linux/amd64${{ github.event_name != 'pull_request' && ', linux/arm64' || '' }} steps: # https://github.com/actions/checkout @@ -61,9 +67,6 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - ############################################### - # Build/Push the 'dspace/dspace-angular' image - ############################################### # https://github.com/docker/metadata-action # Get Metadata for docker_build step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-angular' image @@ -77,7 +80,7 @@ jobs: # https://github.com/docker/build-push-action - name: Build and push 'dspace-angular' image id: docker_build - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile @@ -89,9 +92,36 @@ jobs: tags: ${{ steps.meta_build.outputs.tags }} labels: ${{ steps.meta_build.outputs.labels }} - ##################################################### - # Build/Push the 'dspace/dspace-angular' image ('-dist' tag) - ##################################################### + ############################################################# + # Build/Push the 'dspace/dspace-angular' image ('-dist' tag) + ############################################################# + dspace-angular-dist: + # Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular' + if: github.repository == 'dspace/dspace-angular' + runs-on: ubuntu-latest + + steps: + # https://github.com/actions/checkout + - name: Checkout codebase + uses: actions/checkout@v3 + + # https://github.com/docker/setup-buildx-action + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU emulation to build for multiple architectures + uses: docker/setup-qemu-action@v2 + + # https://github.com/docker/login-action + - name: Login to DockerHub + # Only login if not a PR, as PRs only trigger a Docker build and not a push + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + # https://github.com/docker/metadata-action # Get Metadata for docker_build_dist step below - name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-angular-dist' image @@ -107,7 +137,7 @@ jobs: - name: Build and push 'dspace-angular-dist' image id: docker_build_dist - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile.dist From 648925f3e1c0a51fc3eb61b7257e7a44ea57fee6 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 19 Jul 2023 14:01:41 +0200 Subject: [PATCH 010/875] 104312: DsDynamicLookupRelationExternalSourceTabComponent should have the form value already filled in the search input --- .../dynamic-lookup-relation-modal.component.html | 1 + ...ookup-relation-external-source-tab.component.ts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html index f95cd98c65..4c635b931f 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html @@ -42,6 +42,7 @@ [collection]="collection" [relationship]="relationshipOptions" [context]="context" + [query]="query" [externalSource]="source" (importedObject)="imported($event)" class="d-block pt-3"> diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts index ca2535cb91..ef646c28eb 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts @@ -74,6 +74,12 @@ export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit * The context to displaying lists for */ @Input() context: Context; + + /** + * The search query + */ + @Input() query: string; + @Input() repeatable: boolean; /** * Emit an event when an object has been imported (or selected from similar local entries) @@ -147,8 +153,12 @@ export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit this.resetRoute(); this.entriesRD$ = this.searchConfigService.paginatedSearchOptions.pipe( - switchMap((searchOptions: PaginatedSearchOptions) => - this.externalSourceService.getExternalSourceEntries(this.externalSource.id, searchOptions).pipe(startWith(undefined))) + switchMap((searchOptions: PaginatedSearchOptions) => { + if (searchOptions.query === '') { + searchOptions.query = this.query; + } + return this.externalSourceService.getExternalSourceEntries(this.externalSource.id, searchOptions).pipe(startWith(undefined)); + }) ); this.currentPagination$ = this.paginationService.getCurrentPagination(this.searchConfigService.paginationID, this.initialPagination); this.importConfig = { From 2a35180a1b2c81ca84e3fe7d9c4a84567f567a6a Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 21 Jul 2023 15:03:46 +0200 Subject: [PATCH 011/875] Created separate pages for edit & create EPersons --- .../access-control-routing-paths.ts | 10 ++ .../access-control-routing.module.ts | 25 ++- .../epeople-registry.component.html | 147 ++++++++-------- .../epeople-registry.component.spec.ts | 30 ---- .../epeople-registry.component.ts | 64 +------ .../eperson-form/eperson-form.component.html | 161 +++++++++--------- .../eperson-form.component.spec.ts | 89 +++++----- .../eperson-form/eperson-form.component.ts | 23 +-- .../eperson-resolver.service.ts | 53 ++++++ src/assets/i18n/en.json5 | 8 + 10 files changed, 310 insertions(+), 300 deletions(-) create mode 100644 src/app/access-control/epeople-registry/eperson-resolver.service.ts diff --git a/src/app/access-control/access-control-routing-paths.ts b/src/app/access-control/access-control-routing-paths.ts index 259aa311e7..c3c42b3155 100644 --- a/src/app/access-control/access-control-routing-paths.ts +++ b/src/app/access-control/access-control-routing-paths.ts @@ -1,6 +1,16 @@ import { URLCombiner } from '../core/url-combiner/url-combiner'; import { getAccessControlModuleRoute } from '../app-routing-paths'; +export const EPERSON_PATH = 'epeople'; + +export function getEPersonsRoute(): string { + return new URLCombiner(getAccessControlModuleRoute(), EPERSON_PATH).toString(); +} + +export function getEPersonEditRoute(id: string): string { + return new URLCombiner(getEPersonsRoute(), id).toString(); +} + export const GROUP_EDIT_PATH = 'groups'; export function getGroupsRoute() { diff --git a/src/app/access-control/access-control-routing.module.ts b/src/app/access-control/access-control-routing.module.ts index 6f6de6cb26..a4082d19e2 100644 --- a/src/app/access-control/access-control-routing.module.ts +++ b/src/app/access-control/access-control-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router'; import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component'; import { GroupFormComponent } from './group-registry/group-form/group-form.component'; import { GroupsRegistryComponent } from './group-registry/groups-registry.component'; -import { GROUP_EDIT_PATH } from './access-control-routing-paths'; +import { EPERSON_PATH, GROUP_EDIT_PATH } from './access-control-routing-paths'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; import { GroupPageGuard } from './group-registry/group-page.guard'; import { @@ -13,12 +13,14 @@ import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; import { BulkAccessComponent } from './bulk-access/bulk-access.component'; +import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-form.component'; +import { EPersonResolver } from './epeople-registry/eperson-resolver.service'; @NgModule({ imports: [ RouterModule.forChild([ { - path: 'epeople', + path: EPERSON_PATH, component: EPeopleRegistryComponent, resolve: { breadcrumb: I18nBreadcrumbResolver @@ -26,6 +28,25 @@ import { BulkAccessComponent } from './bulk-access/bulk-access.component'; data: { title: 'admin.access-control.epeople.title', breadcrumbKey: 'admin.access-control.epeople' }, canActivate: [SiteAdministratorGuard] }, + { + path: `${EPERSON_PATH}/create`, + component: EPersonFormComponent, + resolve: { + breadcrumb: I18nBreadcrumbResolver, + }, + data: { title: 'admin.access-control.epeople.add.title', breadcrumbKey: 'admin.access-control.epeople.add' }, + canActivate: [SiteAdministratorGuard], + }, + { + path: `${EPERSON_PATH}/:id`, + component: EPersonFormComponent, + resolve: { + breadcrumb: I18nBreadcrumbResolver, + ePerson: EPersonResolver, + }, + data: { title: 'admin.access-control.epeople.edit.title', breadcrumbKey: 'admin.access-control.epeople.edit' }, + canActivate: [SiteAdministratorGuard], + }, { path: GROUP_EDIT_PATH, component: GroupsRegistryComponent, diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.html b/src/app/access-control/epeople-registry/epeople-registry.component.html index e3a8e2c590..f3ddd63ae8 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.html +++ b/src/app/access-control/epeople-registry/epeople-registry.component.html @@ -4,96 +4,91 @@
-
+
- + + +
+ +
+
+
+ + -
-
- -
- - - - - -
- - - - - - - - - - - - - - - - - -
{{labelPrefix + 'table.id' | translate}}{{labelPrefix + 'table.name' | translate}}{{labelPrefix + 'table.email' | translate}}{{labelPrefix + 'table.edit' | translate}}
{{epersonDto.eperson.id}}{{ dsoNameService.getName(epersonDto.eperson) }}{{epersonDto.eperson.email}} -
- - -
-
-
- -
- - +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + +
{{labelPrefix + 'table.id' | translate}}{{labelPrefix + 'table.name' | translate}}{{labelPrefix + 'table.email' | translate}}{{labelPrefix + 'table.edit' | translate}}
{{epersonDto.eperson.id}}{{ dsoNameService.getName(epersonDto.eperson) }}{{epersonDto.eperson.email}} +
+ + +
+
+
+ +
+ +
diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts index 4a09913862..e2cee5e935 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts +++ b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts @@ -203,36 +203,6 @@ describe('EPeopleRegistryComponent', () => { }); }); - describe('toggleEditEPerson', () => { - describe('when you click on first edit eperson button', () => { - beforeEach(fakeAsync(() => { - const editButtons = fixture.debugElement.queryAll(By.css('.access-control-editEPersonButton')); - editButtons[0].triggerEventHandler('click', { - preventDefault: () => {/**/ - } - }); - tick(); - fixture.detectChanges(); - })); - - it('editEPerson form is toggled', () => { - const ePeopleIds = fixture.debugElement.queryAll(By.css('#epeople tr td:first-child')); - ePersonDataServiceStub.getActiveEPerson().subscribe((activeEPerson: EPerson) => { - if (ePeopleIds[0] && activeEPerson === ePeopleIds[0].nativeElement.textContent) { - expect(component.isEPersonFormShown).toEqual(false); - } else { - expect(component.isEPersonFormShown).toEqual(true); - } - - }); - }); - - it('EPerson search section is hidden', () => { - expect(fixture.debugElement.query(By.css('#search'))).toBeNull(); - }); - }); - }); - describe('deleteEPerson', () => { describe('when you click on first delete eperson button', () => { let ePeopleIdsFoundBeforeDelete; diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.ts b/src/app/access-control/epeople-registry/epeople-registry.component.ts index fb045ebb88..221fa9fd71 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.ts +++ b/src/app/access-control/epeople-registry/epeople-registry.component.ts @@ -22,6 +22,7 @@ import { PageInfo } from '../../core/shared/page-info.model'; import { NoContent } from '../../core/shared/NoContent.model'; import { PaginationService } from '../../core/pagination/pagination.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { getEPersonEditRoute, getEPersonsRoute } from '../access-control-routing-paths'; @Component({ selector: 'ds-epeople-registry', @@ -64,11 +65,6 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { currentPage: 1 }); - /** - * Whether or not to show the EPerson form - */ - isEPersonFormShown: boolean; - // The search form searchForm; @@ -114,17 +110,11 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { */ initialisePage() { this.searching$.next(true); - this.isEPersonFormShown = false; this.search({scope: this.currentSearchScope, query: this.currentSearchQuery}); - this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => { - if (eperson != null && eperson.id) { - this.isEPersonFormShown = true; - } - })); this.subs.push(this.ePeople$.pipe( switchMap((epeople: PaginatedList) => { if (epeople.pageInfo.totalElements > 0) { - return combineLatest([...epeople.page.map((eperson: EPerson) => { + return combineLatest(epeople.page.map((eperson: EPerson) => { return this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined).pipe( map((authorized) => { const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel(); @@ -133,7 +123,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { return epersonDtoModel; }) ); - })]).pipe(map((dtos: EpersonDtoModel[]) => { + })).pipe(map((dtos: EpersonDtoModel[]) => { return buildPaginatedList(epeople.pageInfo, dtos); })); } else { @@ -160,14 +150,14 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { const query: string = data.query; const scope: string = data.scope; if (query != null && this.currentSearchQuery !== query) { - this.router.navigate([this.epersonService.getEPeoplePageRouterLink()], { + void this.router.navigate([getEPersonsRoute()], { queryParamsHandling: 'merge' }); this.currentSearchQuery = query; this.paginationService.resetPage(this.config.id); } if (scope != null && this.currentSearchScope !== scope) { - this.router.navigate([this.epersonService.getEPeoplePageRouterLink()], { + void this.router.navigate([getEPersonsRoute()], { queryParamsHandling: 'merge' }); this.currentSearchScope = scope; @@ -205,23 +195,6 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { return this.epersonService.getActiveEPerson(); } - /** - * Start editing the selected EPerson - * @param ePerson - */ - toggleEditEPerson(ePerson: EPerson) { - this.getActiveEPerson().pipe(take(1)).subscribe((activeEPerson: EPerson) => { - if (ePerson === activeEPerson) { - this.epersonService.cancelEditEPerson(); - this.isEPersonFormShown = false; - } else { - this.epersonService.editEPerson(ePerson); - this.isEPersonFormShown = true; - } - }); - this.scrollToTop(); - } - /** * Deletes EPerson, show notification on success/failure & updates EPeople list */ @@ -264,16 +237,6 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } - scrollToTop() { - (function smoothscroll() { - const currentScroll = document.documentElement.scrollTop || document.body.scrollTop; - if (currentScroll > 0) { - window.requestAnimationFrame(smoothscroll); - window.scrollTo(0, currentScroll - (currentScroll / 8)); - } - })(); - } - /** * Reset all input-fields to be empty and search all search */ @@ -284,20 +247,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { this.search({query: ''}); } - /** - * This method will set everything to stale, which will cause the lists on this page to update. - */ - reset(): void { - this.epersonService.getBrowseEndpoint().pipe( - take(1), - switchMap((href: string) => { - return this.requestService.setStaleByHrefSubstring(href).pipe( - take(1), - ); - }) - ).subscribe(()=>{ - this.epersonService.cancelEditEPerson(); - this.isEPersonFormShown = false; - }); + getEditEPeoplePage(id: string): string { + return getEPersonEditRoute(id); } } diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html index 228449a8a5..493209f0a2 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html @@ -1,89 +1,98 @@ -
+
+
+
- -

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

-
+
- -

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

-
+ +

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

+
- -
- -
-
- -
-
- - -
- -
+ +

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

+
- + +
+ +
+
+ +
+
+ + +
+ +
-
-
{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}
+ - +
+
{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}
- + -
- - - - - - - - - - - - - - - -
{{messagePrefix + '.table.id' | translate}}{{messagePrefix + '.table.name' | translate}}{{messagePrefix + '.table.collectionOrCommunity' | translate}}
{{group.id}} - - {{ dsoNameService.getName(group) }} - - {{ dsoNameService.getName(undefined) }}
-
+ - +
+ + + + + + + + + + + + + + + +
{{messagePrefix + '.table.id' | translate}}{{messagePrefix + '.table.name' | translate}}{{messagePrefix + '.table.collectionOrCommunity' | translate}}
{{group.id}} + + {{ dsoNameService.getName(group) }} + + {{ dsoNameService.getName(undefined) }}
+
-
diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index fb911e709c..1402f79ae3 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -31,6 +31,10 @@ import { PaginationServiceStub } from '../../../shared/testing/pagination-servic import { FindListOptions } from '../../../core/data/find-list-options.model'; import { ValidateEmailNotTaken } from './validators/email-taken.validator'; import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; +import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RouterStub } from '../../../shared/testing/router.stub'; +import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub'; describe('EPersonFormComponent', () => { let component: EPersonFormComponent; @@ -43,6 +47,8 @@ describe('EPersonFormComponent', () => { let authorizationService: AuthorizationDataService; let groupsDataService: GroupDataService; let epersonRegistrationService: EpersonRegistrationService; + let route: ActivatedRouteStub; + let router: RouterStub; let paginationService; @@ -106,6 +112,9 @@ describe('EPersonFormComponent', () => { }, getEPersonByEmail(email): Observable> { return createSuccessfulRemoteDataObject$(null); + }, + findById(_id: string, _useCachedVersionIfAvailable = true, _reRequestOnStale = true, ..._linksToFollow: FollowLinkConfig[]): Observable> { + return createSuccessfulRemoteDataObject$(null); } }; builderService = Object.assign(getMockFormBuilderService(),{ @@ -182,6 +191,8 @@ describe('EPersonFormComponent', () => { }); paginationService = new PaginationServiceStub(); + route = new ActivatedRouteStub(); + router = new RouterStub(); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, TranslateModule.forRoot({ @@ -202,6 +213,8 @@ describe('EPersonFormComponent', () => { { provide: PaginationService, useValue: paginationService }, { provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring'])}, { provide: EpersonRegistrationService, useValue: epersonRegistrationService }, + { provide: ActivatedRoute, useValue: route }, + { provide: Router, useValue: router }, EPeopleRegistryComponent ], schemas: [NO_ERRORS_SCHEMA] @@ -263,24 +276,18 @@ describe('EPersonFormComponent', () => { fixture.detectChanges(); }); describe('firstName, lastName and email should be required', () => { - it('form should be invalid because the firstName is required', waitForAsync(() => { - fixture.whenStable().then(() => { - expect(component.formGroup.controls.firstName.valid).toBeFalse(); - expect(component.formGroup.controls.firstName.errors.required).toBeTrue(); - }); - })); - it('form should be invalid because the lastName is required', waitForAsync(() => { - fixture.whenStable().then(() => { - expect(component.formGroup.controls.lastName.valid).toBeFalse(); - expect(component.formGroup.controls.lastName.errors.required).toBeTrue(); - }); - })); - it('form should be invalid because the email is required', waitForAsync(() => { - fixture.whenStable().then(() => { - expect(component.formGroup.controls.email.valid).toBeFalse(); - expect(component.formGroup.controls.email.errors.required).toBeTrue(); - }); - })); + it('form should be invalid because the firstName is required', () => { + expect(component.formGroup.controls.firstName.valid).toBeFalse(); + expect(component.formGroup.controls.firstName.errors.required).toBeTrue(); + }); + it('form should be invalid because the lastName is required', () => { + expect(component.formGroup.controls.lastName.valid).toBeFalse(); + expect(component.formGroup.controls.lastName.errors.required).toBeTrue(); + }); + it('form should be invalid because the email is required', () => { + expect(component.formGroup.controls.email.valid).toBeFalse(); + expect(component.formGroup.controls.email.errors.required).toBeTrue(); + }); }); describe('after inserting information firstName,lastName and email not required', () => { @@ -290,24 +297,18 @@ describe('EPersonFormComponent', () => { component.formGroup.controls.email.setValue('test@test.com'); fixture.detectChanges(); }); - it('firstName should be valid because the firstName is set', waitForAsync(() => { - fixture.whenStable().then(() => { + it('firstName should be valid because the firstName is set', () => { expect(component.formGroup.controls.firstName.valid).toBeTrue(); expect(component.formGroup.controls.firstName.errors).toBeNull(); - }); - })); - it('lastName should be valid because the lastName is set', waitForAsync(() => { - fixture.whenStable().then(() => { + }); + it('lastName should be valid because the lastName is set', () => { expect(component.formGroup.controls.lastName.valid).toBeTrue(); expect(component.formGroup.controls.lastName.errors).toBeNull(); - }); - })); - it('email should be valid because the email is set', waitForAsync(() => { - fixture.whenStable().then(() => { + }); + it('email should be valid because the email is set', () => { expect(component.formGroup.controls.email.valid).toBeTrue(); expect(component.formGroup.controls.email.errors).toBeNull(); - }); - })); + }); }); @@ -316,12 +317,10 @@ describe('EPersonFormComponent', () => { component.formGroup.controls.email.setValue('test@test'); fixture.detectChanges(); }); - it('email should not be valid because the email pattern', waitForAsync(() => { - fixture.whenStable().then(() => { + it('email should not be valid because the email pattern', () => { expect(component.formGroup.controls.email.valid).toBeFalse(); expect(component.formGroup.controls.email.errors.pattern).toBeTruthy(); - }); - })); + }); }); describe('after already utilized email', () => { @@ -336,12 +335,10 @@ describe('EPersonFormComponent', () => { fixture.detectChanges(); }); - it('email should not be valid because email is already taken', waitForAsync(() => { - fixture.whenStable().then(() => { + it('email should not be valid because email is already taken', () => { expect(component.formGroup.controls.email.valid).toBeFalse(); expect(component.formGroup.controls.email.errors.emailTaken).toBeTruthy(); - }); - })); + }); }); @@ -393,11 +390,9 @@ describe('EPersonFormComponent', () => { fixture.detectChanges(); }); - it('should emit a new eperson using the correct values', waitForAsync(() => { - fixture.whenStable().then(() => { - expect(component.submitForm.emit).toHaveBeenCalledWith(expected); - }); - })); + it('should emit a new eperson using the correct values', () => { + expect(component.submitForm.emit).toHaveBeenCalledWith(expected); + }); }); describe('with an active eperson', () => { @@ -428,11 +423,9 @@ describe('EPersonFormComponent', () => { fixture.detectChanges(); }); - it('should emit the existing eperson using the correct values', waitForAsync(() => { - fixture.whenStable().then(() => { - expect(component.submitForm.emit).toHaveBeenCalledWith(expectedWithId); - }); - })); + it('should emit the existing eperson using the correct values', () => { + expect(component.submitForm.emit).toHaveBeenCalledWith(expectedWithId); + }); }); }); diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts index d009d56058..d7d5a0b49c 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -38,6 +38,8 @@ import { Registration } from '../../../core/shared/registration.model'; import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; import { TYPE_REQUEST_FORGOT } from '../../../register-email-form/register-email-form.component'; import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { getEPersonsRoute } from '../../access-control-routing-paths'; @Component({ selector: 'ds-eperson-form', @@ -194,6 +196,8 @@ export class EPersonFormComponent implements OnInit, OnDestroy { public requestService: RequestService, private epersonRegistrationService: EpersonRegistrationService, public dsoNameService: DSONameService, + protected route: ActivatedRoute, + protected router: Router, ) { this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => { this.epersonInitial = eperson; @@ -213,7 +217,9 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * This method will initialise the page */ initialisePage() { - + this.subs.push(this.epersonService.findById(this.route.snapshot.params.id).subscribe((ePersonRD: RemoteData) => { + this.epersonService.editEPerson(ePersonRD.payload); + })); observableCombineLatest([ this.translateService.get(`${this.messagePrefix}.firstName`), this.translateService.get(`${this.messagePrefix}.lastName`), @@ -339,6 +345,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { onCancel() { this.epersonService.cancelEditEPerson(); this.cancelForm.emit(); + void this.router.navigate([getEPersonsRoute()]); } /** @@ -390,6 +397,8 @@ export class EPersonFormComponent implements OnInit, OnDestroy { if (rd.hasSucceeded) { this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', { name: this.dsoNameService.getName(ePersonToCreate) })); this.submitForm.emit(ePersonToCreate); + this.epersonService.clearEPersonRequests(); + void this.router.navigateByUrl(getEPersonsRoute()); } else { this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.created.failure', { name: this.dsoNameService.getName(ePersonToCreate) })); this.cancelForm.emit(); @@ -429,6 +438,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { if (rd.hasSucceeded) { this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', { name: this.dsoNameService.getName(editedEperson) })); this.submitForm.emit(editedEperson); + void this.router.navigateByUrl(getEPersonsRoute()); } else { this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.edited.failure', { name: this.dsoNameService.getName(editedEperson) })); this.cancelForm.emit(); @@ -495,6 +505,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { ).subscribe(({ restResponse, eperson }: { restResponse: RemoteData | null, eperson: EPerson }) => { if (restResponse?.hasSucceeded) { this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: this.dsoNameService.getName(eperson) })); + void this.router.navigate([getEPersonsRoute()]); } else { this.notificationsService.error(`Error occurred when trying to delete EPerson with id: ${eperson?.id} with code: ${restResponse?.statusCode} and message: ${restResponse?.errorMessage}`); } @@ -541,16 +552,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy { } } - /** - * This method will ensure that the page gets reset and that the cache is cleared - */ - reset() { - this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => { - this.requestService.removeByHrefSubstring(eperson.self); - }); - this.initialisePage(); - } - /** * Checks for the given ePerson if there is already an ePerson in the system with that email * and shows notification if this is the case diff --git a/src/app/access-control/epeople-registry/eperson-resolver.service.ts b/src/app/access-control/epeople-registry/eperson-resolver.service.ts new file mode 100644 index 0000000000..1db8e70d89 --- /dev/null +++ b/src/app/access-control/epeople-registry/eperson-resolver.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; +import { EPerson } from '../../core/eperson/models/eperson.model'; +import { RemoteData } from '../../core/data/remote-data'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { ResolvedAction } from '../../core/resolving/resolver.actions'; +import { EPersonDataService } from '../../core/eperson/eperson-data.service'; +import { Store } from '@ngrx/store'; +import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; + +export const EPERSON_EDIT_FOLLOW_LINKS: FollowLinkConfig[] = [ + followLink('groups'), +]; + +/** + * This class represents a resolver that requests a specific {@link EPerson} before the route is activated + */ +@Injectable({ + providedIn: 'root', +}) +export class EPersonResolver implements Resolve> { + + constructor( + protected ePersonService: EPersonDataService, + protected store: Store, + ) { + } + + /** + * Method for resolving a {@link EPerson} based on the parameters in the current route + * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot + * @param {RouterStateSnapshot} state The current RouterStateSnapshot + * @returns `Observable<>` Emits the found {@link EPerson} based on the parameters in the current + * route, or an error if something went wrong + */ + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { + const ePersonRD$: Observable> = this.ePersonService.findById(route.params.id, + true, + false, + ...EPERSON_EDIT_FOLLOW_LINKS, + ).pipe( + getFirstCompletedRemoteData(), + ); + + ePersonRD$.subscribe((ePersonRD: RemoteData) => { + this.store.dispatch(new ResolvedAction(state.url, ePersonRD.payload)); + }); + + return ePersonRD$; + } + +} diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 6c91bae4c1..2a55229616 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -248,6 +248,14 @@ "admin.access-control.epeople.title": "EPeople", + "admin.access-control.epeople.edit.breadcrumbs": "New EPerson", + + "admin.access-control.epeople.edit.title": "New EPerson", + + "admin.access-control.epeople.add.breadcrumbs": "Add EPerson", + + "admin.access-control.epeople.add.title": "Add EPerson", + "admin.access-control.epeople.head": "EPeople", "admin.access-control.epeople.search.head": "Search", From 9ac19d40fc26275d53a50deed3a8151e07955113 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 21 Jul 2023 15:11:01 +0200 Subject: [PATCH 012/875] Cleanup access-control components - Use the same methods to retrieve the access-control urls - Fix EPersonDataService.startEditingNewEPerson returning the incorrect link --- .../access-control-routing-paths.ts | 6 ++--- .../access-control-routing.module.ts | 8 +++---- .../epeople-registry.component.ts | 2 +- .../group-form/group-form.component.html | 4 ++-- .../group-form/group-form.component.ts | 24 +++++++++---------- .../groups-registry.component.html | 2 +- src/app/core/eperson/eperson-data.service.ts | 6 ++--- src/app/core/eperson/group-data.service.ts | 7 +++--- .../entry/resource-policy-entry.component.ts | 5 ++-- 9 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/app/access-control/access-control-routing-paths.ts b/src/app/access-control/access-control-routing-paths.ts index c3c42b3155..c8b9e01793 100644 --- a/src/app/access-control/access-control-routing-paths.ts +++ b/src/app/access-control/access-control-routing-paths.ts @@ -11,12 +11,12 @@ export function getEPersonEditRoute(id: string): string { return new URLCombiner(getEPersonsRoute(), id).toString(); } -export const GROUP_EDIT_PATH = 'groups'; +export const GROUP_PATH = 'groups'; export function getGroupsRoute() { - return new URLCombiner(getAccessControlModuleRoute(), GROUP_EDIT_PATH).toString(); + return new URLCombiner(getAccessControlModuleRoute(), GROUP_PATH).toString(); } export function getGroupEditRoute(id: string) { - return new URLCombiner(getAccessControlModuleRoute(), GROUP_EDIT_PATH, id).toString(); + return new URLCombiner(getGroupsRoute(), id).toString(); } diff --git a/src/app/access-control/access-control-routing.module.ts b/src/app/access-control/access-control-routing.module.ts index a4082d19e2..4ef97cb5ea 100644 --- a/src/app/access-control/access-control-routing.module.ts +++ b/src/app/access-control/access-control-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router'; import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component'; import { GroupFormComponent } from './group-registry/group-form/group-form.component'; import { GroupsRegistryComponent } from './group-registry/groups-registry.component'; -import { EPERSON_PATH, GROUP_EDIT_PATH } from './access-control-routing-paths'; +import { EPERSON_PATH, GROUP_PATH } from './access-control-routing-paths'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; import { GroupPageGuard } from './group-registry/group-page.guard'; import { @@ -48,7 +48,7 @@ import { EPersonResolver } from './epeople-registry/eperson-resolver.service'; canActivate: [SiteAdministratorGuard], }, { - path: GROUP_EDIT_PATH, + path: GROUP_PATH, component: GroupsRegistryComponent, resolve: { breadcrumb: I18nBreadcrumbResolver @@ -57,7 +57,7 @@ import { EPersonResolver } from './epeople-registry/eperson-resolver.service'; canActivate: [GroupAdministratorGuard] }, { - path: `${GROUP_EDIT_PATH}/newGroup`, + path: `${GROUP_PATH}/create`, component: GroupFormComponent, resolve: { breadcrumb: I18nBreadcrumbResolver @@ -66,7 +66,7 @@ import { EPersonResolver } from './epeople-registry/eperson-resolver.service'; canActivate: [GroupAdministratorGuard] }, { - path: `${GROUP_EDIT_PATH}/:groupId`, + path: `${GROUP_PATH}/:groupId`, component: GroupFormComponent, resolve: { breadcrumb: I18nBreadcrumbResolver diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.ts b/src/app/access-control/epeople-registry/epeople-registry.component.ts index 221fa9fd71..d2837a5317 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.ts +++ b/src/app/access-control/epeople-registry/epeople-registry.component.ts @@ -215,7 +215,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { if (restResponse.hasSucceeded) { this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', {name: this.dsoNameService.getName(ePerson)})); } else { - this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + ePerson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage); + this.notificationsService.error(`Error occurred when trying to delete EPerson with id: ${ePerson.id} with code: ${restResponse.statusCode} and message: ${restResponse.errorMessage}`); } }); } diff --git a/src/app/access-control/group-registry/group-form/group-form.component.html b/src/app/access-control/group-registry/group-form/group-form.component.html index 77a81a8daa..0515b071ae 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.html +++ b/src/app/access-control/group-registry/group-form/group-form.component.html @@ -2,13 +2,13 @@
-
+

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

- +

{{messagePrefix + 'head' | translate}}

diff --git a/src/app/core/eperson/eperson-data.service.ts b/src/app/core/eperson/eperson-data.service.ts index d30030365c..00620655de 100644 --- a/src/app/core/eperson/eperson-data.service.ts +++ b/src/app/core/eperson/eperson-data.service.ts @@ -34,6 +34,7 @@ import { PatchData, PatchDataImpl } from '../data/base/patch-data'; import { DeleteData, DeleteDataImpl } from '../data/base/delete-data'; import { RestRequestMethod } from '../data/rest-request-method'; import { dataService } from '../data/base/data-service.decorator'; +import { getEPersonEditRoute, getEPersonsRoute } from '../../access-control/access-control-routing-paths'; const ePeopleRegistryStateSelector = (state: AppState) => state.epeopleRegistry; const editEPersonSelector = createSelector(ePeopleRegistryStateSelector, (ePeopleRegistryState: EPeopleRegistryState) => ePeopleRegistryState.editEPerson); @@ -281,15 +282,14 @@ export class EPersonDataService extends IdentifiableDataService impleme this.editEPerson(ePerson); } }); - return '/access-control/epeople'; + return getEPersonEditRoute(ePerson.id); } /** * Get EPeople admin page - * @param ePerson New EPerson to edit */ public getEPeoplePageRouterLink(): string { - return '/access-control/epeople'; + return getEPersonsRoute(); } /** diff --git a/src/app/core/eperson/group-data.service.ts b/src/app/core/eperson/group-data.service.ts index bb38e46758..7b3a14c70b 100644 --- a/src/app/core/eperson/group-data.service.ts +++ b/src/app/core/eperson/group-data.service.ts @@ -40,6 +40,7 @@ import { DeleteData, DeleteDataImpl } from '../data/base/delete-data'; import { Operation } from 'fast-json-patch'; import { RestRequestMethod } from '../data/rest-request-method'; import { dataService } from '../data/base/data-service.decorator'; +import { getGroupEditRoute } from '../../access-control/access-control-routing-paths'; const groupRegistryStateSelector = (state: AppState) => state.groupRegistry; const editGroupSelector = createSelector(groupRegistryStateSelector, (groupRegistryState: GroupRegistryState) => groupRegistryState.editGroup); @@ -264,15 +265,15 @@ export class GroupDataService extends IdentifiableDataService implements * @param group Group we want edit page for */ public getGroupEditPageRouterLink(group: Group): string { - return this.getGroupEditPageRouterLinkWithID(group.id); + return getGroupEditRoute(group.id); } /** * Get Edit page of group * @param groupID Group ID we want edit page for */ - public getGroupEditPageRouterLinkWithID(groupId: string): string { - return '/access-control/groups/' + groupId; + public getGroupEditPageRouterLinkWithID(groupID: string): string { + return getGroupEditRoute(groupID); } /** diff --git a/src/app/shared/resource-policies/entry/resource-policy-entry.component.ts b/src/app/shared/resource-policies/entry/resource-policy-entry.component.ts index 904a79cbe6..83733c7011 100644 --- a/src/app/shared/resource-policies/entry/resource-policy-entry.component.ts +++ b/src/app/shared/resource-policies/entry/resource-policy-entry.component.ts @@ -17,8 +17,7 @@ import { RemoteData } from '../../../core/data/remote-data'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { ActivatedRoute, Router } from '@angular/router'; import { Group } from '../../../core/eperson/models/group.model'; -import { ACCESS_CONTROL_MODULE_PATH } from '../../../app-routing-paths'; -import { GROUP_EDIT_PATH } from '../../../access-control/access-control-routing-paths'; +import { getGroupEditRoute } from '../../../access-control/access-control-routing-paths'; import { GroupDataService } from '../../../core/eperson/group-data.service'; export interface ResourcePolicyCheckboxEntry { @@ -97,7 +96,7 @@ export class ResourcePolicyEntryComponent implements OnInit { getFirstSucceededRemoteDataPayload(), map((group: Group) => group.id), ).subscribe((groupUUID) => { - this.router.navigate([ACCESS_CONTROL_MODULE_PATH, GROUP_EDIT_PATH, groupUUID]); + void this.router.navigate([getGroupEditRoute(groupUUID)]); }); } } From 998e1fac8de2707a62c729250a82f439b173b33c Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 21 Jul 2023 17:48:27 +0200 Subject: [PATCH 013/875] Fix spacing issues for EPerson form buttons --- .../eperson-form/eperson-form.component.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html index 493209f0a2..88c4f99870 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html @@ -29,9 +29,8 @@ {{'admin.access-control.epeople.actions.reset' | translate}}
-
- -
- diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index 1402f79ae3..b9aeeb0af2 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -484,16 +484,16 @@ describe('EPersonFormComponent', () => { }); - it('the delete button should be active if the eperson can be deleted', () => { + it('the delete button should be visible if the ePerson can be deleted', () => { const deleteButton = fixture.debugElement.query(By.css('.delete-button')); - expect(deleteButton.nativeElement.disabled).toBe(false); + expect(deleteButton).not.toBeNull(); }); - it('the delete button should be disabled if the eperson cannot be deleted', () => { + it('the delete button should be hidden if the ePerson cannot be deleted', () => { component.canDelete$ = observableOf(false); fixture.detectChanges(); const deleteButton = fixture.debugElement.query(By.css('.delete-button')); - expect(deleteButton.nativeElement.disabled).toBe(true); + expect(deleteButton).toBeNull(); }); it('should call the epersonFormComponent delete when clicked on the button', () => { diff --git a/src/app/access-control/group-registry/group-form/group-form.component.html b/src/app/access-control/group-registry/group-form/group-form.component.html index 0515b071ae..d5003911ae 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.html +++ b/src/app/access-control/group-registry/group-form/group-form.component.html @@ -39,7 +39,7 @@
-
+
\ No newline at end of file + diff --git a/src/app/shared/log-in/methods/oidc/log-in-oidc.component.spec.ts b/src/app/shared/log-in/methods/oidc/log-in-oidc.component.spec.ts index 078a58dd5a..93559689e8 100644 --- a/src/app/shared/log-in/methods/oidc/log-in-oidc.component.spec.ts +++ b/src/app/shared/log-in/methods/oidc/log-in-oidc.component.spec.ts @@ -3,11 +3,8 @@ import { ActivatedRoute, Router } from '@angular/router'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { provideMockStore } from '@ngrx/store/testing'; -import { Store, StoreModule } from '@ngrx/store'; +import { StoreModule } from '@ngrx/store'; import { TranslateModule } from '@ngx-translate/core'; - -import { EPerson } from '../../../../core/eperson/models/eperson.model'; -import { EPersonMock } from '../../../testing/eperson.mock'; import { authReducer } from '../../../../core/auth/auth.reducer'; import { AuthService } from '../../../../core/auth/auth.service'; import { AuthServiceStub } from '../../../testing/auth-service.stub'; @@ -20,23 +17,22 @@ import { RouterStub } from '../../../testing/router.stub'; import { ActivatedRouteStub } from '../../../testing/active-router.stub'; import { NativeWindowMockFactory } from '../../../mocks/mock-native-window-ref'; import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; +import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; +import { AuthorizationDataServiceStub } from '../../../testing/authorization-service.stub'; describe('LogInOidcComponent', () => { let component: LogInOidcComponent; let fixture: ComponentFixture; - let page: Page; - let user: EPerson; let componentAsAny: any; let setHrefSpy; - let oidcBaseUrl; - let location; + let oidcBaseUrl: string; + let location: string; let initialState: any; let hardRedirectService: HardRedirectService; beforeEach(() => { - user = EPersonMock; oidcBaseUrl = 'dspace-rest.test/oidc?redirectUrl='; location = oidcBaseUrl + 'http://dspace-angular.test/home'; @@ -60,7 +56,7 @@ describe('LogInOidcComponent', () => { beforeEach(waitForAsync(() => { // refine the test module by declaring the test component - TestBed.configureTestingModule({ + void TestBed.configureTestingModule({ imports: [ StoreModule.forRoot({ auth: authReducer }, storeModuleConfig), TranslateModule.forRoot() @@ -70,7 +66,8 @@ describe('LogInOidcComponent', () => { ], providers: [ { provide: AuthService, useClass: AuthServiceStub }, - { provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Oidc, location) }, + { provide: AuthorizationDataService, useClass: AuthorizationDataServiceStub }, + { provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Oidc, 0, location) }, { provide: 'isStandalonePage', useValue: true }, { provide: NativeWindowService, useFactory: NativeWindowMockFactory }, { provide: Router, useValue: new RouterStub() }, @@ -95,7 +92,6 @@ describe('LogInOidcComponent', () => { componentAsAny = component; // create page - page = new Page(component, fixture); setHrefSpy = spyOnProperty(componentAsAny._window.nativeWindow.location, 'href', 'set').and.callThrough(); }); @@ -131,25 +127,3 @@ describe('LogInOidcComponent', () => { }); }); - -/** - * I represent the DOM elements and attach spies. - * - * @class Page - */ -class Page { - - public emailInput: HTMLInputElement; - public navigateSpy: jasmine.Spy; - public passwordInput: HTMLInputElement; - - constructor(private component: LogInOidcComponent, private fixture: ComponentFixture) { - // use injector to get services - const injector = fixture.debugElement.injector; - const store = injector.get(Store); - - // add spies - this.navigateSpy = spyOn(store, 'dispatch'); - } - -} diff --git a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.html b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.html index 6f5453fd60..2260197ab4 100644 --- a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.html +++ b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.html @@ -1,3 +1,3 @@ - \ No newline at end of file + diff --git a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts index 001f0a4959..0782f15720 100644 --- a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts +++ b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts @@ -3,11 +3,8 @@ import { ActivatedRoute, Router } from '@angular/router'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { provideMockStore } from '@ngrx/store/testing'; -import { Store, StoreModule } from '@ngrx/store'; +import { StoreModule } from '@ngrx/store'; import { TranslateModule } from '@ngx-translate/core'; - -import { EPerson } from '../../../../core/eperson/models/eperson.model'; -import { EPersonMock } from '../../../testing/eperson.mock'; import { authReducer } from '../../../../core/auth/auth.reducer'; import { AuthService } from '../../../../core/auth/auth.service'; import { AuthServiceStub } from '../../../testing/auth-service.stub'; @@ -26,17 +23,14 @@ describe('LogInOrcidComponent', () => { let component: LogInOrcidComponent; let fixture: ComponentFixture; - let page: Page; - let user: EPerson; let componentAsAny: any; let setHrefSpy; - let orcidBaseUrl; - let location; + let orcidBaseUrl: string; + let location: string; let initialState: any; let hardRedirectService: HardRedirectService; beforeEach(() => { - user = EPersonMock; orcidBaseUrl = 'dspace-rest.test/orcid?redirectUrl='; location = orcidBaseUrl + 'http://dspace-angular.test/home'; @@ -60,7 +54,7 @@ describe('LogInOrcidComponent', () => { beforeEach(waitForAsync(() => { // refine the test module by declaring the test component - TestBed.configureTestingModule({ + void TestBed.configureTestingModule({ imports: [ StoreModule.forRoot({ auth: authReducer }, storeModuleConfig), TranslateModule.forRoot() @@ -70,7 +64,7 @@ describe('LogInOrcidComponent', () => { ], providers: [ { provide: AuthService, useClass: AuthServiceStub }, - { provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Orcid, location) }, + { provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Orcid, 0, location) }, { provide: 'isStandalonePage', useValue: true }, { provide: NativeWindowService, useFactory: NativeWindowMockFactory }, { provide: Router, useValue: new RouterStub() }, @@ -95,7 +89,6 @@ describe('LogInOrcidComponent', () => { componentAsAny = component; // create page - page = new Page(component, fixture); setHrefSpy = spyOnProperty(componentAsAny._window.nativeWindow.location, 'href', 'set').and.callThrough(); }); @@ -131,25 +124,3 @@ describe('LogInOrcidComponent', () => { }); }); - -/** - * I represent the DOM elements and attach spies. - * - * @class Page - */ -class Page { - - public emailInput: HTMLInputElement; - public navigateSpy: jasmine.Spy; - public passwordInput: HTMLInputElement; - - constructor(private component: LogInOrcidComponent, private fixture: ComponentFixture) { - // use injector to get services - const injector = fixture.debugElement.injector; - const store = injector.get(Store); - - // add spies - this.navigateSpy = spyOn(store, 'dispatch'); - } - -} 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 c1f1016cb8..60477d141d 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,3 +28,12 @@ + + diff --git a/src/app/shared/log-in/methods/password/log-in-password.component.scss b/src/app/shared/log-in/methods/password/log-in-password.component.scss index 0eda382c0a..955252b51e 100644 --- a/src/app/shared/log-in/methods/password/log-in-password.component.scss +++ b/src/app/shared/log-in/methods/password/log-in-password.component.scss @@ -11,3 +11,7 @@ border-top-right-radius: 0; } +.dropdown-item { + white-space: normal; + padding: .25rem .75rem; +} diff --git a/src/app/shared/log-in/methods/password/log-in-password.component.spec.ts b/src/app/shared/log-in/methods/password/log-in-password.component.spec.ts index 5238482770..94d18fe768 100644 --- a/src/app/shared/log-in/methods/password/log-in-password.component.spec.ts +++ b/src/app/shared/log-in/methods/password/log-in-password.component.spec.ts @@ -8,8 +8,6 @@ import { Store, StoreModule } from '@ngrx/store'; import { TranslateModule } from '@ngx-translate/core'; import { LogInPasswordComponent } from './log-in-password.component'; -import { EPerson } from '../../../../core/eperson/models/eperson.model'; -import { EPersonMock } from '../../../testing/eperson.mock'; import { authReducer } from '../../../../core/auth/auth.reducer'; import { AuthService } from '../../../../core/auth/auth.service'; import { AuthServiceStub } from '../../../testing/auth-service.stub'; @@ -18,19 +16,18 @@ import { AuthMethod } from '../../../../core/auth/models/auth.method'; import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; import { BrowserOnlyMockPipe } from '../../../testing/browser-only-mock.pipe'; +import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; +import { AuthorizationDataServiceStub } from '../../../testing/authorization-service.stub'; describe('LogInPasswordComponent', () => { let component: LogInPasswordComponent; let fixture: ComponentFixture; let page: Page; - let user: EPerson; let initialState: any; let hardRedirectService: HardRedirectService; beforeEach(() => { - user = EPersonMock; - hardRedirectService = jasmine.createSpyObj('hardRedirectService', { getCurrentRoute: {} }); @@ -50,7 +47,7 @@ describe('LogInPasswordComponent', () => { beforeEach(waitForAsync(() => { // refine the test module by declaring the test component - TestBed.configureTestingModule({ + void TestBed.configureTestingModule({ imports: [ FormsModule, ReactiveFormsModule, @@ -63,7 +60,8 @@ describe('LogInPasswordComponent', () => { ], providers: [ { provide: AuthService, useClass: AuthServiceStub }, - { provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Password) }, + { provide: AuthorizationDataService, useClass: AuthorizationDataServiceStub }, + { provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Password, 0) }, { provide: 'isStandalonePage', useValue: true }, { provide: HardRedirectService, useValue: hardRedirectService }, provideMockStore({ initialState }), @@ -76,7 +74,7 @@ describe('LogInPasswordComponent', () => { })); - beforeEach(() => { + beforeEach(async () => { // create component and test fixture fixture = TestBed.createComponent(LogInPasswordComponent); @@ -87,10 +85,8 @@ describe('LogInPasswordComponent', () => { page = new Page(component, fixture); // verify the fixture is stable (no pending tasks) - fixture.whenStable().then(() => { - page.addPageElements(); - }); - + await fixture.whenStable(); + page.addPageElements(); }); it('should create a FormGroup comprised of FormControls', () => { diff --git a/src/app/shared/log-in/methods/password/log-in-password.component.ts b/src/app/shared/log-in/methods/password/log-in-password.component.ts index e4a92ded29..e2fe92fafb 100644 --- a/src/app/shared/log-in/methods/password/log-in-password.component.ts +++ b/src/app/shared/log-in/methods/password/log-in-password.component.ts @@ -15,6 +15,9 @@ import { AuthMethod } from '../../../../core/auth/models/auth.method'; import { AuthService } from '../../../../core/auth/auth.service'; import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; import { CoreState } from '../../../../core/core-state.model'; +import { getForgotPasswordRoute, getRegisterRoute } from '../../../../app-routing-paths'; +import { FeatureID } from '../../../../core/data/feature-authorization/feature-id'; +import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; /** * /users/sign-in @@ -66,21 +69,18 @@ export class LogInPasswordComponent implements OnInit { public form: FormGroup; /** - * @constructor - * @param {AuthMethod} injectedAuthMethodModel - * @param {boolean} isStandalonePage - * @param {AuthService} authService - * @param {HardRedirectService} hardRedirectService - * @param {FormBuilder} formBuilder - * @param {Store} store + * Whether the current user (or anonymous) is authorized to register an account */ + public canRegister$: Observable; + constructor( @Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod, @Inject('isStandalonePage') public isStandalonePage: boolean, private authService: AuthService, private hardRedirectService: HardRedirectService, private formBuilder: FormBuilder, - private store: Store + protected store: Store, + protected authorizationService: AuthorizationDataService, ) { this.authMethod = injectedAuthMethodModel; } @@ -115,6 +115,15 @@ export class LogInPasswordComponent implements OnInit { }) ); + this.canRegister$ = this.authorizationService.isAuthorized(FeatureID.EPersonRegistration); + } + + getRegisterRoute() { + return getRegisterRoute(); + } + + getForgotRoute() { + return getForgotPasswordRoute(); } /** diff --git a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.html b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.html index 3a3b935cfa..fca514b83d 100644 --- a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.html +++ b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.html @@ -1,3 +1,3 @@ - diff --git a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.spec.ts b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.spec.ts index 075d33d98e..afb0f2092a 100644 --- a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.spec.ts +++ b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.spec.ts @@ -3,11 +3,8 @@ import { ActivatedRoute, Router } from '@angular/router'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { provideMockStore } from '@ngrx/store/testing'; -import { Store, StoreModule } from '@ngrx/store'; +import { StoreModule } from '@ngrx/store'; import { TranslateModule } from '@ngx-translate/core'; - -import { EPerson } from '../../../../core/eperson/models/eperson.model'; -import { EPersonMock } from '../../../testing/eperson.mock'; import { authReducer } from '../../../../core/auth/auth.reducer'; import { AuthService } from '../../../../core/auth/auth.service'; import { AuthServiceStub } from '../../../testing/auth-service.stub'; @@ -26,17 +23,14 @@ describe('LogInShibbolethComponent', () => { let component: LogInShibbolethComponent; let fixture: ComponentFixture; - let page: Page; - let user: EPerson; let componentAsAny: any; let setHrefSpy; - let shibbolethBaseUrl; - let location; + let shibbolethBaseUrl: string; + let location: string; let initialState: any; let hardRedirectService: HardRedirectService; beforeEach(() => { - user = EPersonMock; shibbolethBaseUrl = 'dspace-rest.test/shibboleth?redirectUrl='; location = shibbolethBaseUrl + 'http://dspace-angular.test/home'; @@ -60,7 +54,7 @@ describe('LogInShibbolethComponent', () => { beforeEach(waitForAsync(() => { // refine the test module by declaring the test component - TestBed.configureTestingModule({ + void TestBed.configureTestingModule({ imports: [ StoreModule.forRoot({ auth: authReducer }, storeModuleConfig), TranslateModule.forRoot() @@ -70,7 +64,7 @@ describe('LogInShibbolethComponent', () => { ], providers: [ { provide: AuthService, useClass: AuthServiceStub }, - { provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Shibboleth, location) }, + { provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Shibboleth, 0, location) }, { provide: 'isStandalonePage', useValue: true }, { provide: NativeWindowService, useFactory: NativeWindowMockFactory }, { provide: Router, useValue: new RouterStub() }, @@ -95,7 +89,6 @@ describe('LogInShibbolethComponent', () => { componentAsAny = component; // create page - page = new Page(component, fixture); setHrefSpy = spyOnProperty(componentAsAny._window.nativeWindow.location, 'href', 'set').and.callThrough(); }); @@ -131,25 +124,3 @@ describe('LogInShibbolethComponent', () => { }); }); - -/** - * I represent the DOM elements and attach spies. - * - * @class Page - */ -class Page { - - public emailInput: HTMLInputElement; - public navigateSpy: jasmine.Spy; - public passwordInput: HTMLInputElement; - - constructor(private component: LogInShibbolethComponent, private fixture: ComponentFixture) { - // use injector to get services - const injector = fixture.debugElement.injector; - const store = injector.get(Store); - - // add spies - this.navigateSpy = spyOn(store, 'dispatch'); - } - -} diff --git a/src/app/shared/testing/auth-service.stub.ts b/src/app/shared/testing/auth-service.stub.ts index b8d822252d..e974c5ca9e 100644 --- a/src/app/shared/testing/auth-service.stub.ts +++ b/src/app/shared/testing/auth-service.stub.ts @@ -6,10 +6,11 @@ import { EPerson } from '../../core/eperson/models/eperson.model'; import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; import { AuthMethod } from '../../core/auth/models/auth.method'; import { hasValue } from '../empty.util'; +import { AuthMethodType } from '../../core/auth/models/auth.method-type'; -export const authMethodsMock = [ - new AuthMethod('password'), - new AuthMethod('shibboleth', 'dspace.test/shibboleth') +export const authMethodsMock: AuthMethod[] = [ + new AuthMethod(AuthMethodType.Password, 0), + new AuthMethod(AuthMethodType.Shibboleth, 1, 'dspace.test/shibboleth'), ]; export class AuthServiceStub { diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 597f226cc7..4e875d61f2 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2560,8 +2560,6 @@ "login.form.new-user": "New user? Click here to register.", - "login.form.or-divider": "or", - "login.form.oidc": "Log in with OIDC", "login.form.orcid": "Log in with ORCID", From 3dc73f90214bc9fd288aaf0e0a06bc5912aadfbb Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 2 Aug 2023 21:25:58 +0200 Subject: [PATCH 021/875] Properly handle AuthMethod subscription in LogInComponent --- src/app/shared/log-in/log-in.component.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index f47ea2d77e..4bfae18332 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; +import { map, Observable } from 'rxjs'; import { select, Store } from '@ngrx/store'; import { AuthMethod } from '../../core/auth/models/auth.method'; import { @@ -35,7 +35,7 @@ export class LogInComponent implements OnInit { * The list of authentication methods available * @type {AuthMethod[]} */ - public authMethods: AuthMethod[]; + public authMethods: Observable; /** * Whether user is authenticated. @@ -55,13 +55,11 @@ export class LogInComponent implements OnInit { } ngOnInit(): void { - - this.store.pipe( + this.authMethods = this.store.pipe( select(getAuthenticationMethods), - ).subscribe(methods => { // ignore the ip authentication method when it's returned by the backend - this.authMethods = methods.filter(a => a.authMethodType !== AuthMethodType.Ip); - }); + map((methods: AuthMethod[]) => methods.filter((authMethod: AuthMethod) => authMethod.authMethodType !== AuthMethodType.Ip)), + ); // set loading this.loading = this.store.pipe(select(isAuthenticationLoading)); From 7c379db7eec9196e1fcbd5eacbcbd67246c4546f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 21:23:09 +0000 Subject: [PATCH 022/875] Bump semver from 5.7.1 to 5.7.2 Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2. - [Release notes](https://github.com/npm/node-semver/releases) - [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md) - [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2) --- updated-dependencies: - dependency-name: semver dependency-type: indirect ... Signed-off-by: dependabot[bot] (cherry picked from commit 0b0c60e38c1aecb690f15b1af20df655bd028c5f) --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 730966fcdb..21de68a480 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10114,33 +10114,33 @@ selfsigned@^2.1.1: dependencies: node-forge "^1" -semver@7.3.8, semver@^7.3.5, semver@^7.3.8: +semver@7.3.8: version "7.3.8" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== dependencies: lru-cache "^6.0.0" semver@^5.3.0, semver@^5.6.0, semver@^5.7.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.7: - version "7.4.0" - resolved "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz" - integrity sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw== +semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" semver@~7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== send@0.16.2: From 0dc74165dc58bbeb36bd95ef86b6666e5245997f Mon Sep 17 00:00:00 2001 From: Mirko Scherf Date: Thu, 20 Jul 2023 14:53:28 +0200 Subject: [PATCH 023/875] refactor: rename aletr-type.ts to alert-type.ts --- .../group-registry/group-form/group-form.component.ts | 2 +- .../edit-item-template-page.component.ts | 2 +- .../dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts | 2 +- .../health-panel/health-component/health-component.component.ts | 2 +- src/app/item-page/alerts/item-alerts.component.ts | 2 +- .../item-version-history/item-version-history.component.ts | 2 +- .../item-page/orcid-page/orcid-queue/orcid-queue.component.ts | 2 +- src/app/item-page/versions/item-versions.component.ts | 2 +- .../item-page/versions/notice/item-versions-notice.component.ts | 2 +- src/app/process-page/detail/process-detail.component.ts | 2 +- src/app/register-email-form/register-email-form.component.ts | 2 +- .../access-control-form-container.component.ts | 2 +- src/app/shared/alert/{aletr-type.ts => alert-type.ts} | 0 src/app/shared/alert/alert.component.spec.ts | 2 +- src/app/shared/alert/alert.component.ts | 2 +- src/app/shared/error/error.component.ts | 2 +- .../sections/container/section-container.component.ts | 2 +- .../sections/identifiers/section-identifiers.component.ts | 2 +- .../publisher-policy/publisher-policy.component.ts | 2 +- .../sherpa-policies/section-sherpa-policies.component.ts | 2 +- src/app/submission/sections/upload/section-upload.component.ts | 2 +- 21 files changed, 20 insertions(+), 20 deletions(-) rename src/app/shared/alert/{aletr-type.ts => alert-type.ts} (100%) diff --git a/src/app/access-control/group-registry/group-form/group-form.component.ts b/src/app/access-control/group-registry/group-form/group-form.component.ts index 3c0547cca5..693e283b4a 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.ts +++ b/src/app/access-control/group-registry/group-form/group-form.component.ts @@ -37,7 +37,7 @@ import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; -import { AlertType } from '../../../shared/alert/aletr-type'; +import { AlertType } from '../../../shared/alert/alert-type'; import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component'; import { hasValue, isNotEmpty, hasValueOperator } from '../../../shared/empty.util'; import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; diff --git a/src/app/collection-page/edit-item-template-page/edit-item-template-page.component.ts b/src/app/collection-page/edit-item-template-page/edit-item-template-page.component.ts index 6425996fd2..238ec5e37a 100644 --- a/src/app/collection-page/edit-item-template-page/edit-item-template-page.component.ts +++ b/src/app/collection-page/edit-item-template-page/edit-item-template-page.component.ts @@ -8,7 +8,7 @@ import { ItemTemplateDataService } from '../../core/data/item-template-data.serv import { getCollectionEditRoute } from '../collection-page-routing-paths'; import { Item } from '../../core/shared/item.model'; import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; -import { AlertType } from '../../shared/alert/aletr-type'; +import { AlertType } from '../../shared/alert/alert-type'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; @Component({ diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts index d67a7ea738..d44817be84 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, Injector, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { AlertType } from '../../shared/alert/aletr-type'; +import { AlertType } from '../../shared/alert/alert-type'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DsoEditMetadataForm } from './dso-edit-metadata-form'; import { map } from 'rxjs/operators'; diff --git a/src/app/health-page/health-panel/health-component/health-component.component.ts b/src/app/health-page/health-panel/health-component/health-component.component.ts index e212a07289..f2391c9c4c 100644 --- a/src/app/health-page/health-panel/health-component/health-component.component.ts +++ b/src/app/health-page/health-panel/health-component/health-component.component.ts @@ -3,7 +3,7 @@ import { Component, Input } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { HealthComponent } from '../../models/health-component.model'; -import { AlertType } from '../../../shared/alert/aletr-type'; +import { AlertType } from '../../../shared/alert/alert-type'; /** * A component to render a "health component" object. diff --git a/src/app/item-page/alerts/item-alerts.component.ts b/src/app/item-page/alerts/item-alerts.component.ts index d7a84db015..2b1df58c9f 100644 --- a/src/app/item-page/alerts/item-alerts.component.ts +++ b/src/app/item-page/alerts/item-alerts.component.ts @@ -1,6 +1,6 @@ import { Component, Input } from '@angular/core'; import { Item } from '../../core/shared/item.model'; -import { AlertType } from '../../shared/alert/aletr-type'; +import { AlertType } from '../../shared/alert/alert-type'; @Component({ selector: 'ds-item-alerts', diff --git a/src/app/item-page/edit-item-page/item-version-history/item-version-history.component.ts b/src/app/item-page/edit-item-page/item-version-history/item-version-history.component.ts index 18878109c2..3845c03578 100644 --- a/src/app/item-page/edit-item-page/item-version-history/item-version-history.component.ts +++ b/src/app/item-page/edit-item-page/item-version-history/item-version-history.component.ts @@ -5,7 +5,7 @@ import { Item } from '../../../core/shared/item.model'; import { map } from 'rxjs/operators'; import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; import { ActivatedRoute } from '@angular/router'; -import { AlertType } from '../../../shared/alert/aletr-type'; +import { AlertType } from '../../../shared/alert/alert-type'; @Component({ selector: 'ds-item-version-history', diff --git a/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts b/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts index 6079287f71..3e88826952 100644 --- a/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts +++ b/src/app/item-page/orcid-page/orcid-queue/orcid-queue.component.ts @@ -15,7 +15,7 @@ import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; -import { AlertType } from '../../../shared/alert/aletr-type'; +import { AlertType } from '../../../shared/alert/alert-type'; import { Item } from '../../../core/shared/item.model'; import { OrcidAuthService } from '../../../core/orcid/orcid-auth.service'; diff --git a/src/app/item-page/versions/item-versions.component.ts b/src/app/item-page/versions/item-versions.component.ts index 700a35552c..e7ee9d5ea2 100644 --- a/src/app/item-page/versions/item-versions.component.ts +++ b/src/app/item-page/versions/item-versions.component.ts @@ -23,7 +23,7 @@ import { PaginatedList } from '../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { VersionHistoryDataService } from '../../core/data/version-history-data.service'; import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; -import { AlertType } from '../../shared/alert/aletr-type'; +import { AlertType } from '../../shared/alert/alert-type'; import { followLink } from '../../shared/utils/follow-link-config.model'; import { hasValue, hasValueOperator } from '../../shared/empty.util'; import { PaginationService } from '../../core/pagination/pagination.service'; diff --git a/src/app/item-page/versions/notice/item-versions-notice.component.ts b/src/app/item-page/versions/notice/item-versions-notice.component.ts index 8a8f5ff76f..0e5e45806b 100644 --- a/src/app/item-page/versions/notice/item-versions-notice.component.ts +++ b/src/app/item-page/versions/notice/item-versions-notice.component.ts @@ -12,7 +12,7 @@ import { } from '../../../core/shared/operators'; import { map, startWith, switchMap } from 'rxjs/operators'; import { VersionHistoryDataService } from '../../../core/data/version-history-data.service'; -import { AlertType } from '../../../shared/alert/aletr-type'; +import { AlertType } from '../../../shared/alert/alert-type'; import { getItemPageRoute } from '../../item-page-routing-paths'; @Component({ diff --git a/src/app/process-page/detail/process-detail.component.ts b/src/app/process-page/detail/process-detail.component.ts index a379dfe337..be0b6ad0f6 100644 --- a/src/app/process-page/detail/process-detail.component.ts +++ b/src/app/process-page/detail/process-detail.component.ts @@ -17,7 +17,7 @@ import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; import { URLCombiner } from '../../core/url-combiner/url-combiner'; -import { AlertType } from '../../shared/alert/aletr-type'; +import { AlertType } from '../../shared/alert/alert-type'; import { hasValue } from '../../shared/empty.util'; import { ProcessStatus } from '../processes/process-status.model'; import { Process } from '../processes/process.model'; diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index ddb77b669c..df7e9bea5e 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -13,7 +13,7 @@ import {isNotEmpty} from '../shared/empty.util'; import {BehaviorSubject, combineLatest, Observable, of, switchMap} from 'rxjs'; import {map, startWith, take} from 'rxjs/operators'; import {CAPTCHA_NAME, GoogleRecaptchaService} from '../core/google-recaptcha/google-recaptcha.service'; -import {AlertType} from '../shared/alert/aletr-type'; +import {AlertType} from '../shared/alert/alert-type'; import {KlaroService} from '../shared/cookies/klaro.service'; import {CookieService} from '../core/services/cookie.service'; import { Subscription } from 'rxjs'; diff --git a/src/app/shared/access-control-form-container/access-control-form-container.component.ts b/src/app/shared/access-control-form-container/access-control-form-container.component.ts index 69a598f7ce..cddd1b1a29 100644 --- a/src/app/shared/access-control-form-container/access-control-form-container.component.ts +++ b/src/app/shared/access-control-form-container/access-control-form-container.component.ts @@ -15,7 +15,7 @@ import { import { BulkAccessConfigDataService } from '../../core/config/bulk-access-config-data.service'; import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { BulkAccessConditionOptions } from '../../core/config/models/bulk-access-condition-options.model'; -import { AlertType } from '../alert/aletr-type'; +import { AlertType } from '../alert/alert-type'; import { createAccessControlInitialFormState } from './access-control-form-container-intial-state'; diff --git a/src/app/shared/alert/aletr-type.ts b/src/app/shared/alert/alert-type.ts similarity index 100% rename from src/app/shared/alert/aletr-type.ts rename to src/app/shared/alert/alert-type.ts diff --git a/src/app/shared/alert/alert.component.spec.ts b/src/app/shared/alert/alert.component.spec.ts index 21e4d197b7..11411c7de0 100644 --- a/src/app/shared/alert/alert.component.spec.ts +++ b/src/app/shared/alert/alert.component.spec.ts @@ -8,7 +8,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { AlertComponent } from './alert.component'; import { createTestComponent } from '../testing/utils.test'; -import { AlertType } from './aletr-type'; +import { AlertType } from './alert-type'; describe('AlertComponent test suite', () => { diff --git a/src/app/shared/alert/alert.component.ts b/src/app/shared/alert/alert.component.ts index 93535d2057..07a8efbd7d 100644 --- a/src/app/shared/alert/alert.component.ts +++ b/src/app/shared/alert/alert.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectorRef, Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; import { trigger } from '@angular/animations'; -import { AlertType } from './aletr-type'; +import { AlertType } from './alert-type'; import { fadeOutLeave, fadeOutState } from '../animations/fade'; /** diff --git a/src/app/shared/error/error.component.ts b/src/app/shared/error/error.component.ts index 9a6b0660bb..6572598c8b 100644 --- a/src/app/shared/error/error.component.ts +++ b/src/app/shared/error/error.component.ts @@ -3,7 +3,7 @@ import { Component, Input } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { Subscription } from 'rxjs'; -import { AlertType } from '../alert/aletr-type'; +import { AlertType } from '../alert/alert-type'; @Component({ selector: 'ds-error', diff --git a/src/app/submission/sections/container/section-container.component.ts b/src/app/submission/sections/container/section-container.component.ts index 8f9ebfbfda..3331629f33 100644 --- a/src/app/submission/sections/container/section-container.component.ts +++ b/src/app/submission/sections/container/section-container.component.ts @@ -3,7 +3,7 @@ import { Component, Injector, Input, OnInit, ViewChild } from '@angular/core'; import { SectionsDirective } from '../sections.directive'; import { SectionDataObject } from '../models/section-data.model'; import { rendersSectionType } from '../sections-decorator'; -import { AlertType } from '../../../shared/alert/aletr-type'; +import { AlertType } from '../../../shared/alert/alert-type'; /** * This component represents a section that contains the submission license form. diff --git a/src/app/submission/sections/identifiers/section-identifiers.component.ts b/src/app/submission/sections/identifiers/section-identifiers.component.ts index 2dc70f668e..ac4af63adb 100644 --- a/src/app/submission/sections/identifiers/section-identifiers.component.ts +++ b/src/app/submission/sections/identifiers/section-identifiers.component.ts @@ -7,7 +7,7 @@ import { SectionModelComponent } from '../models/section.model'; import { renderSectionFor } from '../sections-decorator'; import { SectionDataObject } from '../models/section-data.model'; import { SubmissionService } from '../../submission.service'; -import { AlertType } from '../../../shared/alert/aletr-type'; +import { AlertType } from '../../../shared/alert/alert-type'; import { SectionsService } from '../sections.service'; import { WorkspaceitemSectionIdentifiersObject } from '../../../core/submission/models/workspaceitem-section-identifiers.model'; diff --git a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts index 96ada3904c..25407f5a7b 100644 --- a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts +++ b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts @@ -1,7 +1,7 @@ import { Component, Input } from '@angular/core'; import { Policy } from '../../../../core/submission/models/sherpa-policies-details.model'; -import { AlertType } from '../../../../shared/alert/aletr-type'; +import { AlertType } from '../../../../shared/alert/alert-type'; /** * This component represents a section that contains the publisher policy informations. diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts index e55b75146f..eb273a8420 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts @@ -1,4 +1,4 @@ -import { AlertType } from '../../../shared/alert/aletr-type'; +import { AlertType } from '../../../shared/alert/alert-type'; import { Component, Inject } from '@angular/core'; import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'; diff --git a/src/app/submission/sections/upload/section-upload.component.ts b/src/app/submission/sections/upload/section-upload.component.ts index eefed8a36b..10203adbc0 100644 --- a/src/app/submission/sections/upload/section-upload.component.ts +++ b/src/app/submission/sections/upload/section-upload.component.ts @@ -21,7 +21,7 @@ import { SectionsType } from '../sections-type'; import { renderSectionFor } from '../sections-decorator'; import { SectionDataObject } from '../models/section-data.model'; import { SubmissionObjectEntry } from '../../objects/submission-objects.reducer'; -import { AlertType } from '../../../shared/alert/aletr-type'; +import { AlertType } from '../../../shared/alert/alert-type'; import { RemoteData } from '../../../core/data/remote-data'; import { Group } from '../../../core/eperson/models/group.model'; import { SectionsService } from '../sections.service'; From a7ed053d152df6ec45df02fd458928b2e39bb0b0 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 29 Jun 2023 16:09:54 +0200 Subject: [PATCH 024/875] catch and handle unsuccessful "convert rels to items" responses (cherry picked from commit a35b7d8356e17f4cdd8568389695b1685b27eb84) --- .../shared/item-relationships-utils.ts | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/app/item-page/simple/item-types/shared/item-relationships-utils.ts b/src/app/item-page/simple/item-types/shared/item-relationships-utils.ts index b4c3da2cdc..0c4e82178f 100644 --- a/src/app/item-page/simple/item-types/shared/item-relationships-utils.ts +++ b/src/app/item-page/simple/item-types/shared/item-relationships-utils.ts @@ -5,8 +5,7 @@ import { RemoteData } from '../../../../core/data/remote-data'; import { Relationship } from '../../../../core/shared/item-relationships/relationship.model'; import { Item } from '../../../../core/shared/item.model'; import { - getFirstSucceededRemoteDataPayload, - getFirstSucceededRemoteData + getFirstCompletedRemoteData } from '../../../../core/shared/operators'; import { hasValue } from '../../../../shared/empty.util'; import { InjectionToken } from '@angular/core'; @@ -77,24 +76,42 @@ export const relationsToItems = (thisId: string) => * @param {string} thisId The item's id of which the relations belong to * @returns {(source: Observable) => Observable} */ -export const paginatedRelationsToItems = (thisId: string) => - (source: Observable>>): Observable>> => +export const paginatedRelationsToItems = (thisId: string) => (source: Observable>>): Observable>> => source.pipe( - getFirstSucceededRemoteData(), + getFirstCompletedRemoteData(), switchMap((relationshipsRD: RemoteData>) => { return observableCombineLatest( relationshipsRD.payload.page.map((rel: Relationship) => observableCombineLatest([ - rel.leftItem.pipe(getFirstSucceededRemoteDataPayload()), - rel.rightItem.pipe(getFirstSucceededRemoteDataPayload())] + rel.leftItem.pipe( + getFirstCompletedRemoteData(), + map((rd: RemoteData) => { + if (rd.hasSucceeded) { + return rd.payload; + } else { + return null; + } + }) + ), + rel.rightItem.pipe( + getFirstCompletedRemoteData(), + map((rd: RemoteData) => { + if (rd.hasSucceeded) { + return rd.payload; + } else { + return null; + } + }) + ), + ] ) - )).pipe( + ) + ).pipe( map((arr) => - arr - .map(([leftItem, rightItem]) => { - if (leftItem.id === thisId) { + arr.map(([leftItem, rightItem]) => { + if (hasValue(leftItem) && leftItem.id === thisId) { return rightItem; - } else if (rightItem.id === thisId) { + } else if (hasValue(rightItem) && rightItem.id === thisId) { return leftItem; } }) From f746d45ac153f64c27a5e080083f11137dd1dccf Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Thu, 20 Jul 2023 12:46:30 +0200 Subject: [PATCH 025/875] Show error message from the error response (cherry picked from commit e6546b4499fe87a9308dab7ff2c6767f9f256c67) --- src/app/profile-page/profile-page.component.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/app/profile-page/profile-page.component.ts b/src/app/profile-page/profile-page.component.ts index 343314999b..d49bdedb83 100644 --- a/src/app/profile-page/profile-page.component.ts +++ b/src/app/profile-page/profile-page.component.ts @@ -161,7 +161,7 @@ export class ProfilePageComponent implements OnInit { } else { this.notificationsService.error( this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.title'), - this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.change-failed') + this.getPasswordErrorMessage(response) ); } }); @@ -199,4 +199,18 @@ export class ProfilePageComponent implements OnInit { return this.isResearcherProfileEnabled$.asObservable(); } + /** + * Returns an error message from a password validation request with a specific reason or + * a default message without specific reason. + * @param response from the validation password patch request. + */ + getPasswordErrorMessage(response) { + if (response.hasFailed && isNotEmpty(response.errorMessage)) { + // Response has a specific error message. Show this message in the error notification. + return this.translate.instant(response.errorMessage); + } + // Show default error message notification. + return this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.change-failed'); + } + } From c3b9a1d5c6ed98fadb53b95eb5ca4de7174bca54 Mon Sep 17 00:00:00 2001 From: Mirko Scherf Date: Fri, 21 Jul 2023 10:43:13 +0200 Subject: [PATCH 026/875] fix(i18n): add and update missing status strings New strings for status filter entries: search.filters.namedresourcetype.* Refactored strings introduced with #2068 (refactor badged), e.g. mydspace.status.archived -> mydspace.status.mydspaceArchived --- src/assets/i18n/ar.json5 | 20 ++++++++++---------- src/assets/i18n/bn.json5 | 20 ++++++++++---------- src/assets/i18n/ca.json5 | 20 ++++++++++---------- src/assets/i18n/cs.json5 | 20 ++++++++++---------- src/assets/i18n/de.json5 | 35 +++++++++++++++++++++++++---------- src/assets/i18n/el.json5 | 10 +++++----- src/assets/i18n/en.json5 | 10 ++++++++++ src/assets/i18n/es.json5 | 20 ++++++++++---------- src/assets/i18n/fr.json5 | 20 ++++++++++---------- src/assets/i18n/gd.json5 | 20 ++++++++++---------- src/assets/i18n/hi.json5 | 10 +++++----- src/assets/i18n/hu.json5 | 20 ++++++++++---------- src/assets/i18n/it.json5 | 20 ++++++++++---------- src/assets/i18n/ja.json5 | 20 ++++++++++---------- src/assets/i18n/kk.json5 | 20 ++++++++++---------- src/assets/i18n/lv.json5 | 20 ++++++++++---------- src/assets/i18n/nl.json5 | 20 ++++++++++---------- src/assets/i18n/pl.json5 | 10 +++++----- src/assets/i18n/pt-BR.json5 | 20 ++++++++++---------- src/assets/i18n/pt-PT.json5 | 20 ++++++++++---------- src/assets/i18n/sv.json5 | 20 ++++++++++---------- src/assets/i18n/sw.json5 | 20 ++++++++++---------- src/assets/i18n/tr.json5 | 20 ++++++++++---------- src/assets/i18n/uk.json5 | 20 ++++++++++---------- src/assets/i18n/vi.json5 | 10 +++++----- 25 files changed, 245 insertions(+), 220 deletions(-) diff --git a/src/assets/i18n/ar.json5 b/src/assets/i18n/ar.json5 index 70f5fdadb1..3069104dd9 100644 --- a/src/assets/i18n/ar.json5 +++ b/src/assets/i18n/ar.json5 @@ -4276,25 +4276,25 @@ // TODO New key - Add a translation "mydspace.show.workspace": "Your Submissions", - // "mydspace.status.archived": "Archived", + // "mydspace.status.mydspaceArchived": "Archived", // TODO New key - Add a translation - "mydspace.status.archived": "Archived", + "mydspace.status.mydspaceArchived": "Archived", - // "mydspace.status.validation": "Validation", + // "mydspace.status.mydspaceValidation": "Validation", // TODO New key - Add a translation - "mydspace.status.validation": "Validation", + "mydspace.status.mydspaceValidation": "Validation", - // "mydspace.status.waiting-for-controller": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", // TODO New key - Add a translation - "mydspace.status.waiting-for-controller": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for controller", - // "mydspace.status.workflow": "Workflow", + // "mydspace.status.mydspaceWorkflow": "Workflow", // TODO New key - Add a translation - "mydspace.status.workflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Workflow", - // "mydspace.status.workspace": "Workspace", + // "mydspace.status.mydspaceWorkspace": "Workspace", // TODO New key - Add a translation - "mydspace.status.workspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Workspace", // "mydspace.title": "MyDSpace", // TODO New key - Add a translation diff --git a/src/assets/i18n/bn.json5 b/src/assets/i18n/bn.json5 index cc05d12828..c70cc6f459 100644 --- a/src/assets/i18n/bn.json5 +++ b/src/assets/i18n/bn.json5 @@ -3880,20 +3880,20 @@ // "mydspace.show.workspace": "Your Submissions", "mydspace.show.workspace": "আপনার জমাগুলো", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "সংরক্ষণাগারভুক্ত", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "সংরক্ষণাগারভুক্ত", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "বৈধতা", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "বৈধতা", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "নিয়ামক জন্য অপেক্ষা করছে", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "নিয়ামক জন্য অপেক্ষা করছে", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "ওয়ার্কফ্লো", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "ওয়ার্কফ্লো", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "কর্মক্ষেত্র", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "কর্মক্ষেত্র", // "mydspace.title": "MyDSpace", "mydspace.title": "আমার ডিস্পেস", diff --git a/src/assets/i18n/ca.json5 b/src/assets/i18n/ca.json5 index 34279548bb..ad8fe49424 100644 --- a/src/assets/i18n/ca.json5 +++ b/src/assets/i18n/ca.json5 @@ -4190,20 +4190,20 @@ // "mydspace.show.workspace": "Your Submissions", "mydspace.show.workspace": "Els seus enviaments", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "Arxivat", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "Arxivat", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Validació", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Validació", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "Esperant el controlador", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Esperant el controlador", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "Flux de treball", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Flux de treball", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Espai de treball", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Espai de treball", // "mydspace.title": "MyDSpace", "mydspace.title": "El meu DSpace", diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index f3155e9f57..7f9583a50e 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -4187,25 +4187,25 @@ // TODO New key - Add a translation "mydspace.show.workspace": "Your Submissions", - // "mydspace.status.archived": "Archived", + // "mydspace.status.mydspaceArchived": "Archived", // TODO New key - Add a translation - "mydspace.status.archived": "Archived", + "mydspace.status.mydspaceArchived": "Archived", - // "mydspace.status.validation": "Validation", + // "mydspace.status.mydspaceValidation": "Validation", // TODO New key - Add a translation - "mydspace.status.validation": "Validation", + "mydspace.status.mydspaceValidation": "Validation", - // "mydspace.status.waiting-for-controller": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", // TODO New key - Add a translation - "mydspace.status.waiting-for-controller": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for controller", - // "mydspace.status.workflow": "Workflow", + // "mydspace.status.mydspaceWorkflow": "Workflow", // TODO New key - Add a translation - "mydspace.status.workflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Workflow", - // "mydspace.status.workspace": "Workspace", + // "mydspace.status.mydspaceWorkspace": "Workspace", // TODO New key - Add a translation - "mydspace.status.workspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Workspace", // "mydspace.title": "MyDSpace", // TODO New key - Add a translation diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index b03dc21e5a..4ebce8012d 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -3484,20 +3484,20 @@ // "mydspace.show.workspace": "Your Submissions", "mydspace.show.workspace": "Ihre Veröffentlichungen", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "Archiviert", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "Archiviert", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Validierung", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Validierung", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "Warten auf die Überprüfung", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Warten auf die Überprüfung", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "Geschäftsgang", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Geschäftsgang", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Arbeitsbereich", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Arbeitsbereich", // "mydspace.title": "MyDSpace", "mydspace.title": "Mein DSpace", @@ -4487,6 +4487,21 @@ // "search.filters.discoverable.false": "Yes", "search.filters.discoverable.false": "Ja", + // "search.filters.namedresourcetype.Archived": "Archived", + "search.filters.namedresourcetype.Archived": "Archiviert", + + // "search.filters.namedresourcetype.Validation": "Validation", + "search.filters.namedresourcetype.Validation": "Validierung", + + // "search.filters.namedresourcetype.Waiting for Controller": "Waiting for Controller", + "search.filters.namedresourcetype.Waiting for Controller": "Warten auf die Überprüfung", + + // "search.filters.namedresourcetype.Workflow": "Workflow", + "search.filters.namedresourcetype.Workflow": "Geschäftsgang", + + // "search.filters.namedresourcetype.Workspace": "Workspace", + "search.filters.namedresourcetype.Workspace": "Arbeitsbereich", + // "search.filters.withdrawn.true": "Yes", "search.filters.withdrawn.true": "Ja", diff --git a/src/assets/i18n/el.json5 b/src/assets/i18n/el.json5 index af8a4fce49..176eadff62 100644 --- a/src/assets/i18n/el.json5 +++ b/src/assets/i18n/el.json5 @@ -1315,11 +1315,11 @@ "mydspace.search-form.placeholder": "Αναζήτηση στο mydspace...", "mydspace.show.workflow": "Εργασίες ροής εργασιών", "mydspace.show.workspace": "Οι Υποβολές σας", - "mydspace.status.archived": "Αρχειοθετημένα", - "mydspace.status.validation": "Επικύρωση", - "mydspace.status.waiting-for-controller": "Αναμονή για τον ελεγκτή", - "mydspace.status.workflow": "Ροή εργασιών", - "mydspace.status.workspace": "Χώρος εργασίας", + "mydspace.status.mydspaceArchived": "Αρχειοθετημένα", + "mydspace.status.mydspaceValidation": "Επικύρωση", + "mydspace.status.mydspaceWaitingController": "Αναμονή για τον ελεγκτή", + "mydspace.status.mydspaceWorkflow": "Ροή εργασιών", + "mydspace.status.mydspaceWorkspace": "Χώρος εργασίας", "mydspace.title": "MyDSpace", "mydspace.upload.upload-failed": "Σφάλμα κατά τη δημιουργία νέου χώρου εργασίας. Επαληθεύστε το περιεχόμενο που ανεβάσατε πριν δοκιμάσετε ξανά.", "mydspace.upload.upload-failed-manyentries": "Μη επεξεργάσιμο αρχείο. Εντοπίστηκαν πάρα πολλές καταχωρίσεις, αλλά επιτρέπεται μόνο μία για αρχείο.", diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 6c91bae4c1..5c17fc8e42 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3812,6 +3812,16 @@ "search.filters.discoverable.false": "Yes", + "search.filters.namedresourcetype.Archived": "Archived", + + "search.filters.namedresourcetype.Validation": "Validation", + + "search.filters.namedresourcetype.Waiting for Controller": "Waiting for Controller", + + "search.filters.namedresourcetype.Workflow": "Workflow", + + "search.filters.namedresourcetype.Workspace": "Workspace", + "search.filters.withdrawn.true": "Yes", "search.filters.withdrawn.false": "No", diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index 7a2f2fa3db..5a0e40af42 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -4560,20 +4560,20 @@ // "mydspace.show.supervisedWorkspace": "Supervised items", "mydspace.show.supervisedWorkspace": "Ítems supervisados", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "Archivado", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "Archivado", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Validación", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Validación", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "Esperando al controlador", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Esperando al controlador", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "Flujo de trabajo", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Flujo de trabajo", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Espacio de trabajo", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Espacio de trabajo", // "mydspace.title": "MyDSpace", "mydspace.title": "Mi DSpace", diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 8ee4dc7d22..699ca5cc27 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -3822,20 +3822,20 @@ //"mydspace.show.supervisedWorkspace": "Supervised items", "mydspace.show.supervisedWorkspace": "Items supervisés", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "Archivés", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "Archivés", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "En cours de validation", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "En cours de validation", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "En attente d'assignation", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "En attente d'assignation", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "En traitement", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "En traitement", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Dépôts en cours", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Dépôts en cours", // "mydspace.title": "MyDSpace", "mydspace.title": "Mon compte DSpace", diff --git a/src/assets/i18n/gd.json5 b/src/assets/i18n/gd.json5 index 6096073d56..55a53bc6f1 100644 --- a/src/assets/i18n/gd.json5 +++ b/src/assets/i18n/gd.json5 @@ -3867,20 +3867,20 @@ // "mydspace.show.workspace": "Your Submissions", "mydspace.show.workspace": "Do Chur-a-steachan", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "San Tasglann", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "San Tasglann", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Dearbhadh", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Dearbhadh", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "A' feitheamh riaghladair", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "A' feitheamh riaghladair", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "Sruth-obrach", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Sruth-obrach", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Raon-obrach", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Raon-obrach", // "mydspace.title": "MyDSpace", "mydspace.title": "MoDSpace", diff --git a/src/assets/i18n/hi.json5 b/src/assets/i18n/hi.json5 index 53fc106187..68eb6c1f12 100644 --- a/src/assets/i18n/hi.json5 +++ b/src/assets/i18n/hi.json5 @@ -2677,15 +2677,15 @@ "mydspace.show.workspace": "आपकी प्रस्तुतियां", - "mydspace.status.archived": "संग्रहीत", + "mydspace.status.mydspaceArchived": "संग्रहीत", - "mydspace.status.validation": "सत्यापन", + "mydspace.status.mydspaceValidation": "सत्यापन", - "mydspace.status.waiting-for-controller": "नियंत्रक की प्रतीक्षा कर रहा है", + "mydspace.status.mydspaceWaitingController": "नियंत्रक की प्रतीक्षा कर रहा है", - "mydspace.status.workflow": "कार्यप्रवाह", + "mydspace.status.mydspaceWorkflow": "कार्यप्रवाह", - "mydspace.status.workspace": "कार्यस्थान", + "mydspace.status.mydspaceWorkspace": "कार्यस्थान", "mydspace.title": "मेरा डीस्पेस", diff --git a/src/assets/i18n/hu.json5 b/src/assets/i18n/hu.json5 index 373d73aec5..e1076c7512 100644 --- a/src/assets/i18n/hu.json5 +++ b/src/assets/i18n/hu.json5 @@ -4983,20 +4983,20 @@ // TODO New key - Add a translation "mydspace.show.supervisedWorkspace": "Supervised items", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "Tárolva", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "Tárolva", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Érvényesítés", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Érvényesítés", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "Várakozás a kontrollerre", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Várakozás a kontrollerre", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "Munkafolyamat", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Munkafolyamat", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Munkafelület", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Munkafelület", // "mydspace.title": "MyDSpace", "mydspace.title": "MyDSpace", diff --git a/src/assets/i18n/it.json5 b/src/assets/i18n/it.json5 index 4131d0bee6..1a97eca501 100644 --- a/src/assets/i18n/it.json5 +++ b/src/assets/i18n/it.json5 @@ -4813,20 +4813,20 @@ // TODO New key - Add a translation "mydspace.show.supervisedWorkspace": "Supervised items", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "Archiviati", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "Archiviati", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Convalida", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Convalida", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "In attesa del controllo", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "In attesa del controllo", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "Workflow", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Workflow", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Workspace", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Workspace", // "mydspace.title": "MyDSpace", "mydspace.title": "MyDSpace", diff --git a/src/assets/i18n/ja.json5 b/src/assets/i18n/ja.json5 index 94dfc9aa98..da2385fd62 100644 --- a/src/assets/i18n/ja.json5 +++ b/src/assets/i18n/ja.json5 @@ -4276,25 +4276,25 @@ // TODO New key - Add a translation "mydspace.show.workspace": "Your Submissions", - // "mydspace.status.archived": "Archived", + // "mydspace.status.mydspaceArchived": "Archived", // TODO New key - Add a translation - "mydspace.status.archived": "Archived", + "mydspace.status.mydspaceArchived": "Archived", - // "mydspace.status.validation": "Validation", + // "mydspace.status.mydspaceValidation": "Validation", // TODO New key - Add a translation - "mydspace.status.validation": "Validation", + "mydspace.status.mydspaceValidation": "Validation", - // "mydspace.status.waiting-for-controller": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", // TODO New key - Add a translation - "mydspace.status.waiting-for-controller": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for controller", - // "mydspace.status.workflow": "Workflow", + // "mydspace.status.mydspaceWorkflow": "Workflow", // TODO New key - Add a translation - "mydspace.status.workflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Workflow", - // "mydspace.status.workspace": "Workspace", + // "mydspace.status.mydspaceWorkspace": "Workspace", // TODO New key - Add a translation - "mydspace.status.workspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Workspace", // "mydspace.title": "MyDSpace", // TODO New key - Add a translation diff --git a/src/assets/i18n/kk.json5 b/src/assets/i18n/kk.json5 index 354eb1104a..d23dc23c47 100644 --- a/src/assets/i18n/kk.json5 +++ b/src/assets/i18n/kk.json5 @@ -4139,20 +4139,20 @@ // "mydspace.show.workspace": "Your Submissions", "mydspace.show.workspace": "Сіздің өтініштеріңіз", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "Мұрағатталған", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "Мұрағатталған", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Валидация", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Валидация", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "Контроллерді күтуде", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Контроллерді күтуде", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "Жұмыс барысы", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Жұмыс барысы", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Жұмыс кеңістігі", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Жұмыс кеңістігі", // "mydspace.title": "MyDSpace", "mydspace.title": "MyDSpace", diff --git a/src/assets/i18n/lv.json5 b/src/assets/i18n/lv.json5 index 9b4058a3e6..81e2383a1f 100644 --- a/src/assets/i18n/lv.json5 +++ b/src/assets/i18n/lv.json5 @@ -3492,20 +3492,20 @@ // "mydspace.show.workspace": "Your Submissions", "mydspace.show.workspace": "Jūsu Iesniegumi", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "Arhivēts", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "Arhivēts", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Validācija", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Validācija", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "Gaida kontrolieri", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Gaida kontrolieri", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "Darba plūsma", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Darba plūsma", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Darbavieta", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Darbavieta", // "mydspace.title": "MyDSpace", "mydspace.title": "Mans DSpace", diff --git a/src/assets/i18n/nl.json5 b/src/assets/i18n/nl.json5 index 520a8ca427..280a87b96f 100644 --- a/src/assets/i18n/nl.json5 +++ b/src/assets/i18n/nl.json5 @@ -3764,20 +3764,20 @@ // "mydspace.show.workspace": "Your Submissions", "mydspace.show.workspace": "Uw Submissions", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "Opgeslagen", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "Opgeslagen", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Validatie", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Validatie", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "Wachten op controlleur", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Wachten op controlleur", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "Workflow", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Workflow", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Workspace", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Workspace", // "mydspace.title": "MyDSpace", "mydspace.title": "MyDSpace", diff --git a/src/assets/i18n/pl.json5 b/src/assets/i18n/pl.json5 index d748b94888..3512653705 100644 --- a/src/assets/i18n/pl.json5 +++ b/src/assets/i18n/pl.json5 @@ -1207,11 +1207,11 @@ "mydspace.search-form.placeholder": "Wyszukaj w mydspace...", "mydspace.show.workflow": "Wszystkie zadania", "mydspace.show.workspace": "Twoje zadania", - "mydspace.status.archived": "Zarchiwizowano", - "mydspace.status.validation": "Walidacja", - "mydspace.status.waiting-for-controller": "Oczekiwanie na redaktora", - "mydspace.status.workflow": "Workflow", - "mydspace.status.workspace": "Wersja robocza", + "mydspace.status.mydspaceArchived": "Zarchiwizowano", + "mydspace.status.mydspaceValidation": "Walidacja", + "mydspace.status.mydspaceWaitingController": "Oczekiwanie na redaktora", + "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkspace": "Wersja robocza", "mydspace.title": "Mój DSpace", "mydspace.upload.upload-failed": "Bład podczas tworzenia nowej wersji roboczej. Sprawdź poprawność plików i spróbuj ponownie.", "mydspace.upload.upload-failed-manyentries": "Plik jest niemożliwy do przetworzenia. Wykryto wiele wejść, a dopuszczalne jest tylko jedno dla jednego pliku.", diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index 25f1e0fb7c..5d852129bc 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -3972,20 +3972,20 @@ // "mydspace.show.workspace": "Your Submissions", "mydspace.show.workspace": "Minhas Submissões", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "Arquivado", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "Arquivado", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Validação", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Validação", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "Esperando pelo controlador", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Esperando pelo controlador", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "Fluxo de trabalho", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Fluxo de trabalho", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Espaço de trabalho", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Espaço de trabalho", // "mydspace.title": "MyDSpace", "mydspace.title": "MeuDSpace", diff --git a/src/assets/i18n/pt-PT.json5 b/src/assets/i18n/pt-PT.json5 index a187ff927c..622f8cc48b 100644 --- a/src/assets/i18n/pt-PT.json5 +++ b/src/assets/i18n/pt-PT.json5 @@ -7713,20 +7713,20 @@ // "browse.metadata.srsc": "Subject Category", "browse.metadata.srsc": "vocabulário controlado (SRSC)", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "Depositado", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "Depositado", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Em validação", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Em validação", - // "mydspace.status.waiting-for-controller": "Waiting for Controller", - "mydspace.status.waiting-for-controller": "Aguarda validador", + // "mydspace.status.mydspaceWaitingController": "Waiting for Controller", + "mydspace.status.mydspaceWaitingController": "Aguarda validador", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "Em fluxo de trabalho", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Em fluxo de trabalho", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Depósito por terminar", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Depósito por terminar", // "search.filters.namedresourcetype.Archived": "Archived", "search.filters.namedresourcetype.Archived": "Depositado", diff --git a/src/assets/i18n/sv.json5 b/src/assets/i18n/sv.json5 index c988577c92..4e3576ccfc 100644 --- a/src/assets/i18n/sv.json5 +++ b/src/assets/i18n/sv.json5 @@ -3934,20 +3934,20 @@ // "mydspace.show.workspace": "Your Submissions", "mydspace.show.workspace": "Dina registreringar", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "I arkivet", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "I arkivet", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Validering", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Validering", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "Väntar på kontrollant", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Väntar på kontrollant", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "Arbetsflöde", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Arbetsflöde", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "I arbetsflödet", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "I arbetsflödet", // "mydspace.title": "MyDSpace", "mydspace.title": "Mitt DSpace", diff --git a/src/assets/i18n/sw.json5 b/src/assets/i18n/sw.json5 index 47253dea9f..a470ee4b58 100644 --- a/src/assets/i18n/sw.json5 +++ b/src/assets/i18n/sw.json5 @@ -4276,25 +4276,25 @@ // TODO New key - Add a translation "mydspace.show.workspace": "Your Submissions", - // "mydspace.status.archived": "Archived", + // "mydspace.status.mydspaceArchived": "Archived", // TODO New key - Add a translation - "mydspace.status.archived": "Archived", + "mydspace.status.mydspaceArchived": "Archived", - // "mydspace.status.validation": "Validation", + // "mydspace.status.mydspaceValidation": "Validation", // TODO New key - Add a translation - "mydspace.status.validation": "Validation", + "mydspace.status.mydspaceValidation": "Validation", - // "mydspace.status.waiting-for-controller": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", // TODO New key - Add a translation - "mydspace.status.waiting-for-controller": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for controller", - // "mydspace.status.workflow": "Workflow", + // "mydspace.status.mydspaceWorkflow": "Workflow", // TODO New key - Add a translation - "mydspace.status.workflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Workflow", - // "mydspace.status.workspace": "Workspace", + // "mydspace.status.mydspaceWorkspace": "Workspace", // TODO New key - Add a translation - "mydspace.status.workspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Workspace", // "mydspace.title": "MyDSpace", // TODO New key - Add a translation diff --git a/src/assets/i18n/tr.json5 b/src/assets/i18n/tr.json5 index e9869e0019..153eaa1281 100644 --- a/src/assets/i18n/tr.json5 +++ b/src/assets/i18n/tr.json5 @@ -3257,20 +3257,20 @@ // "mydspace.show.workspace": "Your Submissions", "mydspace.show.workspace": "Gönderimleriniz", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "Arşivlendi", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "Arşivlendi", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Doğrulama", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Doğrulama", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "Kontrolör bekleniyor", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Kontrolör bekleniyor", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "İş akışı", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "İş akışı", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Çalışma alanı", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Çalışma alanı", // "mydspace.title": "MyDSpace", "mydspace.title": "MyDSpace", diff --git a/src/assets/i18n/uk.json5 b/src/assets/i18n/uk.json5 index 5cbb480179..7df55fa236 100644 --- a/src/assets/i18n/uk.json5 +++ b/src/assets/i18n/uk.json5 @@ -3383,20 +3383,20 @@ // "mydspace.show.workspace": "Your Submissions", "mydspace.show.workspace": "Ваші надіслані документи ", - // "mydspace.status.archived": "Archived", - "mydspace.status.archived": "Заархівовані", + // "mydspace.status.mydspaceArchived": "Archived", + "mydspace.status.mydspaceArchived": "Заархівовані", - // "mydspace.status.validation": "Validation", - "mydspace.status.validation": "Перевірка", + // "mydspace.status.mydspaceValidation": "Validation", + "mydspace.status.mydspaceValidation": "Перевірка", - // "mydspace.status.waiting-for-controller": "Waiting for controller", - "mydspace.status.waiting-for-controller": "Чекаємо контролера", + // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Чекаємо контролера", - // "mydspace.status.workflow": "Workflow", - "mydspace.status.workflow": "Робочий процес", + // "mydspace.status.mydspaceWorkflow": "Workflow", + "mydspace.status.mydspaceWorkflow": "Робочий процес", - // "mydspace.status.workspace": "Workspace", - "mydspace.status.workspace": "Робоче середовище", + // "mydspace.status.mydspaceWorkspace": "Workspace", + "mydspace.status.mydspaceWorkspace": "Робоче середовище", // "mydspace.title": "MyDSpace", "mydspace.title": "Моє середовище", diff --git a/src/assets/i18n/vi.json5 b/src/assets/i18n/vi.json5 index 7627818978..f254df34fe 100644 --- a/src/assets/i18n/vi.json5 +++ b/src/assets/i18n/vi.json5 @@ -1449,11 +1449,11 @@ "mydspace.search-form.placeholder": "Tìm kiếm trong trang cá nhân của tôi...", "mydspace.show.workflow": "Tất cả nhiệm vụ", "mydspace.show.workspace": "Tài liệu của tôi", - "mydspace.status.archived": "Đã lưu trữ", - "mydspace.status.validation": "Đang kiểm tra", - "mydspace.status.waiting-for-controller": "Đợi nhận nhiệm vụ", - "mydspace.status.workflow": "Đang kiểm duyệt", - "mydspace.status.workspace": "Đang biên mục", + "mydspace.status.mydspaceArchived": "Đã lưu trữ", + "mydspace.status.mydspaceValidation": "Đang kiểm tra", + "mydspace.status.mydspaceWaitingController": "Đợi nhận nhiệm vụ", + "mydspace.status.mydspaceWorkflow": "Đang kiểm duyệt", + "mydspace.status.mydspaceWorkspace": "Đang biên mục", "mydspace.title": "Trang cá nhân", "mydspace.upload.upload-failed": "Có lỗi xảy ra khi tạo tài liệu mới. Vui lòng xác minh nội dung đã tải lên trước khi thử lại.", "mydspace.upload.upload-failed-manyentries": "Không thể xử lý tệp tin. Có quá nhiều mục trong khi hệ thống chỉ cho phép một mục trong tệp tin.", From cfcf93ecf8bee2a22b1b0d3ddc791d092f27ef94 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 17 Jul 2023 15:08:54 +0200 Subject: [PATCH 027/875] 104126: ProcessDetailComponent test improvement --- .../detail/process-detail.component.spec.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/app/process-page/detail/process-detail.component.spec.ts b/src/app/process-page/detail/process-detail.component.spec.ts index 9552f9a092..9a0d89a882 100644 --- a/src/app/process-page/detail/process-detail.component.spec.ts +++ b/src/app/process-page/detail/process-detail.component.spec.ts @@ -147,7 +147,7 @@ describe('ProcessDetailComponent', () => { providers: [ { provide: ActivatedRoute, - useValue: { data: observableOf({ process: createSuccessfulRemoteDataObject(process) }) } + useValue: { data: observableOf({ process: createSuccessfulRemoteDataObject(process) }), snapshot: { params: { id: 1 } } }, }, { provide: ProcessDataService, useValue: processService }, { provide: BitstreamDataService, useValue: bitstreamDataService }, @@ -310,10 +310,11 @@ describe('ProcessDetailComponent', () => { }); it('should call refresh method every 5 seconds, until process is completed', fakeAsync(() => { - spyOn(component, 'refresh'); - spyOn(component, 'stopRefreshTimer'); + spyOn(component, 'refresh').and.callThrough(); + spyOn(component, 'stopRefreshTimer').and.callThrough(); - process.processStatus = ProcessStatus.COMPLETED; + // start off with a running process in order for the refresh counter starts counting up + process.processStatus = ProcessStatus.RUNNING; // set findbyId to return a completed process (processService.findById as jasmine.Spy).and.returnValue(observableOf(createSuccessfulRemoteDataObject(process))); @@ -336,6 +337,10 @@ describe('ProcessDetailComponent', () => { tick(1001); // 1 second + 1 ms by the setTimeout expect(component.refreshCounter$.value).toBe(0); // 1 - 1 + // set the process to completed right before the counter checks the process + process.processStatus = ProcessStatus.COMPLETED; + (processService.findById as jasmine.Spy).and.returnValue(observableOf(createSuccessfulRemoteDataObject(process))); + tick(1000); // 1 second expect(component.refresh).toHaveBeenCalledTimes(1); From bbb50f2858f9c0fc6b6cd9ddf15ed54c7dc10e30 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 21:28:22 +0000 Subject: [PATCH 028/875] Bump word-wrap from 1.2.3 to 1.2.5 Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.5. - [Release notes](https://github.com/jonschlinkert/word-wrap/releases) - [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5) --- updated-dependencies: - dependency-name: word-wrap dependency-type: indirect ... Signed-off-by: dependabot[bot] (cherry picked from commit 2fec33e70af5de3a2533f4ff33aabaff4aae03fc) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 21de68a480..e53a070d16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11743,9 +11743,9 @@ wildcard@^2.0.0: integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== word-wrap@^1.2.3, word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== wrap-ansi@^6.2.0: version "6.2.0" From 2fd53c7ad21382fa822ef92343f90dd8ae5a1da7 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 22 Aug 2023 16:32:55 -0500 Subject: [PATCH 029/875] Replace mentions of demo7.dspace.org and api7.dspace.org with demo or sandbox --- README.md | 6 +++--- config/config.example.yml | 2 +- config/config.yml | 2 +- docker/README.md | 4 ++-- docker/docker-compose-dist.yml | 2 +- docs/Configuration.md | 4 ++-- src/app/core/services/browser-hard-redirect.service.ts | 4 ++-- src/app/core/services/hard-redirect.service.ts | 4 ++-- src/app/core/services/server-hard-redirect.service.ts | 4 ++-- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 689c64a292..ebc24f8b91 100644 --- a/README.md +++ b/README.md @@ -157,8 +157,8 @@ DSPACE_UI_SSL => DSPACE_SSL The same settings can also be overwritten by setting system environment variables instead, E.g.: ```bash -export DSPACE_HOST=api7.dspace.org -export DSPACE_UI_PORT=4200 +export DSPACE_HOST=demo.dspace.org +export DSPACE_UI_PORT=4000 ``` The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides external config set by `DSPACE_APP_CONFIG_PATH` overrides **`config.(prod or dev).yml`** @@ -288,7 +288,7 @@ E2E tests (aka integration tests) use [Cypress.io](https://www.cypress.io/). Con The test files can be found in the `./cypress/integration/` folder. Before you can run e2e tests, two things are REQUIRED: -1. You MUST be running the DSpace backend (i.e. REST API) locally. The e2e tests will *NOT* succeed if run against our demo REST API (https://api7.dspace.org/server/), as that server is uncontrolled and may have content added/removed at any time. +1. You MUST be running the DSpace backend (i.e. REST API) locally. The e2e tests will *NOT* succeed if run against our demo/sandbox REST API (https://demo.dspace.org/server/ or https://sandbox.dspace.org/server/), as those sites may have content added/removed at any time. * After starting up your backend on localhost, make sure either your `config.prod.yml` or `config.dev.yml` has its `rest` settings defined to use that localhost backend. * If you'd prefer, you may instead use environment variables as described at [Configuring](#configuring). For example: ``` diff --git a/config/config.example.yml b/config/config.example.yml index ea38303fa3..b7ea190c55 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -22,7 +22,7 @@ ui: # 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. rest: ssl: true - host: api7.dspace.org + host: sandbox.dspace.org port: 443 # NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript nameSpace: /server diff --git a/config/config.yml b/config/config.yml index b5eecd112f..109db60ca9 100644 --- a/config/config.yml +++ b/config/config.yml @@ -1,5 +1,5 @@ rest: ssl: true - host: api7.dspace.org + host: sandbox.dspace.org port: 443 nameSpace: /server diff --git a/docker/README.md b/docker/README.md index 42deb793f9..37d071a86f 100644 --- a/docker/README.md +++ b/docker/README.md @@ -101,8 +101,8 @@ and the backend at http://localhost:8080/server/ ## Run DSpace Angular dist build with DSpace Demo site backend -This allows you to run the Angular UI in *production* mode, pointing it at the demo backend -(https://api7.dspace.org/server/). +This allows you to run the Angular UI in *production* mode, pointing it at the demo or sandbox backend +(https://demo.dspace.org/server/ or https://sandbox.dspace.org/server/). ``` docker-compose -f docker/docker-compose-dist.yml pull diff --git a/docker/docker-compose-dist.yml b/docker/docker-compose-dist.yml index 1c75539da9..3d57174b13 100644 --- a/docker/docker-compose-dist.yml +++ b/docker/docker-compose-dist.yml @@ -24,7 +24,7 @@ services: # This is because Server Side Rendering (SSR) currently requires a public URL, # see this bug: https://github.com/DSpace/dspace-angular/issues/1485 DSPACE_REST_SSL: 'true' - DSPACE_REST_HOST: api7.dspace.org + DSPACE_REST_HOST: sandbox.dspace.org DSPACE_REST_PORT: 443 DSPACE_REST_NAMESPACE: /server image: dspace/dspace-angular:dspace-7_x-dist diff --git a/docs/Configuration.md b/docs/Configuration.md index 62fa444cc0..01fd83c94d 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -48,7 +48,7 @@ dspace-angular connects to your DSpace installation by using its REST endpoint. ```yaml rest: ssl: true - host: api7.dspace.org + host: demo.dspace.org port: 443 nameSpace: /server } @@ -57,7 +57,7 @@ rest: Alternately you can set the following environment variables. If any of these are set, it will override all configuration files: ``` DSPACE_REST_SSL=true - DSPACE_REST_HOST=api7.dspace.org + DSPACE_REST_HOST=demo.dspace.org DSPACE_REST_PORT=443 DSPACE_REST_NAMESPACE=/server ``` diff --git a/src/app/core/services/browser-hard-redirect.service.ts b/src/app/core/services/browser-hard-redirect.service.ts index 4ef9548899..827e83f0b7 100644 --- a/src/app/core/services/browser-hard-redirect.service.ts +++ b/src/app/core/services/browser-hard-redirect.service.ts @@ -38,8 +38,8 @@ export class BrowserHardRedirectService extends HardRedirectService { /** * Get the origin of the current URL * i.e. "://" [ ":" ] - * e.g. if the URL is https://demo7.dspace.org/search?query=test, - * the origin would be https://demo7.dspace.org + * e.g. if the URL is https://demo.dspace.org/search?query=test, + * the origin would be https://demo.dspace.org */ getCurrentOrigin(): string { return this.location.origin; diff --git a/src/app/core/services/hard-redirect.service.ts b/src/app/core/services/hard-redirect.service.ts index 826c7e4fa9..e6104cefb9 100644 --- a/src/app/core/services/hard-redirect.service.ts +++ b/src/app/core/services/hard-redirect.service.ts @@ -25,8 +25,8 @@ export abstract class HardRedirectService { /** * Get the origin of the current URL * i.e. "://" [ ":" ] - * e.g. if the URL is https://demo7.dspace.org/search?query=test, - * the origin would be https://demo7.dspace.org + * e.g. if the URL is https://demo.dspace.org/search?query=test, + * the origin would be https://demo.dspace.org */ abstract getCurrentOrigin(): string; } diff --git a/src/app/core/services/server-hard-redirect.service.ts b/src/app/core/services/server-hard-redirect.service.ts index 8c45cc864b..d71318d7b8 100644 --- a/src/app/core/services/server-hard-redirect.service.ts +++ b/src/app/core/services/server-hard-redirect.service.ts @@ -69,8 +69,8 @@ export class ServerHardRedirectService extends HardRedirectService { /** * Get the origin of the current URL * i.e. "://" [ ":" ] - * e.g. if the URL is https://demo7.dspace.org/search?query=test, - * the origin would be https://demo7.dspace.org + * e.g. if the URL is https://demo.dspace.org/search?query=test, + * the origin would be https://demo.dspace.org */ getCurrentOrigin(): string { return this.req.protocol + '://' + this.req.headers.host; From 5853e49bd05eba84141d2bc1aa3f4ff94fabdf04 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 22 Aug 2023 16:44:10 -0500 Subject: [PATCH 030/875] Update default configs to use https://demo.dspace.org/server/ --- config/config.example.yml | 2 +- config/config.yml | 2 +- docker/docker-compose-dist.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index b7ea190c55..7e61155916 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -22,7 +22,7 @@ ui: # 'synced' with the 'dspace.server.url' setting in your backend's local.cfg. rest: ssl: true - host: sandbox.dspace.org + host: demo.dspace.org port: 443 # NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript nameSpace: /server diff --git a/config/config.yml b/config/config.yml index 109db60ca9..dcf5389378 100644 --- a/config/config.yml +++ b/config/config.yml @@ -1,5 +1,5 @@ rest: ssl: true - host: sandbox.dspace.org + host: demo.dspace.org port: 443 nameSpace: /server diff --git a/docker/docker-compose-dist.yml b/docker/docker-compose-dist.yml index 3d57174b13..00225e8052 100644 --- a/docker/docker-compose-dist.yml +++ b/docker/docker-compose-dist.yml @@ -24,7 +24,7 @@ services: # This is because Server Side Rendering (SSR) currently requires a public URL, # see this bug: https://github.com/DSpace/dspace-angular/issues/1485 DSPACE_REST_SSL: 'true' - DSPACE_REST_HOST: sandbox.dspace.org + DSPACE_REST_HOST: demo.dspace.org DSPACE_REST_PORT: 443 DSPACE_REST_NAMESPACE: /server image: dspace/dspace-angular:dspace-7_x-dist From 36868c06f0fb5fd66c21a5a7d39c1f45928afe55 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Tue, 15 Aug 2023 10:16:50 -0500 Subject: [PATCH 031/875] 2437 Correct and clarify commented-out code in several custom components - Correct the path in commented-out code in the custom theme's components - Clarify that the workflow-item-sen-back.component.scss file is a stub - It has no corresponding SCSS file in the base theme (cherry picked from commit 0906234a293a3dbe9057ef13cc4c5b690b1cf25b) --- src/themes/custom/app/footer/footer.component.ts | 4 ++-- .../header-nav-wrapper/header-navbar-wrapper.component.ts | 4 ++-- .../app/shared/auth-nav-menu/auth-nav-menu.component.ts | 4 ++-- .../custom/app/shared/object-list/object-list.component.ts | 2 +- .../workflow-item-send-back.component.ts | 5 ++++- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/themes/custom/app/footer/footer.component.ts b/src/themes/custom/app/footer/footer.component.ts index de80ceb311..81286fb662 100644 --- a/src/themes/custom/app/footer/footer.component.ts +++ b/src/themes/custom/app/footer/footer.component.ts @@ -3,9 +3,9 @@ import { FooterComponent as BaseComponent } from '../../../../app/footer/footer. @Component({ selector: 'ds-footer', - // styleUrls: ['footer.component.scss'], + // styleUrls: ['./footer.component.scss'], styleUrls: ['../../../../app/footer/footer.component.scss'], - // templateUrl: 'footer.component.html' + // templateUrl: './footer.component.html' templateUrl: '../../../../app/footer/footer.component.html' }) export class FooterComponent extends BaseComponent { diff --git a/src/themes/custom/app/header-nav-wrapper/header-navbar-wrapper.component.ts b/src/themes/custom/app/header-nav-wrapper/header-navbar-wrapper.component.ts index 875b5f69b8..e049543630 100644 --- a/src/themes/custom/app/header-nav-wrapper/header-navbar-wrapper.component.ts +++ b/src/themes/custom/app/header-nav-wrapper/header-navbar-wrapper.component.ts @@ -6,9 +6,9 @@ import { HeaderNavbarWrapperComponent as BaseComponent } from '../../../../app/h */ @Component({ selector: 'ds-header-navbar-wrapper', - // styleUrls: ['header-navbar-wrapper.component.scss'], + // styleUrls: ['./header-navbar-wrapper.component.scss'], styleUrls: ['../../../../app/header-nav-wrapper/header-navbar-wrapper.component.scss'], - // templateUrl: 'header-navbar-wrapper.component.html', + // templateUrl: './header-navbar-wrapper.component.html', templateUrl: '../../../../app/header-nav-wrapper/header-navbar-wrapper.component.html', }) export class HeaderNavbarWrapperComponent extends BaseComponent { diff --git a/src/themes/custom/app/shared/auth-nav-menu/auth-nav-menu.component.ts b/src/themes/custom/app/shared/auth-nav-menu/auth-nav-menu.component.ts index af54aacd44..ff5f09eb76 100644 --- a/src/themes/custom/app/shared/auth-nav-menu/auth-nav-menu.component.ts +++ b/src/themes/custom/app/shared/auth-nav-menu/auth-nav-menu.component.ts @@ -9,9 +9,9 @@ import { fadeInOut, fadeOut } from '../../../../../app/shared/animations/fade'; */ @Component({ selector: 'ds-auth-nav-menu', - // templateUrl: 'auth-nav-menu.component.html', + // templateUrl: './auth-nav-menu.component.html', templateUrl: '../../../../../app/shared/auth-nav-menu/auth-nav-menu.component.html', - // styleUrls: ['auth-nav-menu.component.scss'], + // styleUrls: ['./auth-nav-menu.component.scss'], styleUrls: ['../../../../../app/shared/auth-nav-menu/auth-nav-menu.component.scss'], animations: [fadeInOut, fadeOut] }) diff --git a/src/themes/custom/app/shared/object-list/object-list.component.ts b/src/themes/custom/app/shared/object-list/object-list.component.ts index 49f464610f..a376336870 100644 --- a/src/themes/custom/app/shared/object-list/object-list.component.ts +++ b/src/themes/custom/app/shared/object-list/object-list.component.ts @@ -9,7 +9,7 @@ import { ObjectListComponent as BaseComponent} from '../../../../../app/shared/o selector: 'ds-object-list', // styleUrls: ['./object-list.component.scss'], styleUrls: ['../../../../../app/shared/object-list/object-list.component.scss'], - // templateUrl: 'object-list.component.html' + // templateUrl: './object-list.component.html' templateUrl: '../../../../../app/shared/object-list/object-list.component.html' }) diff --git a/src/themes/custom/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts b/src/themes/custom/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts index 49121e64b9..ac8ffc99a9 100644 --- a/src/themes/custom/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts +++ b/src/themes/custom/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts @@ -3,7 +3,10 @@ import { WorkflowItemSendBackComponent as BaseComponent } from '../../../../../a @Component({ selector: 'ds-workflow-item-send-back', - // styleUrls: ['workflow-item-send-back.component.scss'], + // NOTE: the SCSS file for workflow-item-action-page does not have a corresponding file in the original + // implementation, so this commented out line below is a stub, here if you + // need it, but you probably don't need it. + // styleUrls: ['./workflow-item-send-back.component.scss'], // templateUrl: './workflow-item-send-back.component.html' templateUrl: '../../../../../app/workflowitems-edit-page/workflow-item-action-page.component.html' }) From 3292222e47bbea25232a34166b31f048087c1398 Mon Sep 17 00:00:00 2001 From: Hardy Pottinger Date: Tue, 15 Aug 2023 10:26:38 -0500 Subject: [PATCH 032/875] fix lint error in workflow-item-send-back.component.ts (cherry picked from commit 518cc714f2286889d2e66a4f013b954660e7ba5c) --- .../workflow-item-send-back.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/themes/custom/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts b/src/themes/custom/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts index ac8ffc99a9..022c46ef22 100644 --- a/src/themes/custom/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts +++ b/src/themes/custom/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts @@ -6,7 +6,7 @@ import { WorkflowItemSendBackComponent as BaseComponent } from '../../../../../a // NOTE: the SCSS file for workflow-item-action-page does not have a corresponding file in the original // implementation, so this commented out line below is a stub, here if you // need it, but you probably don't need it. - // styleUrls: ['./workflow-item-send-back.component.scss'], + // styleUrls: ['./workflow-item-send-back.component.scss'], // templateUrl: './workflow-item-send-back.component.html' templateUrl: '../../../../../app/workflowitems-edit-page/workflow-item-action-page.component.html' }) From fe8429ebbe0e7733478157cf22a0f25820d79e87 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 23 Aug 2023 17:05:42 -0500 Subject: [PATCH 033/875] Enable new skip merge commit feature --- .github/workflows/port_merged_pull_request.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/port_merged_pull_request.yml b/.github/workflows/port_merged_pull_request.yml index 50faf3f886..109835d14d 100644 --- a/.github/workflows/port_merged_pull_request.yml +++ b/.github/workflows/port_merged_pull_request.yml @@ -39,6 +39,8 @@ jobs: # Copy all labels from original PR to (newly created) port PR # NOTE: The labels matching 'label_pattern' are automatically excluded copy_labels_pattern: '.*' + # Skip any merge commits in the ported PR. This means only non-merge commits are cherry-picked to the new PR + merge_commits: 'skip' # Use a personal access token (PAT) to create PR as 'dspace-bot' user. # A PAT is required in order for the new PR to trigger its own actions (for CI checks) github_token: ${{ secrets.PR_PORT_TOKEN }} \ No newline at end of file From 2078b7593a707f4dace81c78c1f049d86b841e98 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Fri, 4 Aug 2023 12:25:20 +0200 Subject: [PATCH 034/875] 103818 Add warning tooltip to "Inherit policies" checkbox on item move page --- .../edit-item-page/item-move/item-move.component.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/item-move/item-move.component.html b/src/app/item-page/edit-item-page/item-move/item-move.component.html index 589aac655f..475d36fb6f 100644 --- a/src/app/item-page/edit-item-page/item-move/item-move.component.html +++ b/src/app/item-page/edit-item-page/item-move/item-move.component.html @@ -21,7 +21,11 @@

From a5b30ea3c2bebd4a4522078a05938940da7444a3 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Fri, 4 Aug 2023 17:10:06 +0200 Subject: [PATCH 035/875] 103818 Adjusted "Inherit policies" tooltip --- .../item-page/edit-item-page/item-move/item-move.component.html | 2 +- src/assets/i18n/en.json5 | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/item-move/item-move.component.html b/src/app/item-page/edit-item-page/item-move/item-move.component.html index 475d36fb6f..c1a4a5b9a9 100644 --- a/src/app/item-page/edit-item-page/item-move/item-move.component.html +++ b/src/app/item-page/edit-item-page/item-move/item-move.component.html @@ -22,7 +22,7 @@

-
+ {{ dsoNameService.getName(node.payload) }}   {{node.payload.archivedItemsCount}} -
+
From 46ac61dcac5e6c3169a846f63d81eada002cff59 Mon Sep 17 00:00:00 2001 From: Hrafn Malmquist Date: Tue, 22 Aug 2023 04:27:23 +0100 Subject: [PATCH 049/875] Replace h2 with a h1 (cherry picked from commit 05c53ad1d49da41af474322364891289494a5f02) --- src/app/community-list-page/community-list-page.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/community-list-page/community-list-page.component.html b/src/app/community-list-page/community-list-page.component.html index 9759f4405d..4392fb87d0 100644 --- a/src/app/community-list-page/community-list-page.component.html +++ b/src/app/community-list-page/community-list-page.component.html @@ -1,4 +1,4 @@
-

{{ 'communityList.title' | translate }}

+

{{ 'communityList.title' | translate }}

From 9fc4e213df758576f393975e12e0ae5b929330c4 Mon Sep 17 00:00:00 2001 From: Hrafn Malmquist Date: Tue, 22 Aug 2023 04:28:28 +0100 Subject: [PATCH 050/875] Add trackby function so cdktree can differentiate between new and old nodes (cherry picked from commit 5f71de885bc811152729b3fefcb6fefbeda3001e) --- .../community-list/community-list.component.html | 2 +- .../community-list/community-list.component.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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 38c9ed1ad6..18e9e84577 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 @@ -1,5 +1,5 @@ - + diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 5b2f930813..e4ad5d8c29 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -30,6 +30,7 @@ export class CommunityListComponent implements OnInit, OnDestroy { ); dataSource: CommunityListDatasource; + trackBy = (index, node: FlatNode) => node.id; paginationConfig: FindListOptions; From e89a277702aed1b3ec462a88518bffb788ac50ac Mon Sep 17 00:00:00 2001 From: Hrafn Malmquist Date: Tue, 22 Aug 2023 04:29:01 +0100 Subject: [PATCH 051/875] Improve documentation (cherry picked from commit d9b6e9d81f2fba70f7c13a1377e2f2472c20d592) --- .../community-list-service.ts | 6 ++--- .../community-list.component.ts | 25 ++++++++++++++----- .../show-more-flat-node.model.ts | 2 +- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/app/community-list-page/community-list-service.ts b/src/app/community-list-page/community-list-service.ts index 99e9dbeb0d..67715716da 100644 --- a/src/app/community-list-page/community-list-service.ts +++ b/src/app/community-list-page/community-list-service.ts @@ -25,7 +25,7 @@ import { ShowMoreFlatNode } from './show-more-flat-node.model'; import { FindListOptions } from '../core/data/find-list-options.model'; import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface'; -// Helper method to combine an flatten an array of observables of flatNode arrays +// Helper method to combine and flatten an array of observables of flatNode arrays export const combineAndFlatten = (obsList: Observable[]): Observable => observableCombineLatest([...obsList]).pipe( map((matrix: any[][]) => [].concat(...matrix)), @@ -199,7 +199,7 @@ export class CommunityListService { * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself, * followed by flatNodes of its possible subcommunities and collection * It gets called recursively for each subcommunity to add its subcommunities and collections to the list - * Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections. + * Number of subcommunities and collections added, is dependent on the current page the parent is at for respectively subcommunities and collections. * @param community Community being transformed * @param level Depth of the community in the list, subcommunities and collections go one level deeper * @param parent Flatnode of the parent community @@ -275,7 +275,7 @@ export class CommunityListService { /** * Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0 - * Returns an observable that combines the result.payload.totalElements fo the observables that the + * Returns an observable that combines the result.payload.totalElements of the observables that the * respective services return when queried * @param community Community being checked whether it is expandable (if it has subcommunities or collections) */ diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index e4ad5d8c29..bd1d5ecb78 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -59,18 +59,28 @@ export class CommunityListComponent implements OnInit, OnDestroy { this.communityListService.saveCommunityListStateToStore(this.expandedNodes, this.loadingNode); } - // whether or not this node has children (subcommunities or collections) + /** + * Whether this node has children (subcommunities or collections) + * @param _ + * @param node + */ hasChild(_: number, node: FlatNode) { return node.isExpandable$; } - // whether or not it is a show more node (contains no data, but is indication that there are more topcoms, subcoms or collections + /** + * Whether this is a show more node that contains no data, but indicates that there is + * one or more community or collection. + * @param _ + * @param node + */ isShowMore(_: number, node: FlatNode) { return node.isShowMoreNode; } /** - * Toggles the expanded variable of a node, adds it to the expanded nodes list and reloads the tree so this node is expanded + * Toggles the expanded variable of a node, adds it to the expanded nodes list and reloads the tree + * so this node is expanded * @param node Node we want to expand */ toggleExpanded(node: FlatNode) { @@ -93,9 +103,12 @@ export class CommunityListComponent implements OnInit, OnDestroy { /** * Makes sure the next page of a node is added to the tree (top community, sub community of collection) - * > Finds its parent (if not top community) and increases its corresponding collection/subcommunity currentPage - * > Reloads tree with new page added to corresponding top community lis, sub community list or collection list - * @param node The show more node indicating whether it's an increase in top communities, sub communities or collections + * > Finds its parent (if not top community) and increases its corresponding collection/subcommunity + * currentPage + * > Reloads tree with new page added to corresponding top community lis, sub community list or + * collection list + * @param node The show more node indicating whether it's an increase in top communities, sub communities + * or collections */ getNextPage(node: FlatNode): void { this.loadingNode = node; diff --git a/src/app/community-list-page/show-more-flat-node.model.ts b/src/app/community-list-page/show-more-flat-node.model.ts index 801c9e7388..c7b7162d21 100644 --- a/src/app/community-list-page/show-more-flat-node.model.ts +++ b/src/app/community-list-page/show-more-flat-node.model.ts @@ -1,6 +1,6 @@ /** * The show more links in the community tree are also represented by a flatNode so we know where in - * the tree it should be rendered an who its parent is (needed for the action resulting in clicking this link) + * the tree it should be rendered and who its parent is (needed for the action resulting in clicking this link) */ export class ShowMoreFlatNode { } From 8a5d6897c4a2130f56da71e04fb0e2bdba400b07 Mon Sep 17 00:00:00 2001 From: Hrafn Malmquist Date: Tue, 22 Aug 2023 04:59:23 +0100 Subject: [PATCH 052/875] Reorder instance method to come after member declaration (cherry picked from commit e59913acab0e782a743a4f18634e2930de9b71e8) --- .../community-list/community-list.component.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index bd1d5ecb78..90dd6b3c05 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -28,11 +28,9 @@ export class CommunityListComponent implements OnInit, OnDestroy { treeControl = new FlatTreeControl( (node: FlatNode) => node.level, (node: FlatNode) => true ); - dataSource: CommunityListDatasource; - trackBy = (index, node: FlatNode) => node.id; - paginationConfig: FindListOptions; + trackBy = (index, node: FlatNode) => node.id; constructor( protected communityListService: CommunityListService, From 2a55e36082c78427fabfb2de8f4ab0a59f98890b Mon Sep 17 00:00:00 2001 From: Mirko Scherf Date: Wed, 26 Jul 2023 13:04:04 +0200 Subject: [PATCH 053/875] fix(i18m): curation-task.task.registerdoi.label changed curation-task.task.register-doi.label back to curation-task.task.registerdoi.label and added German translation (cherry picked from commit 0ec72f679bb8d1bb7611c1de2125f92cb718b1a9) --- src/assets/i18n/de.json5 | 3 ++- src/assets/i18n/en.json5 | 2 +- src/assets/i18n/es.json5 | 4 ++-- src/assets/i18n/hu.json5 | 4 ++-- src/assets/i18n/it.json5 | 4 ++-- src/assets/i18n/pt-PT.json5 | 4 ++-- src/assets/i18n/vi.json5 | 2 +- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 4ebce8012d..5ab57cf3e6 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -1712,7 +1712,8 @@ // "curation-task.task.vscan.label": "Virus Scan", "curation-task.task.vscan.label": "Virenscan", - + // "curation-task.task.registerdoi.label": "Register DOI", + "curation-task.task.registerdoi.label": "DOI registrieren", // "curation.form.task-select.label": "Task:", "curation.form.task-select.label": "Aufgabe:", diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 5c17fc8e42..e12233eb5b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1392,7 +1392,7 @@ "curation-task.task.vscan.label": "Virus Scan", - "curation-task.task.register-doi.label": "Register DOI", + "curation-task.task.registerdoi.label": "Register DOI", "curation.form.task-select.label": "Task:", diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index 5a0e40af42..bb9334cf11 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -2080,8 +2080,8 @@ // "curation-task.task.vscan.label": "Virus Scan", "curation-task.task.vscan.label": "Búsqueda de virus", - // "curation-task.task.register-doi.label": "Register DOI", - "curation-task.task.register-doi.label": "Registro DOI", + // "curation-task.task.registerdoi.label": "Register DOI", + "curation-task.task.registerdoi.label": "Registro DOI", diff --git a/src/assets/i18n/hu.json5 b/src/assets/i18n/hu.json5 index e1076c7512..d186f6435a 100644 --- a/src/assets/i18n/hu.json5 +++ b/src/assets/i18n/hu.json5 @@ -2228,9 +2228,9 @@ // "curation-task.task.vscan.label": "Virus Scan", "curation-task.task.vscan.label": "Virus ellenőrzés", - // "curation-task.task.register-doi.label": "Register DOI", + // "curation-task.task.registerdoi.label": "Register DOI", // TODO New key - Add a translation - "curation-task.task.register-doi.label": "Register DOI", + "curation-task.task.registerdoi.label": "Register DOI", diff --git a/src/assets/i18n/it.json5 b/src/assets/i18n/it.json5 index 1a97eca501..4fc07cebdf 100644 --- a/src/assets/i18n/it.json5 +++ b/src/assets/i18n/it.json5 @@ -2160,9 +2160,9 @@ // "curation-task.task.vscan.label": "Virus Scan", "curation-task.task.vscan.label": "Scansione antivirus", - // "curation-task.task.register-doi.label": "Register DOI", + // "curation-task.task.registerdoi.label": "Register DOI", // TODO New key - Add a translation - "curation-task.task.register-doi.label": "Register DOI", + "curation-task.task.registerdoi.label": "Register DOI", diff --git a/src/assets/i18n/pt-PT.json5 b/src/assets/i18n/pt-PT.json5 index 622f8cc48b..5aa4817e88 100644 --- a/src/assets/i18n/pt-PT.json5 +++ b/src/assets/i18n/pt-PT.json5 @@ -2054,8 +2054,8 @@ // "curation-task.task.vscan.label": "Virus Scan", "curation-task.task.vscan.label": "Scan de vírus", - // "curation-task.task.register-doi.label": "Register DOI", - "curation-task.task.register-doi.label": "Registo DOI", + // "curation-task.task.registerdoi.label": "Register DOI", + "curation-task.task.registerdoi.label": "Registo DOI", // "curation.form.task-select.label": "Task:", "curation.form.task-select.label": "Tarefa:", diff --git a/src/assets/i18n/vi.json5 b/src/assets/i18n/vi.json5 index f254df34fe..f9fb33ebcb 100644 --- a/src/assets/i18n/vi.json5 +++ b/src/assets/i18n/vi.json5 @@ -671,7 +671,7 @@ "curation-task.task.citationpage.label": "Tạo trang trích dẫn", "curation-task.task.noop.label": "NOOP", "curation-task.task.profileformats.label": "Định dạng tệp tin", - "curation-task.task.register-doi.label": "Đăng ký DOI", + "curation-task.task.registerdoi.label": "Đăng ký DOI", "curation-task.task.requiredmetadata.label": "Kiểm tra các trường dữ liệu bắt buộc", "curation-task.task.translate.label": "Bộ dịch của Microsoft", "curation-task.task.vscan.label": "Quét Virus", From 07a2e333ca5cb0f55178f9352d93b09f4fb311b2 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 26 Aug 2023 22:02:33 +0200 Subject: [PATCH 054/875] Implemented i18n cache busting --- package.json | 2 +- .../translate-browser.loader.ts | 4 +- .../translate-server.loader.ts | 3 +- webpack/helpers.ts | 47 +++++++++++++++---- webpack/webpack.common.ts | 11 +++-- 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 719b13b23b..a2dd76d01b 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "analyze": "webpack-bundle-analyzer dist/browser/stats.json", "build": "ng build --configuration development", "build:stats": "ng build --stats-json", - "build:prod": "yarn run build:ssr", + "build:prod": "cross-env NODE_ENV=production yarn run build:ssr", "build:ssr": "ng build --configuration production && ng run dspace-angular:server:production", "test": "ng test --source-map=true --watch=false --configuration test", "test:watch": "nodemon --exec \"ng test --source-map=true --watch=true --configuration test\"", diff --git a/src/ngx-translate-loaders/translate-browser.loader.ts b/src/ngx-translate-loaders/translate-browser.loader.ts index a6188c9f15..85d59c96f0 100644 --- a/src/ngx-translate-loaders/translate-browser.loader.ts +++ b/src/ngx-translate-loaders/translate-browser.loader.ts @@ -5,6 +5,7 @@ import { NGX_TRANSLATE_STATE, NgxTranslateState } from './ngx-translate-state'; import { hasValue } from '../app/shared/empty.util'; import { map } from 'rxjs/operators'; import { of as observableOf, Observable } from 'rxjs'; +import { environment } from '../environments/environment'; /** * A TranslateLoader for ngx-translate to retrieve i18n messages from the TransferState, or download @@ -33,9 +34,10 @@ export class TranslateBrowserLoader implements TranslateLoader { if (hasValue(messages)) { return observableOf(messages); } else { + const translationHash: string = environment.production ? `.${(process.env.languageHashes as any)[lang + '.json5']}` : ''; // If they're not available on the transfer state (e.g. when running in dev mode), retrieve // them using HttpClient - return this.http.get('' + this.prefix + lang + this.suffix, { responseType: 'text' }).pipe( + return this.http.get(`${this.prefix}${lang}${translationHash}${this.suffix}`, { responseType: 'text' }).pipe( map((json: any) => JSON.parse(json)) ); } diff --git a/src/ngx-translate-loaders/translate-server.loader.ts b/src/ngx-translate-loaders/translate-server.loader.ts index c09c71f049..1f47dfe95b 100644 --- a/src/ngx-translate-loaders/translate-server.loader.ts +++ b/src/ngx-translate-loaders/translate-server.loader.ts @@ -23,8 +23,9 @@ export class TranslateServerLoader implements TranslateLoader { * @param lang the language code */ public getTranslation(lang: string): Observable { + const translationHash: string = (process.env.languageHashes as any)[lang + '.json5']; // Retrieve the file for the given language, and parse it - const messages = JSON.parse(readFileSync(`${this.prefix}${lang}${this.suffix}`, 'utf8')); + const messages = JSON.parse(readFileSync(`${this.prefix}${lang}.${translationHash}${this.suffix}`, 'utf8')); // Store the parsed messages in the transfer state so they'll be available immediately when the // app loads on the client this.storeInTransferState(lang, messages); diff --git a/webpack/helpers.ts b/webpack/helpers.ts index 43855f6c72..f0b42a8a69 100644 --- a/webpack/helpers.ts +++ b/webpack/helpers.ts @@ -1,18 +1,49 @@ -const path = require('path'); +import { readFileSync, readdirSync, statSync, Stats } from 'fs'; +import { join, resolve } from 'path'; + +const md5 = require('md5'); export const projectRoot = (relativePath) => { - return path.resolve(__dirname, '..', relativePath); + return resolve(__dirname, '..', relativePath); }; export const globalCSSImports = () => { return [ - projectRoot(path.join('src', 'styles', '_variables.scss')), - projectRoot(path.join('src', 'styles', '_mixins.scss')), + projectRoot(join('src', 'styles', '_variables.scss')), + projectRoot(join('src', 'styles', '_mixins.scss')), ]; }; +/** + * Calculates the md5 hash of a file + * + * @param filePath The path of the file + */ +export function calculateFileHash(filePath: string): string { + const fileContent: Buffer = readFileSync(filePath); + return md5(fileContent); +} -module.exports = { - projectRoot, - globalCSSImports -}; +/** + * Calculate the hashes of all the files (matching the given regex) in a certain folder + * + * @param folderPath The path of the folder + * @param regExp A regex of the files in the folder for which a hash needs to be generated + */ +export function getFileHashes(folderPath: string, regExp: RegExp): { [fileName: string]: string } { + const files: string[] = readdirSync(folderPath); + let hashes: { [fileName: string]: string } = {}; + + for (const file of files) { + if (file.match(regExp)) { + const filePath: string = join(folderPath, file); + const stats: Stats = statSync(filePath); + + if (stats.isFile()) { + hashes[file] = calculateFileHash(filePath); + } + } + } + + return hashes; +} diff --git a/webpack/webpack.common.ts b/webpack/webpack.common.ts index 1a1ecfd6ef..8d433edf39 100644 --- a/webpack/webpack.common.ts +++ b/webpack/webpack.common.ts @@ -1,4 +1,5 @@ -import { globalCSSImports, projectRoot } from './helpers'; +import { globalCSSImports, projectRoot, getFileHashes, calculateFileHash } from './helpers'; +import { EnvironmentPlugin } from 'webpack'; const CopyWebpackPlugin = require('copy-webpack-plugin'); const path = require('path'); @@ -18,12 +19,13 @@ export const copyWebpackOptions = { // use [\/|\\] to match both POSIX and Windows separators const matches = absoluteFilename.match(/.*[\/|\\]assets[\/|\\](.+)\.json5$/); if (matches) { + const fileHash: string = process.env.NODE_ENV === 'production' ? `.${calculateFileHash(absoluteFilename)}` : ''; // matches[1] is the relative path from src/assets to the JSON5 file, without the extension - return path.join('assets', matches[1] + '.json'); + return path.join('assets', `${matches[1]}${fileHash}.json`); } }, transform(content) { - return JSON.stringify(JSON5.parse(content.toString())) + return JSON.stringify(JSON5.parse(content.toString())); } }, { @@ -77,6 +79,9 @@ const SCSS_LOADERS = [ export const commonExports = { plugins: [ + new EnvironmentPlugin({ + languageHashes: getFileHashes(path.join(__dirname, '..', 'src', 'assets', 'i18n'), /.*\.json5/g), + }), new CopyWebpackPlugin(copyWebpackOptions), ], module: { From 13c0cb48ed9fd02a421fdcdfae3c578130f4c985 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 28 Aug 2023 13:53:19 -0500 Subject: [PATCH 055/875] Update to latest cypress (cherry picked from commit 68a3323fcaf9b97d260f5032c9eb2fa5f6096bc2) --- package.json | 2 +- yarn.lock | 49 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 06d7063240..0cb2b8d41f 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,7 @@ "compression-webpack-plugin": "^9.2.0", "copy-webpack-plugin": "^6.4.1", "cross-env": "^7.0.3", - "cypress": "12.10.0", + "cypress": "12.17.4", "cypress-axe": "^1.4.0", "deep-freeze": "0.0.1", "eslint": "^8.39.0", diff --git a/yarn.lock b/yarn.lock index e53a070d16..a7c8af6c04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1551,10 +1551,10 @@ resolved "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz" integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw== -"@cypress/request@^2.88.10": - version "2.88.11" - resolved "https://registry.npmjs.org/@cypress/request/-/request-2.88.11.tgz" - integrity sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w== +"@cypress/request@2.88.12": + version "2.88.12" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.12.tgz#ba4911431738494a85e93fb04498cb38bc55d590" + integrity sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -1571,7 +1571,7 @@ performance-now "^2.1.0" qs "~6.10.3" safe-buffer "^5.1.2" - tough-cookie "~2.5.0" + tough-cookie "^4.1.3" tunnel-agent "^0.6.0" uuid "^8.3.2" @@ -2457,11 +2457,16 @@ resolved "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz" integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== -"@types/node@*", "@types/node@>=10.0.0", "@types/node@^14.14.31", "@types/node@^14.14.9": +"@types/node@*", "@types/node@>=10.0.0", "@types/node@^14.14.9": version "14.18.42" resolved "https://registry.npmjs.org/@types/node/-/node-14.18.42.tgz" integrity sha512-xefu+RBie4xWlK8hwAzGh3npDz/4VhF6icY/shU+zv/1fNn+ZVG7T7CRwe9LId9sAYRPxI+59QBPuKL3WpyGRg== +"@types/node@^16.18.39": + version "16.18.46" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.46.tgz#9f2102d0ba74a318fcbe170cbff5463f119eab59" + integrity sha512-Mnq3O9Xz52exs3mlxMcQuA7/9VFe/dXcrgAyfjLkABIqxXKOgBRjyazTxUbjsxDa4BP7hhPliyjVTP9RDP14xg== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz" @@ -4437,14 +4442,14 @@ cypress-axe@^1.4.0: resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-1.4.0.tgz#e67482bfe9e740796bf77c7823f19781a8a2faff" integrity sha512-Ut7NKfzjyKm0BEbt2WxuKtLkIXmx6FD2j0RwdvO/Ykl7GmB/qRQkwbKLk3VP35+83hiIr8GKD04PDdrTK5BnyA== -cypress@12.10.0: - version "12.10.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.10.0.tgz#b6264f77c214d63530ebac2b33c4d099bd40b715" - integrity sha512-Y0wPc221xKKW1/4iAFCphkrG2jNR4MjOne3iGn4mcuCaE7Y5EtXL83N8BzRsAht7GYfWVjJ/UeTqEdDKHz39HQ== +cypress@12.17.4: + version "12.17.4" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.17.4.tgz#b4dadf41673058493fa0d2362faa3da1f6ae2e6c" + integrity sha512-gAN8Pmns9MA5eCDFSDJXWKUpaL3IDd89N9TtIupjYnzLSmlpVr+ZR+vb4U/qaMp+lB6tBvAmt7504c3Z4RU5KQ== dependencies: - "@cypress/request" "^2.88.10" + "@cypress/request" "2.88.12" "@cypress/xvfb" "^1.2.4" - "@types/node" "^14.14.31" + "@types/node" "^16.18.39" "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" arch "^2.2.0" @@ -4477,9 +4482,10 @@ cypress@12.10.0: minimist "^1.2.8" ospath "^1.2.2" pretty-bytes "^5.6.0" + process "^0.11.10" proxy-from-env "1.0.0" request-progress "^3.0.0" - semver "^7.3.2" + semver "^7.5.3" supports-color "^8.1.1" tmp "~0.2.1" untildify "^4.0.0" @@ -9267,6 +9273,11 @@ process-nextick-args@~2.0.0: resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz" @@ -10131,7 +10142,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: +semver@^7.0.0, semver@^7.1.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -10912,6 +10923,16 @@ tough-cookie@^4.0.0, tough-cookie@^4.1.2: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" + integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz" From 7ebdc43ca2c5c2d8826da036aa9253158e7ad416 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 28 Aug 2023 13:55:05 -0500 Subject: [PATCH 056/875] Update to latest axe-core (cherry picked from commit 50899f1d1b16e322e323507ad7dd42e5173cfbe5) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0cb2b8d41f..849002f60e 100644 --- a/package.json +++ b/package.json @@ -159,7 +159,7 @@ "@types/sanitize-html": "^2.9.0", "@typescript-eslint/eslint-plugin": "^5.59.1", "@typescript-eslint/parser": "^5.59.1", - "axe-core": "^4.7.0", + "axe-core": "^4.7.2", "compression-webpack-plugin": "^9.2.0", "copy-webpack-plugin": "^6.4.1", "cross-env": "^7.0.3", diff --git a/yarn.lock b/yarn.lock index a7c8af6c04..58ddfaca4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3363,10 +3363,10 @@ aws4@^1.8.0: resolved "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz" integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== -axe-core@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" - integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== +axe-core@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.2.tgz#040a7342b20765cb18bb50b628394c21bccc17a0" + integrity sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g== axios@0.21.4: version "0.21.4" From baecf2ac111ea4b528ea9893d00d7cee22e4d3a8 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Aug 2023 11:41:03 -0500 Subject: [PATCH 057/875] Reenable accessibility check fixed in #2251 (cherry picked from commit 158ebb0e3268a314e18bc4855457166d8b57f2e8) --- cypress/e2e/community-list.cy.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/cypress/e2e/community-list.cy.ts b/cypress/e2e/community-list.cy.ts index 7b60b59dbc..d91260eca1 100644 --- a/cypress/e2e/community-list.cy.ts +++ b/cypress/e2e/community-list.cy.ts @@ -13,13 +13,6 @@ describe('Community List Page', () => { cy.get('[data-test="expand-button"]').click({ multiple: true }); // Analyze for accessibility issues - // Disable heading-order checks until it is fixed - testA11y('ds-community-list-page', - { - rules: { - 'heading-order': { enabled: false } - } - } as Options - ); + testA11y('ds-community-list-page'); }); }); From a7a807c0bb9b736ea9aa04860ae9ce8035ba5eac Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Aug 2023 11:43:36 -0500 Subject: [PATCH 058/875] Enable excessibility checking of login menu, and remove unnecessary exclusion from header (cherry picked from commit 339ed63734d4192fb37a12e67b0395aa1669acdb) --- cypress/e2e/header.cy.ts | 3 +-- cypress/e2e/login-modal.cy.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/header.cy.ts b/cypress/e2e/header.cy.ts index 236208db68..1a9b841eb7 100644 --- a/cypress/e2e/header.cy.ts +++ b/cypress/e2e/header.cy.ts @@ -11,8 +11,7 @@ describe('Header', () => { testA11y({ include: ['ds-header'], exclude: [ - ['#search-navbar-container'], // search in navbar has duplicative ID. Will be fixed in #1174 - ['.dropdownLogin'] // "Log in" link has color contrast issues. Will be fixed in #1149 + ['#search-navbar-container'] // search in navbar has duplicative ID. Will be fixed in #1174 ], }); }); diff --git a/cypress/e2e/login-modal.cy.ts b/cypress/e2e/login-modal.cy.ts index b169634cfa..d29c13c2f9 100644 --- a/cypress/e2e/login-modal.cy.ts +++ b/cypress/e2e/login-modal.cy.ts @@ -1,4 +1,5 @@ import { TEST_ADMIN_PASSWORD, TEST_ADMIN_USER, TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e'; +import { testA11y } from 'cypress/support/utils'; const page = { openLoginMenu() { @@ -123,4 +124,15 @@ describe('Login Modal', () => { cy.location('pathname').should('eq', '/forgot'); cy.get('ds-forgot-email').should('exist'); }); + + it('should pass accessibility tests', () => { + cy.visit('/'); + + page.openLoginMenu(); + + cy.get('ds-log-in').should('exist'); + + // Analyze for accessibility issues + testA11y('ds-log-in'); + }); }); From 13ead8174abf4ab630b40e10002a17e3f988a79a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Aug 2023 11:44:03 -0500 Subject: [PATCH 059/875] Fix heading order issue with item page & update accessibility tests to prove it now passes (cherry picked from commit ba244bf6b14bfed06f1b1052cc6407f2537a175b) --- cypress/e2e/item-page.cy.ts | 20 ++++++++++--------- .../item-page-title-field.component.html | 4 ++-- .../metadata-field-wrapper.component.html | 2 +- .../metadata-field-wrapper.component.scss | 3 +++ 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/cypress/e2e/item-page.cy.ts b/cypress/e2e/item-page.cy.ts index 9eed711776..9dba6eb8ce 100644 --- a/cypress/e2e/item-page.cy.ts +++ b/cypress/e2e/item-page.cy.ts @@ -1,4 +1,3 @@ -import { Options } from 'cypress-axe'; import { TEST_ENTITY_PUBLICATION } from 'cypress/support/e2e'; import { testA11y } from 'cypress/support/utils'; @@ -19,13 +18,16 @@ describe('Item Page', () => { cy.get('ds-item-page').should('be.visible'); // Analyze for accessibility issues - // Disable heading-order checks until it is fixed - testA11y('ds-item-page', - { - rules: { - 'heading-order': { enabled: false } - } - } as Options - ); + testA11y('ds-item-page'); + }); + + it('should pass accessibility tests on full item page', () => { + cy.visit(ENTITYPAGE + '/full'); + + // tag must be loaded + cy.get('ds-full-item-page').should('be.visible'); + + // Analyze for accessibility issues + testA11y('ds-full-item-page'); }); }); diff --git a/src/app/item-page/simple/field-components/specific-field/title/item-page-title-field.component.html b/src/app/item-page/simple/field-components/specific-field/title/item-page-title-field.component.html index 15960bdc9d..85975d4533 100644 --- a/src/app/item-page/simple/field-components/specific-field/title/item-page-title-field.component.html +++ b/src/app/item-page/simple/field-components/specific-field/title/item-page-title-field.component.html @@ -1,6 +1,6 @@ -

+

{{ type.toLowerCase() + '.page.titleprefix' | translate }}
{{ dsoNameService.getName(item) }} -

+ diff --git a/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.html b/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.html index d69f87883b..7748e385ca 100644 --- a/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.html +++ b/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.html @@ -1,5 +1,5 @@
-
{{ label }}
+

{{ label }}

diff --git a/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.scss b/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.scss index 75dfd09d0d..bf17d63d6c 100644 --- a/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.scss +++ b/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.scss @@ -2,4 +2,7 @@ .simple-view-element { margin-bottom: 15px; } + .simple-view-element-header { + font-size: 1.25rem; + } } From b90d102e5e252e1bd4bb2b36b02508e36d94da30 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Aug 2023 14:58:49 -0500 Subject: [PATCH 060/875] Update ng2-nouislider and nouislider to latest versions (cherry picked from commit 91d8b7e4f7187b68f70c1cf2906f4e6e8d8af2b9) --- package.json | 4 ++-- src/styles/_vendor.scss | 2 +- yarn.lock | 18 ++++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 849002f60e..9aec4e0823 100644 --- a/package.json +++ b/package.json @@ -116,12 +116,12 @@ "morgan": "^1.10.0", "ng-mocks": "^14.10.0", "ng2-file-upload": "1.4.0", - "ng2-nouislider": "^1.8.3", + "ng2-nouislider": "^2.0.0", "ngx-infinite-scroll": "^15.0.0", "ngx-pagination": "6.0.3", "ngx-sortablejs": "^11.1.0", "ngx-ui-switch": "^14.0.3", - "nouislider": "^14.6.3", + "nouislider": "^15.7.1", "pem": "1.14.7", "prop-types": "^15.8.1", "react-copy-to-clipboard": "^5.1.0", diff --git a/src/styles/_vendor.scss b/src/styles/_vendor.scss index 9d9842b9b3..b2b94c2826 100644 --- a/src/styles/_vendor.scss +++ b/src/styles/_vendor.scss @@ -1,5 +1,5 @@ // node_modules imports meant for all the themes @import '~node_modules/bootstrap/scss/bootstrap.scss'; -@import '~node_modules/nouislider/distribute/nouislider.min'; +@import '~node_modules/nouislider/dist/nouislider.min'; @import '~node_modules/ngx-ui-switch/ui-switch.component.scss'; diff --git a/yarn.lock b/yarn.lock index 58ddfaca4b..44625c4bd0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8197,10 +8197,12 @@ ng2-file-upload@1.4.0: dependencies: tslib "^1.9.0" -ng2-nouislider@^1.8.3: - version "1.8.3" - resolved "https://registry.npmjs.org/ng2-nouislider/-/ng2-nouislider-1.8.3.tgz" - integrity sha512-Vl8tHCcJ/ioJLAs2t6FBC35sZq1P/O5ZdqdFwYxOCOMVbILGWNg+2gWZIjFstvv9pqb/mVvVUYe6qGG/mA/RBQ== +ng2-nouislider@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ng2-nouislider/-/ng2-nouislider-2.0.0.tgz#a62fd6cf3f1561be19a2691c2f68d21a46dc6006" + integrity sha512-NGbF/0w0+bZqclpSPFOlWIeVJaVwRRYFJzD1x8PClbw9GIeo7fCHoBzZ81y7K7FTJg6to+cgjSTFETPZG/Dizg== + dependencies: + tslib "^2.3.0" ngx-infinite-scroll@^15.0.0: version "15.0.0" @@ -8343,10 +8345,10 @@ normalize-url@^4.5.0: resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== -nouislider@^14.6.3: - version "14.7.0" - resolved "https://registry.npmjs.org/nouislider/-/nouislider-14.7.0.tgz" - integrity sha512-4RtQ1+LHJKesDCNJrXkQcwXAWCrC2aggdLYMstS/G5fEWL+fXZbUA9pwVNHFghMGuFGRATlDLNInRaPeRKzpFQ== +nouislider@^15.7.1: + version "15.7.1" + resolved "https://registry.yarnpkg.com/nouislider/-/nouislider-15.7.1.tgz#77d55e47d9b4cd771728515713df43b489db9705" + integrity sha512-5N7C1ru/i8y3dg9+Z6ilj6+m1EfabvOoaRa7ztpxBSKKRZso4vA52DGSbBJjw5XLtFr/LZ9SgGAXqyVtlVHO5w== npm-bundled@^3.0.0: version "3.0.0" From 3cdcdaf475f19e2d264c4e3685cf94ac2fc847c5 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Aug 2023 15:01:21 -0500 Subject: [PATCH 061/875] Fix accessibility of date sliders by adding aria-labels (cherry picked from commit 2a881791ba76091d2f85d0b068f926043ef33bc9) --- cypress/e2e/my-dspace.cy.ts | 6 +--- cypress/e2e/search-page.cy.ts | 6 +--- .../search-range-filter.component.html | 10 +++---- .../search-range-filter.component.ts | 29 +++++++++++++++++++ 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/cypress/e2e/my-dspace.cy.ts b/cypress/e2e/my-dspace.cy.ts index 79786c298a..af4aab41f3 100644 --- a/cypress/e2e/my-dspace.cy.ts +++ b/cypress/e2e/my-dspace.cy.ts @@ -22,15 +22,11 @@ describe('My DSpace page', () => { testA11y( { include: ['ds-my-dspace-page'], - exclude: [ - ['nouislider'] // Date filter slider is missing ARIA labels. Will be fixed by #1175 - ], }, { rules: { - // Search filters fail these two "moderate" impact rules + // Search filters fail this "moderate" impact rules 'heading-order': { enabled: false }, - 'landmark-unique': { enabled: false } } } as Options ); diff --git a/cypress/e2e/search-page.cy.ts b/cypress/e2e/search-page.cy.ts index 24519cc236..0d4f70ef03 100644 --- a/cypress/e2e/search-page.cy.ts +++ b/cypress/e2e/search-page.cy.ts @@ -30,15 +30,11 @@ describe('Search Page', () => { testA11y( { include: ['ds-search-page'], - exclude: [ - ['nouislider'] // Date filter slider is missing ARIA labels. Will be fixed by #1175 - ], }, { rules: { - // Search filters fail these two "moderate" impact rules + // Search filters fail this "moderate" impact rule 'heading-order': { enabled: false }, - 'landmark-unique': { enabled: false } } } as Options ); diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html index 7834c4c557..251e5ac420 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.html @@ -9,8 +9,8 @@
@@ -21,8 +21,8 @@
@@ -33,7 +33,7 @@ - diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts index 938f67412e..ed20e63c52 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts @@ -2,6 +2,7 @@ import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription import { map, startWith } from 'rxjs/operators'; import { isPlatformBrowser } from '@angular/common'; import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service'; import { FilterType } from '../../../models/filter-type.model'; import { renderFacetFor } from '../search-filter-type-decorator'; @@ -53,11 +54,27 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple */ min = 1950; + /** + * i18n Label to use for minimum field + */ + minLabel: string; + /** * Fallback maximum for the range */ max = new Date().getUTCFullYear(); + /** + * i18n Label to use for maximum field + */ + maxLabel: string; + + /** + * Base configuration for nouislider + * https://refreshless.com/nouislider/slider-options/ + */ + config = {}; + /** * The current range of the filter */ @@ -78,6 +95,7 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple protected filterService: SearchFilterService, protected router: Router, protected rdbs: RemoteDataBuildService, + private translateService: TranslateService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, @Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean, @Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig, @@ -96,6 +114,8 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple super.ngOnInit(); this.min = yearFromString(this.filterConfig.minValue) || this.min; this.max = yearFromString(this.filterConfig.maxValue) || this.max; + this.minLabel = this.translateService.instant('search.filters.filter.' + this.filterConfig.name + '.min.placeholder'); + this.maxLabel = this.translateService.instant('search.filters.filter.' + this.filterConfig.name + '.max.placeholder'); const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MIN_SUFFIX).pipe(startWith(undefined)); const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MAX_SUFFIX).pipe(startWith(undefined)); this.sub = observableCombineLatest(iniMin, iniMax).pipe( @@ -105,6 +125,15 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple return [minimum, maximum]; }) ).subscribe((minmax) => this.range = minmax); + + // Default/base config for nouislider + this.config = { + // Ensure draggable handles have labels + handleAttributes: [ + { 'aria-label': this.minLabel }, + { 'aria-label': this.maxLabel }, + ], + }; } /** From 6df76515ba0980709049ee288e2b2b737bf3614b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Aug 2023 15:01:51 -0500 Subject: [PATCH 062/875] Minor fixes to cypress tests (cherry picked from commit 70a7bbe3cbdd24abaf7f6f791ef60e88a3ae8922) --- cypress/e2e/community-list.cy.ts | 1 - cypress/e2e/pagenotfound.cy.ts | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/community-list.cy.ts b/cypress/e2e/community-list.cy.ts index d91260eca1..c371f6ceae 100644 --- a/cypress/e2e/community-list.cy.ts +++ b/cypress/e2e/community-list.cy.ts @@ -1,4 +1,3 @@ -import { Options } from 'cypress-axe'; import { testA11y } from 'cypress/support/utils'; describe('Community List Page', () => { diff --git a/cypress/e2e/pagenotfound.cy.ts b/cypress/e2e/pagenotfound.cy.ts index 43e3c3af24..d02aa8541c 100644 --- a/cypress/e2e/pagenotfound.cy.ts +++ b/cypress/e2e/pagenotfound.cy.ts @@ -1,8 +1,13 @@ +import { testA11y } from 'cypress/support/utils'; + describe('PageNotFound', () => { it('should contain element ds-pagenotfound when navigating to page that doesnt exist', () => { // request an invalid page (UUIDs at root path aren't valid) cy.visit('/e9019a69-d4f1-4773-b6a3-bd362caa46f2', { failOnStatusCode: false }); cy.get('ds-pagenotfound').should('be.visible'); + + // Analyze for accessibility issues + testA11y('ds-pagenotfound'); }); it('should not contain element ds-pagenotfound when navigating to existing page', () => { From 63c752b3f4ad2d4a04438dd66f97c8ffff9ca87b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Aug 2023 15:10:12 -0500 Subject: [PATCH 063/875] Fix heading order accessibility issue in search filters/facets (cherry picked from commit 276d80895e38225fcbde38cab01d79cd31a34e9b) --- cypress/e2e/my-dspace.cy.ts | 12 +----------- cypress/e2e/search-page.cy.ts | 12 +----------- .../search-filter/search-filter.component.html | 4 ++-- .../shared/sidebar/sidebar-dropdown.component.html | 2 +- src/themes/dspace/styles/_global-styles.scss | 2 +- 5 files changed, 6 insertions(+), 26 deletions(-) diff --git a/cypress/e2e/my-dspace.cy.ts b/cypress/e2e/my-dspace.cy.ts index af4aab41f3..13f4a1b547 100644 --- a/cypress/e2e/my-dspace.cy.ts +++ b/cypress/e2e/my-dspace.cy.ts @@ -19,17 +19,7 @@ describe('My DSpace page', () => { cy.get('.filter-toggle').click({ multiple: true }); // Analyze for accessibility issues - testA11y( - { - include: ['ds-my-dspace-page'], - }, - { - rules: { - // Search filters fail this "moderate" impact rules - 'heading-order': { enabled: false }, - } - } as Options - ); + testA11y('ds-my-dspace-page'); }); it('should have a working detailed view that passes accessibility tests', () => { diff --git a/cypress/e2e/search-page.cy.ts b/cypress/e2e/search-page.cy.ts index 0d4f70ef03..755f8eaac6 100644 --- a/cypress/e2e/search-page.cy.ts +++ b/cypress/e2e/search-page.cy.ts @@ -27,17 +27,7 @@ describe('Search Page', () => { cy.get('[data-test="filter-toggle"]').click({ multiple: true }); // Analyze for accessibility issues - testA11y( - { - include: ['ds-search-page'], - }, - { - rules: { - // Search filters fail this "moderate" impact rule - 'heading-order': { enabled: false }, - } - } as Options - ); + testA11y('ds-search-page'); }); it('should have a working grid view that passes accessibility tests', () => { diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-filter.component.html index a6fb0021b7..421d1ede2c 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.html @@ -6,9 +6,9 @@ [attr.aria-label]="(((collapsed$ | async) ? 'search.filters.filter.expand' : 'search.filters.filter.collapse') | translate) + ' ' + (('search.filters.filter.' + filter.name + '.head') | translate | lowercase)" [attr.data-test]="'filter-toggle' | dsBrowserOnly" > -
+

{{'search.filters.filter.' + filter.name + '.head'| translate}} -

+ diff --git a/src/app/shared/sidebar/sidebar-dropdown.component.html b/src/app/shared/sidebar/sidebar-dropdown.component.html index 0c2a1c05d2..2eadac09f7 100644 --- a/src/app/shared/sidebar/sidebar-dropdown.component.html +++ b/src/app/shared/sidebar/sidebar-dropdown.component.html @@ -1,5 +1,5 @@
-
+

diff --git a/src/themes/dspace/styles/_global-styles.scss b/src/themes/dspace/styles/_global-styles.scss index e41dae0e3f..5bd4c19bc0 100644 --- a/src/themes/dspace/styles/_global-styles.scss +++ b/src/themes/dspace/styles/_global-styles.scss @@ -17,7 +17,7 @@ background-color: var(--bs-primary); } - h5 { + h4 { font-size: 1.1rem } } From d1ebf07456681dec8b804099aca6423603d8573c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Fern=C3=A1ndez=20Celorio?= Date: Tue, 22 Aug 2023 11:59:54 +0200 Subject: [PATCH 064/875] Spanish translation updated to 7.6 (cherry picked from commit 4cc4192e93aea3eb7dddc8486fe1fad35b6103bc) --- src/assets/i18n/es.json5 | 766 ++++++++++++++++++++------------------- 1 file changed, 385 insertions(+), 381 deletions(-) diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index bb9334cf11..24ce7159e3 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -9,8 +9,6 @@ // "401.unauthorized": "unauthorized", "401.unauthorized": "no autorizado", - - // "403.help": "You don't have permission to access this page. You can use the button below to get back to the home page.", "403.help": "No tiene permisos para acceder a esta página. Puede utilizar el botón de abajo para volver a la página de inicio.", @@ -29,7 +27,6 @@ // "500.link.home-page": "Take me to the home page", "500.link.home-page": "Llévame a la página de inicio", - // "404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ", "404.help": "No podemos encontrar la página que busca. La página puede haber sido movida o eliminada. Puede utilizar el botón de abajo para volver a la página de inicio. ", @@ -52,7 +49,7 @@ "error-page.description.404": "página no encontrada", // "error-page.orcid.generic-error": "An error occurred during login via ORCID. Make sure you have shared your ORCID account email address with DSpace. If the error persists, contact the administrator", - "error-page.orcid.generic-error": "Hubo un error en el login via ORCID. Asegúrese que ha compartido el correo electrónico de su cuenta ORCID con Dspace. Si continuase el error, contacte con el administrador", + "error-page.orcid.generic-error": "Hubo un error en el login vía ORCID. Asegúrese que ha compartido el correo electrónico de su cuenta ORCID con Dspace. Si continuase el error, contacte con el administrador", // "access-status.embargo.listelement.badge": "Embargo", "access-status.embargo.listelement.badge": "Embargo", @@ -194,7 +191,8 @@ // "admin.registries.bitstream-formats.table.name": "Name", "admin.registries.bitstream-formats.table.name": "Nombre", - // "admin.registries.bitstream-formats.table.id" : "ID", + + // "admin.registries.bitstream-formats.table.id": "ID", "admin.registries.bitstream-formats.table.id": "ID", // "admin.registries.bitstream-formats.table.return": "Back", @@ -215,8 +213,6 @@ // "admin.registries.bitstream-formats.title": "Bitstream Format Registry", "admin.registries.bitstream-formats.title": "Registro de formato Archivo", - - // "admin.registries.metadata.breadcrumbs": "Metadata registry", "admin.registries.metadata.breadcrumbs": "Registro de metadatos", @@ -256,8 +252,6 @@ // "admin.registries.metadata.title": "Metadata Registry", "admin.registries.metadata.title": "Registro de metadatos", - - // "admin.registries.schema.breadcrumbs": "Metadata schema", "admin.registries.schema.breadcrumbs": "Esquema de metadatos", @@ -275,7 +269,8 @@ // "admin.registries.schema.fields.table.field": "Field", "admin.registries.schema.fields.table.field": "Campo", - // "admin.registries.schema.fields.table.id" : "ID", + + // "admin.registries.schema.fields.table.id": "ID", "admin.registries.schema.fields.table.id": "ID", // "admin.registries.schema.fields.table.scopenote": "Scope Note", @@ -335,7 +330,29 @@ // "admin.registries.schema.title": "Metadata Schema Registry", "admin.registries.schema.title": "Registro de esquemas de metadatos", + // "admin.access-control.bulk-access.breadcrumbs": "Bulk Access Management", + "admin.access-control.bulk-access.breadcrumbs": "Gestión de Acceso Masivo", + // "administrativeBulkAccess.search.results.head": "Search Results", + "administrativeBulkAccess.search.results.head": "Resultados de la búsqueda", + + // "admin.access-control.bulk-access": "Bulk Access Management", + "admin.access-control.bulk-access": "Gestión de Acceso Masivo", + + // "admin.access-control.bulk-access.title": "Bulk Access Management", + "admin.access-control.bulk-access.title": "Gestión de Acceso Masivo", + + // "admin.access-control.bulk-access-browse.header": "Step 1: Select Objects", + "admin.access-control.bulk-access-browse.header": "Paso 1: Seleccione Objetos", + + // "admin.access-control.bulk-access-browse.search.header": "Search", + "admin.access-control.bulk-access-browse.search.header": "Buscar", + + // "admin.access-control.bulk-access-browse.selected.header": "Current selection({{number}})", + "admin.access-control.bulk-access-browse.selected.header": "Selección actual({{number}})", + + // "admin.access-control.bulk-access-settings.header": "Step 2: Operation to Perform", + "admin.access-control.bulk-access-settings.header": "Paso 2: Operación a realizar", // "admin.access-control.epeople.actions.delete": "Delete EPerson", "admin.access-control.epeople.actions.delete": "Eliminar usuario", @@ -347,7 +364,7 @@ "admin.access-control.epeople.actions.reset": "Restablecer la contraseña", // "admin.access-control.epeople.actions.stop-impersonating": "Stop impersonating EPerson", - "admin.access-control.epeople.actions.stop-impersonating": "Deja de hacerse pasar por usuario", + "admin.access-control.epeople.actions.stop-impersonating": "Dejar de hacerse pasar por usuario", // "admin.access-control.epeople.breadcrumbs": "EPeople", "admin.access-control.epeople.breadcrumbs": "Usuarios", @@ -478,8 +495,6 @@ // "admin.access-control.epeople.notification.deleted.success": "Successfully deleted EPerson: \"{{name}}\"", "admin.access-control.epeople.notification.deleted.success": "Usuario eliminado correctamente: \"{{ name }}\"", - - // "admin.access-control.groups.title": "Groups", "admin.access-control.groups.title": "Grupos", @@ -549,8 +564,6 @@ // "admin.access-control.groups.notification.deleted.failure.content": "Cause: \"{{cause}}\"", "admin.access-control.groups.notification.deleted.failure.content": "Causa: \"{{ cause }}\"", - - // "admin.access-control.groups.form.alert.permanent": "This group is permanent, so it can't be edited or deleted. You can still add and remove group members using this page.", "admin.access-control.groups.form.alert.permanent": "Este grupo es permanente, por lo que no se puede editar ni eliminar. Sin embargo, puedes añadir y eliminar miembros del grupo utilizando esta página.", @@ -791,9 +804,6 @@ // "administrativeView.search.results.head": "Administrative Search", "administrativeView.search.results.head": "Búsqueda administrativa", - - - // "admin.workflow.breadcrumbs": "Administer Workflow", "admin.workflow.breadcrumbs": "Administrar flujo de trabajo", @@ -818,8 +828,6 @@ // "admin.workflow.item.supervision": "Supervision", "admin.workflow.item.supervision": "Supervisión", - - // "admin.metadata-import.breadcrumbs": "Import Metadata", "admin.metadata-import.breadcrumbs": "Importar metadatos", @@ -844,6 +852,9 @@ // "admin.batch-import.page.help": "Select the Collection to import into. Then, drop or browse to a Simple Archive Format (SAF) zip file that includes the Items to import", "admin.batch-import.page.help": "Seleccione la Colección a la que importar. Luego, suelte o busque el archivo zip en formato Simple Archive Format (SAF) que incluye los ítems a importar", + // "admin.batch-import.page.toggle.help": "It is possible to perform import either with file upload or via URL, use above toggle to set the input source", + "admin.batch-import.page.toggle.help": "Es posible realizar una importación tanto mediante una subida de fichero como a través de una URL. Use el selector de arriba para especificar la fuente de entrada.", + // "admin.metadata-import.page.dropMsg": "Drop a metadata CSV to import", "admin.metadata-import.page.dropMsg": "Suelta un CSV de metadatos para importar", @@ -863,14 +874,26 @@ "admin.metadata-import.page.button.proceed": "Continuar", // "admin.metadata-import.page.button.select-collection": "Select Collection", - "admin.metadata-import.page.button.select-collection": "Selecciona la Colleción", + "admin.metadata-import.page.button.select-collection": "Selecciona la Colección", // "admin.metadata-import.page.error.addFile": "Select file first!", "admin.metadata-import.page.error.addFile": "¡Seleccione el archivo primero!", + // "admin.metadata-import.page.error.addFileUrl": "Insert file url first!", + "admin.metadata-import.page.error.addFileUrl": "¡Seleccione primero la URL del archivo!", + // "admin.batch-import.page.error.addFile": "Select Zip file first!", "admin.batch-import.page.error.addFile": "¡Seleccione el archivo ZIP primero!", + // "admin.metadata-import.page.toggle.upload": "Upload", + "admin.metadata-import.page.toggle.upload": "Subir", + + // "admin.metadata-import.page.toggle.url": "URL", + "admin.metadata-import.page.toggle.url": "URL", + + // "admin.metadata-import.page.urlMsg": "Insert the batch ZIP url to import", + "admin.metadata-import.page.urlMsg": "¡Seleccione primero la URL del archivo ZIP!", + // "admin.metadata-import.page.validateOnly": "Validate Only", "admin.metadata-import.page.validateOnly": "Solo Validar", @@ -895,14 +918,12 @@ // "advanced-workflow-action.rating.description-requiredDescription": "Please select a rating below and also add a review", "advanced-workflow-action.rating.description-requiredDescription": "Por favor, seleccione una evaluación y también agregue una revisión", - // "advanced-workflow-action.select-reviewer.description-single": "Please select a single reviewer below before submitting", "advanced-workflow-action.select-reviewer.description-single": "Por favor, seleccione un revisor antes de realizar el envío", // "advanced-workflow-action.select-reviewer.description-multiple": "Please select one or more reviewers below before submitting", "advanced-workflow-action.select-reviewer.description-multiple": "Por favor, seleccione uno o mas revisores antes de realizar el envío", - // "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.head": "EPeople", "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.head": "Usuario", @@ -987,15 +1008,12 @@ // "auth.messages.token-refresh-failed": "Refreshing your session token failed. Please log in again.", "auth.messages.token-refresh-failed": "No se pudo actualizar el token de la sesión. Inicie sesión de nuevo.", - - - // "bitstream.download.page": "Now downloading {{bitstream}}..." , + // "bitstream.download.page": "Now downloading {{bitstream}}...", "bitstream.download.page": "Descargando {{ bitstream }}...", - // "bitstream.download.page.back": "Back" , + // "bitstream.download.page.back": "Back", "bitstream.download.page.back": "Atrás" , - // "bitstream.edit.authorizations.link": "Edit bitstream's Policies", "bitstream.edit.authorizations.link": "Editar las políticas del archivo", @@ -1047,6 +1065,9 @@ // "bitstream.edit.notifications.error.format.title": "An error occurred saving the bitstream's format", "bitstream.edit.notifications.error.format.title": "Se produjo un error al guardar el formato del archivo.", + // "bitstream.edit.notifications.error.primaryBitstream.title": "An error occurred saving the primary bitstream", + "bitstream.edit.notifications.error.primaryBitstream.title": "Se produjo un error al guardar el archivo primario", + // "bitstream.edit.form.iiifLabel.label": "IIIF Label", "bitstream.edit.form.iiifLabel.label": "Etiqueta IIIF", @@ -1071,7 +1092,6 @@ // "bitstream.edit.form.iiifHeight.hint": "The canvas height should usually match the image height.", "bitstream.edit.form.iiifHeight.hint": "La altura del marco normalmente debería coincidir con la altura de la imagen", - // "bitstream.edit.notifications.saved.content": "Your changes to this bitstream were saved.", "bitstream.edit.notifications.saved.content": "Se guardaron sus cambios en este archivo.", @@ -1095,6 +1115,7 @@ // "bitstream-request-a-copy.intro.bitstream.one": "Requesting the following file: ", "bitstream-request-a-copy.intro.bitstream.one": "Solicitando el siguiente fichero: ", + // "bitstream-request-a-copy.intro.bitstream.all": "Requesting all files. ", "bitstream-request-a-copy.intro.bitstream.all": "Solicitando todos los ficheros. ", @@ -1137,8 +1158,6 @@ // "bitstream-request-a-copy.submit.error": "Something went wrong with submitting the item request.", "bitstream-request-a-copy.submit.error": "Hubo un fallo en el envío de la solicitud de ítem.", - - // "browse.back.all-results": "All browse results", "browse.back.all-results": "Todos los resultados de la búsqueda", @@ -1151,8 +1170,11 @@ // "browse.comcol.by.subject": "By Subject", "browse.comcol.by.subject": "Por materia", + // "browse.comcol.by.srsc": "By Subject Category", + "browse.comcol.by.srsc": "Por categoría", + // "browse.comcol.by.title": "By Title", - "browse.comcol.by.title": "Por titulo", + "browse.comcol.by.title": "Por título", // "browse.comcol.head": "Browse", "browse.comcol.head": "Examinar", @@ -1181,6 +1203,9 @@ // "browse.metadata.subject.breadcrumbs": "Browse by Subject", "browse.metadata.subject.breadcrumbs": "Examinar por materia", + // "browse.metadata.srsc.breadcrumbs": "Browse by Subject Category", + "browse.metadata.srsc.breadcrumbs": "Examinar por categoría", + // "browse.metadata.title.breadcrumbs": "Browse by Title", "browse.metadata.title.breadcrumbs": "Examinar por título", @@ -1262,21 +1287,24 @@ // "browse.startsWith.type_text": "Filter results by typing the first few letters", "browse.startsWith.type_text": "Seleccione resultados tecleando las primeras letras", + // "browse.startsWith.input": "Filter", + "browse.startsWith.input": "Filtrar", + + // "browse.taxonomy.button": "Browse", + "browse.taxonomy.button": "Examinar", + // "browse.title": "Browsing {{ collection }} by {{ field }}{{ startsWith }} {{ value }}", "browse.title": "Examinando {{ collection }} por {{ field }} {{ value }}", // "browse.title.page": "Browsing {{ collection }} by {{ field }} {{ value }}", "browse.title.page": "Examinando {{ collection }} por {{ field }} {{ value }}", - // "search.browse.item-back": "Back to Results", "search.browse.item-back": "Volver a los resultados", - // "chips.remove": "Remove chip", "chips.remove": "Quitar chip", - // "claimed-approved-search-result-list-element.title": "Approved", "claimed-approved-search-result-list-element.title": "Aprobado", @@ -1286,7 +1314,6 @@ // "claimed-declined-task-search-result-list-element.title": "Declined, sent back to Review Manager's workflow", "claimed-declined-task-search-result-list-element.title": "Declinado, enviar de vuelta al Administrador de Revisión del flujo de trabajo", - // "collection.create.head": "Create a Collection", "collection.create.head": "Crear una colección", @@ -1320,8 +1347,6 @@ // "collection.delete.text": "Are you sure you want to delete collection \"{{ dso }}\"", "collection.delete.text": "¿Estás seguro de que quieres eliminar la colección \"{{ dso }}\"?", - - // "collection.edit.delete": "Delete this collection", "collection.edit.delete": "Eliminar esta colección", @@ -1331,8 +1356,6 @@ // "collection.edit.breadcrumbs": "Edit Collection", "collection.edit.breadcrumbs": "Editar colección", - - // "collection.edit.tabs.mapper.head": "Item Mapper", "collection.edit.tabs.mapper.head": "Mapeo de ítems", @@ -1393,7 +1416,6 @@ // "collection.edit.item-mapper.tabs.map": "Map new items", "collection.edit.item-mapper.tabs.map": "Asignar nuevos ítems", - // "collection.edit.logo.delete.title": "Delete logo", "collection.edit.logo.delete.title": "Eliminar logo", @@ -1421,21 +1443,23 @@ // "collection.edit.logo.upload": "Drop a Collection Logo to upload", "collection.edit.logo.upload": "Suelta un logotipo de colección para cargarlo", - - // "collection.edit.notifications.success": "Successfully edited the Collection", "collection.edit.notifications.success": "Editó con éxito la colección", // "collection.edit.return": "Back", "collection.edit.return": "Atrás", + // "collection.edit.tabs.access-control.head": "Access Control", + "collection.edit.tabs.access-control.head": "Control de acceso", + // "collection.edit.tabs.access-control.title": "Collection Edit - Access Control", + "collection.edit.tabs.access-control.title": "Edición de colección: Control de acceso", // "collection.edit.tabs.curate.head": "Curate", "collection.edit.tabs.curate.head": "Curar", // "collection.edit.tabs.curate.title": "Collection Edit - Curate", - "collection.edit.tabs.curate.title": "Edición de colección - Curar", + "collection.edit.tabs.curate.title": "Edición de colección: Curar", // "collection.edit.tabs.authorizations.head": "Authorizations", "collection.edit.tabs.authorizations.head": "Autorizaciones", @@ -1518,8 +1542,6 @@ // "collection.edit.tabs.source.title": "Collection Edit - Content Source", "collection.edit.tabs.source.title": "Edición de colección: fuente de contenido", - - // "collection.edit.template.add-button": "Add", "collection.edit.template.add-button": "Agregar", @@ -1556,8 +1578,6 @@ // "collection.edit.template.title": "Edit Template Item", "collection.edit.template.title": "Editar plantilla de ítem", - - // "collection.form.abstract": "Short Description", "collection.form.abstract": "Breve descripción", @@ -1585,13 +1605,9 @@ // "collection.form.entityType": "Entity Type", "collection.form.entityType": "Tipo de entidad", - - // "collection.listelement.badge": "Collection", "collection.listelement.badge": "Colección", - - // "collection.page.browse.recent.head": "Recent Submissions", "collection.page.browse.recent.head": "Envíos recientes", @@ -1610,8 +1626,6 @@ // "collection.page.news": "News", "collection.page.news": "Noticias", - - // "collection.select.confirm": "Confirm selected", "collection.select.confirm": "Confirmar seleccionado", @@ -1621,63 +1635,81 @@ // "collection.select.table.title": "Title", "collection.select.table.title": "Título", - // "collection.source.controls.head": "Harvest Controls", "collection.source.controls.head": "Controles de recolección", + // "collection.source.controls.test.submit.error": "Something went wrong with initiating the testing of the settings", "collection.source.controls.test.submit.error": "Hubo fallos al realizar las pruebas de comprobación de los ajustes", + // "collection.source.controls.test.failed": "The script to test the settings has failed", "collection.source.controls.test.failed": "La prueba de los ajustes ha fallado", + // "collection.source.controls.test.completed": "The script to test the settings has successfully finished", "collection.source.controls.test.completed": "El script de prueba de los ajustes ha terminado correctamente", + // "collection.source.controls.test.submit": "Test configuration", "collection.source.controls.test.submit": "Probar la configuración", + // "collection.source.controls.test.running": "Testing configuration...", "collection.source.controls.test.running": "Probando la configuración...", + // "collection.source.controls.import.submit.success": "The import has been successfully initiated", "collection.source.controls.import.submit.success": "La importación ha comenzado correctamente", + // "collection.source.controls.import.submit.error": "Something went wrong with initiating the import", "collection.source.controls.import.submit.error": "Hubo algún fallo al iniciar la importación", + // "collection.source.controls.import.submit": "Import now", "collection.source.controls.import.submit": "Importar ahora", + // "collection.source.controls.import.running": "Importing...", "collection.source.controls.import.running": "Importando...", + // "collection.source.controls.import.failed": "An error occurred during the import", "collection.source.controls.import.failed": "Ha ocurrido un error durante la importación", + // "collection.source.controls.import.completed": "The import completed", "collection.source.controls.import.completed": "La importación finalizó", + // "collection.source.controls.reset.submit.success": "The reset and reimport has been successfully initiated", "collection.source.controls.reset.submit.success": "La restauración y reimportación ha comenzado correctamente", + // "collection.source.controls.reset.submit.error": "Something went wrong with initiating the reset and reimport", "collection.source.controls.reset.submit.error": "Ha ocurrido un error al iniciar la restauración y reimportación", + // "collection.source.controls.reset.failed": "An error occurred during the reset and reimport", "collection.source.controls.reset.failed": "Ha ocurrido un error en la restauración y reimportación", + // "collection.source.controls.reset.completed": "The reset and reimport completed", "collection.source.controls.reset.completed": "Restauración y reimportación finalizadas", + // "collection.source.controls.reset.submit": "Reset and reimport", "collection.source.controls.reset.submit": "Restauración y reimportación", + // "collection.source.controls.reset.running": "Resetting and reimporting...", "collection.source.controls.reset.running": "Restaurando y reimportando...", + // "collection.source.controls.harvest.status": "Harvest status:", "collection.source.controls.harvest.status": "Estado de la Recolección:", + // "collection.source.controls.harvest.start": "Harvest start time:", "collection.source.controls.harvest.start": "Comienzo de la recolección:", + // "collection.source.controls.harvest.last": "Last time harvested:", "collection.source.controls.harvest.last": "Fecha de la última recolección:", + // "collection.source.controls.harvest.message": "Harvest info:", "collection.source.controls.harvest.message": "Información de recolección:", + // "collection.source.controls.harvest.no-information": "N/A", "collection.source.controls.harvest.no-information": "N/A", - // "collection.source.update.notifications.error.content": "The provided settings have been tested and didn't work.", "collection.source.update.notifications.error.content": "La configuración proporcionada se ha probado y no funcionó.", // "collection.source.update.notifications.error.title": "Server Error", "collection.source.update.notifications.error.title": "Error del Servidor", - - // "communityList.breadcrumbs": "Community List", "communityList.breadcrumbs": "Lista de comunidades", @@ -1690,8 +1722,6 @@ // "communityList.showMore": "Show More", "communityList.showMore": "Mostrar más", - - // "community.create.head": "Create a Community", "community.create.head": "Crear una comunidad", @@ -1734,7 +1764,6 @@ // "community.edit.breadcrumbs": "Edit Community", "community.edit.breadcrumbs": "Editar comunidad", - // "community.edit.logo.delete.title": "Delete logo", "community.edit.logo.delete.title": "Eliminar logo", @@ -1762,8 +1791,6 @@ // "community.edit.logo.upload": "Drop a Community Logo to upload", "community.edit.logo.upload": "Suelta un logotipo de la comunidad para cargar", - - // "community.edit.notifications.success": "Successfully edited the Community", "community.edit.notifications.success": "Editó con éxito la comunidad", @@ -1776,14 +1803,18 @@ // "community.edit.return": "Back", "community.edit.return": "Atrás", - - // "community.edit.tabs.curate.head": "Curate", "community.edit.tabs.curate.head": "Curar", // "community.edit.tabs.curate.title": "Community Edit - Curate", "community.edit.tabs.curate.title": "Edición de la comunidad - Curar", + // "community.edit.tabs.access-control.head": "Access Control", + "community.edit.tabs.access-control.head": "Control de acceso", + + // "community.edit.tabs.access-control.title": "Community Edit - Access Control", + "community.edit.tabs.access-control.title": "Edición de la comunidad: Control de acceso", + // "community.edit.tabs.metadata.head": "Edit Metadata", "community.edit.tabs.metadata.head": "Editar metadatos", @@ -1802,13 +1833,9 @@ // "community.edit.tabs.authorizations.title": "Community Edit - Authorizations", "community.edit.tabs.authorizations.title": "Edición de la comunidad: autorizaciones", - - // "community.listelement.badge": "Community", "community.listelement.badge": "Comunidad", - - // "comcol-role.edit.no-group": "None", "comcol-role.edit.no-group": "Ninguno", @@ -1827,28 +1854,24 @@ // "comcol-role.edit.delete.error.title": "Failed to delete the '{{ role }}' role's group", "comcol-role.edit.delete.error.title": "Error al borrar el grupo del rol '{{ role }}'", - // "comcol-role.edit.community-admin.name": "Administrators", "comcol-role.edit.community-admin.name": "Administradores", // "comcol-role.edit.collection-admin.name": "Administrators", "comcol-role.edit.collection-admin.name": "Administradores", - // "comcol-role.edit.community-admin.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", "comcol-role.edit.community-admin.description": "Los administradores de la comunidad pueden crear subcomunidades o colecciones y gestionar o asignar la gestión para esas subcomunidades o colecciones. Además, deciden quién puede enviar ítems a las subcolecciones, editar los metadatos del ítem (después del envío) y agregar (mapear) ítems existentes de otras colecciones (sujeto a autorización).", // "comcol-role.edit.collection-admin.description": "Collection administrators decide who can submit items to the collection, edit item metadata (after submission), and add (map) existing items from other collections to this collection (subject to authorization for that collection).", "comcol-role.edit.collection-admin.description": "Los administradores de la colección deciden quién puede enviar ítems a la colección, editar los metadatos del ítem (después del envío) y agregar (mapear) ítems existentes de otras colecciones a esta colección (sujeto a autorización para esa colección).", - // "comcol-role.edit.submitters.name": "Submitters", "comcol-role.edit.submitters.name": "Remitentes", // "comcol-role.edit.submitters.description": "The E-People and Groups that have permission to submit new items to this collection.", "comcol-role.edit.submitters.description": "Los Usuarios y Grupos que tienen permiso para enviar nuevos ítems a esta colección.", - // "comcol-role.edit.item_read.name": "Default item read access", "comcol-role.edit.item_read.name": "Acceso de lectura predeterminado del ítem", @@ -1858,7 +1881,6 @@ // "comcol-role.edit.item_read.anonymous-group": "Default read for incoming items is currently set to Anonymous.", "comcol-role.edit.item_read.anonymous-group": "La lectura predeterminada para los ítems entrantes está configurada actualmente como Anónimo.", - // "comcol-role.edit.bitstream_read.name": "Default bitstream read access", "comcol-role.edit.bitstream_read.name": "Acceso de lectura predeterminado de archivos", @@ -1868,28 +1890,24 @@ // "comcol-role.edit.bitstream_read.anonymous-group": "Default read for incoming bitstreams is currently set to Anonymous.", "comcol-role.edit.bitstream_read.anonymous-group": "La lectura predeterminada para los archivos entrantes se establece actualmente en Anónimo.", - // "comcol-role.edit.editor.name": "Editors", "comcol-role.edit.editor.name": "Editores", // "comcol-role.edit.editor.description": "Editors are able to edit the metadata of incoming submissions, and then accept or reject them.", "comcol-role.edit.editor.description": "Los editores pueden editar los metadatos de los envíos entrantes y luego aceptarlos o rechazarlos.", - // "comcol-role.edit.finaleditor.name": "Final editors", "comcol-role.edit.finaleditor.name": "Editores finales", // "comcol-role.edit.finaleditor.description": "Final editors are able to edit the metadata of incoming submissions, but will not be able to reject them.", "comcol-role.edit.finaleditor.description": "Los editores finales pueden editar los metadatos de los envíos entrantes, pero no podrán rechazarlos.", - // "comcol-role.edit.reviewer.name": "Reviewers", "comcol-role.edit.reviewer.name": "Revisores", // "comcol-role.edit.reviewer.description": "Reviewers are able to accept or reject incoming submissions. However, they are not able to edit the submission's metadata.", "comcol-role.edit.reviewer.description": "Los revisores pueden aceptar o rechazar envíos entrantes. Sin embargo, no pueden editar los metadatos del envío.", - // "comcol-role.edit.scorereviewers.name": "Score Reviewers", "comcol-role.edit.scorereviewers.name": "Revisores de puntuación", @@ -1929,14 +1947,12 @@ // "community.all-lists.head": "Subcommunities and Collections", "community.all-lists.head": "Subcomunidades y colecciones", - // "community.sub-collection-list.head": "Collections of this Community", + // "community.sub-collection-list.head": "Collections in this Community", "community.sub-collection-list.head": "Colecciones de esta comunidad", - // "community.sub-community-list.head": "Communities of this Community", + // "community.sub-community-list.head": "Communities in this Community", "community.sub-community-list.head": "Comunidades de esta comunidad", - - // "cookies.consent.accept-all": "Accept all", "cookies.consent.accept-all": "Aceptar todo", @@ -2015,38 +2031,30 @@ // "cookies.consent.app.description.authentication": "Required for signing you in", "cookies.consent.app.description.authentication": "Requerido para iniciar sesión", - // "cookies.consent.app.title.preferences": "Preferences", "cookies.consent.app.title.preferences": "Preferencias", // "cookies.consent.app.description.preferences": "Required for saving your preferences", "cookies.consent.app.description.preferences": "Requerido para guardar sus preferencias", - - // "cookies.consent.app.title.acknowledgement": "Acknowledgement", "cookies.consent.app.title.acknowledgement": "Reconocimiento", // "cookies.consent.app.description.acknowledgement": "Required for saving your acknowledgements and consents", "cookies.consent.app.description.acknowledgement": "Requerido para guardar sus reconocimientos y consentimientos", - - // "cookies.consent.app.title.google-analytics": "Google Analytics", "cookies.consent.app.title.google-analytics": "Google Analytics", // "cookies.consent.app.description.google-analytics": "Allows us to track statistical data", "cookies.consent.app.description.google-analytics": "Nos permite rastrear datos estadísticos", - - // "cookies.consent.app.title.google-recaptcha": "Google reCaptcha", "cookies.consent.app.title.google-recaptcha": "Google reCaptcha", // "cookies.consent.app.description.google-recaptcha": "We use google reCAPTCHA service during registration and password recovery", "cookies.consent.app.description.google-recaptcha": "Utilizamos el servicio google reCAPTCHA durante el registro y la recuperación de contraseña", - // "cookies.consent.purpose.functional": "Functional", "cookies.consent.purpose.functional": "Funcional", @@ -2059,10 +2067,10 @@ // "cookies.consent.purpose.sharing": "Sharing", "cookies.consent.purpose.sharing": "Compartición", - // "curation-task.task.citationpage.label": "Generate Citation Page", + // "curation-task.task.citationpage.label": "Generate Citation Page", "curation-task.task.citationpage.label": "Generar página de cita", - // "curation-task.task.checklinks.label": "Check Links in Metadata", + // "curation-task.task.checklinks.label": "Check Links in Metadata", "curation-task.task.checklinks.label": "Comprobar enlaces en metadatos", // "curation-task.task.noop.label": "NOOP", @@ -2083,8 +2091,6 @@ // "curation-task.task.registerdoi.label": "Register DOI", "curation-task.task.registerdoi.label": "Registro DOI", - - // "curation.form.task-select.label": "Task:", "curation.form.task-select.label": "Tarea:", @@ -2112,10 +2118,8 @@ // "curation.form.handle.hint": "Hint: Enter [your-handle-prefix]/0 to run a task across entire site (not all tasks may support this capability)", "curation.form.handle.hint": "Sugerencia: Introduzca [su-prefijo-handle]/0 para ejecutar una tarea en toda su instalación (no todas las tareas permiten esta opción)", - - // "deny-request-copy.email.message": "Dear {{ recipientName }},\nIn response to your request I regret to inform you that it's not possible to send you a copy of the file(s) you have requested, concerning the document: \"{{ itemUrl }}\" ({{ itemName }}), of which I am an author.\n\nBest regards,\n{{ authorName }} <{{ authorEmail }}>", - "deny-request-copy.email.message": "Estimado {{ recipientName }},\nEn respuesta a su solicitud, lamento informarle que no es posible enviarle una copia de los archivos que ha solicitado, en relación con el documento: \"{{ itemUrl }}\" ({{ itemName }}), del cual soy autor.\n\nSaludos cordiales,\n{{ authorName }} <{{ authorEmail }}>", + "deny-request-copy.email.message": "Estimado {{ recipientName }},\nEn respuesta a su solicitud, lamento informarle de que no es posible enviarle una copia de los archivos que ha solicitado, en relación con el documento: \"{{ itemUrl }}\" ({{ itemName }}), del cual soy autor.\n\nSaludos cordiales,\n{{ authorName }} <{{ authorEmail }}>", // "deny-request-copy.email.subject": "Request copy of document", "deny-request-copy.email.subject": "Solicitar una copia del documento", @@ -2127,17 +2131,16 @@ "deny-request-copy.header": "Denegar copia del documento", // "deny-request-copy.intro": "This message will be sent to the applicant of the request", - "deny-request-copy.intro": "Éste es el texto que será enviado al solicitante.", + "deny-request-copy.intro": "Éste es el texto que será enviado al solicitante", // "deny-request-copy.success": "Successfully denied item request", "deny-request-copy.success": "Solicitud de copia de documento denegada", - - // "dso.name.untitled": "Untitled", "dso.name.untitled": "Sin título", - + // "dso.name.unnamed": "Unnamed", + "dso.name.unnamed": "Sin nombre", // "dso-selector.create.collection.head": "New collection", "dso-selector.create.collection.head": "Nueva colección", @@ -2259,7 +2262,7 @@ // "supervision-group-selector.notification.create.failure.title": "Error", "supervision-group-selector.notification.create.failure.title": "Error", - // "supervision-group-selector.notification.create.already-existing" : "A supervision order already exists on this item for selected group", + // "supervision-group-selector.notification.create.already-existing": "A supervision order already exists on this item for selected group", "supervision-group-selector.notification.create.already-existing": "Ya existe una orden de supervisión para este ítem en el grupo selecionado", // "confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}", @@ -2374,7 +2377,7 @@ "error.top-level-communities": "Error al recuperar las comunidades de primer nivel", // "error.validation.license.notgranted": "You must grant this license to complete your submission. If you are unable to grant this license at this time you may save your work and return later or remove the submission.", - "error.validation.license.notgranted": "Debe conceda esta licencia de depósito para completar el envío. Si no puede conceder esta licencia en este momento, puede guardar su trabajo y regresar más tarde o bien eliminar el envío.", + "error.validation.license.notgranted": "Debe conceder esta licencia de depósito para completar el envío. Si no puede conceder esta licencia en este momento, puede guardar su trabajo y regresar más tarde o bien eliminar el envío.", // "error.validation.pattern": "This input is restricted by the current pattern: {{ pattern }}.", "error.validation.pattern": "Esta entrada está restringida por este patrón: {{ pattern }}.", @@ -2394,16 +2397,33 @@ // "error.validation.groupExists": "This group already exists", "error.validation.groupExists": "Este grupo ya existe", + // "error.validation.metadata.name.invalid-pattern": "This field cannot contain dots, commas or spaces. Please use the Element & Qualifier fields instead", + "error.validation.metadata.name.invalid-pattern": "Este campo no puede contener puntos, comas o espacios. Use preferiblemente los campos Elemento y Cualificador", + + // "error.validation.metadata.name.max-length": "This field may not contain more than 32 characters", + "error.validation.metadata.name.max-length": "Este campo no puede contener mas de 32 caracteres", + + // "error.validation.metadata.namespace.max-length": "This field may not contain more than 256 characters", + "error.validation.metadata.namespace.max-length": "Este campo no puede contener mas de 256 caracteres", + + // "error.validation.metadata.element.invalid-pattern": "This field cannot contain dots, commas or spaces. Please use the Qualifier field instead", + "error.validation.metadata.element.invalid-pattern": "Este campo no puede contener puntos, comas o espacios. Use preferiblemente el campo Cualificador", + + // "error.validation.metadata.element.max-length": "This field may not contain more than 64 characters", + "error.validation.metadata.element.max-length": "Este campo no puede contener mas de 64 caracteres", + + // "error.validation.metadata.qualifier.invalid-pattern": "This field cannot contain dots, commas or spaces", + "error.validation.metadata.qualifier.invalid-pattern": "This field cannot contain dots, commas or spaces", + + // "error.validation.metadata.qualifier.max-length": "This field may not contain more than 64 characters", + "error.validation.metadata.qualifier.max-length": "Este campo no puede contener mas de 64 caracteres", // "feed.description": "Syndication feed", "feed.description": "Hilo de sindicación", - // "file-section.error.header": "Error obtaining files for this item", "file-section.error.header": "Error al obtener archivos para este ítem", - - // "footer.copyright": "copyright © 2002-{{ year }}", "footer.copyright": "copyright © 2002-{{ year }}", @@ -2419,14 +2439,12 @@ // "footer.link.privacy-policy": "Privacy policy", "footer.link.privacy-policy": "Política de privacidad", - // "footer.link.end-user-agreement":"End User Agreement", + // "footer.link.end-user-agreement": "End User Agreement", "footer.link.end-user-agreement": "Acuerdo de usuario final", - // "footer.link.feedback":"Send Feedback", + // "footer.link.feedback": "Send Feedback", "footer.link.feedback": "Enviar Sugerencias", - - // "forgot-email.form.header": "Forgot Password", "forgot-email.form.header": "Olvido de contraseña", @@ -2460,8 +2478,6 @@ // "forgot-email.form.error.content": "An error occured when attempting to reset the password for the account associated with the following email address: {{ email }}", "forgot-email.form.error.content": "Ha ocurrido una error intentando restablecer la contraseña para la cuenta asociada al correo electrónico: {{ email }}", - - // "forgot-password.title": "Forgot Password", "forgot-password.title": "Olvido de contraseña", @@ -2504,7 +2520,6 @@ // "forgot-password.form.submit": "Submit password", "forgot-password.form.submit": "Enviar contraseña", - // "form.add": "Add more", "form.add": "Añadir más", @@ -2565,6 +2580,24 @@ // "form.no-value": "No value entered", "form.no-value": "No se introdujo ningún valor", + // "form.other-information.email": "Email", + "form.other-information.email": "Correo electrónico", + + // "form.other-information.first-name": "First Name", + "form.other-information.first-name": "Nombre", + + // "form.other-information.insolr": "In Solr Index", + "form.other-information.insolr": "en el índice Solr", + + // "form.other-information.institution": "Institution", + "form.other-information.institution": "Institución", + + // "form.other-information.last-name": "Last Name", + "form.other-information.last-name": "Apellido", + + // "form.other-information.orcid": "ORCID", + "form.other-information.orcid": "ORCID", + // "form.remove": "Remove", "form.remove": "Eliminar", @@ -2589,16 +2622,14 @@ // "form.repeatable.sort.tip": "Drop the item in the new position", "form.repeatable.sort.tip": "Suelte el ítem en la nueva posición", - - // "grant-deny-request-copy.deny": "Don't send copy", "grant-deny-request-copy.deny": "No envíe copia", // "grant-deny-request-copy.email.back": "Back", "grant-deny-request-copy.email.back": "Atrás", - // "grant-deny-request-copy.email.message": "Message", - "grant-deny-request-copy.email.message": "Mensaje", + // "grant-deny-request-copy.email.message": "Optional additional message", + "grant-deny-request-copy.email.message": "Mensaje adicional (opcional)", // "grant-deny-request-copy.email.message.empty": "Please enter a message", "grant-deny-request-copy.email.message.empty": "Por favor, introduzca un mensaje", @@ -2636,11 +2667,6 @@ // "grant-deny-request-copy.processed": "This request has already been processed. You can use the button below to get back to the home page.", "grant-deny-request-copy.processed": "Esta solicitud ya fue procesada. Puede usar los botones inferiores para regresas a la página de inicio", - - - // "grant-request-copy.email.message": "Dear {{ recipientName }},\nIn response to your request I have the pleasure to send you in attachment a copy of the file(s) concerning the document: \"{{ itemUrl }}\" ({{ itemName }}), of which I am an author.\n\nBest regards,\n{{ authorName }} <{{ authorEmail }}>", - "grant-request-copy.email.message": "Estimado {{ recipientName }},\nRespondiendo a su solicitud, le envío anexada una copia del fichero correspondiente al documento: \"{{ itemUrl }}\" ({{ itemName }}), del que soy autor.\n\nUn cordial saludo,\n{{ authorName }} <{{ authorEmail }}>", - // "grant-request-copy.email.subject": "Request copy of document", "grant-request-copy.email.subject": "Solicitar copia de documento", @@ -2650,24 +2676,23 @@ // "grant-request-copy.header": "Grant document copy request", "grant-request-copy.header": "Conceder solicitud de copia de documento", - // "grant-request-copy.intro": "This message will be sent to the applicant of the request. The requested document(s) will be attached.", - "grant-request-copy.intro": "Este mensaje se enviará al solicitante de la copia. Se le anexará una copia del documento.", + // "grant-request-copy.intro": "A message will be sent to the applicant of the request. The requested document(s) will be attached.", + "grant-request-copy.intro": "Este mensaje se enviará al solicitante de la copia. Se le anexará una copia del documento.", // "grant-request-copy.success": "Successfully granted item request", "grant-request-copy.success": "Solicitud de ítem concedida exitosamente", - // "health.breadcrumbs": "Health", "health.breadcrumbs": "Chequeos", - // "health-page.heading" : "Health", - "health-page.heading": "Chequeos", + // "health-page.heading": "Health", + "health-page.heading": "Chequeos", - // "health-page.info-tab" : "Info", - "health-page.info-tab": "Información", + // "health-page.info-tab": "Info", + "health-page.info-tab": "Información", - // "health-page.status-tab" : "Status", - "health-page.status-tab": "Estado", + // "health-page.status-tab": "Status", + "health-page.status-tab": "Estado", // "health-page.error.msg": "The health check service is temporarily unavailable", "health-page.error.msg": "El servicio de comprobación no se encuentra temporalmente disponible", @@ -2717,7 +2742,6 @@ // "health-page.section.no-issues": "No issues detected", "health-page.section.no-issues": "No se detectaron problemas", - // "home.description": "", "home.description": "", @@ -2736,8 +2760,6 @@ // "home.top-level-communities.help": "Select a community to browse its collections.", "home.top-level-communities.help": "Seleccione una comunidad para explorar sus colecciones.", - - // "info.end-user-agreement.accept": "I have read and I agree to the End User Agreement", "info.end-user-agreement.accept": "He leído y acepto el Acuerdo de usuario final.", @@ -2762,6 +2784,9 @@ // "info.end-user-agreement.title": "End User Agreement", "info.end-user-agreement.title": "Acuerdo de usuario final", + // "info.end-user-agreement.hosting-country": "the United States", + "info.end-user-agreement.hosting-country": "los Estados Unidos de América", + // "info.privacy.breadcrumbs": "Privacy Statement", "info.privacy.breadcrumbs": "Declaracion de privacidad", @@ -2795,47 +2820,39 @@ // "info.feedback.email-label": "Your Email", "info.feedback.email-label": "Su correo electrónico", - // "info.feedback.create.success" : "Feedback Sent Successfully!", + // "info.feedback.create.success": "Feedback Sent Successfully!", "info.feedback.create.success": "Envío exitoso de sugerencia", - // "info.feedback.error.email.required" : "A valid email address is required", + // "info.feedback.error.email.required": "A valid email address is required", "info.feedback.error.email.required": "se requiere una dirección válida de correo electrónico", - // "info.feedback.error.message.required" : "A comment is required", + // "info.feedback.error.message.required": "A comment is required", "info.feedback.error.message.required": "Se requiere un comentario", - // "info.feedback.page-label" : "Page", + // "info.feedback.page-label": "Page", "info.feedback.page-label": "Página", - // "info.feedback.page_help" : "Tha page related to your feedback", + // "info.feedback.page_help": "Tha page related to your feedback", "info.feedback.page_help": "La página relacionada con su sugerencia", - - // "item.alerts.private": "This item is non-discoverable", "item.alerts.private": "Este ítem es privado", // "item.alerts.withdrawn": "This item has been withdrawn", "item.alerts.withdrawn": "Este ítem ha sido retirado", - - // "item.edit.authorizations.heading": "With this editor you can view and alter the policies of an item, plus alter policies of individual item components: bundles and bitstreams. Briefly, an item is a container of bundles, and bundles are containers of bitstreams. Containers usually have ADD/REMOVE/READ/WRITE policies, while bitstreams only have READ/WRITE policies.", "item.edit.authorizations.heading": "Con este editor puede ver y modificar las políticas de un ítem, además de modificar las políticas de los componentes individuales del ítem: paquetes y archivos. Brevemente, un ítem es un contenedor de paquetes y los paquetes son contenedores de archivos. Los contenedores suelen tener políticas AGREGAR/ELIMINAR/LEER/ESCRIBIR, mientras que los archivos solo tienen políticas LEER/ESCRIBIR.", // "item.edit.authorizations.title": "Edit item's Policies", "item.edit.authorizations.title": "Editar las políticas del ítem", - - // "item.badge.private": "Non-discoverable", "item.badge.private": "Privado", // "item.badge.withdrawn": "Withdrawn", "item.badge.withdrawn": "Retirado", - - // "item.bitstreams.upload.bundle": "Bundle", "item.bitstreams.upload.bundle": "Bloque", @@ -2869,8 +2886,6 @@ // "item.bitstreams.upload.title": "Upload bitstream", "item.bitstreams.upload.title": "Subir archivo", - - // "item.edit.bitstreams.bundle.edit.buttons.upload": "Upload", "item.edit.bitstreams.bundle.edit.buttons.upload": "Subir", @@ -2961,8 +2976,6 @@ // "item.edit.bitstreams.upload-button": "Upload", "item.edit.bitstreams.upload-button": "Subir", - - // "item.edit.delete.cancel": "Cancel", "item.edit.delete.cancel": "Cancelar", @@ -2990,7 +3003,6 @@ // "item.edit.tabs.disabled.tooltip": "You're not authorized to access this tab", "item.edit.tabs.disabled.tooltip": "No tienes autorización para acceder a esta pestaña", - // "item.edit.tabs.mapper.head": "Collection Mapper", "item.edit.tabs.mapper.head": "Mapeador de colecciones", @@ -3114,8 +3126,6 @@ // "item.edit.item-mapper.tabs.map": "Map new collections", "item.edit.item-mapper.tabs.map": "Mapear nuevas colecciones", - - // "item.edit.metadata.add-button": "Add", "item.edit.metadata.add-button": "Agregar", @@ -3200,8 +3210,6 @@ // "item.edit.metadata.save-button": "Save", "item.edit.metadata.save-button": "Guardar", - - // "item.edit.modify.overview.field": "Field", "item.edit.modify.overview.field": "Campo", @@ -3211,8 +3219,6 @@ // "item.edit.modify.overview.value": "Value", "item.edit.modify.overview.value": "Valor", - - // "item.edit.move.cancel": "Back", "item.edit.move.cancel": "Atrás", @@ -3252,8 +3258,6 @@ // "item.edit.move.title": "Move item", "item.edit.move.title": "Mover ítem", - - // "item.edit.private.cancel": "Cancel", "item.edit.private.cancel": "Cancelar", @@ -3272,8 +3276,6 @@ // "item.edit.private.success": "The item is now non-discoverable", "item.edit.private.success": "El ítem ahora es privado", - - // "item.edit.public.cancel": "Cancel", "item.edit.public.cancel": "Cancelar", @@ -3292,8 +3294,6 @@ // "item.edit.public.success": "The item is now discoverable", "item.edit.public.success": "El ítem ahora es público", - - // "item.edit.reinstate.cancel": "Cancel", "item.edit.reinstate.cancel": "Cancelar", @@ -3312,8 +3312,6 @@ // "item.edit.reinstate.success": "The item was reinstated successfully", "item.edit.reinstate.success": "El ítem se reintegró correctamente", - - // "item.edit.relationships.discard-button": "Discard", "item.edit.relationships.discard-button": "Descartar", @@ -3359,11 +3357,9 @@ // "item.edit.relationships.no-entity-type": "Add 'dspace.entity.type' metadata to enable relationships for this item", "item.edit.relationships.no-entity-type": "Agregue metadatos 'dspace.entity.type' para habilitar las relaciones para este ítem", - // "item.edit.return": "Back", "item.edit.return": "Atrás", - // "item.edit.tabs.bitstreams.head": "Bitstreams", "item.edit.tabs.bitstreams.head": "Archivos", @@ -3376,6 +3372,15 @@ // "item.edit.tabs.curate.title": "Item Edit - Curate", "item.edit.tabs.curate.title": "Edición de ítem: Curar", + // "item.edit.curate.title": "Curate Item: {{item}}", + "item.edit.curate.title": "Curar ítem: {{item}}", + + // "item.edit.tabs.access-control.head": "Access Control", + "item.edit.tabs.access-control.head": "Control de acceso", + + // "item.edit.tabs.access-control.title": "Item Edit - Access Control", + "item.edit.tabs.access-control.title": "Edición de ítem: Control de acceso", + // "item.edit.tabs.metadata.head": "Metadata", "item.edit.tabs.metadata.head": "Metadatos", @@ -3407,7 +3412,7 @@ "item.edit.tabs.status.buttons.mappedCollections.label": "Administrar colecciones mapeadas", // "item.edit.tabs.status.buttons.move.button": "Move this Item to a different Collection", - "item.edit.tabs.status.buttons.move.button": "Mover éste ítem a una colección diferente", + "item.edit.tabs.status.buttons.move.button": "Mover este ítem a una colección diferente", // "item.edit.tabs.status.buttons.move.label": "Move item to another collection", "item.edit.tabs.status.buttons.move.label": "Mover ítem a otra colección", @@ -3434,7 +3439,7 @@ "item.edit.tabs.status.buttons.unauthorized": "No estás autorizado para realizar esta acción.", // "item.edit.tabs.status.buttons.withdraw.button": "Withdraw this item", - "item.edit.tabs.status.buttons.withdraw.button": "Retirar éste ítem", + "item.edit.tabs.status.buttons.withdraw.button": "Retirar este ítem", // "item.edit.tabs.status.buttons.withdraw.label": "Withdraw item from the repository", "item.edit.tabs.status.buttons.withdraw.label": "Retirar ítem del repositorio", @@ -3475,8 +3480,6 @@ // "item.edit.tabs.view.title": "Item Edit - View", "item.edit.tabs.view.title": "Edición de ítem - Ver", - - // "item.edit.withdraw.cancel": "Cancel", "item.edit.withdraw.cancel": "Cancelar", @@ -3498,7 +3501,6 @@ // "item.orcid.return": "Back", "item.orcid.return": "Atrás", - // "item.listelement.badge": "Item", "item.listelement.badge": "Ítem", @@ -3556,8 +3558,6 @@ // "workflow-item.search.result.list.element.supervised.remove-tooltip": "Remove supervision group", "workflow-item.search.result.list.element.supervised.remove-tooltip": "Borrar grupo de supervisión", - - // "item.page.abstract": "Abstract", "item.page.abstract": "Resumen", @@ -3648,10 +3648,10 @@ // "item.page.bitstreams.collapse": "Collapse", "item.page.bitstreams.collapse": "Contraer", - // "item.page.filesection.original.bundle" : "Original bundle", + // "item.page.filesection.original.bundle": "Original bundle", "item.page.filesection.original.bundle": "Bloque original", - // "item.page.filesection.license.bundle" : "License bundle", + // "item.page.filesection.license.bundle": "License bundle", "item.page.filesection.license.bundle": "Bloque de licencias", // "item.page.return": "Back", @@ -3696,27 +3696,30 @@ // "item.preview.dc.type": "Type:", "item.preview.dc.type": "Tipo:", - // "item.preview.oaire.citation.issue" : "Issue", + // "item.preview.oaire.citation.issue": "Issue", "item.preview.oaire.citation.issue": "Número", - // "item.preview.oaire.citation.volume" : "Volume", + // "item.preview.oaire.citation.volume": "Volume", "item.preview.oaire.citation.volume": "Volumen", - // "item.preview.dc.relation.issn" : "ISSN", + // "item.preview.dc.relation.issn": "ISSN", "item.preview.dc.relation.issn": "ISSN", - // "item.preview.dc.identifier.isbn" : "ISBN", + // "item.preview.dc.identifier.isbn": "ISBN", "item.preview.dc.identifier.isbn": "ISBN", // "item.preview.dc.identifier": "Identifier:", "item.preview.dc.identifier": "Identificador:", - // "item.preview.dc.relation.ispartof" : "Journal or Serie", + // "item.preview.dc.relation.ispartof": "Journal or Series", "item.preview.dc.relation.ispartof": "Revista o Serie", - // "item.preview.dc.identifier.doi" : "DOI", + // "item.preview.dc.identifier.doi": "DOI", "item.preview.dc.identifier.doi": "DOI", + // "item.preview.dc.publisher": "Publisher:", + "item.preview.dc.publisher": "Editor:", + // "item.preview.person.familyName": "Surname:", "item.preview.person.familyName": "Apellido:", @@ -3744,8 +3747,6 @@ // "item.preview.oaire.fundingStream": "Funding Stream:", "item.preview.oaire.fundingStream": "Línea de financiación:", - - // "item.select.confirm": "Confirm selected", "item.select.confirm": "Confirmar seleccionado", @@ -3761,7 +3762,6 @@ // "item.select.table.title": "Title", "item.select.table.title": "Título", - // "item.version.history.empty": "There are no other versions for this item yet.", "item.version.history.empty": "Aún no hay otras versiones para este ítem.", @@ -3822,11 +3822,9 @@ // "item.version.history.table.action.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history", "item.version.history.table.action.hasDraft": "No es posible crear una nueva versión puesto que existe en el historial de versiones un envío pendiente", - // "item.version.notice": "This is not the latest version of this item. The latest version can be found here.", "item.version.notice": "Esta no es la última versión de este ítem. La última versión se puede encontrar aquí.", - // "item.version.create.modal.header": "New version", "item.version.create.modal.header": "Nueva versión", @@ -3860,21 +3858,20 @@ // "item.version.create.modal.submitted.text": "The new version is being created. This may take some time if the item has a lot of relationships.", "item.version.create.modal.submitted.text": "Se está creando la nueva versión. Si el ítem tiene muchas relaciones, este proceso podría tardar.", - // "item.version.create.notification.success" : "New version has been created with version number {{version}}", + // "item.version.create.notification.success": "New version has been created with version number {{version}}", "item.version.create.notification.success": "Se ha creado una nueva versión con número {{version}}", - // "item.version.create.notification.failure" : "New version has not been created", + // "item.version.create.notification.failure": "New version has not been created", "item.version.create.notification.failure": "No se ha creado una nueva versión", - // "item.version.create.notification.inProgress" : "A new version cannot be created because there is an inprogress submission in the version history", + // "item.version.create.notification.inProgress": "A new version cannot be created because there is an inprogress submission in the version history", "item.version.create.notification.inProgress": "No es posible crear una nueva versión puesto que existe en el historial de versiones un envío pendiente", - // "item.version.delete.modal.header": "Delete version", "item.version.delete.modal.header": "Borrar versión", // "item.version.delete.modal.text": "Do you want to delete version {{version}}?", - "item.version.delete.modal.text": "Quiere borrar la versión {{version}}?", + "item.version.delete.modal.text": "¿Quiere borrar la versión {{version}}?", // "item.version.delete.modal.button.confirm": "Delete", "item.version.delete.modal.button.confirm": "Borrar", @@ -3888,21 +3885,18 @@ // "item.version.delete.modal.button.cancel.tooltip": "Do not delete this version", "item.version.delete.modal.button.cancel.tooltip": "No borrar esta versión", - // "item.version.delete.notification.success" : "Version number {{version}} has been deleted", + // "item.version.delete.notification.success": "Version number {{version}} has been deleted", "item.version.delete.notification.success": "Se ha borrado la versión número {{version}}", - // "item.version.delete.notification.failure" : "Version number {{version}} has not been deleted", + // "item.version.delete.notification.failure": "Version number {{version}} has not been deleted", "item.version.delete.notification.failure": "No se ha borrado la versión número {{version}}", + // "item.version.edit.notification.success": "The summary of version number {{version}} has been changed", + "item.version.edit.notification.success": "Se ha cambiado el resumen de la versión número {{version}}", - // "item.version.edit.notification.success" : "The summary of version number {{version}} has been changed", - "item.version.edit.notification.success": "Ha cambiado el resumen de la versión número {{version}}", - - // "item.version.edit.notification.failure" : "The summary of version number {{version}} has not been changed", + // "item.version.edit.notification.failure": "The summary of version number {{version}} has not been changed", "item.version.edit.notification.failure": "No ha cambiado el resumen de la versión número {{version}}", - - // "itemtemplate.edit.metadata.add-button": "Add", "itemtemplate.edit.metadata.add-button": "Agregar", @@ -3984,8 +3978,6 @@ // "itemtemplate.edit.metadata.save-button": "Save", "itemtemplate.edit.metadata.save-button": "Guardar", - - // "journal.listelement.badge": "Journal", "journal.listelement.badge": "Revista", @@ -4016,8 +4008,6 @@ // "journal.search.title": "Journal Search", "journal.search.title": "Búsqueda de revistas", - - // "journalissue.listelement.badge": "Journal Issue", "journalissue.listelement.badge": "Número de la revista", @@ -4045,8 +4035,6 @@ // "journalissue.page.titleprefix": "Journal Issue: ", "journalissue.page.titleprefix": "Número de la revista: ", - - // "journalvolume.listelement.badge": "Journal Volume", "journalvolume.listelement.badge": "Volumen de la revista", @@ -4065,7 +4053,6 @@ // "journalvolume.page.volume": "Volume", "journalvolume.page.volume": "Volumen", - // "iiifsearchable.listelement.badge": "Document Media", "iiifsearchable.listelement.badge": "Soporte (media) del documento", @@ -4099,7 +4086,6 @@ // "iiif.page.description": "Description: ", "iiif.page.description": "Descripción: ", - // "loading.bitstream": "Loading bitstream...", "loading.bitstream": "Cargando archivo...", @@ -4154,8 +4140,6 @@ // "loading.top-level-communities": "Loading top-level communities...", "loading.top-level-communities": "Cargando comunidades de primer nivel...", - - // "login.form.email": "Email address", "login.form.email": "Correo electrónico", @@ -4192,8 +4176,6 @@ // "login.breadcrumbs": "Login", "login.breadcrumbs": "Acceso", - - // "logout.form.header": "Log out from DSpace", "logout.form.header": "Cerrar sesión en DSpace", @@ -4203,8 +4185,6 @@ // "logout.title": "Logout", "logout.title": "Cerrar sesión", - - // "menu.header.admin": "Management", "menu.header.admin": "Gestión", @@ -4214,27 +4194,24 @@ // "menu.header.admin.description": "Management menu", "menu.header.admin.description": "Menú de gestión", - - // "menu.section.access_control": "Access Control", "menu.section.access_control": "Control de acceso", // "menu.section.access_control_authorizations": "Authorizations", "menu.section.access_control_authorizations": "Autorizaciones", + // "menu.section.access_control_bulk": "Bulk Access Management", + "menu.section.access_control_bulk": "Gestión de Acceso Masivo", + // "menu.section.access_control_groups": "Groups", "menu.section.access_control_groups": "Grupos", // "menu.section.access_control_people": "People", "menu.section.access_control_people": "Usuarios", - - // "menu.section.admin_search": "Admin Search", "menu.section.admin_search": "Búsqueda de administrador", - - // "menu.section.browse_community": "This Community", "menu.section.browse_community": "Esta comunidad", @@ -4259,22 +4236,21 @@ // "menu.section.browse_global_by_subject": "By Subject", "menu.section.browse_global_by_subject": "Por tema", + // "menu.section.browse_global_by_srsc": "By Subject Category", + "menu.section.browse_global_by_srsc": "Por categoría", + // "menu.section.browse_global_by_title": "By Title", "menu.section.browse_global_by_title": "Por titulo", // "menu.section.browse_global_communities_and_collections": "Communities & Collections", "menu.section.browse_global_communities_and_collections": "Comunidades", - - // "menu.section.control_panel": "Control Panel", "menu.section.control_panel": "Panel de control", // "menu.section.curation_task": "Curation Task", "menu.section.curation_task": "Tareas de curación", - - // "menu.section.edit": "Edit", "menu.section.edit": "Editar", @@ -4287,8 +4263,6 @@ // "menu.section.edit_item": "Item", "menu.section.edit_item": "Ítem", - - // "menu.section.export": "Export", "menu.section.export": "Exportar", @@ -4307,7 +4281,6 @@ // "menu.section.export_batch": "Batch Export (ZIP)", "menu.section.export_batch": "Exportación por lotes (ZIP)", - // "menu.section.icon.access_control": "Access Control menu section", "menu.section.icon.access_control": "Sección del menú de control de acceso", @@ -4356,8 +4329,6 @@ // "menu.section.icon.unpin": "Unpin sidebar", "menu.section.icon.unpin": "Desanclar la barra lateral", - - // "menu.section.import": "Import", "menu.section.import": "Importar", @@ -4367,8 +4338,6 @@ // "menu.section.import_metadata": "Metadata", "menu.section.import_metadata": "Metadatos", - - // "menu.section.new": "New", "menu.section.new": "Nuevo", @@ -4387,24 +4356,18 @@ // "menu.section.new_process": "Process", "menu.section.new_process": "Proceso", - - // "menu.section.pin": "Pin sidebar", "menu.section.pin": "Anclar barra lateral", // "menu.section.unpin": "Unpin sidebar", "menu.section.unpin": "Desanclar la barra lateral", - - // "menu.section.processes": "Processes", "menu.section.processes": "Procesos", // "menu.section.health": "Health", "menu.section.health": "Chequeo", - - // "menu.section.registries": "Registries", "menu.section.registries": "Registros", @@ -4414,16 +4377,12 @@ // "menu.section.registries_metadata": "Metadata", "menu.section.registries_metadata": "Metadatos", - - // "menu.section.statistics": "Statistics", "menu.section.statistics": "Estadísticas", // "menu.section.statistics_task": "Statistics Task", "menu.section.statistics_task": "Tarea de estadísticas", - - // "menu.section.toggle.access_control": "Toggle Access Control section", "menu.section.toggle.access_control": "Alternar sección de control de acceso", @@ -4454,19 +4413,18 @@ // "menu.section.toggle.statistics_task": "Toggle Statistics Task section", "menu.section.toggle.statistics_task": "Alternar sección de Tarea de estadísticas", - // "menu.section.workflow": "Administer Workflow", "menu.section.workflow": "Administrar flujo de trabajo", - // "metadata-export-search.tooltip": "Export search results as CSV", "metadata-export-search.tooltip": "Exportar los resultados de búsqueda a CSV", + // "metadata-export-search.submit.success": "The export was started successfully", "metadata-export-search.submit.success": "La exportación ha comenzado satisfactoriamente", + // "metadata-export-search.submit.error": "Starting the export has failed", "metadata-export-search.submit.error": "Ha fallado el comienzo de la exportación", - // "mydspace.breadcrumbs": "MyDSpace", "mydspace.breadcrumbs": "Mi DSpace", @@ -4593,8 +4551,6 @@ // "mydspace.view-btn": "View", "mydspace.view-btn": "Ver", - - // "nav.browse.header": "All of DSpace", "nav.browse.header": "Todo DSpace", @@ -4628,25 +4584,27 @@ // "nav.search": "Search", "nav.search": "Buscar", + // "nav.search.button": "Submit search", + "nav.search.button": "Buscar", + // "nav.statistics.header": "Statistics", "nav.statistics.header": "Estadísticas", // "nav.stop-impersonating": "Stop impersonating EPerson", - "nav.stop-impersonating": "Dejar de hacerse pasar por usuario", + "nav.stop-impersonating": "Dejar de impersonar al usuario", - // "nav.subscriptions" : "Subscriptions", + // "nav.subscriptions": "Subscriptions", "nav.subscriptions": "Suscripciones", - // "nav.toggle" : "Toggle navigation", + // "nav.toggle": "Toggle navigation", "nav.toggle": "Alternar navegación", - // "nav.user.description" : "User profile bar", + // "nav.user.description": "User profile bar", "nav.user.description": "Barra de perfil de usuario", // "none.listelement.badge": "Item", "none.listelement.badge": "Ítem", - // "orgunit.listelement.badge": "Organizational Unit", "orgunit.listelement.badge": "Unidad organizativa", @@ -4674,8 +4632,6 @@ // "orgunit.page.titleprefix": "Organizational Unit: ", "orgunit.page.titleprefix": "Unidad organizativa: ", - - // "pagination.options.description": "Pagination options", "pagination.options.description": "Opciones de paginación", @@ -4691,8 +4647,6 @@ // "pagination.sort-direction": "Sort Options", "pagination.sort-direction": "Opciones de ordenación", - - // "person.listelement.badge": "Person", "person.listelement.badge": "Persona", @@ -4741,8 +4695,6 @@ // "person.search.title": "Person Search", "person.search.title": "Búsqueda de personas", - - // "process.new.select-parameters": "Parameters", "process.new.select-parameters": "Parámetros", @@ -4791,6 +4743,9 @@ // "process.new.notification.error.content": "An error occurred while creating this process", "process.new.notification.error.content": "Se produjo un error al crear este proceso.", + // "process.new.notification.error.max-upload.content": "The file exceeds the maximum upload size", + "process.new.notification.error.max-upload.content": "El fichero sobrepasa el límte máximo de subida", + // "process.new.header": "Create a new process", "process.new.header": "Crea un nuevo proceso", @@ -4800,18 +4755,16 @@ // "process.new.breadcrumbs": "Create a new process", "process.new.breadcrumbs": "Crea un nuevo proceso", - - - // "process.detail.arguments" : "Arguments", + // "process.detail.arguments": "Arguments", "process.detail.arguments": "Argumentos", - // "process.detail.arguments.empty" : "This process doesn't contain any arguments", + // "process.detail.arguments.empty": "This process doesn't contain any arguments", "process.detail.arguments.empty": "Este proceso no contiene ningún argumento", - // "process.detail.back" : "Back", + // "process.detail.back": "Back", "process.detail.back": "Atrás", - // "process.detail.output" : "Process Output", + // "process.detail.output": "Process Output", "process.detail.output": "Salida del proceso", // "process.detail.logs.button": "Retrieve process output", @@ -4823,28 +4776,29 @@ // "process.detail.logs.none": "This process has no output", "process.detail.logs.none": "Este proceso no tiene salida", - // "process.detail.output-files" : "Output Files", + // "process.detail.output-files": "Output Files", "process.detail.output-files": "Archivos de salida", - // "process.detail.output-files.empty" : "This process doesn't contain any output files", + // "process.detail.output-files.empty": "This process doesn't contain any output files", "process.detail.output-files.empty": "Este proceso no contiene ningún archivo de salida", - // "process.detail.script" : "Script", + // "process.detail.script": "Script", "process.detail.script": "Secuencia de comandos", - // "process.detail.title" : "Process: {{ id }} - {{ name }}", + // "process.detail.title": "Process: {{ id }} - {{ name }}", + "process.detail.title": "Proceso: {{ id }} - {{ name }}", - // "process.detail.start-time" : "Start time", + // "process.detail.start-time": "Start time", "process.detail.start-time": "Hora de inicio", - // "process.detail.end-time" : "Finish time", + // "process.detail.end-time": "Finish time", "process.detail.end-time": "Hora de finalización", - // "process.detail.status" : "Status", + // "process.detail.status": "Status", "process.detail.status": "Estado", - // "process.detail.create" : "Create similar process", + // "process.detail.create": "Create similar process", "process.detail.create": "Crear un proceso similar", // "process.detail.actions": "Actions", @@ -4871,24 +4825,22 @@ // "process.detail.delete.error": "Something went wrong when deleting the process", "process.detail.delete.error": "Algo falló eliminando el proceso", - - - // "process.overview.table.finish" : "Finish time (UTC)", + // "process.overview.table.finish": "Finish time (UTC)", "process.overview.table.finish": "Hora de finalización (UTC)", - // "process.overview.table.id" : "Process ID", + // "process.overview.table.id": "Process ID", "process.overview.table.id": "ID de proceso", - // "process.overview.table.name" : "Name", + // "process.overview.table.name": "Name", "process.overview.table.name": "Nombre", - // "process.overview.table.start" : "Start time (UTC)", + // "process.overview.table.start": "Start time (UTC)", "process.overview.table.start": "Hora de inicio (UTC)", - // "process.overview.table.status" : "Status", + // "process.overview.table.status": "Status", "process.overview.table.status": "Estado", - // "process.overview.table.user" : "User", + // "process.overview.table.user": "User", "process.overview.table.user": "Usuario", // "process.overview.title": "Processes Overview", @@ -4927,8 +4879,6 @@ // "process.bulk.delete.success": "{{count}} process(es) have been succesfully deleted", "process.bulk.delete.success": "{{count}} proceso(s) se han eliminado correctamente", - - // "profile.breadcrumbs": "Update Profile", "profile.breadcrumbs": "Actualización del perfil", @@ -5058,8 +5008,6 @@ // "project-relationships.search.results.head": "Project Search Results", "project-relationships.search.results.head": "Resultados de búsqueda de proyectos", - - // "publication.listelement.badge": "Publication", "publication.listelement.badge": "Publicación", @@ -5093,7 +5041,6 @@ // "publication.search.title": "Publication Search", "publication.search.title": "Búsqueda de publicaciones", - // "media-viewer.next": "Next", "media-viewer.next": "Siguiente", @@ -5103,7 +5050,6 @@ // "media-viewer.playlist": "Playlist", "media-viewer.playlist": "Lista de reproducción", - // "register-email.title": "New user registration", "register-email.title": "Registro de nuevo usuario", @@ -5167,7 +5113,6 @@ // "register-page.create-profile.submit.success.head": "Registration completed", "register-page.create-profile.submit.success.head": "Registro completado", - // "register-page.registration.header": "New user registration", "register-page.registration.header": "Registro de nuevo usuario", @@ -5209,6 +5154,7 @@ // "register-page.registration.google-recaptcha.must-accept-cookies": "In order to register you must accept the Registration and Password recovery (Google reCaptcha) cookies.", "register-page.registration.google-recaptcha.must-accept-cookies": "Para registrarse debe aceptar las cookies de Registro y recuperación de contraseña (Google reCaptcha).", + // "register-page.registration.error.maildomain": "This email address is not on the list of domains who can register. Allowed domains are {{ domains }}", "register-page.registration.error.maildomain": "Este correo electrónico no esta en la lista de dominios que pueden registrarse. Los dominios permitidos son {{ domains }}", @@ -5223,6 +5169,7 @@ // "register-page.registration.google-recaptcha.notification.message.expired": "Verification expired. Please verify again.", "register-page.registration.google-recaptcha.notification.message.expired": "Verificación caducada. Verifique de nuevo.", + // "register-page.registration.info.maildomain": "Accounts can be registered for mail addresses of the domains", "register-page.registration.info.maildomain": "Las cuentas pueden registrarse para las direcciones de correo de los dominios", @@ -5289,16 +5236,14 @@ // "relationships.isFundingAgencyOf.OrgUnit": "Funder", "relationships.isFundingAgencyOf.OrgUnit": "Financiador", - // "repository.image.logo": "Repository logo", "repository.image.logo": "Logotipo del repositorio", - // "repository.title.prefix": "DSpace Angular :: ", - "repository.title.prefix": "DSpace Angular :: ", - - // "repository.title.prefixDSpace": "DSpace Angular ::", - "repository.title.prefixDSpace": "DSpace Angular ::", + // "repository.title": "DSpace Repository", + "repository.title": "Repositorio DSpace", + // "repository.title.prefix": "DSpace Repository :: ", + "repository.title.prefix": "Repositorio DSpace :: ", // "resource-policies.add.button": "Add", "resource-policies.add.button": "Agregar", @@ -5471,8 +5416,6 @@ // "resource-policies.table.headers.title.for.collection": "Policies for Collection", "resource-policies.table.headers.title.for.collection": "Políticas para la colección", - - // "search.description": "", "search.description": "", @@ -5488,7 +5431,6 @@ // "search.search-form.placeholder": "Search the repository ...", "search.search-form.placeholder": "Buscar en el repositorio ...", - // "search.filters.applied.f.author": "Author", "search.filters.applied.f.author": "Autor", @@ -5537,8 +5479,6 @@ // "search.filters.applied.f.withdrawn": "Withdrawn", "search.filters.applied.f.withdrawn": "Retirado", - - // "search.filters.filter.author.head": "Author", "search.filters.filter.author.head": "Autor", @@ -5758,8 +5698,6 @@ // "search.filters.filter.supervisedBy.label": "Search Supervised By", "search.filters.filter.supervisedBy.label": "Búsqueda Supervisada Por", - - // "search.filters.entityType.JournalIssue": "Journal Issue", "search.filters.entityType.JournalIssue": "Número de la revista", @@ -5787,7 +5725,6 @@ // "search.filters.withdrawn.false": "No", "search.filters.withdrawn.false": "No", - // "search.filters.head": "Filters", "search.filters.head": "Filtros", @@ -5797,8 +5734,6 @@ // "search.filters.search.submit": "Submit", "search.filters.search.submit": "Enviar", - - // "search.form.search": "Search", "search.form.search": "Buscar", @@ -5808,8 +5743,6 @@ // "search.form.scope.all": "All of DSpace", "search.form.scope.all": "Todo DSpace", - - // "search.results.head": "Search Results", "search.results.head": "Resultados de la búsqueda", @@ -5834,7 +5767,6 @@ // "default-relationships.search.results.head": "Search Results", "default-relationships.search.results.head": "Resultados de la búsqueda", - // "search.sidebar.close": "Back to results", "search.sidebar.close": "Volver a resultados", @@ -5856,8 +5788,6 @@ // "search.sidebar.settings.title": "Settings", "search.sidebar.settings.title": "Ajustes", - - // "search.view-switch.show-detail": "Show detail", "search.view-switch.show-detail": "Mostrar detalle", @@ -5867,8 +5797,6 @@ // "search.view-switch.show-list": "Show as list", "search.view-switch.show-list": "Mostrar como lista", - - // "sorting.ASC": "Ascending", "sorting.ASC": "Ascendente", @@ -5905,7 +5833,6 @@ // "sorting.lastModified.DESC": "Last modified Descending", "sorting.lastModified.DESC": "Última modificación Descendente", - // "statistics.title": "Statistics", "statistics.title": "Estadísticas", @@ -5942,7 +5869,6 @@ // "statistics.table.no-name": "(object name could not be loaded)", "statistics.table.no-name": "(el nombre del objeto no pudo ser cargado)", - // "submission.edit.breadcrumbs": "Edit Submission", "submission.edit.breadcrumbs": "Editar envío", @@ -5952,7 +5878,7 @@ // "submission.general.cancel": "Cancel", "submission.general.cancel": "Cancelar", - // "submission.general.cannot_submit": "You have not the privilege to make a new submission.", + // "submission.general.cannot_submit": "You don't have permission to make a new submission.", "submission.general.cannot_submit": "No tiene los permisos para realizar un nuevo envío.", // "submission.general.deposit": "Deposit", @@ -5985,7 +5911,6 @@ // "submission.general.save-later": "Save for later", "submission.general.save-later": "Guardar para más adelante", - // "submission.import-external.page.title": "Import metadata from an external source", "submission.import-external.page.title": "Importar metadatos desde una fuente externa", @@ -6297,6 +6222,7 @@ // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalOfPublication": "Local Journals ({{ count }})", "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalOfPublication": "Revistas locales ({{ count }})", + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.Project": "Local Projects ({{ count }})", "submission.sections.describe.relationship-lookup.search-tab.tab-title.Project": "Proyectos locales ({{ count }})", @@ -6320,16 +6246,18 @@ // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalIssueOfPublication": "Local Journal Issues ({{ count }})", "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalIssueOfPublication": "Números de revista locales ({{ count }})", + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalIssue": "Local Journal Issues ({{ count }})", "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalIssue": "Números de revista locales ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalVolumeOfPublication": "Local Journal Volumes ({{ count }})", "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalVolumeOfPublication": "Volúmenes de revista locales ({{ count }})", + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalVolume": "Local Journal Volumes ({{ count }})", "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalVolume": "Volúmenes de revista locales ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaJournal": "Sherpa Journals ({{ count }})", - "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaJournal": "Revistas Sherpa ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaJournal": "Revistas Sherpa ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaPublisher": "Sherpa Publishers ({{ count }})", "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaPublisher": "Editores Sherpa ({{ count }})", @@ -6379,9 +6307,6 @@ // "submission.sections.describe.relationship-lookup.title.isFundingAgencyOfProject": "Funder of the Project", "submission.sections.describe.relationship-lookup.title.isFundingAgencyOfProject": "Financiador del Proyecto", - - - // "submission.sections.describe.relationship-lookup.selection-tab.search-form.placeholder": "Search...", "submission.sections.describe.relationship-lookup.selection-tab.search-form.placeholder": "Buscar...", @@ -6390,11 +6315,13 @@ // "submission.sections.describe.relationship-lookup.title.isJournalIssueOfPublication": "Journal Issues", "submission.sections.describe.relationship-lookup.title.isJournalIssueOfPublication": "Números de revista", + // "submission.sections.describe.relationship-lookup.title.JournalIssue": "Journal Issues", "submission.sections.describe.relationship-lookup.title.JournalIssue": "Números de revista", // "submission.sections.describe.relationship-lookup.title.isJournalVolumeOfPublication": "Journal Volumes", "submission.sections.describe.relationship-lookup.title.isJournalVolumeOfPublication": "Volúmenes de la revista", + // "submission.sections.describe.relationship-lookup.title.JournalVolume": "Journal Volumes", "submission.sections.describe.relationship-lookup.title.JournalVolume": "Volúmenes de la revista", @@ -6406,6 +6333,7 @@ // "submission.sections.describe.relationship-lookup.title.isFundingAgencyOfPublication": "Funding Agency", "submission.sections.describe.relationship-lookup.title.isFundingAgencyOfPublication": "Agencia financiadora", + // "submission.sections.describe.relationship-lookup.title.Project": "Projects", "submission.sections.describe.relationship-lookup.title.Project": "Proyectos", @@ -6453,6 +6381,7 @@ // "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalVolumeOfPublication": "Selected Journal Volume", "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalVolumeOfPublication": "Volumen de revista seleccionada", + // "submission.sections.describe.relationship-lookup.selection-tab.title.Project": "Selected Projects", "submission.sections.describe.relationship-lookup.selection-tab.title.Project": "Proyectos seleccionados", @@ -6476,6 +6405,7 @@ // "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalIssueOfPublication": "Selected Issue", "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalIssueOfPublication": "Número de revista seleccionados", + // "submission.sections.describe.relationship-lookup.selection-tab.title.JournalVolume": "Selected Journal Volume", "submission.sections.describe.relationship-lookup.selection-tab.title.JournalVolume": "Volúmenes de revista seleccionados", @@ -6484,6 +6414,7 @@ // "submission.sections.describe.relationship-lookup.selection-tab.title.isFundingOfPublication": "Selected Funding", "submission.sections.describe.relationship-lookup.selection-tab.title.isFundingOfPublication": "Financiamientos seleccionados", + // "submission.sections.describe.relationship-lookup.selection-tab.title.JournalIssue": "Selected Issue", "submission.sections.describe.relationship-lookup.selection-tab.title.JournalIssue": "Número seleccionado", @@ -6658,7 +6589,6 @@ // "submission.sections.submit.progressbar.sherpaPolicies": "Publisher open access policy information", "submission.sections.submit.progressbar.sherpaPolicies": "Información sobre políticas editoriales de acceso abierto", - // "submission.sections.sherpa-policy.title-empty": "No publisher policy information available. If your work has an associated ISSN, please enter it above to see any related publisher open access policies.", "submission.sections.sherpa-policy.title-empty": "No se encuentra disponible la información sobre política editorial. Si tiene un ISSN asociado, introdúzcalo arriba para ver información sobre políticas editoriales de acceso abierto.", @@ -6839,10 +6769,9 @@ // "submission.sections.license.required": "You must accept the license", "submission.sections.license.required": "Debe aceptar la licencia", - // "submission.sections.license.notgranted": "You must accept the license", + // "submission.sections.license.notgranted": "You must accept the license", "submission.sections.license.notgranted": "Debe aceptar la licencia", - // "submission.sections.sherpa.publication.information": "Publication information", "submission.sections.sherpa.publication.information": "Información de la Publicación", @@ -6868,7 +6797,7 @@ "submission.sections.sherpa.publisher.policy": "Política editorial", // "submission.sections.sherpa.publisher.policy.description": "The below information was found via Sherpa Romeo. Based on the policies of your publisher, it provides advice regarding whether an embargo may be necessary and/or which files you are allowed to upload. If you have questions, please contact your site administrator via the feedback form in the footer.", - "submission.sections.sherpa.publisher.policy.description": "La siguiente informacióm se encontró via Sherpa Romeo. En base a esas políticas, se proporcionan consejos sobre las necesidad de un embargo y de aquellos ficheros que puede utilizar. Si tiene dudas, por favor, contacte con el administrador del repositorio mediante el formulario de sugerencias.", + "submission.sections.sherpa.publisher.policy.description": "La siguiente informacióm se encontró vía Sherpa Romeo. En base a esas políticas, se proporcionan consejos sobre las necesidad de un embargo y de aquellos ficheros que puede utilizar. Si tiene dudas, por favor, contacte con el administrador del repositorio mediante el formulario de sugerencias.", // "submission.sections.sherpa.publisher.policy.openaccess": "Open Access pathways permitted by this journal's policy are listed below by article version. Click on a pathway for a more detailed view", "submission.sections.sherpa.publisher.policy.openaccess": "Los caminos hacia el Acceso Abierto que esta revista permite se relacionan, por versión de artículo, abajo. Pulse en un camino para una visión mas detallada", @@ -6921,16 +6850,12 @@ // "submission.sections.sherpa.error.message": "There was an error retrieving sherpa informations", "submission.sections.sherpa.error.message": "Hubo un error recuperando la información de Sherpa", - - // "submission.submit.breadcrumbs": "New submission", "submission.submit.breadcrumbs": "Nuevo envío", // "submission.submit.title": "New submission", "submission.submit.title": "Nuevo envío", - - // "submission.workflow.generic.delete": "Delete", "submission.workflow.generic.delete": "Eliminar", @@ -6949,21 +6874,18 @@ // "submission.workflow.generic.view-help": "Select this option to view the item's metadata.", "submission.workflow.generic.view-help": "Seleccione esta opción para ver los metadatos del ítem.", - // "submission.workflow.generic.submit_select_reviewer": "Select Reviewer", "submission.workflow.generic.submit_select_reviewer": "Seleccionar revisor", // "submission.workflow.generic.submit_select_reviewer-help": "", "submission.workflow.generic.submit_select_reviewer-help": "", - // "submission.workflow.generic.submit_score": "Rate", "submission.workflow.generic.submit_score": "Evaluar", // "submission.workflow.generic.submit_score-help": "", "submission.workflow.generic.submit_score-help": "", - // "submission.workflow.tasks.claimed.approve": "Approve", "submission.workflow.tasks.claimed.approve": "Aprobar", @@ -7006,8 +6928,6 @@ // "submission.workflow.tasks.claimed.return_help": "Return the task to the pool so that another user may perform the task.", "submission.workflow.tasks.claimed.return_help": "Devuelva la tarea al pool para que otro usuario pueda realizarla.", - - // "submission.workflow.tasks.generic.error": "Error occurred during operation...", "submission.workflow.tasks.generic.error": "Ocurrió un error durante la operación...", @@ -7020,8 +6940,6 @@ // "submission.workflow.tasks.generic.success": "Operation successful", "submission.workflow.tasks.generic.success": "Operación exitosa", - - // "submission.workflow.tasks.pool.claim": "Claim", "submission.workflow.tasks.pool.claim": "Asumir tarea", @@ -7034,13 +6952,14 @@ // "submission.workflow.tasks.pool.show-detail": "Show detail", "submission.workflow.tasks.pool.show-detail": "Mostrar detalle", - // "submission.workspace.generic.view": "View", "submission.workspace.generic.view": "Ver", // "submission.workspace.generic.view-help": "Select this option to view the item's metadata.", "submission.workspace.generic.view-help": "Seleccione esta opción para ver los metadatos del ítem.", + // "submitter.empty": "N/A", + "submitter.empty": "N/A", // "subscriptions.title": "Subscriptions", "subscriptions.title": "Suscripciones", @@ -7147,7 +7066,6 @@ // "subscriptions.table.empty.message": "You do not have any subscriptions at this time. To subscribe to email updates for a Community or Collection, use the subscription button on the object's page.", "subscriptions.table.empty.message": "Usted no tiene suscripciones. Para subscribirse a las actualizaciones por correo electrónico de una Comunidad o Colección, utilice el botón de suscripción en la página del objeto.", - // "thumbnail.default.alt": "Thumbnail Image", "thumbnail.default.alt": "Miniatura", @@ -7172,13 +7090,9 @@ // "thumbnail.person.placeholder": "No Profile Picture Available", "thumbnail.person.placeholder": "No hay imagen de perfil disponible", - - // "title": "DSpace", "title": "DSpace", - - // "vocabulary-treeview.header": "Hierarchical tree view", "vocabulary-treeview.header": "Vista de árbol jerárquico", @@ -7230,8 +7144,6 @@ // "virtual-metadata.delete-relationship.modal-head": "Select the items for which you want to save the virtual metadata as real metadata", "virtual-metadata.delete-relationship.modal-head": "Seleccione los ítems para los que desea guardar los metadatos virtuales como metadatos reales", - - // "supervisedWorkspace.search.results.head": "Supervised Items", "supervisedWorkspace.search.results.head": "Ítems supervisados", @@ -7247,8 +7159,6 @@ // "supervision.search.results.head": "Workflow and Workspace tasks", "supervision.search.results.head": "Tareas del flujo de trabajo y del espacio de trabajo", - - // "workflow-item.edit.breadcrumbs": "Edit workflowitem", "workflow-item.edit.breadcrumbs": "Editar ítem del flujo de trabajo", @@ -7279,7 +7189,6 @@ // "workflow-item.delete.button.confirm": "Delete", "workflow-item.delete.button.confirm": "Borrar", - // "workflow-item.send-back.notification.success.title": "Sent back to submitter", "workflow-item.send-back.notification.success.title": "Devolver al remitente", @@ -7313,11 +7222,33 @@ // "workspace-item.view.title": "Workspace View", "workspace-item.view.title": "Vista del flujo de trabajo", + // "workspace-item.delete.breadcrumbs": "Workspace Delete", + "workspace-item.delete.breadcrumbs": "Borrar espacio de trabajo", + + // "workspace-item.delete.header": "Delete workspace item", + "workspace-item.delete.header": "Borrar ítem del espacio de trabajo", + + // "workspace-item.delete.button.confirm": "Delete", + "workspace-item.delete.button.confirm": "Borrar", + + // "workspace-item.delete.button.cancel": "Cancel", + "workspace-item.delete.button.cancel": "Cancelar", + + // "workspace-item.delete.notification.success.title": "Deleted", + "workspace-item.delete.notification.success.title": "Borrado", + + // "workspace-item.delete.title": "This workspace item was successfully deleted", + "workspace-item.delete.title": "Este ítem del espacio de trabajo se eliminó correctamente", + + // "workspace-item.delete.notification.error.title": "Something went wrong", + "workspace-item.delete.notification.error.title": "Algo salió mal", + + // "workspace-item.delete.notification.error.content": "The workspace item could not be deleted", + "workspace-item.delete.notification.error.content": "Este ítem del espacio de trabajo no pudo borrarse", // "workflow-item.advanced.title": "Advanced workflow", "workflow-item.advanced.title": "Flujo de trabajo avanzado", - // "workflow-item.selectrevieweraction.notification.success.title": "Selected reviewer", "workflow-item.selectrevieweraction.notification.success.title": "Revisor seleccionado", @@ -7342,7 +7273,6 @@ // "workflow-item.selectrevieweraction.button.confirm": "Confirm", "workflow-item.selectrevieweraction.button.confirm": "Confirmar", - // "workflow-item.scorereviewaction.notification.success.title": "Rating review", "workflow-item.scorereviewaction.notification.success.title": "Revisión de evaluación", @@ -7379,7 +7309,7 @@ // "idle-modal.extend-session": "Extend session", "idle-modal.extend-session": "Prolongar la sesión", - // "researcher.profile.action.processing" : "Processing...", + // "researcher.profile.action.processing": "Processing...", "researcher.profile.action.processing": "Procesando...", // "researcher.profile.associated": "Researcher profile associated", @@ -7412,10 +7342,10 @@ // "researcher.profile.view": "View", "researcher.profile.view": "Ver", - // "researcher.profile.private.visibility" : "PRIVATE", + // "researcher.profile.private.visibility": "PRIVATE", "researcher.profile.private.visibility": "PRIVADO", - // "researcher.profile.public.visibility" : "PUBLIC", + // "researcher.profile.public.visibility": "PUBLIC", "researcher.profile.public.visibility": "PÚBLICO", // "researcher.profile.status": "Status:", @@ -7424,16 +7354,16 @@ // "researcherprofile.claim.not-authorized": "You are not authorized to claim this item. For more details contact the administrator(s).", "researcherprofile.claim.not-authorized": "No está autorizado pare reclamar este ítem. Contacte con el administrador para mas detalles.", - // "researcherprofile.error.claim.body" : "An error occurred while claiming the profile, please try again later", + // "researcherprofile.error.claim.body": "An error occurred while claiming the profile, please try again later", "researcherprofile.error.claim.body": "Hubo un error reclamando el perfil, por favor, inténtelo mas tarde", - // "researcherprofile.error.claim.title" : "Error", + // "researcherprofile.error.claim.title": "Error", "researcherprofile.error.claim.title": "Error", - // "researcherprofile.success.claim.body" : "Profile claimed with success", + // "researcherprofile.success.claim.body": "Profile claimed with success", "researcherprofile.success.claim.body": "Perfil reclamado con éxito", - // "researcherprofile.success.claim.title" : "Success", + // "researcherprofile.success.claim.title": "Success", "researcherprofile.success.claim.title": "Éxito", // "person.page.orcid.create": "Create an ORCID ID", @@ -7442,7 +7372,7 @@ // "person.page.orcid.granted-authorizations": "Granted authorizations", "person.page.orcid.granted-authorizations": "Autorizaciones concedidas", - // "person.page.orcid.grant-authorizations" : "Grant authorizations", + // "person.page.orcid.grant-authorizations": "Grant authorizations", "person.page.orcid.grant-authorizations": "Conceder autorizaciones", // "person.page.orcid.link": "Connect to ORCID ID", @@ -7458,7 +7388,7 @@ "person.page.orcid.orcid-not-linked-message": "El ORCID iD de este perfil ({{ orcid }}) no se ha conectado aún con una cuenta en el registro ORCID o la conexión caducó.", // "person.page.orcid.unlink": "Disconnect from ORCID", - "person.page.orcid.unlink": "Desconectar de ORCID", + "person.page.orcid.unlink": "Desconectar de ORCID", // "person.page.orcid.unlink.processing": "Processing...", "person.page.orcid.unlink.processing": "Procesando...", @@ -7490,44 +7420,44 @@ // "person.page.orcid.save.preference.changes": "Update settings", "person.page.orcid.save.preference.changes": "Actualizar configuración", - // "person.page.orcid.sync-profile.affiliation" : "Affiliation", + // "person.page.orcid.sync-profile.affiliation": "Affiliation", "person.page.orcid.sync-profile.affiliation": "Afiliación", - // "person.page.orcid.sync-profile.biographical" : "Biographical data", + // "person.page.orcid.sync-profile.biographical": "Biographical data", "person.page.orcid.sync-profile.biographical": "Biografía", - // "person.page.orcid.sync-profile.education" : "Education", + // "person.page.orcid.sync-profile.education": "Education", "person.page.orcid.sync-profile.education": "Grados", - // "person.page.orcid.sync-profile.identifiers" : "Identifiers", + // "person.page.orcid.sync-profile.identifiers": "Identifiers", "person.page.orcid.sync-profile.identifiers": "Identificadores", - // "person.page.orcid.sync-fundings.all" : "All fundings", + // "person.page.orcid.sync-fundings.all": "All fundings", "person.page.orcid.sync-fundings.all": "Todas las financiaciones", - // "person.page.orcid.sync-fundings.mine" : "My fundings", + // "person.page.orcid.sync-fundings.mine": "My fundings", "person.page.orcid.sync-fundings.mine": "Mi financiación", - // "person.page.orcid.sync-fundings.my_selected" : "Selected fundings", + // "person.page.orcid.sync-fundings.my_selected": "Selected fundings", "person.page.orcid.sync-fundings.my_selected": "Financiaciones seleccionadas", - // "person.page.orcid.sync-fundings.disabled" : "Disabled", + // "person.page.orcid.sync-fundings.disabled": "Disabled", "person.page.orcid.sync-fundings.disabled": "Deshabilitado", - // "person.page.orcid.sync-publications.all" : "All publications", + // "person.page.orcid.sync-publications.all": "All publications", "person.page.orcid.sync-publications.all": "Todas las publicaciones", - // "person.page.orcid.sync-publications.mine" : "My publications", + // "person.page.orcid.sync-publications.mine": "My publications", "person.page.orcid.sync-publications.mine": "Mis publicaciones", - // "person.page.orcid.sync-publications.my_selected" : "Selected publications", + // "person.page.orcid.sync-publications.my_selected": "Selected publications", "person.page.orcid.sync-publications.my_selected": "Publicaciones seleccionadas", - // "person.page.orcid.sync-publications.disabled" : "Disabled", + // "person.page.orcid.sync-publications.disabled": "Disabled", "person.page.orcid.sync-publications.disabled": "Deshabilitado", - // "person.page.orcid.sync-queue.discard" : "Discard the change and do not synchronize with the ORCID registry", - "person.page.orcid.sync-queue.discard": "Deshechar el cambio y no sincronizar con ORCID", + // "person.page.orcid.sync-queue.discard": "Discard the change and do not synchronize with the ORCID registry", + "person.page.orcid.sync-queue.discard": "Deshechar el cambio y no sincronizar con el registro ORCID", // "person.page.orcid.sync-queue.discard.error": "The discarding of the ORCID queue record failed", "person.page.orcid.sync-queue.discard.error": "Falló el borrado del registro ORCID en la cola", @@ -7538,13 +7468,13 @@ // "person.page.orcid.sync-queue.empty-message": "The ORCID queue registry is empty", "person.page.orcid.sync-queue.empty-message": "La cola del registro de ORCID está vacía", - // "person.page.orcid.sync-queue.table.header.type" : "Type", + // "person.page.orcid.sync-queue.table.header.type": "Type", "person.page.orcid.sync-queue.table.header.type": "Tipo", - // "person.page.orcid.sync-queue.table.header.description" : "Description", + // "person.page.orcid.sync-queue.table.header.description": "Description", "person.page.orcid.sync-queue.table.header.description": "Descripción", - // "person.page.orcid.sync-queue.table.header.action" : "Action", + // "person.page.orcid.sync-queue.table.header.action": "Action", "person.page.orcid.sync-queue.table.header.action": "Acción", // "person.page.orcid.sync-queue.description.affiliation": "Affiliations", @@ -7610,7 +7540,7 @@ // "person.page.orcid.sync-queue.tooltip.researcher_urls": "Researcher url", "person.page.orcid.sync-queue.tooltip.researcher_urls": "URL del investigador", - // "person.page.orcid.sync-queue.send" : "Synchronize with ORCID registry", + // "person.page.orcid.sync-queue.send": "Synchronize with ORCID registry", "person.page.orcid.sync-queue.send": "Sincronizar con el registro ORCID", // "person.page.orcid.sync-queue.send.unauthorized-error.title": "The submission to ORCID failed for missing authorizations.", @@ -7620,13 +7550,13 @@ "person.page.orcid.sync-queue.send.unauthorized-error.content": "Pulse aquí para conceder de nuevo los permisos requeridos. Si el problema continuase, contacte con el administrador", // "person.page.orcid.sync-queue.send.bad-request-error": "The submission to ORCID failed because the resource sent to ORCID registry is not valid", - "person.page.orcid.sync-queue.send.bad-request-error": "El envío a ORCID falló debido a que el recurso que se envión no era válido", + "person.page.orcid.sync-queue.send.bad-request-error": "El envío a ORCID falló debido a que el recurso que se envión no era válido", // "person.page.orcid.sync-queue.send.error": "The submission to ORCID failed", "person.page.orcid.sync-queue.send.error": "Falló el envío a ORCID", // "person.page.orcid.sync-queue.send.conflict-error": "The submission to ORCID failed because the resource is already present on the ORCID registry", - "person.page.orcid.sync-queue.send.conflict-error": "El envío a ORCID falló debido a que el recurso ya existía en el registro ORCID", + "person.page.orcid.sync-queue.send.conflict-error": "El envío a ORCID falló debido a que el recurso ya existía en el registro ORCID", // "person.page.orcid.sync-queue.send.not-found-warning": "The resource does not exists anymore on the ORCID registry.", "person.page.orcid.sync-queue.send.not-found-warning": "El recurso no existe ya en el registro ORCID.", @@ -7664,8 +7594,8 @@ // "person.page.orcid.sync-queue.send.validation-error.organization.name-required": "The organization's name is required", "person.page.orcid.sync-queue.send.validation-error.organization.name-required": "Se requiere el nombre de la organización", - // "person.page.orcid.sync-queue.send.validation-error.publication.date-invalid" : "The publication date must be one year after 1900", - "person.page.orcid.sync-queue.send.validation-error.publication.date-invalid": "la fecha de la publicación debe ser posterior a 1900", + // "person.page.orcid.sync-queue.send.validation-error.publication.date-invalid": "The publication date must be one year after 1900", + "person.page.orcid.sync-queue.send.validation-error.publication.date-invalid": "La fecha de publicación debe ser posterior a 1900", // "person.page.orcid.sync-queue.send.validation-error.organization.address-required": "The organization to be sent requires an address", "person.page.orcid.sync-queue.send.validation-error.organization.address-required": "Se requiere la dirección de la organización", @@ -7698,7 +7628,7 @@ "person.page.orcid.synchronization-mode.label": "Modo sincronización", // "person.page.orcid.synchronization-mode-message": "Please select how you would like synchronization to ORCID to occur. The options include \"Manual\" (you must send your data to ORCID manually), or \"Batch\" (the system will send your data to ORCID via a scheduled script).", - "person.page.orcid.synchronization-mode-message": "Seleccione cómo prefiere realizar la sincronización con ORCID. Puede escoger \"Manual\" (usted envía los datos a ORCID manualmente), o \"Batch\" (el sistema enviará sus datos a ORCID via un programa planificado).", + "person.page.orcid.synchronization-mode-message": "Seleccione cómo prefiere realizar la sincronización con ORCID. Puede escoger \"Manual\" (usted envía los datos a ORCID manualmente), o \"Batch\" (el sistema enviará sus datos a ORCID vía un programa planificado).", // "person.page.orcid.synchronization-mode-funding-message": "Select whether to send your linked Project entities to your ORCID record's list of funding information.", "person.page.orcid.synchronization-mode-funding-message": "Seleccione si enviará información de sus entidades conectadas de tipo Proyecto a la información de financiación de ORCID.", @@ -7744,13 +7674,13 @@ // "person.orcid.registry.auth": "ORCID Authorizations", "person.orcid.registry.auth": "Autorizaciones ORCID", + // "home.recent-submissions.head": "Recent Submissions", "home.recent-submissions.head": "Envíos recientes", // "listable-notification-object.default-message": "This object couldn't be retrieved", "listable-notification-object.default-message": "Este objeto no se pudo recuperar", - // "system-wide-alert-banner.retrieval.error": "Something went wrong retrieving the system-wide alert banner", "system-wide-alert-banner.retrieval.error": "Algo salió mal al recuperar el banner de alerta del sistema", @@ -7766,8 +7696,6 @@ // "system-wide-alert-banner.countdown.minutes": "{{minutes}} minute(s):", "system-wide-alert-banner.countdown.minutes": "{{minutes}} minuto(s):", - - // "menu.section.system-wide-alert": "System-wide Alert", "menu.section.system-wide-alert": "Alerta del Sistema", @@ -7822,5 +7750,81 @@ // "admin.system-wide-alert.title": "System-wide Alerts", "admin.system-wide-alert.title": "Alertas del sistema", + // "item-access-control-title": "This form allows you to perform changes to the access conditions of the item's metadata or its bitstreams.", + "item-access-control-title": "Esta pantalla le permite realizar cambios en las condiciones de acceso a los metadatos o a los archivos del ítem.", + + // "collection-access-control-title": "This form allows you to perform changes to the access conditions of all the items owned by this collection. Changes may be performed to either all Item metadata or all content (bitstreams).", + "collection-access-control-title": "Esta pantalla le permite realizar cambios en las condiciones de acceso de todos los ítems de la colección. Puede realizar los cambios a los metadatos o a los archivos de todos los ítems.", + + // "community-access-control-title": "This form allows you to perform changes to the access conditions of all the items owned by any collection under this community. Changes may be performed to either all Item metadata or all content (bitstreams).", + "community-access-control-title": "Esta pantalla le permite realizar cambios en las condiciones de acceso de todos los ítems de todas las colecciones de esta comunidad. Puede realizar los cambios a los metadatos o a los archivos de todos los ítems.", + + // "access-control-item-header-toggle": "Item's Metadata", + "access-control-item-header-toggle": "Metadatos del ítem", + + // "access-control-bitstream-header-toggle": "Bitstreams", + "access-control-bitstream-header-toggle": "Archivos", + + // "access-control-mode": "Mode", + "access-control-mode": "Modo", + + // "access-control-access-conditions": "Access conditions", + "access-control-access-conditions": "Condiciones de accceso", + + // "access-control-no-access-conditions-warning-message": "Currently, no access conditions are specified below. If executed, this will replace the current access conditions with the default access conditions inherited from the owning collection.", + "access-control-no-access-conditions-warning-message": "No ha especificado condiciones de acceso. Si lo ejecuta, se sustituirán las condiciones actuales con las condiones heredadas de la colección de pertenencia.", + + // "access-control-replace-all": "Replace access conditions", + "access-control-replace-all": "Sustituir condiciones de accceso", + + // "access-control-add-to-existing": "Add to existing ones", + "access-control-add-to-existing": "Añadir a las existentes", + + // "access-control-limit-to-specific": "Limit the changes to specific bitstreams", + "access-control-limit-to-specific": "Limitar los cambios a ficheros específicos", + + // "access-control-process-all-bitstreams": "Update all the bitstreams in the item", + "access-control-process-all-bitstreams": "Actualizar todos los archivos del ítem", + + // "access-control-bitstreams-selected": "bitstreams selected", + "access-control-bitstreams-selected": "archivos seleccionados", + + // "access-control-cancel": "Cancel", + "access-control-cancel": "Cancelar", + + // "access-control-execute": "Execute", + "access-control-execute": "Ejecutar", + + // "access-control-add-more": "Add more", + "access-control-add-more": "Añadir mas", + + // "access-control-select-bitstreams-modal.title": "Select bitstreams", + "access-control-select-bitstreams-modal.title": "Seleccionar archivos", + + // "access-control-select-bitstreams-modal.no-items": "No items to show.", + "access-control-select-bitstreams-modal.no-items": "No hay ítems para mostrar.", + + // "access-control-select-bitstreams-modal.close": "Close", + "access-control-select-bitstreams-modal.close": "Cerrar", + + // "access-control-option-label": "Access condition type", + "access-control-option-label": "Tipo de condición de acceso", + + // "access-control-option-note": "Choose an access condition to apply to selected objects.", + "access-control-option-note": "Seleccione una condición de acceso para aplicar a los objetos seleccionados.", + + // "access-control-option-start-date": "Grant access from", + "access-control-option-start-date": "Establecer acceso desde", + + // "access-control-option-start-date-note": "Select the date from which the related access condition is applied", + "access-control-option-start-date-note": "Escoja la fecha desde la cuál se aplicarán las condiciones de acceso especificadas", + + // "access-control-option-end-date": "Grant access until", + "access-control-option-end-date": "Establecer acceso hasta", + + // "access-control-option-end-date-note": "Select the date until which the related access condition is applied", + "access-control-option-end-date-note": "Escoja la fecha hasta la cuál se aplicarán las condiciones de acceso especificadas", + + } From a4eaf02a4749142828245f58c2ab82fa36f196cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Fern=C3=A1ndez=20Celorio?= Date: Tue, 22 Aug 2023 12:57:08 +0200 Subject: [PATCH 065/875] Some lint errors fixed (cherry picked from commit 1885638ba6fadca4c99043db4ce52646bce435a3) --- src/assets/i18n/es.json5 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index 24ce7159e3..0d5b27473c 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -854,7 +854,7 @@ // "admin.batch-import.page.toggle.help": "It is possible to perform import either with file upload or via URL, use above toggle to set the input source", "admin.batch-import.page.toggle.help": "Es posible realizar una importación tanto mediante una subida de fichero como a través de una URL. Use el selector de arriba para especificar la fuente de entrada.", - + // "admin.metadata-import.page.dropMsg": "Drop a metadata CSV to import", "admin.metadata-import.page.dropMsg": "Suelta un CSV de metadatos para importar", @@ -2677,7 +2677,7 @@ "grant-request-copy.header": "Conceder solicitud de copia de documento", // "grant-request-copy.intro": "A message will be sent to the applicant of the request. The requested document(s) will be attached.", - "grant-request-copy.intro": "Este mensaje se enviará al solicitante de la copia. Se le anexará una copia del documento.", + "grant-request-copy.intro": "Este mensaje se enviará al solicitante de la copia. Se le anexará una copia del documento.", // "grant-request-copy.success": "Successfully granted item request", "grant-request-copy.success": "Solicitud de ítem concedida exitosamente", @@ -2686,13 +2686,13 @@ "health.breadcrumbs": "Chequeos", // "health-page.heading": "Health", - "health-page.heading": "Chequeos", + "health-page.heading": "Chequeos", // "health-page.info-tab": "Info", - "health-page.info-tab": "Información", + "health-page.info-tab": "Información", // "health-page.status-tab": "Status", - "health-page.status-tab": "Estado", + "health-page.status-tab": "Estado", // "health-page.error.msg": "The health check service is temporarily unavailable", "health-page.error.msg": "El servicio de comprobación no se encuentra temporalmente disponible", @@ -4744,7 +4744,7 @@ "process.new.notification.error.content": "Se produjo un error al crear este proceso.", // "process.new.notification.error.max-upload.content": "The file exceeds the maximum upload size", - "process.new.notification.error.max-upload.content": "El fichero sobrepasa el límte máximo de subida", + "process.new.notification.error.max-upload.content": "El fichero sobrepasa el límte máximo de subida", // "process.new.header": "Create a new process", "process.new.header": "Crea un nuevo proceso", From 9afbd8d746738c2b766267a8fac0da9d9f096d2e Mon Sep 17 00:00:00 2001 From: Hugo Dominguez Date: Fri, 18 Aug 2023 13:20:19 -0600 Subject: [PATCH 066/875] =?UTF-8?q?=F0=9F=90=9B=20fix=20when=20navbar=20ex?= =?UTF-8?q?pands=20on=20firefox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 60706720e47abc19b7528719e63676b9b5fa50be) --- src/themes/dspace/app/navbar/navbar.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/themes/dspace/app/navbar/navbar.component.scss b/src/themes/dspace/app/navbar/navbar.component.scss index d3aea9f078..2bcd38d5f4 100644 --- a/src/themes/dspace/app/navbar/navbar.component.scss +++ b/src/themes/dspace/app/navbar/navbar.component.scss @@ -7,7 +7,7 @@ nav.navbar { /** Mobile menu styling **/ @media screen and (max-width: map-get($grid-breakpoints, md)-0.02) { .navbar { - width: 100%; + width: 100vw; background-color: var(--bs-white); position: absolute; overflow: hidden; From f88638e9fee32b820c2d983dd112ea65cecb6540 Mon Sep 17 00:00:00 2001 From: Hugo Dominguez Date: Sun, 13 Aug 2023 12:10:25 -0600 Subject: [PATCH 067/875] =?UTF-8?q?=F0=9F=90=9B=20Fix=20Value=20of=20dropd?= =?UTF-8?q?own=20changes=20automatically=20on=20item=20submission=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 651305952d706f6c45eb47ff37dfb94f91979760) --- .../dynamic-scrollable-dropdown.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html index 6e2d29b789..1ac38e9943 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html @@ -43,7 +43,7 @@
diff --git a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.html b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.html index b84ef6f6a8..cc12b5dea6 100644 --- a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.html +++ b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.html @@ -15,15 +15,23 @@
- {{entry.value | dsTruncate:['150']}} + {{entry.value | dsTruncate:['150']}} - {{'submission.sections.upload.no-entry' | translate}} {{fileDescrKey}} + {{'submission.sections.upload.no-entry' | translate}} {{fileDescrKey}} + +
+ {{'admin.registries.bitstream-formats.edit.head' | translate:{format: fileFormat} }} +
+
+ Checksum {{fileCheckSum.checkSumAlgorithm}}: {{fileCheckSum.value}} +
diff --git a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts index bb2fea20f8..b0ea2487e1 100644 --- a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts +++ b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts @@ -1,9 +1,11 @@ -import { Component, Input, OnInit } from '@angular/core'; +import {Component, Input, OnInit} from '@angular/core'; -import { WorkspaceitemSectionUploadFileObject } from '../../../../../core/submission/models/workspaceitem-section-upload-file.model'; -import { isNotEmpty } from '../../../../../shared/empty.util'; -import { Metadata } from '../../../../../core/shared/metadata.utils'; -import { MetadataMap, MetadataValue } from '../../../../../core/shared/metadata.models'; +import { + WorkspaceitemSectionUploadFileObject +} from '../../../../../core/submission/models/workspaceitem-section-upload-file.model'; +import {isNotEmpty} from '../../../../../shared/empty.util'; +import {Metadata} from '../../../../../core/shared/metadata.utils'; +import {MetadataMap, MetadataValue} from '../../../../../core/shared/metadata.models'; /** * This component allow to show bitstream's metadata @@ -38,6 +40,13 @@ export class SubmissionSectionUploadFileViewComponent implements OnInit { */ public fileDescrKey = 'Description'; + public fileFormat!: string; + + public fileCheckSum!: { + checkSumAlgorithm: string; + value: string; + }; + /** * Initialize instance variables */ @@ -46,6 +55,8 @@ export class SubmissionSectionUploadFileViewComponent implements OnInit { this.metadata[this.fileTitleKey] = Metadata.all(this.fileData.metadata, 'dc.title'); this.metadata[this.fileDescrKey] = Metadata.all(this.fileData.metadata, 'dc.description'); } + this.fileCheckSum = this.fileData.checkSum; + this.fileFormat = this.fileData.format.shortDescription; } /** From 5daf993451d8d75c3fe2260f70d2171184a0f1c9 Mon Sep 17 00:00:00 2001 From: Hugo Dominguez Date: Fri, 8 Sep 2023 11:51:42 -0600 Subject: [PATCH 076/875] =?UTF-8?q?=F0=9F=8E=A8revert=20unnecessary=20form?= =?UTF-8?q?at?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 13e4052c4da07014a1c01087bfc6d293a8bb71c5) --- .../workspaceitem-section-upload-file.model.ts | 4 ++-- .../file/view/section-upload-file-view.component.ts | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/app/core/submission/models/workspaceitem-section-upload-file.model.ts b/src/app/core/submission/models/workspaceitem-section-upload-file.model.ts index 59a8faf634..785d7c402f 100644 --- a/src/app/core/submission/models/workspaceitem-section-upload-file.model.ts +++ b/src/app/core/submission/models/workspaceitem-section-upload-file.model.ts @@ -1,5 +1,5 @@ -import {SubmissionUploadFileAccessConditionObject} from './submission-upload-file-access-condition.model'; -import {WorkspaceitemSectionFormObject} from './workspaceitem-section-form.model'; +import { SubmissionUploadFileAccessConditionObject } from './submission-upload-file-access-condition.model'; +import { WorkspaceitemSectionFormObject } from './workspaceitem-section-form.model'; /** * An interface to represent submission's upload section file entry. diff --git a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts index b0ea2487e1..b15b0ab321 100644 --- a/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts +++ b/src/app/submission/sections/upload/file/view/section-upload-file-view.component.ts @@ -1,11 +1,9 @@ -import {Component, Input, OnInit} from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; -import { - WorkspaceitemSectionUploadFileObject -} from '../../../../../core/submission/models/workspaceitem-section-upload-file.model'; -import {isNotEmpty} from '../../../../../shared/empty.util'; -import {Metadata} from '../../../../../core/shared/metadata.utils'; -import {MetadataMap, MetadataValue} from '../../../../../core/shared/metadata.models'; +import { WorkspaceitemSectionUploadFileObject } from '../../../../../core/submission/models/workspaceitem-section-upload-file.model'; +import { isNotEmpty } from '../../../../../shared/empty.util'; +import { Metadata } from '../../../../../core/shared/metadata.utils'; +import { MetadataMap, MetadataValue } from '../../../../../core/shared/metadata.models'; /** * This component allow to show bitstream's metadata From 22538f30dc5d615675fc1eb444c803fef80bbb5f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 8 Sep 2023 14:28:23 -0500 Subject: [PATCH 077/875] Update workspaceitem-section-upload-file.model.ts Fix code comment (cherry picked from commit 01c8a4d9c3347cb5b30d4d912a3f7e18b81f989a) --- .../models/workspaceitem-section-upload-file.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/submission/models/workspaceitem-section-upload-file.model.ts b/src/app/core/submission/models/workspaceitem-section-upload-file.model.ts index 785d7c402f..725e646d76 100644 --- a/src/app/core/submission/models/workspaceitem-section-upload-file.model.ts +++ b/src/app/core/submission/models/workspaceitem-section-upload-file.model.ts @@ -30,7 +30,7 @@ export class WorkspaceitemSectionUploadFileObject { }; /** - * The file check sum + * The file format information */ format: { shortDescription: string, From 8d295419c7f9b0f30194d4302c739d4e0b5d2658 Mon Sep 17 00:00:00 2001 From: Hugo Dominguez Date: Sat, 19 Aug 2023 14:40:28 -0600 Subject: [PATCH 078/875] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20chain?= =?UTF-8?q?=20of=20observables=20to=20avoid=20async=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 2dc9fd44d7e151f7e96bff16665edf0a09226249) --- .../metadata-schema-form.component.ts | 179 ++++++++++-------- 1 file changed, 100 insertions(+), 79 deletions(-) diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts index 1992289ff7..f5fcdef78d 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts @@ -1,4 +1,10 @@ -import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; +import { + Component, + EventEmitter, + OnDestroy, + OnInit, + Output, +} from '@angular/core'; import { DynamicFormControlModel, DynamicFormGroupModel, @@ -8,20 +14,19 @@ import { import { UntypedFormGroup } from '@angular/forms'; import { RegistryService } from '../../../../core/registry/registry.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; -import { take } from 'rxjs/operators'; +import { switchMap, take } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; -import { combineLatest } from 'rxjs'; +import { Observable, combineLatest } from 'rxjs'; import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model'; @Component({ selector: 'ds-metadata-schema-form', - templateUrl: './metadata-schema-form.component.html' + templateUrl: './metadata-schema-form.component.html', }) /** * A form used for creating and editing metadata schemas */ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { - /** * A unique id used for ds-form */ @@ -53,14 +58,14 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { formLayout: DynamicFormLayout = { name: { grid: { - host: 'col col-sm-6 d-inline-block' - } + host: 'col col-sm-6 d-inline-block', + }, }, namespace: { grid: { - host: 'col col-sm-6 d-inline-block' - } - } + host: 'col col-sm-6 d-inline-block', + }, + }, }; /** @@ -73,63 +78,67 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { */ @Output() submitForm: EventEmitter = new EventEmitter(); - constructor(public registryService: RegistryService, private formBuilderService: FormBuilderService, private translateService: TranslateService) { - } + constructor( + public registryService: RegistryService, + private formBuilderService: FormBuilderService, + private translateService: TranslateService + ) {} ngOnInit() { combineLatest([ this.translateService.get(`${this.messagePrefix}.name`), - this.translateService.get(`${this.messagePrefix}.namespace`) + this.translateService.get(`${this.messagePrefix}.namespace`), ]).subscribe(([name, namespace]) => { this.name = new DynamicInputModel({ - id: 'name', - label: name, - name: 'name', - validators: { - required: null, - pattern: '^[^. ,]*$', - maxLength: 32, - }, - required: true, - errorMessages: { - pattern: 'error.validation.metadata.name.invalid-pattern', - maxLength: 'error.validation.metadata.name.max-length', - }, - }); + id: 'name', + label: name, + name: 'name', + validators: { + required: null, + pattern: '^[^. ,]*$', + maxLength: 32, + }, + required: true, + errorMessages: { + pattern: 'error.validation.metadata.name.invalid-pattern', + maxLength: 'error.validation.metadata.name.max-length', + }, + }); this.namespace = new DynamicInputModel({ - id: 'namespace', - label: namespace, - name: 'namespace', - validators: { - required: null, - maxLength: 256, - }, - required: true, - errorMessages: { - maxLength: 'error.validation.metadata.namespace.max-length', - }, - }); + id: 'namespace', + label: namespace, + name: 'namespace', + validators: { + required: null, + maxLength: 256, + }, + required: true, + errorMessages: { + maxLength: 'error.validation.metadata.namespace.max-length', + }, + }); this.formModel = [ - new DynamicFormGroupModel( - { - id: 'metadatadataschemagroup', - group:[this.namespace, this.name] - }) + new DynamicFormGroupModel({ + id: 'metadatadataschemagroup', + group: [this.namespace, this.name], + }), ]; this.formGroup = this.formBuilderService.createFormGroup(this.formModel); - this.registryService.getActiveMetadataSchema().subscribe((schema: MetadataSchema) => { - if (schema == null) { - this.clearFields(); - } else { - this.formGroup.patchValue({ - metadatadataschemagroup: { - name: schema.prefix, - namespace: schema.namespace, - }, - }); - this.name.disabled = true; - } - }); + this.registryService + .getActiveMetadataSchema() + .subscribe((schema: MetadataSchema) => { + if (schema == null) { + this.clearFields(); + } else { + this.formGroup.patchValue({ + metadatadataschemagroup: { + name: schema.prefix, + namespace: schema.namespace, + }, + }); + this.name.disabled = true; + } + }); }); } @@ -147,30 +156,42 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { * Emit the updated/created schema using the EventEmitter submitForm */ onSubmit(): void { - this.registryService.clearMetadataSchemaRequests().subscribe(); - this.registryService.getActiveMetadataSchema().pipe(take(1)).subscribe( - (schema: MetadataSchema) => { - const values = { - prefix: this.name.value, - namespace: this.namespace.value - }; - if (schema == null) { - this.registryService.createOrUpdateMetadataSchema(Object.assign(new MetadataSchema(), values)).subscribe((newSchema) => { - this.submitForm.emit(newSchema); - }); - } else { - this.registryService.createOrUpdateMetadataSchema(Object.assign(new MetadataSchema(), schema, { - id: schema.id, - prefix: schema.prefix, - namespace: values.namespace, - })).subscribe((updatedSchema: MetadataSchema) => { - this.submitForm.emit(updatedSchema); - }); - } + const clearMetadataSchemaRequests$ = + this.registryService.clearMetadataSchemaRequests(); + + clearMetadataSchemaRequests$ + .pipe( + switchMap(() => + this.registryService.getActiveMetadataSchema().pipe(take(1)) + ), + switchMap((schema: MetadataSchema) => { + const metadataValues = { + prefix: this.name.value, + namespace: this.namespace.value, + }; + + let createOrUpdate$: Observable; + + if (schema == null) { + createOrUpdate$ = this.registryService.createOrUpdateMetadataSchema( + Object.assign(new MetadataSchema(), metadataValues) + ); + } else { + const updatedSchema = Object.assign(new MetadataSchema(), schema, { + namespace: metadataValues.namespace, + }); + createOrUpdate$ = + this.registryService.createOrUpdateMetadataSchema(updatedSchema); + } + + return createOrUpdate$; + }) + ) + .subscribe((updatedOrCreatedSchema: MetadataSchema) => { + this.submitForm.emit(updatedOrCreatedSchema); this.clearFields(); this.registryService.cancelEditMetadataSchema(); - } - ); + }); } /** From e3ea2cb2b001ccf08c28b1dd863bfaa0b82da146 Mon Sep 17 00:00:00 2001 From: Hugo Dominguez Date: Sat, 19 Aug 2023 15:59:33 -0600 Subject: [PATCH 079/875] =?UTF-8?q?=F0=9F=90=9B=20fix=20bug=20of=20caching?= =?UTF-8?q?=20when=20add=20new=20schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 9fb9e5848c70274b7917bead52643e3611308174) --- .../metadata-schema-form.component.ts | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts index f5fcdef78d..61766a4a10 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts @@ -14,7 +14,7 @@ import { import { UntypedFormGroup } from '@angular/forms'; import { RegistryService } from '../../../../core/registry/registry.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; -import { switchMap, take } from 'rxjs/operators'; +import { switchMap, take, tap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { Observable, combineLatest } from 'rxjs'; import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model'; @@ -156,42 +156,48 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { * Emit the updated/created schema using the EventEmitter submitForm */ onSubmit(): void { - const clearMetadataSchemaRequests$ = - this.registryService.clearMetadataSchemaRequests(); + this.registryService + .getActiveMetadataSchema() + .pipe( + take(1), + switchMap((schema: MetadataSchema) => { + const metadataValues = { + prefix: this.name.value, + namespace: this.namespace.value, + }; - clearMetadataSchemaRequests$ - .pipe( - switchMap(() => - this.registryService.getActiveMetadataSchema().pipe(take(1)) - ), - switchMap((schema: MetadataSchema) => { - const metadataValues = { - prefix: this.name.value, - namespace: this.namespace.value, - }; + let createOrUpdate$: Observable; - let createOrUpdate$: Observable; + if (schema == null) { + createOrUpdate$ = + this.registryService.createOrUpdateMetadataSchema( + Object.assign(new MetadataSchema(), metadataValues) + ); + } else { + const updatedSchema = Object.assign( + new MetadataSchema(), + schema, + { + namespace: metadataValues.namespace, + } + ); + createOrUpdate$ = + this.registryService.createOrUpdateMetadataSchema( + updatedSchema + ); + } - if (schema == null) { - createOrUpdate$ = this.registryService.createOrUpdateMetadataSchema( - Object.assign(new MetadataSchema(), metadataValues) - ); - } else { - const updatedSchema = Object.assign(new MetadataSchema(), schema, { - namespace: metadataValues.namespace, - }); - createOrUpdate$ = - this.registryService.createOrUpdateMetadataSchema(updatedSchema); - } - - return createOrUpdate$; - }) - ) - .subscribe((updatedOrCreatedSchema: MetadataSchema) => { - this.submitForm.emit(updatedOrCreatedSchema); - this.clearFields(); - this.registryService.cancelEditMetadataSchema(); - }); + return createOrUpdate$; + }), + tap(() => { + this.registryService.clearMetadataSchemaRequests().subscribe(); + }) + ) + .subscribe((updatedOrCreatedSchema: MetadataSchema) => { + this.submitForm.emit(updatedOrCreatedSchema); + this.clearFields(); + this.registryService.cancelEditMetadataSchema(); + }); } /** From 161d7e069bcc074163ca5ab53c2e343742f3c719 Mon Sep 17 00:00:00 2001 From: Hugo Dominguez Date: Fri, 8 Sep 2023 11:31:26 -0600 Subject: [PATCH 080/875] =?UTF-8?q?=F0=9F=8E=A8=20revert=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 3e5524de69fa09808e3a7d0ab4042e5e3ffc98e0) --- .../metadata-schema-form.component.ts | 119 ++++++++---------- 1 file changed, 55 insertions(+), 64 deletions(-) diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts index 61766a4a10..24bf430661 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts @@ -1,10 +1,4 @@ -import { - Component, - EventEmitter, - OnDestroy, - OnInit, - Output, -} from '@angular/core'; +import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; import { DynamicFormControlModel, DynamicFormGroupModel, @@ -21,12 +15,13 @@ import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model' @Component({ selector: 'ds-metadata-schema-form', - templateUrl: './metadata-schema-form.component.html', + templateUrl: './metadata-schema-form.component.html' }) /** * A form used for creating and editing metadata schemas */ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { + /** * A unique id used for ds-form */ @@ -58,14 +53,14 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { formLayout: DynamicFormLayout = { name: { grid: { - host: 'col col-sm-6 d-inline-block', - }, + host: 'col col-sm-6 d-inline-block' + } }, namespace: { grid: { - host: 'col col-sm-6 d-inline-block', - }, - }, + host: 'col col-sm-6 d-inline-block' + } + } }; /** @@ -78,67 +73,63 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { */ @Output() submitForm: EventEmitter = new EventEmitter(); - constructor( - public registryService: RegistryService, - private formBuilderService: FormBuilderService, - private translateService: TranslateService - ) {} + constructor(public registryService: RegistryService, private formBuilderService: FormBuilderService, private translateService: TranslateService) { + } ngOnInit() { combineLatest([ this.translateService.get(`${this.messagePrefix}.name`), - this.translateService.get(`${this.messagePrefix}.namespace`), + this.translateService.get(`${this.messagePrefix}.namespace`) ]).subscribe(([name, namespace]) => { this.name = new DynamicInputModel({ - id: 'name', - label: name, - name: 'name', - validators: { - required: null, - pattern: '^[^. ,]*$', - maxLength: 32, - }, - required: true, - errorMessages: { - pattern: 'error.validation.metadata.name.invalid-pattern', - maxLength: 'error.validation.metadata.name.max-length', - }, - }); + id: 'name', + label: name, + name: 'name', + validators: { + required: null, + pattern: '^[^. ,]*$', + maxLength: 32, + }, + required: true, + errorMessages: { + pattern: 'error.validation.metadata.name.invalid-pattern', + maxLength: 'error.validation.metadata.name.max-length', + }, + }); this.namespace = new DynamicInputModel({ - id: 'namespace', - label: namespace, - name: 'namespace', - validators: { - required: null, - maxLength: 256, - }, - required: true, - errorMessages: { - maxLength: 'error.validation.metadata.namespace.max-length', - }, - }); + id: 'namespace', + label: namespace, + name: 'namespace', + validators: { + required: null, + maxLength: 256, + }, + required: true, + errorMessages: { + maxLength: 'error.validation.metadata.namespace.max-length', + }, + }); this.formModel = [ - new DynamicFormGroupModel({ - id: 'metadatadataschemagroup', - group: [this.namespace, this.name], - }), + new DynamicFormGroupModel( + { + id: 'metadatadataschemagroup', + group:[this.namespace, this.name] + }) ]; this.formGroup = this.formBuilderService.createFormGroup(this.formModel); - this.registryService - .getActiveMetadataSchema() - .subscribe((schema: MetadataSchema) => { - if (schema == null) { - this.clearFields(); - } else { - this.formGroup.patchValue({ - metadatadataschemagroup: { - name: schema.prefix, - namespace: schema.namespace, - }, - }); - this.name.disabled = true; - } - }); + this.registryService.getActiveMetadataSchema().subscribe((schema: MetadataSchema) => { + if (schema == null) { + this.clearFields(); + } else { + this.formGroup.patchValue({ + metadatadataschemagroup: { + name: schema.prefix, + namespace: schema.namespace, + }, + }); + this.name.disabled = true; + } + }); }); } From 0905a53db5eca45220809831663ba8d6761e55c5 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 8 Sep 2023 22:35:27 +0200 Subject: [PATCH 081/875] Fix innerText still being undefined in ssr mode --- src/app/shared/log-in/log-in.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/log-in/log-in.component.html b/src/app/shared/log-in/log-in.component.html index 05d8d4370f..857b977360 100644 --- a/src/app/shared/log-in/log-in.component.html +++ b/src/app/shared/log-in/log-in.component.html @@ -1,7 +1,7 @@ diff --git a/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.ts b/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.ts index 8e4a7008af..1925099418 100644 --- a/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.ts +++ b/src/app/shared/dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component.ts @@ -13,7 +13,6 @@ import { hasValue } from '../../../empty.util'; * Represents an expandable section in the dso edit menus */ @Component({ - /* tslint:disable:component-selector */ selector: 'ds-dso-edit-menu-expandable-section', templateUrl: './dso-edit-menu-expandable-section.component.html', styleUrls: ['./dso-edit-menu-expandable-section.component.scss'], diff --git a/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component.ts b/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component.ts index af3381ef71..060049ef5f 100644 --- a/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component.ts +++ b/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component.ts @@ -10,7 +10,6 @@ import { MenuSection } from '../../../menu/menu-section.model'; * Represents a non-expandable section in the dso edit menus */ @Component({ - /* tslint:disable:component-selector */ selector: 'ds-dso-edit-menu-section', templateUrl: './dso-edit-menu-section.component.html', styleUrls: ['./dso-edit-menu-section.component.scss'] diff --git a/src/themes/dspace/app/navbar/navbar.component.html b/src/themes/dspace/app/navbar/navbar.component.html index d9631aad18..c14671cf68 100644 --- a/src/themes/dspace/app/navbar/navbar.component.html +++ b/src/themes/dspace/app/navbar/navbar.component.html @@ -10,9 +10,9 @@
  • - +
  • - +
  • - diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts index 804ae63491..d5a5dee1f5 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts @@ -293,6 +293,15 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges } } + add() { + const userVocabularyEntry = { + value: this.searchText, + display: this.searchText, + } as VocabularyEntryDetail; + this.select.emit(userVocabularyEntry); + } + + /** * Unsubscribe from all subscriptions */ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 3b3578fac5..741ff0ffc9 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -5240,4 +5240,6 @@ "access-control-option-end-date-note": "Select the date until which the related access condition is applied", + "vocabulary-treeview.search.form.add": "Add", + } From e815b1d938066b06ec4ec887e1866327affaea7b Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Wed, 8 Nov 2023 12:00:46 +0100 Subject: [PATCH 146/875] 108055: add user input to tag list (cherry picked from commit aac58e612d7fb01f87dc7a6a46b92c9c4c2fe685) --- .../models/tag/dynamic-tag.component.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/tag/dynamic-tag.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/tag/dynamic-tag.component.ts index 4abb68a53b..7805dad1f3 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/tag/dynamic-tag.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/tag/dynamic-tag.component.ts @@ -89,6 +89,18 @@ export class DsDynamicTagComponent extends DsDynamicVocabularyComponent implemen } }), map((list: PaginatedList) => list.page), + // Add user input as last item of the list + map((list: VocabularyEntry[]) => { + if (list && list.length > 0) { + if (isNotEmpty(this.currentValue)) { + let vocEntry = new VocabularyEntry(); + vocEntry.display = this.currentValue; + vocEntry.value = this.currentValue; + list.push(vocEntry); + } + } + return list; + }), tap(() => this.changeSearchingStatus(false)), merge(this.hideSearchingWhenUnsubscribed)); From bc21085398cfa976893de3b097a6fc3100240784 Mon Sep 17 00:00:00 2001 From: Andreas Mahnke Date: Wed, 25 Oct 2023 16:10:05 +0200 Subject: [PATCH 147/875] Support type-bind of elements based on repeatable list type-bound element (CHECKBOX_GROUP) (cherry picked from commit 09aaa46875146081cf812ed6f904178740ae8d30) --- .../ds-dynamic-type-bind-relation.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.ts index d5e735ed1a..5f7e2e3e22 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service.ts @@ -183,7 +183,8 @@ export class DsDynamicTypeBindRelationService { const initValue = (hasNoValue(relatedModel.value) || typeof relatedModel.value === 'string') ? relatedModel.value : (Array.isArray(relatedModel.value) ? relatedModel.value : relatedModel.value.value); - const valueChanges = relatedModel.valueChanges.pipe( + const updateSubject = (relatedModel.type === 'CHECKBOX_GROUP' ? relatedModel.valueUpdates : relatedModel.valueChanges); + const valueChanges = updateSubject.pipe( startWith(initValue) ); From 0d0c2dac17ecd9af8217e9231288af9bed865a50 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 30 Oct 2023 18:27:00 +0100 Subject: [PATCH 148/875] [DURACOM-195] Simplify vertical spacing in header and breadcrumbs (cherry picked from commit a3e6d9b09a2d6e529dc28f7d1a1924b2830077e6) --- src/app/breadcrumbs/breadcrumbs.component.html | 2 +- src/app/breadcrumbs/breadcrumbs.component.scss | 5 ++--- src/app/home-page/home-news/home-news.component.html | 2 +- src/app/home-page/home-news/home-news.component.scss | 2 -- src/app/root/root.component.html | 4 ++-- src/styles/_custom_variables.scss | 2 +- src/styles/_global-styles.scss | 10 ++++++++-- .../app/home-page/home-news/home-news.component.html | 2 +- .../app/home-page/home-news/home-news.component.scss | 1 - 9 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/app/breadcrumbs/breadcrumbs.component.html b/src/app/breadcrumbs/breadcrumbs.component.html index bff792eeff..3bba89622f 100644 --- a/src/app/breadcrumbs/breadcrumbs.component.html +++ b/src/app/breadcrumbs/breadcrumbs.component.html @@ -1,6 +1,6 @@ diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts index 88efd2a711..9522be29ce 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts @@ -143,7 +143,7 @@ describe('AdminSidebarComponent', () => { describe('when the collapse link is clicked', () => { beforeEach(() => { spyOn(menuService, 'toggleMenu'); - const sidebarToggler = fixture.debugElement.query(By.css('#sidebar-collapse-toggle > a')); + const sidebarToggler = fixture.debugElement.query(By.css('#sidebar-collapse-toggle > button')); sidebarToggler.triggerEventHandler('click', { preventDefault: () => {/**/ } diff --git a/src/app/footer/footer.component.html b/src/app/footer/footer.component.html index 13d84e6e2e..d4c0cd1a37 100644 --- a/src/app/footer/footer.component.html +++ b/src/app/footer/footer.component.html @@ -62,10 +62,11 @@ {{ 'footer.link.lyrasis' | translate}}

    -
    diff --git a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html index 770f465a4b..0f20e0e927 100644 --- a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html +++ b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html @@ -87,8 +87,8 @@ -
    - \ No newline at end of file + From d697d0f7a75148d130126ee7f29e6ef8c1fc077c Mon Sep 17 00:00:00 2001 From: Vlad Novski Date: Thu, 29 Aug 2024 18:56:28 +0200 Subject: [PATCH 474/875] fix[i18n]: typo in German translation (cherry picked from commit 3b4e0d51cd3e6772314bc965eb19d87e426011fc) --- src/assets/i18n/de.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 325a35437d..8efda0390e 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -3049,7 +3049,7 @@ "journalissue.listelement.badge": "Zeitschriftenheft", // "journalissue.page.description": "Description", - "journalissue.page.description": "Beschreibeung", + "journalissue.page.description": "Beschreibung", // "journalissue.page.edit": "Edit this item", "journalissue.page.edit": "Dieses Item bearbeiten", From dc3bf375219626e970503ac7c6ea69eb63702357 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 30 Aug 2024 12:05:04 -0500 Subject: [PATCH 475/875] Bump axios from 1.6.7 to 1.7.4 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ae3cf5ac51..1d7bb0ee20 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "@types/grecaptcha": "^3.0.4", "angular-idle-preload": "3.0.0", "angulartics2": "^12.2.0", - "axios": "^1.6.0", + "axios": "^1.7.4", "bootstrap": "^4.6.1", "cerialize": "0.1.18", "cli-progress": "^3.12.0", diff --git a/yarn.lock b/yarn.lock index 8b3e3f6f13..0a0d2b3913 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3516,10 +3516,10 @@ axios@0.21.4: dependencies: follow-redirects "^1.14.0" -axios@^1.6.0: - version "1.6.8" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" - integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== +axios@^1.7.4: + version "1.7.5" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.5.tgz#21eed340eb5daf47d29b6e002424b3e88c8c54b1" + integrity sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" From 70f0af66117722e4d12d2b828ee85537946e69de Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 3 Sep 2024 11:36:04 +0200 Subject: [PATCH 476/875] 117287: Removed method calls returning observables from the EPerson form --- .../epeople-registry.component.html | 2 +- .../epeople-registry.component.ts | 22 +- .../eperson-form/eperson-form.component.html | 4 +- .../eperson-form.component.spec.ts | 69 ++--- .../eperson-form/eperson-form.component.ts | 244 +++++++++--------- 5 files changed, 147 insertions(+), 194 deletions(-) diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.html b/src/app/access-control/epeople-registry/epeople-registry.component.html index e3a8e2c590..bd47124856 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.html +++ b/src/app/access-control/epeople-registry/epeople-registry.component.html @@ -66,7 +66,7 @@ + [ngClass]="{'table-primary' : (activeEPerson$ | async) === epersonDto.eperson}"> {{epersonDto.eperson.id}} {{ dsoNameService.getName(epersonDto.eperson) }} {{epersonDto.eperson.email}} diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.ts b/src/app/access-control/epeople-registry/epeople-registry.component.ts index fb045ebb88..f180534587 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.ts +++ b/src/app/access-control/epeople-registry/epeople-registry.component.ts @@ -45,6 +45,8 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { */ ePeopleDto$: BehaviorSubject> = new BehaviorSubject>({} as any); + activeEPerson$: Observable; + /** * An observable for the pageInfo, needed to pass to the pagination component */ @@ -121,6 +123,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { this.isEPersonFormShown = true; } })); + this.activeEPerson$ = this.epersonService.getActiveEPerson(); this.subs.push(this.ePeople$.pipe( switchMap((epeople: PaginatedList) => { if (epeople.pageInfo.totalElements > 0) { @@ -188,29 +191,12 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy { ); } - /** - * Checks whether the given EPerson is active (being edited) - * @param eperson - */ - isActive(eperson: EPerson): Observable { - return this.getActiveEPerson().pipe( - map((activeEPerson) => eperson === activeEPerson) - ); - } - - /** - * Gets the active eperson (being edited) - */ - getActiveEPerson(): Observable { - return this.epersonService.getActiveEPerson(); - } - /** * Start editing the selected EPerson * @param ePerson */ toggleEditEPerson(ePerson: EPerson) { - this.getActiveEPerson().pipe(take(1)).subscribe((activeEPerson: EPerson) => { + this.activeEPerson$.pipe(take(1)).subscribe((activeEPerson: EPerson) => { if (ePerson === activeEPerson) { this.epersonService.cancelEditEPerson(); this.isEPersonFormShown = false; diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html index 228449a8a5..3c5b2d13b4 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html @@ -1,4 +1,4 @@ -
    +

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

    @@ -39,7 +39,7 @@ -
    +
    {{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}
    diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index fb911e709c..dc2e380c33 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -1,11 +1,11 @@ import { Observable, of as observableOf } from 'rxjs'; import { CommonModule } from '@angular/common'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { UntypedFormControl, UntypedFormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { BrowserModule, By } from '@angular/platform-browser'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateModule } from '@ngx-translate/core'; import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model'; import { RemoteData } from '../../../core/data/remote-data'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; @@ -19,7 +19,6 @@ import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock' import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; -import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; import { AuthService } from '../../../core/auth/auth.service'; import { AuthServiceStub } from '../../../shared/testing/auth-service.stub'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; @@ -31,6 +30,7 @@ import { PaginationServiceStub } from '../../../shared/testing/pagination-servic import { FindListOptions } from '../../../core/data/find-list-options.model'; import { ValidateEmailNotTaken } from './validators/email-taken.validator'; import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service'; +import { RouterTestingModule } from '@angular/router/testing'; describe('EPersonFormComponent', () => { let component: EPersonFormComponent; @@ -53,9 +53,6 @@ describe('EPersonFormComponent', () => { ePersonDataServiceStub = { activeEPerson: null, allEpeople: mockEPeople, - getEPeople(): Observable>> { - return createSuccessfulRemoteDataObject$(buildPaginatedList(null, this.allEpeople)); - }, getActiveEPerson(): Observable { return observableOf(this.activeEPerson); }, @@ -184,12 +181,8 @@ describe('EPersonFormComponent', () => { paginationService = new PaginationServiceStub(); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock - } - }), + RouterTestingModule, + TranslateModule.forRoot(), ], declarations: [EPersonFormComponent], providers: [ @@ -204,7 +197,7 @@ describe('EPersonFormComponent', () => { { provide: EpersonRegistrationService, useValue: epersonRegistrationService }, EPeopleRegistryComponent ], - schemas: [NO_ERRORS_SCHEMA] + schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents(); })); @@ -223,37 +216,13 @@ describe('EPersonFormComponent', () => { }); describe('check form validation', () => { - let firstName; - let lastName; - let email; - let canLogIn; - let requireCertificate; + let canLogIn: boolean; + let requireCertificate: boolean; - let expected; beforeEach(() => { - firstName = 'testName'; - lastName = 'testLastName'; - email = 'testEmail@test.com'; canLogIn = false; requireCertificate = false; - expected = Object.assign(new EPerson(), { - metadata: { - 'eperson.firstname': [ - { - value: firstName - } - ], - 'eperson.lastname': [ - { - value: lastName - }, - ], - }, - email: email, - canLogIn: canLogIn, - requireCertificate: requireCertificate, - }); spyOn(component.submitForm, 'emit'); component.canLogIn.value = canLogIn; component.requireCertificate.value = requireCertificate; @@ -343,15 +312,13 @@ describe('EPersonFormComponent', () => { }); })); }); - - - }); + describe('when submitting the form', () => { let firstName; let lastName; let email; - let canLogIn; + let canLogIn: boolean; let requireCertificate; let expected; @@ -380,6 +347,7 @@ describe('EPersonFormComponent', () => { requireCertificate: requireCertificate, }); spyOn(component.submitForm, 'emit'); + component.ngOnInit(); component.firstName.value = firstName; component.lastName.value = lastName; component.email.value = email; @@ -421,9 +389,17 @@ describe('EPersonFormComponent', () => { email: email, canLogIn: canLogIn, requireCertificate: requireCertificate, - _links: undefined + _links: { + groups: { + href: '', + }, + self: { + href: '', + }, + }, }); spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(observableOf(expectedWithId)); + component.ngOnInit(); component.onSubmit(); fixture.detectChanges(); }); @@ -473,22 +449,19 @@ describe('EPersonFormComponent', () => { }); describe('delete', () => { - - let ePersonId; let eperson: EPerson; let modalService; beforeEach(() => { spyOn(authService, 'impersonate').and.callThrough(); - ePersonId = 'testEPersonId'; eperson = EPersonMock; component.epersonInitial = eperson; component.canDelete$ = observableOf(true); spyOn(component.epersonService, 'getActiveEPerson').and.returnValue(observableOf(eperson)); modalService = (component as any).modalService; spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: observableOf(true) }) })); + component.ngOnInit(); fixture.detectChanges(); - }); it('the delete button should be active if the eperson can be deleted', () => { diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts index d009d56058..296038ca2b 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -137,6 +137,11 @@ export class EPersonFormComponent implements OnInit, OnDestroy { */ canImpersonate$: Observable; + /** + * The current {@link EPerson} + */ + activeEPerson$: Observable; + /** * List of subscriptions */ @@ -195,7 +200,11 @@ export class EPersonFormComponent implements OnInit, OnDestroy { private epersonRegistrationService: EpersonRegistrationService, public dsoNameService: DSONameService, ) { - this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => { + } + + ngOnInit() { + this.activeEPerson$ = this.epersonService.getActiveEPerson(); + this.subs.push(this.activeEPerson$.subscribe((eperson: EPerson) => { this.epersonInitial = eperson; if (hasValue(eperson)) { this.isImpersonated = this.authService.isImpersonatingUser(eperson.id); @@ -203,9 +212,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy { this.submitLabel = 'form.submit'; } })); - } - - ngOnInit() { this.initialisePage(); } @@ -213,124 +219,112 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * This method will initialise the page */ initialisePage() { - - observableCombineLatest([ - this.translateService.get(`${this.messagePrefix}.firstName`), - this.translateService.get(`${this.messagePrefix}.lastName`), - this.translateService.get(`${this.messagePrefix}.email`), - this.translateService.get(`${this.messagePrefix}.canLogIn`), - this.translateService.get(`${this.messagePrefix}.requireCertificate`), - this.translateService.get(`${this.messagePrefix}.emailHint`), - ]).subscribe(([firstName, lastName, email, canLogIn, requireCertificate, emailHint]) => { - this.firstName = new DynamicInputModel({ - id: 'firstName', - label: firstName, - name: 'firstName', - validators: { - required: null, - }, - required: true, - }); - this.lastName = new DynamicInputModel({ - id: 'lastName', - label: lastName, - name: 'lastName', - validators: { - required: null, - }, - required: true, - }); - this.email = new DynamicInputModel({ - id: 'email', - label: email, - name: 'email', - validators: { - required: null, - pattern: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$', - }, - required: true, - errorMessages: { - emailTaken: 'error.validation.emailTaken', - pattern: 'error.validation.NotValidEmail' - }, - hint: emailHint - }); - this.canLogIn = new DynamicCheckboxModel( - { - id: 'canLogIn', - label: canLogIn, - name: 'canLogIn', - value: (this.epersonInitial != null ? this.epersonInitial.canLogIn : true) - }); - this.requireCertificate = new DynamicCheckboxModel( - { - id: 'requireCertificate', - label: requireCertificate, - name: 'requireCertificate', - value: (this.epersonInitial != null ? this.epersonInitial.requireCertificate : false) - }); - this.formModel = [ - this.firstName, - this.lastName, - this.email, - this.canLogIn, - this.requireCertificate, - ]; - this.formGroup = this.formBuilderService.createFormGroup(this.formModel); - this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => { - if (eperson != null) { - this.groups = this.groupsDataService.findListByHref(eperson._links.groups.href, { - currentPage: 1, - elementsPerPage: this.config.pageSize - }); - } - this.formGroup.patchValue({ - firstName: eperson != null ? eperson.firstMetadataValue('eperson.firstname') : '', - lastName: eperson != null ? eperson.firstMetadataValue('eperson.lastname') : '', - email: eperson != null ? eperson.email : '', - canLogIn: eperson != null ? eperson.canLogIn : true, - requireCertificate: eperson != null ? eperson.requireCertificate : false - }); - - if (eperson === null && !!this.formGroup.controls.email) { - this.formGroup.controls.email.setAsyncValidators(ValidateEmailNotTaken.createValidator(this.epersonService)); - this.emailValueChangeSubscribe = this.email.valueChanges.pipe(debounceTime(300)).subscribe(() => { - this.changeDetectorRef.detectChanges(); - }); - } - })); - - const activeEPerson$ = this.epersonService.getActiveEPerson(); - - this.groups = activeEPerson$.pipe( - switchMap((eperson) => { - return observableCombineLatest([observableOf(eperson), this.paginationService.getFindListOptions(this.config.id, { - currentPage: 1, - elementsPerPage: this.config.pageSize - })]); - }), - switchMap(([eperson, findListOptions]) => { - if (eperson != null) { - return this.groupsDataService.findListByHref(eperson._links.groups.href, findListOptions, true, true, followLink('object')); - } - return observableOf(undefined); - }) - ); - - this.canImpersonate$ = activeEPerson$.pipe( - switchMap((eperson) => { - if (hasValue(eperson)) { - return this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, eperson.self); - } else { - return observableOf(false); - } - }) - ); - this.canDelete$ = activeEPerson$.pipe( - switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined)) - ); - this.canReset$ = observableOf(true); + this.firstName = new DynamicInputModel({ + id: 'firstName', + label: this.translateService.instant(`${this.messagePrefix}.firstName`), + name: 'firstName', + validators: { + required: null, + }, + required: true, }); + this.lastName = new DynamicInputModel({ + id: 'lastName', + label: this.translateService.instant(`${this.messagePrefix}.lastName`), + name: 'lastName', + validators: { + required: null, + }, + required: true, + }); + this.email = new DynamicInputModel({ + id: 'email', + label: this.translateService.instant(`${this.messagePrefix}.email`), + name: 'email', + validators: { + required: null, + pattern: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$', + }, + required: true, + errorMessages: { + emailTaken: 'error.validation.emailTaken', + pattern: 'error.validation.NotValidEmail' + }, + hint: this.translateService.instant(`${this.messagePrefix}.emailHint`), + }); + this.canLogIn = new DynamicCheckboxModel( + { + id: 'canLogIn', + label: this.translateService.instant(`${this.messagePrefix}.canLogIn`), + name: 'canLogIn', + value: (this.epersonInitial != null ? this.epersonInitial.canLogIn : true) + }); + this.requireCertificate = new DynamicCheckboxModel( + { + id: 'requireCertificate', + label: this.translateService.instant(`${this.messagePrefix}.requireCertificate`), + name: 'requireCertificate', + value: (this.epersonInitial != null ? this.epersonInitial.requireCertificate : false) + }); + this.formModel = [ + this.firstName, + this.lastName, + this.email, + this.canLogIn, + this.requireCertificate, + ]; + this.formGroup = this.formBuilderService.createFormGroup(this.formModel); + this.subs.push(this.activeEPerson$.subscribe((eperson: EPerson) => { + if (eperson != null) { + this.groups = this.groupsDataService.findListByHref(eperson._links.groups.href, { + currentPage: 1, + elementsPerPage: this.config.pageSize + }); + } + this.formGroup.patchValue({ + firstName: eperson != null ? eperson.firstMetadataValue('eperson.firstname') : '', + lastName: eperson != null ? eperson.firstMetadataValue('eperson.lastname') : '', + email: eperson != null ? eperson.email : '', + canLogIn: eperson != null ? eperson.canLogIn : true, + requireCertificate: eperson != null ? eperson.requireCertificate : false + }); + + if (eperson === null && !!this.formGroup.controls.email) { + this.formGroup.controls.email.setAsyncValidators(ValidateEmailNotTaken.createValidator(this.epersonService)); + this.emailValueChangeSubscribe = this.email.valueChanges.pipe(debounceTime(300)).subscribe(() => { + this.changeDetectorRef.detectChanges(); + }); + } + })); + + this.groups = this.activeEPerson$.pipe( + switchMap((eperson) => { + return observableCombineLatest([observableOf(eperson), this.paginationService.getFindListOptions(this.config.id, { + currentPage: 1, + elementsPerPage: this.config.pageSize + })]); + }), + switchMap(([eperson, findListOptions]) => { + if (eperson != null) { + return this.groupsDataService.findListByHref(eperson._links.groups.href, findListOptions, true, true, followLink('object')); + } + return observableOf(undefined); + }) + ); + + this.canImpersonate$ = this.activeEPerson$.pipe( + switchMap((eperson) => { + if (hasValue(eperson)) { + return this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, eperson.self); + } else { + return observableOf(false); + } + }) + ); + this.canDelete$ = this.activeEPerson$.pipe( + switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined)) + ); + this.canReset$ = observableOf(true); } /** @@ -348,7 +342,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * Emit the updated/created eperson using the EventEmitter submitForm */ onSubmit() { - this.epersonService.getActiveEPerson().pipe(take(1)).subscribe( + this.activeEPerson$.pipe(take(1)).subscribe( (ePerson: EPerson) => { const values = { metadata: { @@ -464,7 +458,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * It'll either show a success or error message depending on whether the delete was successful or not. */ delete(): void { - this.epersonService.getActiveEPerson().pipe( + this.activeEPerson$.pipe( take(1), switchMap((eperson: EPerson) => { const modalRef = this.modalService.open(ConfirmationModalComponent); @@ -545,7 +539,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * This method will ensure that the page gets reset and that the cache is cleared */ reset() { - this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => { + this.activeEPerson$.pipe(take(1)).subscribe((eperson: EPerson) => { this.requestService.removeByHrefSubstring(eperson.self); }); this.initialisePage(); @@ -577,7 +571,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * Update the list of groups by fetching it from the rest api or cache */ private updateGroups(options) { - this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => { + this.subs.push(this.activeEPerson$.subscribe((eperson: EPerson) => { this.groups = this.groupsDataService.findListByHref(eperson._links.groups.href, options); })); } From d3019e4006aaa72f1dd8561a8d83050e48a9cebb Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 3 Sep 2024 18:44:49 +0200 Subject: [PATCH 477/875] 117287: Removed method calls returning observables from the Registry Formats page --- .../bitstream-formats.component.html | 16 +++---- .../bitstream-formats.component.spec.ts | 22 ++++------ .../bitstream-formats.component.ts | 42 +++++++++---------- 3 files changed, 34 insertions(+), 46 deletions(-) diff --git a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.html b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.html index 0a2e9f0f92..97740d5ea1 100644 --- a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.html +++ b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.html @@ -9,10 +9,10 @@
    @@ -27,11 +27,11 @@ - + @@ -45,13 +45,13 @@
    -
    diff --git a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts index 8a44240b7e..ec59f57c80 100644 --- a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts +++ b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts @@ -15,8 +15,7 @@ import { NotificationsService } from '../../../shared/notifications/notification import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level'; -import { cold, getTestScheduler, hot } from 'jasmine-marbles'; -import { TestScheduler } from 'rxjs/testing'; +import { hot } from 'jasmine-marbles'; import { createNoContentRemoteDataObject$, createSuccessfulRemoteDataObject, @@ -31,7 +30,6 @@ describe('BitstreamFormatsComponent', () => { let comp: BitstreamFormatsComponent; let fixture: ComponentFixture; let bitstreamFormatService; - let scheduler: TestScheduler; let notificationsServiceStub; let paginationService; @@ -86,8 +84,6 @@ describe('BitstreamFormatsComponent', () => { const initAsync = () => { notificationsServiceStub = new NotificationsServiceStub(); - scheduler = getTestScheduler(); - bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { findAll: observableOf(mockFormatsRD), find: createSuccessfulRemoteDataObject$(mockFormatsList[0]), @@ -178,17 +174,17 @@ describe('BitstreamFormatsComponent', () => { beforeEach(waitForAsync(initAsync)); beforeEach(initBeforeEach); it('should return an observable of true if the provided bistream is in the list returned by the service', () => { - const result = comp.isSelected(bitstreamFormat1); - - expect(result).toBeObservable(cold('b', { b: true })); + comp.selectedBitstreamFormatIDs().subscribe((selectedBitstreamFormatIDs: string[]) => { + expect(selectedBitstreamFormatIDs).toContain(bitstreamFormat1.id); + }); }); it('should return an observable of false if the provided bistream is not in the list returned by the service', () => { const format = new BitstreamFormat(); format.uuid = 'new'; - const result = comp.isSelected(format); - - expect(result).toBeObservable(cold('b', { b: false })); + comp.selectedBitstreamFormatIDs().subscribe((selectedBitstreamFormatIDs: string[]) => { + expect(selectedBitstreamFormatIDs).not.toContain(format.id); + }); }); }); @@ -214,8 +210,6 @@ describe('BitstreamFormatsComponent', () => { beforeEach(waitForAsync(() => { notificationsServiceStub = new NotificationsServiceStub(); - scheduler = getTestScheduler(); - bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { findAll: observableOf(mockFormatsRD), find: createSuccessfulRemoteDataObject$(mockFormatsList[0]), @@ -263,8 +257,6 @@ describe('BitstreamFormatsComponent', () => { beforeEach(waitForAsync(() => { notificationsServiceStub = new NotificationsServiceStub(); - scheduler = getTestScheduler(); - bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { findAll: observableOf(mockFormatsRD), find: createSuccessfulRemoteDataObject$(mockFormatsList[0]), diff --git a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.ts index 162bf2bdb2..263c1aede2 100644 --- a/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.ts +++ b/src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.ts @@ -1,5 +1,5 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; -import { combineLatest as observableCombineLatest, Observable} from 'rxjs'; +import { Observable} from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; @@ -7,7 +7,6 @@ import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service'; import { map, mergeMap, switchMap, take, toArray } from 'rxjs/operators'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; -import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { NoContent } from '../../../core/shared/NoContent.model'; import { PaginationService } from '../../../core/pagination/pagination.service'; @@ -26,7 +25,12 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy { /** * A paginated list of bitstream formats to be shown on the page */ - bitstreamFormats: Observable>>; + bitstreamFormats$: Observable>>; + + /** + * The currently selected {@link BitstreamFormat} IDs + */ + selectedBitstreamFormatIDs$: Observable; /** * The current pagination configuration for the page @@ -39,7 +43,6 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy { }); constructor(private notificationsService: NotificationsService, - private router: Router, private translateService: TranslateService, private bitstreamFormatService: BitstreamFormatDataService, private paginationService: PaginationService, @@ -94,14 +97,11 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy { } /** - * Checks whether a given bitstream format is selected in the list (checkbox) - * @param bitstreamFormat + * Returns the list of all the bitstream formats that are selected in the list (checkbox) */ - isSelected(bitstreamFormat: BitstreamFormat): Observable { + selectedBitstreamFormatIDs(): Observable { return this.bitstreamFormatService.getSelectedBitstreamFormats().pipe( - map((bitstreamFormats: BitstreamFormat[]) => { - return bitstreamFormats.find((selectedFormat) => selectedFormat.id === bitstreamFormat.id) != null; - }) + map((bitstreamFormats: BitstreamFormat[]) => bitstreamFormats.map((selectedFormat) => selectedFormat.id)), ); } @@ -125,27 +125,23 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy { const prefix = 'admin.registries.bitstream-formats.delete'; const suffix = success ? 'success' : 'failure'; - const messages = observableCombineLatest( - this.translateService.get(`${prefix}.${suffix}.head`), - this.translateService.get(`${prefix}.${suffix}.amount`, {amount: amount}) - ); - messages.subscribe(([head, content]) => { + const head: string = this.translateService.instant(`${prefix}.${suffix}.head`); + const content: string = this.translateService.instant(`${prefix}.${suffix}.amount`, { amount: amount }); - if (success) { - this.notificationsService.success(head, content); - } else { - this.notificationsService.error(head, content); - } - }); + if (success) { + this.notificationsService.success(head, content); + } else { + this.notificationsService.error(head, content); + } } ngOnInit(): void { - - this.bitstreamFormats = this.paginationService.getFindListOptions(this.pageConfig.id, this.pageConfig).pipe( + this.bitstreamFormats$ = this.paginationService.getFindListOptions(this.pageConfig.id, this.pageConfig).pipe( switchMap((findListOptions: FindListOptions) => { return this.bitstreamFormatService.findAll(findListOptions); }) ); + this.selectedBitstreamFormatIDs$ = this.selectedBitstreamFormatIDs(); } From fa95f56be0f67c200f0b8d2b982894b5750e151c Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 3 Sep 2024 14:52:10 +0200 Subject: [PATCH 478/875] Ignore some paths from file watcher Watching all these directories can cause systems to exceed maximum watched files / inotify limits, especially in dev mode with IDEs etc also watching files. (cherry picked from commit 8152d39ad0002a06dcfdc39861c94c78d0b7d2bc) --- webpack/webpack.browser.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/webpack/webpack.browser.ts b/webpack/webpack.browser.ts index 5a3b4910ae..168185ef24 100644 --- a/webpack/webpack.browser.ts +++ b/webpack/webpack.browser.ts @@ -35,5 +35,12 @@ module.exports = Object.assign({}, commonExports, { buildAppConfig(join(process.cwd(), 'src/assets/config.json')); return middlewares; } - } + }, + watchOptions: { + // Ignore directories that should not be watched for recompiling angular + ignored: [ + '**/node_modules', '**/_build', '**/.git', '**/docker', + '**/.angular', '**/.idea', '**/.vscode', '**/.history', '**/.vsix' + ] + }, }); From 4ee1c8307311d072ff3e7d0fea997fbaeba80ad7 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 4 Sep 2024 17:02:50 -0500 Subject: [PATCH 479/875] Bump micromatch to 4.0.8 --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0a0d2b3913..c6f4d25211 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3728,7 +3728,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.2, braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -8092,11 +8092,11 @@ mhchemparser@^4.1.0: integrity sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA== micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": From 680ed3bccf38732ada602a7f01487c60035067ac Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 3 Sep 2024 22:08:38 +0200 Subject: [PATCH 480/875] 117287: Removed method calls returning observables from the Group form --- .../group-form/group-form.component.html | 35 +- .../group-form/group-form.component.spec.ts | 140 +++++--- .../group-form/group-form.component.ts | 337 ++++++++---------- 3 files changed, 259 insertions(+), 253 deletions(-) diff --git a/src/app/access-control/group-registry/group-form/group-form.component.html b/src/app/access-control/group-registry/group-form/group-form.component.html index 77a81a8daa..546d4598df 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.html +++ b/src/app/access-control/group-registry/group-form/group-form.component.html @@ -2,7 +2,7 @@
    -
    +

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

    @@ -23,11 +23,15 @@
    - - - + + + + + + + {{messagePrefix + '.return' | translate}}
    -
    +
    -
    - -
    - - - - + +
    + +
    + +
    diff --git a/src/app/access-control/group-registry/group-form/group-form.component.spec.ts b/src/app/access-control/group-registry/group-form/group-form.component.spec.ts index f8c5f3cd87..8d148b66fb 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.spec.ts +++ b/src/app/access-control/group-registry/group-form/group-form.component.spec.ts @@ -1,13 +1,13 @@ import { CommonModule } from '@angular/common'; import { HttpClient } from '@angular/common/http'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { UntypedFormControl, UntypedFormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { BrowserModule, By } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { Store } from '@ngrx/store'; -import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; +import { TranslateModule } from '@ngx-translate/core'; import { Observable, of as observableOf } from 'rxjs'; import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; @@ -29,8 +29,6 @@ import { GroupMock, GroupMock2 } from '../../../shared/testing/group-mock'; import { GroupFormComponent } from './group-form.component'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock'; -import { getMockTranslateService } from '../../../shared/mocks/translate.service.mock'; -import { TranslateLoaderMock } from '../../../shared/testing/translate-loader.mock'; import { RouterMock } from '../../../shared/mocks/router.mock'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { Operation } from 'fast-json-patch'; @@ -38,23 +36,24 @@ import { ValidateGroupExists } from './validators/group-exists.validator'; import { NoContent } from '../../../core/shared/NoContent.model'; import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock } from '../../../shared/mocks/dso-name.service.mock'; +import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub'; describe('GroupFormComponent', () => { let component: GroupFormComponent; let fixture: ComponentFixture; - let translateService: TranslateService; let builderService: FormBuilderService; let ePersonDataServiceStub: any; let groupsDataServiceStub: any; let dsoDataServiceStub: any; let authorizationService: AuthorizationDataService; let notificationService: NotificationsServiceStub; - let router; + let router: RouterMock; + let route: ActivatedRouteStub; - let groups; - let groupName; - let groupDescription; - let expected; + let groups: Group[]; + let groupName: string; + let groupDescription: string; + let expected: Group; beforeEach(waitForAsync(() => { groups = [GroupMock, GroupMock2]; @@ -69,6 +68,15 @@ describe('GroupFormComponent', () => { } ], }, + object: createSuccessfulRemoteDataObject$(undefined), + _links: { + self: { + href: 'group-selflink', + }, + object: { + href: 'group-objectlink', + } + }, }); ePersonDataServiceStub = {}; groupsDataServiceStub = { @@ -105,7 +113,14 @@ describe('GroupFormComponent', () => { create(group: Group): Observable> { this.allGroups = [...this.allGroups, group]; this.createdGroup = Object.assign({}, group, { - _links: { self: { href: 'group-selflink' } } + _links: { + self: { + href: 'group-selflink', + }, + object: { + href: 'group-objectlink', + }, + }, }); return createSuccessfulRemoteDataObject$(this.createdGroup); }, @@ -187,17 +202,13 @@ describe('GroupFormComponent', () => { return typeof value === 'object' && value !== null; } }); - translateService = getMockTranslateService(); router = new RouterMock(); + route = new ActivatedRouteStub(); notificationService = new NotificationsServiceStub(); + return TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock - } - }), + TranslateModule.forRoot(), ], declarations: [GroupFormComponent], providers: [ @@ -214,14 +225,11 @@ describe('GroupFormComponent', () => { { provide: Store, useValue: {} }, { provide: RemoteDataBuildService, useValue: {} }, { provide: HALEndpointService, useValue: {} }, - { - provide: ActivatedRoute, - useValue: { data: observableOf({ dso: { payload: {} } }), params: observableOf({}) } - }, + { provide: ActivatedRoute, useValue: route }, { provide: Router, useValue: router }, { provide: AuthorizationDataService, useValue: authorizationService }, ], - schemas: [NO_ERRORS_SCHEMA] + schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents(); })); @@ -234,8 +242,8 @@ describe('GroupFormComponent', () => { describe('when submitting the form', () => { beforeEach(() => { spyOn(component.submitForm, 'emit'); - component.groupName.value = groupName; - component.groupDescription.value = groupDescription; + component.groupName.setValue(groupName); + component.groupDescription.setValue(groupDescription); }); describe('without active Group', () => { beforeEach(() => { @@ -243,14 +251,22 @@ describe('GroupFormComponent', () => { fixture.detectChanges(); }); - it('should emit a new group using the correct values', (async () => { - await fixture.whenStable().then(() => { - expect(component.submitForm.emit).toHaveBeenCalledWith(expected); - }); + it('should emit a new group using the correct values', (() => { + expect(component.submitForm.emit).toHaveBeenCalledWith(jasmine.objectContaining({ + name: groupName, + metadata: { + 'dc.description': [ + { + value: groupDescription, + }, + ], + }, + })); })); }); + describe('with active Group', () => { - let expected2; + let expected2: Group; beforeEach(() => { expected2 = Object.assign(new Group(), { name: 'newGroupName', @@ -261,15 +277,24 @@ describe('GroupFormComponent', () => { } ], }, + object: createSuccessfulRemoteDataObject$(undefined), + _links: { + self: { + href: 'group-selflink', + }, + object: { + href: 'group-objectlink', + }, + }, }); spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(observableOf(expected)); spyOn(groupsDataServiceStub, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(expected2)); - component.groupName.value = 'newGroupName'; - component.onSubmit(); - fixture.detectChanges(); + component.ngOnInit(); }); it('should edit with name and description operations', () => { + component.groupName.setValue('newGroupName'); + component.onSubmit(); const operations = [{ op: 'add', path: '/metadata/dc.description', @@ -283,9 +308,8 @@ describe('GroupFormComponent', () => { }); it('should edit with description operations', () => { - component.groupName.value = null; + component.groupName.setValue(null); component.onSubmit(); - fixture.detectChanges(); const operations = [{ op: 'add', path: '/metadata/dc.description', @@ -295,9 +319,9 @@ describe('GroupFormComponent', () => { }); it('should edit with name operations', () => { - component.groupDescription.value = null; + component.groupName.setValue('newGroupName'); + component.groupDescription.setValue(null); component.onSubmit(); - fixture.detectChanges(); const operations = [{ op: 'replace', path: '/name', @@ -306,12 +330,13 @@ describe('GroupFormComponent', () => { expect(groupsDataServiceStub.patch).toHaveBeenCalledWith(expected, operations); }); - it('should emit the existing group using the correct new values', (async () => { - await fixture.whenStable().then(() => { - expect(component.submitForm.emit).toHaveBeenCalledWith(expected2); - }); - })); + it('should emit the existing group using the correct new values', () => { + component.onSubmit(); + expect(component.submitForm.emit).toHaveBeenCalledWith(expected2); + }); + it('should emit success notification', () => { + component.onSubmit(); expect(notificationService.success).toHaveBeenCalled(); }); }); @@ -326,11 +351,8 @@ describe('GroupFormComponent', () => { describe('check form validation', () => { - let groupCommunity; - beforeEach(() => { groupName = 'testName'; - groupCommunity = 'testgroupCommunity'; groupDescription = 'testgroupDescription'; expected = Object.assign(new Group(), { @@ -342,8 +364,17 @@ describe('GroupFormComponent', () => { } ], }, + _links: { + self: { + href: 'group-selflink', + }, + object: { + href: 'group-objectlink', + }, + }, }); spyOn(component.submitForm, 'emit'); + spyOn(dsoDataServiceStub, 'findByHref').and.returnValue(observableOf(expected)); fixture.detectChanges(); component.initialisePage(); @@ -393,21 +424,20 @@ describe('GroupFormComponent', () => { }); describe('delete', () => { - let deleteButton; - - beforeEach(() => { - component.initialisePage(); + let deleteButton: HTMLButtonElement; + beforeEach(async () => { + spyOn(groupsDataServiceStub, 'delete').and.callThrough(); + component.activeGroup$ = observableOf({ + id: 'active-group', + permanent: false, + } as Group); component.canEdit$ = observableOf(true); - component.groupBeingEdited = { - permanent: false - } as Group; + + component.initialisePage(); fixture.detectChanges(); deleteButton = fixture.debugElement.query(By.css('.delete-button')).nativeElement; - - spyOn(groupsDataServiceStub, 'delete').and.callThrough(); - spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(observableOf({ id: 'active-group' })); }); describe('if confirmed via modal', () => { diff --git a/src/app/access-control/group-registry/group-form/group-form.component.ts b/src/app/access-control/group-registry/group-form/group-form.component.ts index 3c0547cca5..37ce30473f 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.ts +++ b/src/app/access-control/group-registry/group-form/group-form.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, HostListener, OnDestroy, OnInit, Output, ChangeDetectorRef } from '@angular/core'; -import { UntypedFormGroup } from '@angular/forms'; +import { UntypedFormGroup, AbstractControl } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { @@ -10,13 +10,10 @@ import { } from '@ng-dynamic-forms/core'; import { TranslateService } from '@ngx-translate/core'; import { - ObservedValueOf, - combineLatest as observableCombineLatest, Observable, - of as observableOf, - Subscription, + Subscription, combineLatest, } from 'rxjs'; -import { catchError, map, switchMap, take, filter, debounceTime } from 'rxjs/operators'; +import { map, switchMap, take, debounceTime, startWith, filter } from 'rxjs/operators'; import { getCollectionEditRolesRoute } from '../../../collection-page/collection-page-routing-paths'; import { getCommunityEditRolesRoute } from '../../../community-page/community-page-routing-paths'; import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service'; @@ -25,21 +22,20 @@ import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { RemoteData } from '../../../core/data/remote-data'; import { RequestService } from '../../../core/data/request.service'; -import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; import { GroupDataService } from '../../../core/eperson/group-data.service'; import { Group } from '../../../core/eperson/models/group.model'; import { Collection } from '../../../core/shared/collection.model'; import { Community } from '../../../core/shared/community.model'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { + getAllCompletedRemoteData, getRemoteDataPayload, getFirstSucceededRemoteData, getFirstCompletedRemoteData, - getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; import { AlertType } from '../../../shared/alert/aletr-type'; import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component'; -import { hasValue, isNotEmpty, hasValueOperator } from '../../../shared/empty.util'; +import { hasValue, isNotEmpty, hasValueOperator, hasNoValue } from '../../../shared/empty.util'; import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { followLink } from '../../../shared/utils/follow-link-config.model'; @@ -68,9 +64,9 @@ export class GroupFormComponent implements OnInit, OnDestroy { /** * Dynamic models for the inputs of form */ - groupName: DynamicInputModel; - groupCommunity: DynamicInputModel; - groupDescription: DynamicTextAreaModel; + groupName: AbstractControl; + groupCommunity: AbstractControl; + groupDescription: AbstractControl; /** * A list of all dynamic input models @@ -113,21 +109,30 @@ export class GroupFormComponent implements OnInit, OnDestroy { */ subs: Subscription[] = []; - /** - * Group currently being edited - */ - groupBeingEdited: Group; - /** * Observable whether or not the logged in user is allowed to delete the Group & doesn't have a linked object (community / collection linked to workspace group */ canEdit$: Observable; /** - * The AlertType enumeration - * @type {AlertType} + * The current {@link Group} */ - public AlertTypeEnum = AlertType; + activeGroup$: Observable; + + /** + * The current {@link Group}'s linked {@link Community}/{@link Collection} + */ + activeGroupLinkedDSO$: Observable; + + /** + * Link to the current {@link Group}'s {@link Community}/{@link Collection} edit role tab + */ + linkedEditRolesRoute$: Observable; + + /** + * The AlertType enumeration + */ + public readonly AlertType = AlertType; /** * Subscription to email field value change @@ -137,124 +142,110 @@ export class GroupFormComponent implements OnInit, OnDestroy { constructor( public groupDataService: GroupDataService, - private ePersonDataService: EPersonDataService, - private dSpaceObjectDataService: DSpaceObjectDataService, - private formBuilderService: FormBuilderService, - private translateService: TranslateService, - private notificationsService: NotificationsService, - private route: ActivatedRoute, + protected dSpaceObjectDataService: DSpaceObjectDataService, + protected formBuilderService: FormBuilderService, + protected translateService: TranslateService, + protected notificationsService: NotificationsService, + protected route: ActivatedRoute, protected router: Router, - private authorizationService: AuthorizationDataService, - private modalService: NgbModal, + protected authorizationService: AuthorizationDataService, + protected modalService: NgbModal, public requestService: RequestService, protected changeDetectorRef: ChangeDetectorRef, public dsoNameService: DSONameService, ) { } - ngOnInit() { + ngOnInit(): void { + if (this.route.snapshot.params.groupId !== 'newGroup') { + this.setActiveGroup(this.route.snapshot.params.groupId); + } + this.activeGroup$ = this.groupDataService.getActiveGroup(); + this.activeGroupLinkedDSO$ = this.getActiveGroupLinkedDSO(); + this.linkedEditRolesRoute$ = this.getLinkedEditRolesRoute(); + this.canEdit$ = this.activeGroupLinkedDSO$.pipe( + filter((dso: DSpaceObject) => hasNoValue(dso)), + switchMap(() => this.activeGroup$), + hasValueOperator(), + switchMap((group: Group) => this.authorizationService.isAuthorized(FeatureID.CanDelete, group.self)), + startWith(false), + ); this.initialisePage(); } initialisePage() { - this.subs.push(this.route.params.subscribe((params) => { - if (params.groupId !== 'newGroup') { - this.setActiveGroup(params.groupId); - } - })); - this.canEdit$ = this.groupDataService.getActiveGroup().pipe( - hasValueOperator(), - switchMap((group: Group) => { - return observableCombineLatest( - this.authorizationService.isAuthorized(FeatureID.CanDelete, isNotEmpty(group) ? group.self : undefined), - this.hasLinkedDSO(group), - (isAuthorized: ObservedValueOf>, hasLinkedDSO: ObservedValueOf>) => { - return isAuthorized && !hasLinkedDSO; - }); + const groupNameModel = new DynamicInputModel({ + id: 'groupName', + label: this.translateService.instant(`${this.messagePrefix}.groupName`), + name: 'groupName', + validators: { + required: null, + }, + required: true, + }); + const groupCommunityModel = new DynamicInputModel({ + id: 'groupCommunity', + label: this.translateService.instant(`${this.messagePrefix}.groupCommunity`), + name: 'groupCommunity', + required: false, + readOnly: true, + }); + const groupDescriptionModel = new DynamicTextAreaModel({ + id: 'groupDescription', + label: this.translateService.instant(`${this.messagePrefix}.groupDescription`), + name: 'groupDescription', + required: false, + spellCheck: environment.form.spellCheck, + }); + this.formModel = [ + groupNameModel, + groupDescriptionModel, + ]; + this.formGroup = this.formBuilderService.createFormGroup(this.formModel); + this.groupName = this.formGroup.get('groupName'); + this.groupDescription = this.formGroup.get('groupDescription'); + + if (hasValue(this.groupName)) { + this.groupName.setAsyncValidators(ValidateGroupExists.createValidator(this.groupDataService)); + this.groupNameValueChangeSubscribe = this.groupName.valueChanges.pipe(debounceTime(300)).subscribe(() => { + this.changeDetectorRef.detectChanges(); + }); + } + + this.subs.push( + combineLatest([ + this.activeGroup$, + this.canEdit$, + this.activeGroupLinkedDSO$.pipe(take(1)), + ]).subscribe(([activeGroup, canEdit, linkedObject]) => { + + if (activeGroup != null) { + + // Disable group name exists validator + this.formGroup.controls.groupName.clearAsyncValidators(); + + if (linkedObject?.name) { + this.formBuilderService.insertFormGroupControl(1, this.formGroup, this.formModel, groupCommunityModel); + this.groupDescription = this.formGroup.get('groupCommunity'); + this.formGroup.patchValue({ + groupName: activeGroup.name, + groupCommunity: linkedObject?.name ?? '', + groupDescription: activeGroup.firstMetadataValue('dc.description'), + }); + } else { + this.formGroup.patchValue({ + groupName: activeGroup.name, + groupDescription: activeGroup.firstMetadataValue('dc.description'), + }); + } + setTimeout(() => { + if (!canEdit || activeGroup.permanent) { + this.formGroup.disable(); + } + }, 200); + } }) ); - observableCombineLatest( - this.translateService.get(`${this.messagePrefix}.groupName`), - this.translateService.get(`${this.messagePrefix}.groupCommunity`), - this.translateService.get(`${this.messagePrefix}.groupDescription`) - ).subscribe(([groupName, groupCommunity, groupDescription]) => { - this.groupName = new DynamicInputModel({ - id: 'groupName', - label: groupName, - name: 'groupName', - validators: { - required: null, - }, - required: true, - }); - this.groupCommunity = new DynamicInputModel({ - id: 'groupCommunity', - label: groupCommunity, - name: 'groupCommunity', - required: false, - readOnly: true, - }); - this.groupDescription = new DynamicTextAreaModel({ - id: 'groupDescription', - label: groupDescription, - name: 'groupDescription', - required: false, - spellCheck: environment.form.spellCheck, - }); - this.formModel = [ - this.groupName, - this.groupDescription, - ]; - this.formGroup = this.formBuilderService.createFormGroup(this.formModel); - - if (!!this.formGroup.controls.groupName) { - this.formGroup.controls.groupName.setAsyncValidators(ValidateGroupExists.createValidator(this.groupDataService)); - this.groupNameValueChangeSubscribe = this.groupName.valueChanges.pipe(debounceTime(300)).subscribe(() => { - this.changeDetectorRef.detectChanges(); - }); - } - - this.subs.push( - observableCombineLatest( - this.groupDataService.getActiveGroup(), - this.canEdit$, - this.groupDataService.getActiveGroup() - .pipe(filter((activeGroup) => hasValue(activeGroup)),switchMap((activeGroup) => this.getLinkedDSO(activeGroup).pipe(getFirstSucceededRemoteDataPayload()))) - ).subscribe(([activeGroup, canEdit, linkedObject]) => { - - if (activeGroup != null) { - - // Disable group name exists validator - this.formGroup.controls.groupName.clearAsyncValidators(); - - this.groupBeingEdited = activeGroup; - - if (linkedObject?.name) { - this.formBuilderService.insertFormGroupControl(1, this.formGroup, this.formModel, this.groupCommunity); - this.formGroup.patchValue({ - groupName: activeGroup.name, - groupCommunity: linkedObject?.name ?? '', - groupDescription: activeGroup.firstMetadataValue('dc.description'), - }); - } else { - this.formModel = [ - this.groupName, - this.groupDescription, - ]; - this.formGroup.patchValue({ - groupName: activeGroup.name, - groupDescription: activeGroup.firstMetadataValue('dc.description'), - }); - } - setTimeout(() => { - if (!canEdit || activeGroup.permanent) { - this.formGroup.disable(); - } - }, 200); - } - }) - ); - }); } /** @@ -273,25 +264,22 @@ export class GroupFormComponent implements OnInit, OnDestroy { * Emit the updated/created eperson using the EventEmitter submitForm */ onSubmit() { - this.groupDataService.getActiveGroup().pipe(take(1)).subscribe( - (group: Group) => { - const values = { + this.activeGroup$.pipe(take(1)).subscribe((group: Group) => { + if (group === null) { + this.createNewGroup({ name: this.groupName.value, metadata: { 'dc.description': [ { - value: this.groupDescription.value - } - ] + value: this.groupDescription.value, + }, + ], }, - }; - if (group === null) { - this.createNewGroup(values); - } else { - this.editGroup(group); - } + }); + } else { + this.editGroup(group); } - ); + }); } /** @@ -397,7 +385,7 @@ export class GroupFormComponent implements OnInit, OnDestroy { * @param groupSelfLink SelfLink of group to set as active */ setActiveGroupWithLink(groupSelfLink: string) { - this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => { + this.activeGroup$.pipe(take(1)).subscribe((activeGroup: Group) => { if (activeGroup === null) { this.groupDataService.cancelEditGroup(); this.groupDataService.findByHref(groupSelfLink, false, false, followLink('subgroups'), followLink('epersons'), followLink('object')) @@ -416,7 +404,7 @@ export class GroupFormComponent implements OnInit, OnDestroy { * It'll either show a success or error message depending on whether the delete was successful or not. */ delete() { - this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((group: Group) => { + this.activeGroup$.pipe(take(1)).subscribe((group: Group) => { const modalRef = this.modalService.open(ConfirmationModalComponent); modalRef.componentInstance.dso = group; modalRef.componentInstance.headerLabel = this.messagePrefix + '.delete-group.modal.header'; @@ -460,52 +448,37 @@ export class GroupFormComponent implements OnInit, OnDestroy { } /** - * Check if group has a linked object (community or collection linked to a workflow group) - * @param group + * Get the active {@link Group}'s linked object if it has one ({@link Community} or {@link Collection} linked to a + * workflow group) */ - hasLinkedDSO(group: Group): Observable { - if (hasValue(group) && hasValue(group._links.object.href)) { - return this.getLinkedDSO(group).pipe( - map((rd: RemoteData) => { - return hasValue(rd) && hasValue(rd.payload); - }), - catchError(() => observableOf(false)), - ); - } + getActiveGroupLinkedDSO(): Observable { + return this.activeGroup$.pipe( + hasValueOperator(), + switchMap((group: Group) => { + if (group.object === undefined) { + return this.dSpaceObjectDataService.findByHref(group._links.object.href); + } + return group.object; + }), + getAllCompletedRemoteData(), + getRemoteDataPayload(), + ); } /** - * Get group's linked object if it has one (community or collection linked to a workflow group) - * @param group + * Get the route to the edit roles tab of the active {@link Group}'s linked object (community or collection linked + * to a workflow group) if it has one */ - getLinkedDSO(group: Group): Observable> { - if (hasValue(group) && hasValue(group._links.object.href)) { - if (group.object === undefined) { - return this.dSpaceObjectDataService.findByHref(group._links.object.href); - } - return group.object; - } - } - - /** - * Get the route to the edit roles tab of the group's linked object (community or collection linked to a workflow group) if it has one - * @param group - */ - getLinkedEditRolesRoute(group: Group): Observable { - if (hasValue(group) && hasValue(group._links.object.href)) { - return this.getLinkedDSO(group).pipe( - map((rd: RemoteData) => { - if (hasValue(rd) && hasValue(rd.payload)) { - const dso = rd.payload; - switch ((dso as any).type) { - case Community.type.value: - return getCommunityEditRolesRoute(rd.payload.id); - case Collection.type.value: - return getCollectionEditRolesRoute(rd.payload.id); - } - } - }) - ); - } + getLinkedEditRolesRoute(): Observable { + return this.activeGroupLinkedDSO$.pipe( + map((dso: DSpaceObject) => { + switch ((dso as any).type) { + case Community.type.value: + return getCommunityEditRolesRoute(dso.id); + case Collection.type.value: + return getCollectionEditRolesRoute(dso.id); + } + }) + ); } } From b55686e1870cfb5083832239b5b2dbe18160c419 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 3 Sep 2024 22:32:32 +0200 Subject: [PATCH 481/875] 117287: Removed method calls returning observables from the pagination component --- .../pagination/pagination.component.html | 2 +- .../shared/pagination/pagination.component.ts | 104 +++++++++--------- 2 files changed, 51 insertions(+), 55 deletions(-) diff --git a/src/app/shared/pagination/pagination.component.html b/src/app/shared/pagination/pagination.component.html index 7d90c6a439..d1a820b1da 100644 --- a/src/app/shared/pagination/pagination.component.html +++ b/src/app/shared/pagination/pagination.component.html @@ -3,7 +3,7 @@
    {{ 'pagination.showing.label' | translate }} - {{ 'pagination.showing.detail' | translate:(getShowingDetails(collectionSize)|async)}} + {{ 'pagination.showing.detail' | translate:(showingDetails$ | async)}}
    diff --git a/src/app/shared/pagination/pagination.component.ts b/src/app/shared/pagination/pagination.component.ts index 6da813cbc7..0e39345595 100644 --- a/src/app/shared/pagination/pagination.component.ts +++ b/src/app/shared/pagination/pagination.component.ts @@ -4,27 +4,33 @@ import { Component, EventEmitter, Input, + OnChanges, OnDestroy, OnInit, Output, - ViewEncapsulation + SimpleChanges, + ViewEncapsulation, } from '@angular/core'; -import { Observable, of as observableOf, Subscription } from 'rxjs'; +import { Observable, of as observableOf, Subscription, switchMap } from 'rxjs'; import { HostWindowService } from '../host-window.service'; -import { HostWindowState } from '../search/host-window.reducer'; import { PaginationComponentOptions } from './pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; -import { hasValue } from '../empty.util'; +import { hasValue, hasValueOperator } from '../empty.util'; import { PageInfo } from '../../core/shared/page-info.model'; import { PaginationService } from '../../core/pagination/pagination.service'; -import { map, take } from 'rxjs/operators'; +import { map, take, startWith } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { ListableObject } from '../object-collection/shared/listable-object.model'; import { ViewMode } from '../../core/shared/view-mode.model'; +interface PaginationDetails { + range: string; + total: number; +} + /** * The default pagination controls component. */ @@ -36,7 +42,7 @@ import { ViewMode } from '../../core/shared/view-mode.model'; changeDetection: ChangeDetectionStrategy.Default, encapsulation: ViewEncapsulation.Emulated }) -export class PaginationComponent implements OnDestroy, OnInit { +export class PaginationComponent implements OnChanges, OnDestroy, OnInit { /** * ViewMode that should be passed to {@link ListableObjectComponentLoaderComponent}. */ @@ -143,11 +149,6 @@ export class PaginationComponent implements OnDestroy, OnInit { */ public currentPageState: number = undefined; - /** - * An observable of HostWindowState type - */ - public hostWindow: Observable; - /** * ID for the pagination instance. This ID is used in the routing to retrieve the pagination options. * This ID needs to be unique between different pagination components when more than one will be displayed on the same page. @@ -186,6 +187,9 @@ export class PaginationComponent implements OnDestroy, OnInit { public sortField$; public defaultSortField = 'name'; + + public showingDetails$: Observable; + /** * Array to track all subscriptions and unsubscribe them onDestroy * @type {Array} @@ -214,6 +218,12 @@ export class PaginationComponent implements OnDestroy, OnInit { this.initializeConfig(); } + ngOnChanges(changes: SimpleChanges): void { + if (changes.collectionSize.currentValue !== changes.collectionSize.previousValue) { + this.showingDetails$ = this.getShowingDetails(this.collectionSize); + } + } + /** * Method provided by Angular. Invoked when the instance is destroyed. */ @@ -251,19 +261,11 @@ export class PaginationComponent implements OnDestroy, OnInit { ); } - /** - * @param cdRef - * ChangeDetectorRef is a singleton service provided by Angular. - * @param route - * Route is a singleton service provided by Angular. - * @param router - * Router is a singleton service provided by Angular. - * @param hostWindowService - * the HostWindowService singleton. - */ - constructor(private cdRef: ChangeDetectorRef, - private paginationService: PaginationService, - public hostWindowService: HostWindowService) { + constructor( + protected cdRef: ChangeDetectorRef, + protected paginationService: PaginationService, + public hostWindowService: HostWindowService, + ) { } /** @@ -299,17 +301,6 @@ export class PaginationComponent implements OnDestroy, OnInit { this.emitPaginationChange(); } - /** - * Method to change the route to the given sort field - * - * @param sortField - * The sort field being navigated to. - */ - public doSortFieldChange(field: string) { - this.updateParams({ pageId: this.id, page: 1, sortField: field }); - this.emitPaginationChange(); - } - /** * Method to emit a general pagination change event */ @@ -328,26 +319,31 @@ export class PaginationComponent implements OnDestroy, OnInit { /** * Method to get pagination details of the current viewed page. */ - public getShowingDetails(collectionSize: number): Observable { - let showingDetails = observableOf({ range: null + ' - ' + null, total: null }); - if (collectionSize) { - showingDetails = this.paginationService.getCurrentPagination(this.id, this.paginationOptions).pipe( - map((currentPaginationOptions) => { - let firstItem; - let lastItem; - const pageMax = currentPaginationOptions.pageSize * currentPaginationOptions.currentPage; + public getShowingDetails(collectionSize: number): Observable { + return observableOf(collectionSize).pipe( + hasValueOperator(), + switchMap(() => this.paginationService.getCurrentPagination(this.id, this.paginationOptions)), + map((currentPaginationOptions) => { + let firstItem: number; + let lastItem: number; + const pageMax = currentPaginationOptions.pageSize * currentPaginationOptions.currentPage; - firstItem = currentPaginationOptions.pageSize * (currentPaginationOptions.currentPage - 1) + 1; - if (collectionSize > pageMax) { - lastItem = pageMax; - } else { - lastItem = collectionSize; - } - return {range: firstItem + ' - ' + lastItem, total: collectionSize}; - }) - ); - } - return showingDetails; + firstItem = currentPaginationOptions.pageSize * (currentPaginationOptions.currentPage - 1) + 1; + if (collectionSize > pageMax) { + lastItem = pageMax; + } else { + lastItem = collectionSize; + } + return { + range: `${firstItem} - ${lastItem}`, + total: collectionSize, + }; + }), + startWith({ + range: `${null} - ${null}`, + total: null, + }), + ); } /** From c74c178533d8ffd2c4cca926896b703e10af00b8 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 5 Sep 2024 10:50:13 +0200 Subject: [PATCH 482/875] 117287: Removed method calls returning observables from the metadata registry --- .../metadata-registry.component.html | 4 +- .../metadata-registry.component.spec.ts | 42 ++--- .../metadata-registry.component.ts | 128 +++++++--------- .../metadata-schema-form.component.html | 2 +- .../metadata-schema-form.component.spec.ts | 37 ++--- .../metadata-schema-form.component.ts | 145 +++++++++--------- .../shared/testing/registry.service.stub.ts | 107 +++++++++++++ 7 files changed, 268 insertions(+), 197 deletions(-) create mode 100644 src/app/shared/testing/registry.service.stub.ts diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.html b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.html index 35bffad185..0f9df119c7 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.html +++ b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.html @@ -27,11 +27,11 @@ + [ngClass]="{'table-primary' : (activeMetadataSchema$ | async)?.id === schema.id}"> diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts index 944288a7a5..d69d14aacf 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.spec.ts @@ -1,7 +1,6 @@ import { MetadataRegistryComponent } from './metadata-registry.component'; import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing'; import { of as observableOf } from 'rxjs'; -import { buildPaginatedList } from '../../../core/data/paginated-list.model'; import { TranslateModule } from '@ngx-translate/core'; import { By } from '@angular/platform-browser'; import { CommonModule } from '@angular/common'; @@ -15,18 +14,21 @@ import { HostWindowService } from '../../../shared/host-window.service'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; -import { RestResponse } from '../../../core/cache/response.models'; import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; +import { RegistryServiceStub } from '../../../shared/testing/registry.service.stub'; +import { createPaginatedList } from '../../../shared/testing/utils.test'; describe('MetadataRegistryComponent', () => { let comp: MetadataRegistryComponent; let fixture: ComponentFixture; - let registryService: RegistryService; - let paginationService; - const mockSchemasList = [ + + let paginationService: PaginationServiceStub; + let registryService: RegistryServiceStub; + + const mockSchemasList: MetadataSchema[] = [ { id: 1, _links: { @@ -47,32 +49,18 @@ describe('MetadataRegistryComponent', () => { prefix: 'mock', namespace: 'http://dspace.org/mockschema' } - ]; - const mockSchemas = createSuccessfulRemoteDataObject$(buildPaginatedList(null, mockSchemasList)); - /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ - const registryServiceStub = { - getMetadataSchemas: () => mockSchemas, - getActiveMetadataSchema: () => observableOf(undefined), - getSelectedMetadataSchemas: () => observableOf([]), - editMetadataSchema: (schema) => { - }, - cancelEditMetadataSchema: () => { - }, - deleteMetadataSchema: () => observableOf(new RestResponse(true, 200, 'OK')), - deselectAllMetadataSchema: () => { - }, - clearMetadataSchemaRequests: () => observableOf(undefined) - }; - /* eslint-enable no-empty, @typescript-eslint/no-empty-function */ - - paginationService = new PaginationServiceStub(); + ] as MetadataSchema[]; beforeEach(waitForAsync(() => { + paginationService = new PaginationServiceStub(); + registryService = new RegistryServiceStub(); + spyOn(registryService, 'getMetadataSchemas').and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList(mockSchemasList))); + TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], declarations: [MetadataRegistryComponent, PaginationComponent, EnumKeysPipe], providers: [ - { provide: RegistryService, useValue: registryServiceStub }, + { provide: RegistryService, useValue: registryService }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: PaginationService, useValue: paginationService }, { provide: NotificationsService, useValue: new NotificationsServiceStub() } @@ -123,7 +111,7 @@ describe('MetadataRegistryComponent', () => { })); it('should cancel editing the selected schema when clicked again', waitForAsync(() => { - spyOn(registryService, 'getActiveMetadataSchema').and.returnValue(observableOf(mockSchemasList[0] as MetadataSchema)); + comp.activeMetadataSchema$ = observableOf(mockSchemasList[0] as MetadataSchema); spyOn(registryService, 'cancelEditMetadataSchema'); row.click(); fixture.detectChanges(); @@ -138,7 +126,7 @@ describe('MetadataRegistryComponent', () => { beforeEach(() => { spyOn(registryService, 'deleteMetadataSchema').and.callThrough(); - spyOn(registryService, 'getSelectedMetadataSchemas').and.returnValue(observableOf(selectedSchemas as MetadataSchema[])); + comp.selectedMetadataSchemaIDs$ = observableOf(selectedSchemas.map((selectedSchema: MetadataSchema) => selectedSchema.id)); comp.deleteSchemas(); fixture.detectChanges(); }); diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts index 857034604e..dbeb2b4f52 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts @@ -1,13 +1,11 @@ -import { Component } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { RegistryService } from '../../../core/registry/registry.service'; -import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, zip } from 'rxjs'; +import { BehaviorSubject, Observable, zip, Subscription } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { filter, map, switchMap, take } from 'rxjs/operators'; -import { hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; -import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { toFindListOptions } from '../../../shared/pagination/pagination.utils'; @@ -24,13 +22,23 @@ import { PaginationService } from '../../../core/pagination/pagination.service'; * A component used for managing all existing metadata schemas within the repository. * The admin can create, edit or delete metadata schemas here. */ -export class MetadataRegistryComponent { +export class MetadataRegistryComponent implements OnDestroy, OnInit { /** * A list of all the current metadata schemas within the repository */ metadataSchemas: Observable>>; + /** + * The {@link MetadataSchema}that is being edited + */ + activeMetadataSchema$: Observable; + + /** + * The selected {@link MetadataSchema} IDs + */ + selectedMetadataSchemaIDs$: Observable; + /** * Pagination config used to display the list of metadata schemas */ @@ -40,15 +48,25 @@ export class MetadataRegistryComponent { }); /** - * Whether or not the list of MetadataSchemas needs an update + * Whether the list of MetadataSchemas needs an update */ needsUpdate$: BehaviorSubject = new BehaviorSubject(true); - constructor(private registryService: RegistryService, - private notificationsService: NotificationsService, - private router: Router, - private paginationService: PaginationService, - private translateService: TranslateService) { + subscriptions: Subscription[] = []; + + constructor( + protected registryService: RegistryService, + protected notificationsService: NotificationsService, + protected paginationService: PaginationService, + protected translateService: TranslateService, + ) { + } + + ngOnInit(): void { + this.activeMetadataSchema$ = this.registryService.getActiveMetadataSchema(); + this.selectedMetadataSchemaIDs$ = this.registryService.getSelectedMetadataSchemas().pipe( + map((schemas: MetadataSchema[]) => schemas.map((schema: MetadataSchema) => schema.id)), + ); this.updateSchemas(); } @@ -77,30 +95,13 @@ export class MetadataRegistryComponent { * @param schema */ editSchema(schema: MetadataSchema) { - this.getActiveSchema().pipe(take(1)).subscribe((activeSchema) => { + this.subscriptions.push(this.activeMetadataSchema$.pipe(take(1)).subscribe((activeSchema: MetadataSchema) => { if (schema === activeSchema) { this.registryService.cancelEditMetadataSchema(); } else { this.registryService.editMetadataSchema(schema); } - }); - } - - /** - * Checks whether the given metadata schema is active (being edited) - * @param schema - */ - isActive(schema: MetadataSchema): Observable { - return this.getActiveSchema().pipe( - map((activeSchema) => schema === activeSchema) - ); - } - - /** - * Gets the active metadata schema (being edited) - */ - getActiveSchema(): Observable { - return this.registryService.getActiveMetadataSchema(); + })); } /** @@ -114,42 +115,25 @@ export class MetadataRegistryComponent { this.registryService.deselectMetadataSchema(schema); } - /** - * Checks whether a given metadata schema is selected in the list (checkbox) - * @param schema - */ - isSelected(schema: MetadataSchema): Observable { - return this.registryService.getSelectedMetadataSchemas().pipe( - map((schemas) => schemas.find((selectedSchema) => selectedSchema === schema) != null) - ); - } - /** * Delete all the selected metadata schemas */ deleteSchemas() { - this.registryService.getSelectedMetadataSchemas().pipe(take(1)).subscribe( - (schemas) => { - const tasks$ = []; - for (const schema of schemas) { - if (hasValue(schema.id)) { - tasks$.push(this.registryService.deleteMetadataSchema(schema.id).pipe(getFirstCompletedRemoteData())); - } - } - zip(...tasks$).subscribe((responses: RemoteData[]) => { - const successResponses = responses.filter((response: RemoteData) => response.hasSucceeded); - const failedResponses = responses.filter((response: RemoteData) => response.hasFailed); - if (successResponses.length > 0) { - this.showNotification(true, successResponses.length); - } - if (failedResponses.length > 0) { - this.showNotification(false, failedResponses.length); - } - this.registryService.deselectAllMetadataSchema(); - this.registryService.cancelEditMetadataSchema(); - }); + this.subscriptions.push(this.selectedMetadataSchemaIDs$.pipe( + take(1), + switchMap((schemaIDs: number[]) => zip(schemaIDs.map((schemaID: number) => this.registryService.deleteMetadataSchema(schemaID).pipe(getFirstCompletedRemoteData())))), + ).subscribe((responses: RemoteData[]) => { + const successResponses: RemoteData[] = responses.filter((response: RemoteData) => response.hasSucceeded); + const failedResponses: RemoteData[] = responses.filter((response: RemoteData) => response.hasFailed); + if (successResponses.length > 0) { + this.showNotification(true, successResponses.length); } - ); + if (failedResponses.length > 0) { + this.showNotification(false, failedResponses.length); + } + this.registryService.deselectAllMetadataSchema(); + this.registryService.cancelEditMetadataSchema(); + })); } /** @@ -160,20 +144,20 @@ export class MetadataRegistryComponent { showNotification(success: boolean, amount: number) { const prefix = 'admin.registries.schema.notification'; const suffix = success ? 'success' : 'failure'; - const messages = observableCombineLatest( - this.translateService.get(success ? `${prefix}.${suffix}` : `${prefix}.${suffix}`), - this.translateService.get(`${prefix}.deleted.${suffix}`, {amount: amount}) - ); - messages.subscribe(([head, content]) => { - if (success) { - this.notificationsService.success(head, content); - } else { - this.notificationsService.error(head, content); - } - }); + + const head: string = this.translateService.instant(success ? `${prefix}.${suffix}` : `${prefix}.${suffix}`); + const content: string = this.translateService.instant(`${prefix}.deleted.${suffix}`, {amount: amount}); + + if (success) { + this.notificationsService.success(head, content); + } else { + this.notificationsService.error(head, content); + } } + ngOnDestroy(): void { this.paginationService.clearPagination(this.config.id); + this.subscriptions.map((subscription: Subscription) => subscription.unsubscribe()); } } diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.html b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.html index a4a4613565..521fb06a6e 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.html +++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.html @@ -1,4 +1,4 @@ -
    +

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

    diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts index b758767ddb..442a06dc67 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing'; import { MetadataSchemaFormComponent } from './metadata-schema-form.component'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { TranslateModule } from '@ngx-translate/core'; @@ -10,41 +10,26 @@ import { RegistryService } from '../../../../core/registry/registry.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; import { of as observableOf } from 'rxjs'; import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model'; +import { RegistryServiceStub } from '../../../../shared/testing/registry.service.stub'; +import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock'; describe('MetadataSchemaFormComponent', () => { let component: MetadataSchemaFormComponent; let fixture: ComponentFixture; - let registryService: RegistryService; - /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ - const registryServiceStub = { - getActiveMetadataSchema: () => observableOf(undefined), - createOrUpdateMetadataSchema: (schema: MetadataSchema) => observableOf(schema), - cancelEditMetadataSchema: () => { - }, - clearMetadataSchemaRequests: () => observableOf(undefined) - }; - const formBuilderServiceStub = { - createFormGroup: () => { - return { - patchValue: () => { - }, - reset(_value?: any, _options?: { onlySelf?: boolean; emitEvent?: boolean; }): void { - }, - }; - } - }; - /* eslint-enable no-empty, @typescript-eslint/no-empty-function */ + let registryService: RegistryServiceStub; beforeEach(waitForAsync(() => { + registryService = new RegistryServiceStub(); + return TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], declarations: [MetadataSchemaFormComponent, EnumKeysPipe], providers: [ - { provide: RegistryService, useValue: registryServiceStub }, - { provide: FormBuilderService, useValue: formBuilderServiceStub } + { provide: RegistryService, useValue: registryService }, + { provide: FormBuilderService, useValue: getMockFormBuilderService() } ], - schemas: [NO_ERRORS_SCHEMA] + schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents(); })); @@ -75,7 +60,7 @@ describe('MetadataSchemaFormComponent', () => { describe('without an active schema', () => { beforeEach(() => { - spyOn(registryService, 'getActiveMetadataSchema').and.returnValue(observableOf(undefined)); + component.activeMetadataSchema$ = observableOf(undefined); component.onSubmit(); fixture.detectChanges(); }); @@ -94,7 +79,7 @@ describe('MetadataSchemaFormComponent', () => { } as MetadataSchema); beforeEach(() => { - spyOn(registryService, 'getActiveMetadataSchema').and.returnValue(observableOf(expectedWithId)); + component.activeMetadataSchema$ = observableOf(expectedWithId); component.onSubmit(); fixture.detectChanges(); }); diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts index 1992289ff7..78680f6d9a 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts @@ -8,10 +8,10 @@ import { import { UntypedFormGroup } from '@angular/forms'; import { RegistryService } from '../../../../core/registry/registry.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; -import { take } from 'rxjs/operators'; +import { take, switchMap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; -import { combineLatest } from 'rxjs'; import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model'; +import { Subscription, Observable } from 'rxjs'; @Component({ selector: 'ds-metadata-schema-form', @@ -73,64 +73,71 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { */ @Output() submitForm: EventEmitter = new EventEmitter(); - constructor(public registryService: RegistryService, private formBuilderService: FormBuilderService, private translateService: TranslateService) { + /** + * The {@link MetadataSchema} that is currently being edited + */ + activeMetadataSchema$: Observable; + + subscriptions: Subscription[] = []; + + constructor( + protected registryService: RegistryService, + protected formBuilderService: FormBuilderService, + protected translateService: TranslateService, + ) { } ngOnInit() { - combineLatest([ - this.translateService.get(`${this.messagePrefix}.name`), - this.translateService.get(`${this.messagePrefix}.namespace`) - ]).subscribe(([name, namespace]) => { - this.name = new DynamicInputModel({ - id: 'name', - label: name, - name: 'name', - validators: { - required: null, - pattern: '^[^. ,]*$', - maxLength: 32, - }, - required: true, - errorMessages: { - pattern: 'error.validation.metadata.name.invalid-pattern', - maxLength: 'error.validation.metadata.name.max-length', - }, - }); - this.namespace = new DynamicInputModel({ - id: 'namespace', - label: namespace, - name: 'namespace', - validators: { - required: null, - maxLength: 256, - }, - required: true, - errorMessages: { - maxLength: 'error.validation.metadata.namespace.max-length', - }, - }); - this.formModel = [ - new DynamicFormGroupModel( - { - id: 'metadatadataschemagroup', - group:[this.namespace, this.name] - }) - ]; - this.formGroup = this.formBuilderService.createFormGroup(this.formModel); - this.registryService.getActiveMetadataSchema().subscribe((schema: MetadataSchema) => { - if (schema == null) { - this.clearFields(); - } else { - this.formGroup.patchValue({ - metadatadataschemagroup: { - name: schema.prefix, - namespace: schema.namespace, - }, - }); - this.name.disabled = true; - } + this.name = new DynamicInputModel({ + id: 'name', + label: this.translateService.instant(`${this.messagePrefix}.name`), + name: 'name', + validators: { + required: null, + pattern: '^[^. ,]*$', + maxLength: 32, + }, + required: true, + errorMessages: { + pattern: 'error.validation.metadata.name.invalid-pattern', + maxLength: 'error.validation.metadata.name.max-length', + }, }); - }); + this.namespace = new DynamicInputModel({ + id: 'namespace', + label: this.translateService.instant(`${this.messagePrefix}.namespace`), + name: 'namespace', + validators: { + required: null, + maxLength: 256, + }, + required: true, + errorMessages: { + maxLength: 'error.validation.metadata.namespace.max-length', + }, + }); + this.formModel = [ + new DynamicFormGroupModel( + { + id: 'metadatadataschemagroup', + group:[this.namespace, this.name] + }) + ]; + this.formGroup = this.formBuilderService.createFormGroup(this.formModel); + this.activeMetadataSchema$ = this.registryService.getActiveMetadataSchema(); + this.subscriptions.push(this.activeMetadataSchema$.subscribe((schema: MetadataSchema) => { + if (schema == null) { + this.clearFields(); + } else { + this.formGroup.patchValue({ + metadatadataschemagroup: { + name: schema.prefix, + namespace: schema.namespace, + }, + }); + this.name.disabled = true; + } + })); } /** @@ -148,29 +155,28 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { */ onSubmit(): void { this.registryService.clearMetadataSchemaRequests().subscribe(); - this.registryService.getActiveMetadataSchema().pipe(take(1)).subscribe( - (schema: MetadataSchema) => { + this.subscriptions.push(this.activeMetadataSchema$.pipe( + take(1), + switchMap((schema: MetadataSchema) => { const values = { prefix: this.name.value, namespace: this.namespace.value }; if (schema == null) { - this.registryService.createOrUpdateMetadataSchema(Object.assign(new MetadataSchema(), values)).subscribe((newSchema) => { - this.submitForm.emit(newSchema); - }); + return this.registryService.createOrUpdateMetadataSchema(Object.assign(new MetadataSchema(), values)); } else { - this.registryService.createOrUpdateMetadataSchema(Object.assign(new MetadataSchema(), schema, { + return this.registryService.createOrUpdateMetadataSchema(Object.assign(new MetadataSchema(), schema, { id: schema.id, prefix: schema.prefix, namespace: values.namespace, - })).subscribe((updatedSchema: MetadataSchema) => { - this.submitForm.emit(updatedSchema); - }); + })); } - this.clearFields(); - this.registryService.cancelEditMetadataSchema(); - } - ); + }), + ).subscribe((schema: MetadataSchema) => { + this.clearFields(); + this.registryService.cancelEditMetadataSchema(); + this.submitForm.emit(schema); + })); } /** @@ -186,5 +192,6 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy { */ ngOnDestroy(): void { this.onCancel(); + this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe()); } } diff --git a/src/app/shared/testing/registry.service.stub.ts b/src/app/shared/testing/registry.service.stub.ts new file mode 100644 index 0000000000..de5e4f933f --- /dev/null +++ b/src/app/shared/testing/registry.service.stub.ts @@ -0,0 +1,107 @@ +/* eslint-disable no-empty,@typescript-eslint/no-empty-function */ +import { FindListOptions } from '../../core/data/find-list-options.model'; +import { FollowLinkConfig } from '../utils/follow-link-config.model'; +import { MetadataSchema } from '../../core/metadata/metadata-schema.model'; +import { Observable, of as observableOf } from 'rxjs'; +import { RemoteData } from '../../core/data/remote-data'; +import { PaginatedList } from '../../core/data/paginated-list.model'; +import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; +import { createPaginatedList } from './utils.test'; +import { MetadataField } from '../../core/metadata/metadata-field.model'; +import { NoContent } from '../../core/shared/NoContent.model'; + +/** + * Stub class of {@link RegistryService} + */ +export class RegistryServiceStub { + + getMetadataSchemas(_options: FindListOptions = {}, _useCachedVersionIfAvailable = true, _reRequestOnStale = true, ..._linksToFollow: FollowLinkConfig[]): Observable>> { + return createSuccessfulRemoteDataObject$(createPaginatedList()); + } + + getMetadataSchemaByPrefix(_prefix: string, _useCachedVersionIfAvailable = true, _reRequestOnStale = true, ..._linksToFollow: FollowLinkConfig[]): Observable> { + return createSuccessfulRemoteDataObject$(undefined); + } + + getMetadataFieldsBySchema(_schema: MetadataSchema, _options: FindListOptions = {}, _useCachedVersionIfAvailable = true, _reRequestOnStale = true, ..._linksToFollow: FollowLinkConfig[]): Observable>> { + return createSuccessfulRemoteDataObject$(createPaginatedList()); + } + + editMetadataSchema(_schema: MetadataSchema): void { + } + + cancelEditMetadataSchema(): void { + } + + getActiveMetadataSchema(): Observable { + return observableOf(undefined); + } + + selectMetadataSchema(_schema: MetadataSchema): void { + } + + deselectMetadataSchema(_schema: MetadataSchema): void { + } + + deselectAllMetadataSchema(): void { + } + + getSelectedMetadataSchemas(): Observable { + return observableOf([]); + } + + editMetadataField(_field: MetadataField): void { + } + + cancelEditMetadataField(): void { + } + + getActiveMetadataField(): Observable { + return observableOf(undefined); + } + + selectMetadataField(_field: MetadataField): void { + } + + deselectMetadataField(_field: MetadataField): void { + } + + deselectAllMetadataField(): void { + } + + getSelectedMetadataFields(): Observable { + return observableOf([]); + } + + createOrUpdateMetadataSchema(schema: MetadataSchema): Observable { + return observableOf(schema); + } + + deleteMetadataSchema(_id: number): Observable> { + return createSuccessfulRemoteDataObject$(undefined); + } + + clearMetadataSchemaRequests(): Observable { + return observableOf(''); + } + + createMetadataField(_field: MetadataField, _schema: MetadataSchema): Observable { + return observableOf(undefined); + } + + updateMetadataField(_field: MetadataField): Observable { + return observableOf(undefined); + } + + deleteMetadataField(_id: number): Observable> { + return createSuccessfulRemoteDataObject$(undefined); + } + + clearMetadataFieldRequests(): void { + } + + queryMetadataFields(_query: string, _options: FindListOptions = {}, _useCachedVersionIfAvailable = true, _reRequestOnStale = true, ..._linksToFollow: FollowLinkConfig[]): Observable>> { + return createSuccessfulRemoteDataObject$(createPaginatedList()); + } + +} From f03ed89687e12fa849e73b336e95fb2ab2996033 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 5 Sep 2024 11:49:18 +0200 Subject: [PATCH 483/875] 117287: Removed method calls returning observables from the metadata schema registry --- .../metadata-field-form.component.spec.ts | 38 ++--- .../metadata-schema.component.html | 4 +- .../metadata-schema.component.spec.ts | 65 ++++---- .../metadata-schema.component.ts | 139 +++++++----------- .../shared/testing/registry.service.stub.ts | 8 +- 5 files changed, 95 insertions(+), 159 deletions(-) diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts index ad7b54945d..7ae672f9e1 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts @@ -9,14 +9,17 @@ import { TranslateModule } from '@ngx-translate/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { EnumKeysPipe } from '../../../../shared/utils/enum-keys-pipe'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { MetadataField } from '../../../../core/metadata/metadata-field.model'; import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model'; +import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock'; +import { RegistryServiceStub } from '../../../../shared/testing/registry.service.stub'; describe('MetadataFieldFormComponent', () => { let component: MetadataFieldFormComponent; let fixture: ComponentFixture; - let registryService: RegistryService; + + let registryService: RegistryServiceStub; const metadataSchema = Object.assign(new MetadataSchema(), { id: 1, @@ -24,38 +27,17 @@ describe('MetadataFieldFormComponent', () => { prefix: 'fake' }); - /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ - const registryServiceStub = { - getActiveMetadataField: () => observableOf(undefined), - createMetadataField: (field: MetadataField) => observableOf(field), - updateMetadataField: (field: MetadataField) => observableOf(field), - cancelEditMetadataField: () => { - }, - cancelEditMetadataSchema: () => { - }, - clearMetadataFieldRequests: () => observableOf(undefined) - }; - const formBuilderServiceStub = { - createFormGroup: () => { - return { - patchValue: () => { - }, - reset(_value?: any, _options?: { onlySelf?: boolean; emitEvent?: boolean; }): void { - }, - }; - } - }; - /* eslint-enable no-empty, @typescript-eslint/no-empty-function */ - beforeEach(waitForAsync(() => { + registryService = new RegistryServiceStub(); + return TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], declarations: [MetadataFieldFormComponent, EnumKeysPipe], providers: [ - { provide: RegistryService, useValue: registryServiceStub }, - { provide: FormBuilderService, useValue: formBuilderServiceStub } + { provide: RegistryService, useValue: registryService }, + { provide: FormBuilderService, useValue: getMockFormBuilderService() } ], - schemas: [NO_ERRORS_SCHEMA] + schemas: [CUSTOM_ELEMENTS_SCHEMA] }).compileComponents(); })); diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.html b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.html index 557741df80..6d895d187b 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.html +++ b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.html @@ -32,11 +32,11 @@ + [ngClass]="{'table-primary' : (activeField$ | async)?.id === field.id}"> diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts index 2b660a6363..ce4987a02c 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts @@ -4,7 +4,7 @@ import { of as observableOf } from 'rxjs'; import { buildPaginatedList } from '../../../core/data/paginated-list.model'; import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { By } from '@angular/platform-browser'; import { RegistryService } from '../../../core/registry/registry.service'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; @@ -12,25 +12,28 @@ import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe'; import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub'; import { HostWindowService } from '../../../shared/host-window.service'; -import { RouterStub } from '../../../shared/testing/router.stub'; import { RouterTestingModule } from '@angular/router/testing'; import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; -import { RestResponse } from '../../../core/cache/response.models'; import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { MetadataField } from '../../../core/metadata/metadata-field.model'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { VarDirective } from '../../../shared/utils/var.directive'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; +import { RegistryServiceStub } from '../../../shared/testing/registry.service.stub'; describe('MetadataSchemaComponent', () => { let comp: MetadataSchemaComponent; let fixture: ComponentFixture; - let registryService: RegistryService; - const mockSchemasList = [ + + let registryService: RegistryServiceStub; + let activatedRoute: ActivatedRouteStub; + let paginationService: PaginationServiceStub; + + const mockSchemasList: MetadataSchema[] = [ { id: 1, _links: { @@ -51,8 +54,8 @@ describe('MetadataSchemaComponent', () => { prefix: 'mock', namespace: 'http://dspace.org/mockschema' } - ]; - const mockFieldsList = [ + ] as MetadataSchema[]; + const mockFieldsList: MetadataField[] = [ { id: 1, _links: { @@ -101,47 +104,29 @@ describe('MetadataSchemaComponent', () => { scopeNote: null, schema: createSuccessfulRemoteDataObject$(mockSchemasList[1]) } - ]; - const mockSchemas = createSuccessfulRemoteDataObject$(buildPaginatedList(null, mockSchemasList)); - /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ - const registryServiceStub = { - getMetadataSchemas: () => mockSchemas, - getMetadataFieldsBySchema: (schema: MetadataSchema) => createSuccessfulRemoteDataObject$(buildPaginatedList(null, mockFieldsList.filter((value) => value.id === 3 || value.id === 4))), - getMetadataSchemaByPrefix: (schemaName: string) => createSuccessfulRemoteDataObject$(mockSchemasList.filter((value) => value.prefix === schemaName)[0]), - getActiveMetadataField: () => observableOf(undefined), - getSelectedMetadataFields: () => observableOf([]), - editMetadataField: (schema) => { - }, - cancelEditMetadataField: () => { - }, - deleteMetadataField: () => observableOf(new RestResponse(true, 200, 'OK')), - deselectAllMetadataField: () => { - }, - clearMetadataFieldRequests: () => observableOf(undefined) - }; - /* eslint-enable no-empty, @typescript-eslint/no-empty-function */ + ] as MetadataField[]; const schemaNameParam = 'mock'; - const activatedRouteStub = Object.assign(new ActivatedRouteStub(), { - params: observableOf({ - schemaName: schemaNameParam - }) - }); - - const paginationService = new PaginationServiceStub(); beforeEach(waitForAsync(() => { + activatedRoute = new ActivatedRouteStub({ + schemaName: schemaNameParam, + }); + paginationService = new PaginationServiceStub(); + registryService = new RegistryServiceStub(); + spyOn(registryService, 'getMetadataFieldsBySchema').and.returnValue(createSuccessfulRemoteDataObject$(buildPaginatedList(null, mockFieldsList.filter((value) => value.id === 3 || value.id === 4)))); + spyOn(registryService, 'getMetadataSchemaByPrefix').and.callFake((schemaName) => createSuccessfulRemoteDataObject$(mockSchemasList.filter((value) => value.prefix === schemaName)[0])); + TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], declarations: [MetadataSchemaComponent, PaginationComponent, EnumKeysPipe, VarDirective], providers: [ - { provide: RegistryService, useValue: registryServiceStub }, - { provide: ActivatedRoute, useValue: activatedRouteStub }, + { provide: RegistryService, useValue: registryService }, + { provide: ActivatedRoute, useValue: activatedRoute }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, - { provide: Router, useValue: new RouterStub() }, { provide: PaginationService, useValue: paginationService }, { provide: NotificationsService, useValue: new NotificationsServiceStub() } ], - schemas: [NO_ERRORS_SCHEMA] + schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents(); })); @@ -190,7 +175,7 @@ describe('MetadataSchemaComponent', () => { })); it('should cancel editing the selected field when clicked again', waitForAsync(() => { - spyOn(registryService, 'getActiveMetadataField').and.returnValue(observableOf(mockFieldsList[2] as MetadataField)); + comp.activeField$ = observableOf(mockFieldsList[2] as MetadataField); spyOn(registryService, 'cancelEditMetadataField'); row.click(); fixture.detectChanges(); @@ -205,7 +190,7 @@ describe('MetadataSchemaComponent', () => { beforeEach(() => { spyOn(registryService, 'deleteMetadataField').and.callThrough(); - spyOn(registryService, 'getSelectedMetadataFields').and.returnValue(observableOf(selectedFields as MetadataField[])); + comp.selectedMetadataFieldIDs$ = observableOf(selectedFields.map((metadataField: MetadataField) => metadataField.id)); comp.deleteFields(); fixture.detectChanges(); }); diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts index d0827e6e4d..1a5fd15ccd 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts @@ -1,19 +1,18 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { RegistryService } from '../../../core/registry/registry.service'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { BehaviorSubject, - combineLatest as observableCombineLatest, combineLatest, Observable, of as observableOf, - zip + zip, + Subscription, } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { map, switchMap, take } from 'rxjs/operators'; -import { hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { MetadataField } from '../../../core/metadata/metadata-field.model'; @@ -32,7 +31,7 @@ import { PaginationService } from '../../../core/pagination/pagination.service'; * A component used for managing all existing metadata fields within the current metadata schema. * The admin can create, edit or delete metadata fields here. */ -export class MetadataSchemaComponent implements OnInit { +export class MetadataSchemaComponent implements OnDestroy, OnInit { /** * The metadata schema */ @@ -57,27 +56,33 @@ export class MetadataSchemaComponent implements OnInit { */ needsUpdate$: BehaviorSubject = new BehaviorSubject(true); - constructor(private registryService: RegistryService, - private route: ActivatedRoute, - private notificationsService: NotificationsService, - private router: Router, - private paginationService: PaginationService, - private translateService: TranslateService) { + /** + * The current {@link MetadataField} that is being edited + */ + activeField$: Observable; + /** + * The selected {@link MetadataField} IDs + */ + selectedMetadataFieldIDs$: Observable; + + subscriptions: Subscription[] = []; + + constructor( + protected registryService: RegistryService, + protected route: ActivatedRoute, + protected notificationsService: NotificationsService, + protected paginationService: PaginationService, + protected translateService: TranslateService, + ) { } ngOnInit(): void { - this.route.params.subscribe((params) => { - this.initialize(params); - }); - } - - /** - * Initialize the component using the params within the url (schemaName) - * @param params - */ - initialize(params) { - this.metadataSchema$ = this.registryService.getMetadataSchemaByPrefix(params.schemaName).pipe(getFirstSucceededRemoteDataPayload()); + this.metadataSchema$ = this.registryService.getMetadataSchemaByPrefix(this.route.snapshot.params.schemaName).pipe(getFirstSucceededRemoteDataPayload()); + this.activeField$ = this.registryService.getActiveMetadataField(); + this.selectedMetadataFieldIDs$ = this.registryService.getSelectedMetadataFields().pipe( + map((metadataFields: MetadataField[]) => metadataFields.map((metadataField: MetadataField) => metadataField.id)), + ); this.updateFields(); } @@ -86,7 +91,7 @@ export class MetadataSchemaComponent implements OnInit { */ private updateFields() { this.metadataFields$ = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( - switchMap((currentPagination) => combineLatest(this.metadataSchema$, this.needsUpdate$, observableOf(currentPagination))), + switchMap((currentPagination) => combineLatest([this.metadataSchema$, this.needsUpdate$, observableOf(currentPagination)])), switchMap(([schema, update, currentPagination]: [MetadataSchema, boolean, PaginationComponentOptions]) => { if (update) { this.needsUpdate$.next(false); @@ -110,30 +115,13 @@ export class MetadataSchemaComponent implements OnInit { * @param field */ editField(field: MetadataField) { - this.getActiveField().pipe(take(1)).subscribe((activeField) => { + this.subscriptions.push(this.activeField$.pipe(take(1)).subscribe((activeField) => { if (field === activeField) { this.registryService.cancelEditMetadataField(); } else { this.registryService.editMetadataField(field); } - }); - } - - /** - * Checks whether the given metadata field is active (being edited) - * @param field - */ - isActive(field: MetadataField): Observable { - return this.getActiveField().pipe( - map((activeField) => field === activeField) - ); - } - - /** - * Gets the active metadata field (being edited) - */ - getActiveField(): Observable { - return this.registryService.getActiveMetadataField(); + })); } /** @@ -147,42 +135,25 @@ export class MetadataSchemaComponent implements OnInit { this.registryService.deselectMetadataField(field); } - /** - * Checks whether a given metadata field is selected in the list (checkbox) - * @param field - */ - isSelected(field: MetadataField): Observable { - return this.registryService.getSelectedMetadataFields().pipe( - map((fields) => fields.find((selectedField) => selectedField === field) != null) - ); - } - /** * Delete all the selected metadata fields */ deleteFields() { - this.registryService.getSelectedMetadataFields().pipe(take(1)).subscribe( - (fields) => { - const tasks$ = []; - for (const field of fields) { - if (hasValue(field.id)) { - tasks$.push(this.registryService.deleteMetadataField(field.id).pipe(getFirstCompletedRemoteData())); - } - } - zip(...tasks$).subscribe((responses: RemoteData[]) => { - const successResponses = responses.filter((response: RemoteData) => response.hasSucceeded); - const failedResponses = responses.filter((response: RemoteData) => response.hasFailed); - if (successResponses.length > 0) { - this.showNotification(true, successResponses.length); - } - if (failedResponses.length > 0) { - this.showNotification(false, failedResponses.length); - } - this.registryService.deselectAllMetadataField(); - this.registryService.cancelEditMetadataField(); - }); + this.subscriptions.push(this.selectedMetadataFieldIDs$.pipe( + take(1), + switchMap((fieldIDs) => zip(fieldIDs.map((fieldID) => this.registryService.deleteMetadataField(fieldID).pipe(getFirstCompletedRemoteData())))), + ).subscribe((responses: RemoteData[]) => { + const successResponses = responses.filter((response: RemoteData) => response.hasSucceeded); + const failedResponses = responses.filter((response: RemoteData) => response.hasFailed); + if (successResponses.length > 0) { + this.showNotification(true, successResponses.length); } - ); + if (failedResponses.length > 0) { + this.showNotification(false, failedResponses.length); + } + this.registryService.deselectAllMetadataField(); + this.registryService.cancelEditMetadataField(); + })); } /** @@ -193,20 +164,18 @@ export class MetadataSchemaComponent implements OnInit { showNotification(success: boolean, amount: number) { const prefix = 'admin.registries.schema.notification'; const suffix = success ? 'success' : 'failure'; - const messages = observableCombineLatest( - this.translateService.get(success ? `${prefix}.${suffix}` : `${prefix}.${suffix}`), - this.translateService.get(`${prefix}.field.deleted.${suffix}`, { amount: amount }) - ); - messages.subscribe(([head, content]) => { - if (success) { - this.notificationsService.success(head, content); - } else { - this.notificationsService.error(head, content); - } - }); + const head = this.translateService.instant(success ? `${prefix}.${suffix}` : `${prefix}.${suffix}`); + const content = this.translateService.instant(`${prefix}.field.deleted.${suffix}`, { amount: amount }); + if (success) { + this.notificationsService.success(head, content); + } else { + this.notificationsService.error(head, content); + } } + ngOnDestroy(): void { this.paginationService.clearPagination(this.config.id); + this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe()); } } diff --git a/src/app/shared/testing/registry.service.stub.ts b/src/app/shared/testing/registry.service.stub.ts index de5e4f933f..b52f32af4e 100644 --- a/src/app/shared/testing/registry.service.stub.ts +++ b/src/app/shared/testing/registry.service.stub.ts @@ -85,12 +85,12 @@ export class RegistryServiceStub { return observableOf(''); } - createMetadataField(_field: MetadataField, _schema: MetadataSchema): Observable { - return observableOf(undefined); + createMetadataField(field: MetadataField, _schema: MetadataSchema): Observable { + return observableOf(field); } - updateMetadataField(_field: MetadataField): Observable { - return observableOf(undefined); + updateMetadataField(field: MetadataField): Observable { + return observableOf(field); } deleteMetadataField(_id: number): Observable> { From 98d9f639f7b8a99094cbf3a9a4b2dd25eaf7b96c Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Mon, 6 May 2024 16:19:03 -0700 Subject: [PATCH 484/875] Updated browser init to update cache after external auth. --- src/modules/app/browser-init.service.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/modules/app/browser-init.service.ts b/src/modules/app/browser-init.service.ts index bf40f0c68b..3704892e6e 100644 --- a/src/modules/app/browser-init.service.ts +++ b/src/modules/app/browser-init.service.ts @@ -30,9 +30,11 @@ import { filter, find, map } from 'rxjs/operators'; import { isNotEmpty } from '../../app/shared/empty.util'; import { logStartupMessage } from '../../../startup-message'; import { MenuService } from '../../app/shared/menu/menu.service'; +import { RequestService } from '../../app/core/data/request.service'; import { RootDataService } from '../../app/core/data/root-data.service'; import { firstValueFrom, Subscription } from 'rxjs'; import { ServerCheckGuard } from '../../app/core/server-check/server-check.guard'; +import { HALEndpointService } from '../../app/core/shared/hal-endpoint.service'; /** * Performs client-side initialization. @@ -59,6 +61,8 @@ export class BrowserInitService extends InitService { protected menuService: MenuService, private rootDataService: RootDataService, protected serverCheckGuard: ServerCheckGuard, + private requestService: RequestService, + private halService: HALEndpointService, ) { super( store, @@ -145,17 +149,15 @@ export class BrowserInitService extends InitService { } /** - * During an external authentication flow invalidate the SSR transferState + * During an external authentication flow invalidate the * data in the cache. This allows the app to fetch fresh content. * @private */ private externalAuthCheck() { - this.sub = this.authService.isExternalAuthentication().pipe( filter((externalAuth: boolean) => externalAuth) ).subscribe(() => { - // Clear the transferState data. - this.rootDataService.invalidateRootCache(); + this.requestService.setStaleByHrefSubstring(this.halService.getRootHref()); this.authService.setExternalAuthStatus(false); } ); From f7706760cd730006a79093ba65cc13413e4aff95 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 11 Sep 2024 16:30:40 -0500 Subject: [PATCH 485/875] Bump Express to v4.20.0 --- package.json | 2 +- yarn.lock | 178 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 155 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 1d7bb0ee20..86959196d0 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "date-fns-tz": "^1.3.7", "deepmerge": "^4.3.1", "ejs": "^3.1.10", - "express": "^4.19.2", + "express": "^4.20.0", "express-rate-limit": "^5.1.3", "fast-json-patch": "^3.1.1", "filesize": "^6.1.0", diff --git a/yarn.lock b/yarn.lock index c6f4d25211..efc65983c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3652,10 +3652,10 @@ bluebird@^3.7.2: resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -body-parser@1.20.2: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== dependencies: bytes "3.1.2" content-type "~1.0.5" @@ -3665,7 +3665,7 @@ body-parser@1.20.2: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.11.0" + qs "6.13.0" raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" @@ -3958,6 +3958,17 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" @@ -4786,6 +4797,15 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz" @@ -5089,6 +5109,11 @@ encodeurl@~1.0.1, encodeurl@~1.0.2: resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + encoding@^0.1.13: version "0.1.13" resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz" @@ -5246,6 +5271,18 @@ es-abstract@^1.19.0, es-abstract@^1.20.4: unbox-primitive "^1.0.2" which-typed-array "^1.1.9" +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + es-get-iterator@^1.1.2: version "1.1.3" resolved "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz" @@ -5663,37 +5700,37 @@ express-static-gzip@^2.1.7: dependencies: serve-static "^1.14.1" -express@^4.17.3, express@^4.18.2, express@^4.19.2: - version "4.19.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" - integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== +express@^4.17.3, express@^4.18.2, express@^4.20.0: + version "4.20.0" + resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48" + integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.2" + body-parser "1.20.3" content-disposition "0.5.4" content-type "~1.0.4" cookie "0.6.0" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" finalhandler "1.2.0" fresh "0.5.2" http-errors "2.0.0" - merge-descriptors "1.0.1" + merge-descriptors "1.0.3" methods "~1.1.2" on-finished "2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.7" + path-to-regexp "0.1.10" proxy-addr "~2.0.7" qs "6.11.0" range-parser "~1.2.1" safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" + send "0.19.0" + serve-static "1.16.0" setprototypeof "1.2.0" statuses "2.0.1" type-is "~1.6.18" @@ -6052,6 +6089,11 @@ function-bind@^1.1.1: resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + function.prototype.name@^1.1.5: version "1.1.5" resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz" @@ -6100,6 +6142,17 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has "^1.0.3" has-symbols "^1.0.3" +get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" @@ -6342,6 +6395,13 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.1.1" +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + has-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz" @@ -6371,6 +6431,13 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + hdr-histogram-js@^2.0.1: version "2.0.3" resolved "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz" @@ -8066,10 +8133,10 @@ mensch@^0.3.4: resolved "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz" integrity sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g== -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== merge-stream@^2.0.0: version "2.0.0" @@ -8698,6 +8765,11 @@ object-inspect@^1.12.3, object-inspect@^1.9.0: resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz" integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + object-is@^1.1.5: version "1.1.5" resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz" @@ -9067,10 +9139,10 @@ path-scurry@^1.6.1: lru-cache "^9.0.0" minipass "^5.0.0" -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +path-to-regexp@0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" + integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== path-type@^4.0.0: version "4.0.0" @@ -9646,6 +9718,13 @@ qs@6.11.0, qs@^6.11.0: dependencies: side-channel "^1.0.4" +qs@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + qs@~6.10.3: version "6.10.4" resolved "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz" @@ -10509,6 +10588,25 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + serialize-javascript@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz" @@ -10546,7 +10644,17 @@ serve-static@1.13.2: parseurl "~1.3.2" send "0.16.2" -serve-static@1.15.0, serve-static@^1.14.1: +serve-static@1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92" + integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +serve-static@^1.14.1: version "1.15.0" resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz" integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== @@ -10566,6 +10674,18 @@ set-blocking@^2.0.0: resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz" @@ -10614,6 +10734,16 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" From 685e6d83c5a85269d0c47678ab4a282346184d99 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 16 Sep 2024 13:44:06 +0200 Subject: [PATCH 486/875] 117544: PR feedback - added typedocs - changed directive to only be present for buttons - various other small fixes --- .../filtered-items.component.html | 0 .../dso-edit-metadata-value.component.html | 47 +++++++++++++++++++ .../item-delete/item-delete.component.html | 2 +- .../access-control-array-form.component.html | 2 +- ...cess-control-form-container.component.html | 12 ++--- src/app/shared/disabled-directive.spec.ts | 8 ++++ src/app/shared/disabled-directive.ts | 28 ++++++++++- ...amic-form-control-container.component.html | 2 +- .../date-picker/date-picker.component.html | 6 +-- .../disabled/dynamic-disabled.component.html | 2 +- .../dynamic-disabled.component.spec.ts | 2 +- .../lookup/dynamic-lookup.component.html | 4 +- .../onebox/dynamic-onebox.component.html | 4 +- ...dynamic-scrollable-dropdown.component.html | 2 +- .../number-picker.component.html | 2 +- .../vocabulary-treeview.component.html | 4 +- .../collection-select.component.html | 2 +- .../item-select/item-select.component.html | 2 +- .../item-select/item-select.component.spec.ts | 3 +- .../pagination/pagination.component.html | 2 +- ...mission-section-cc-licenses.component.html | 4 +- 21 files changed, 110 insertions(+), 30 deletions(-) create mode 100644 src/app/admin/admin-reports/filtered-items/filtered-items.component.html diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items.component.html b/src/app/admin/admin-reports/filtered-items/filtered-items.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html index f3d92ace2d..1837801902 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html @@ -6,6 +6,53 @@ + + + + +
    + + + {{ dsoType + '.edit.metadata.authority.label' | translate }} {{ mdValue.newValue.authority }} + +
    +
    +
    + + + + +
    +
    {{ mdRepresentationName$ | async }} diff --git a/src/app/item-page/edit-item-page/item-delete/item-delete.component.html b/src/app/item-page/edit-item-page/item-delete/item-delete.component.html index eadec9babf..667363a381 100644 --- a/src/app/item-page/edit-item-page/item-delete/item-delete.component.html +++ b/src/app/item-page/edit-item-page/item-delete/item-delete.component.html @@ -18,7 +18,7 @@
    diff --git a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html index 630812c345..f2a5bdd422 100644 --- a/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html +++ b/src/app/shared/access-control-form-container/access-control-array-form/access-control-array-form.component.html @@ -13,7 +13,7 @@
    + [dsBtnDisabled]="!form.valid"> {{"login.form.submit" | translate}}
    diff --git a/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.html b/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.html index e4f52cf361..9785715ee8 100644 --- a/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.html +++ b/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.html @@ -1,7 +1,7 @@ diff --git a/src/app/shared/object-select/item-select/item-select.component.html b/src/app/shared/object-select/item-select/item-select.component.html index 58eed6ae4a..d1c2986076 100644 --- a/src/app/shared/object-select/item-select/item-select.component.html +++ b/src/app/shared/object-select/item-select/item-select.component.html @@ -42,7 +42,7 @@ diff --git a/src/app/shared/object-select/item-select/item-select.component.spec.ts b/src/app/shared/object-select/item-select/item-select.component.spec.ts index 7d2e5c4146..9fb9a0260a 100644 --- a/src/app/shared/object-select/item-select/item-select.component.spec.ts +++ b/src/app/shared/object-select/item-select/item-select.component.spec.ts @@ -24,7 +24,7 @@ import { LinkHeadService } from '../../../core/services/link-head.service'; import { GroupDataService } from '../../../core/eperson/group-data.service'; import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub'; import { ConfigurationProperty } from '../../../core/shared/configuration-property.model'; -import {DisabledDirective} from '../../disabled-directive'; +import {BtnDisabledDirective} from '../../btn-disabled.directive'; describe('ItemSelectComponent', () => { let comp: ItemSelectComponent; @@ -99,7 +99,7 @@ describe('ItemSelectComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], - declarations: [DisabledDirective], + declarations: [BtnDisabledDirective], providers: [ { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockItemList[1].id]) }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, diff --git a/src/app/shared/pagination/pagination.component.html b/src/app/shared/pagination/pagination.component.html index b85dcb01ee..f7f2c64e2c 100644 --- a/src/app/shared/pagination/pagination.component.html +++ b/src/app/shared/pagination/pagination.component.html @@ -55,12 +55,12 @@
    -
    diff --git a/src/app/submission/form/collection/submission-form-collection.component.html b/src/app/submission/form/collection/submission-form-collection.component.html index 7f50e5a341..fe9527c266 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.html +++ b/src/app/submission/form/collection/submission-form-collection.component.html @@ -25,7 +25,7 @@ class="btn btn-outline-primary" (blur)="onClose()" (click)="onClose()" - [dsDisabled]="(processingChange$ | async) || collectionModifiable == false || isReadonly" + [dsBtnDisabled]="(processingChange$ | async) || collectionModifiable == false || isReadonly" ngbDropdownToggle> {{ selectedCollectionName$ | async }} diff --git a/src/app/submission/form/collection/submission-form-collection.component.spec.ts b/src/app/submission/form/collection/submission-form-collection.component.spec.ts index 4cb40e2293..467f164f89 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.spec.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.spec.ts @@ -25,7 +25,7 @@ import { Collection } from '../../../core/shared/collection.model'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock } from '../../../shared/mocks/dso-name.service.mock'; -import {DisabledDirective} from '../../../shared/disabled-directive'; +import {BtnDisabledDirective} from '../../../shared/btn-disabled.directive'; describe('SubmissionFormCollectionComponent Component', () => { @@ -137,7 +137,7 @@ describe('SubmissionFormCollectionComponent Component', () => { declarations: [ SubmissionFormCollectionComponent, TestComponent, - DisabledDirective + BtnDisabledDirective ], providers: [ { provide: DSONameService, useValue: new DSONameServiceMock() }, 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 f7e8bae792..457a56cc76 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.html +++ b/src/app/submission/form/footer/submission-form-footer.component.html @@ -5,7 +5,7 @@ id="discard" [attr.data-test]="'discard' | dsBrowserOnly" class="btn btn-danger" - [dsDisabled]="(processingSaveStatus | async) || (processingDepositStatus | async)" + [dsBtnDisabled]="(processingSaveStatus | async) || (processingDepositStatus | async)" (click)="$event.preventDefault();confirmDiscard(content)"> {{'submission.general.discard.submit' | translate}} @@ -28,7 +28,7 @@ class="btn btn-secondary" id="save" [attr.data-test]="'save' | dsBrowserOnly" - [dsDisabled]="(processingSaveStatus | async) || !(hasUnsavedModification | async)" + [dsBtnDisabled]="(processingSaveStatus | async) || !(hasUnsavedModification | async)" (click)="save($event)"> {{'submission.general.save' | translate}} @@ -38,7 +38,7 @@ class="btn" id="saveForLater" [attr.data-test]="'save-for-later' | dsBrowserOnly" - [dsDisabled]="(processingSaveStatus | async) || (processingDepositStatus | async)" + [dsBtnDisabled]="(processingSaveStatus | async) || (processingDepositStatus | async)" (click)="saveLater($event)"> {{'submission.general.save-later' | translate}} @@ -47,7 +47,7 @@ id="deposit" [attr.data-test]="'deposit' | dsBrowserOnly" class="btn btn-success" - [dsDisabled]="(processingSaveStatus | async) || (processingDepositStatus | async)" + [dsBtnDisabled]="(processingSaveStatus | async) || (processingDepositStatus | async)" (click)="deposit($event)"> {{'submission.general.deposit' | translate}} diff --git a/src/app/submission/form/footer/submission-form-footer.component.spec.ts b/src/app/submission/form/footer/submission-form-footer.component.spec.ts index 9071805514..658926834c 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.spec.ts +++ b/src/app/submission/form/footer/submission-form-footer.component.spec.ts @@ -16,7 +16,7 @@ import { SubmissionFormFooterComponent } from './submission-form-footer.componen import { SubmissionRestService } from '../../../core/submission/submission-rest.service'; import { createTestComponent } from '../../../shared/testing/utils.test'; import { BrowserOnlyMockPipe } from '../../../shared/testing/browser-only-mock.pipe'; -import {DisabledDirective} from '../../../shared/disabled-directive'; +import {BtnDisabledDirective} from '../../../shared/btn-disabled.directive'; const submissionServiceStub: SubmissionServiceStub = new SubmissionServiceStub(); @@ -40,7 +40,7 @@ describe('SubmissionFormFooterComponent', () => { SubmissionFormFooterComponent, TestComponent, BrowserOnlyMockPipe, - DisabledDirective + BtnDisabledDirective ], providers: [ { provide: SubmissionService, useValue: submissionServiceStub }, diff --git a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.html b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.html index 28fa4695fb..cb8dde18c5 100644 --- a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.html +++ b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.html @@ -23,6 +23,6 @@

    {{'submission.import-external.source.loading' | translate}}

    - +
    diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html index 1ce811ce66..b529d9aee4 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.html @@ -1,7 +1,7 @@
    diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.html b/src/app/submission/sections/upload/file/section-upload-file.component.html index 589e7b6ebf..3567ef5c64 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.html +++ b/src/app/submission/sections/upload/file/section-upload-file.component.html @@ -19,7 +19,7 @@ -
    From 4a7ebeea16cdd902f99da11ba2dc9e5ab155620e Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 16 Sep 2024 16:09:41 +0200 Subject: [PATCH 488/875] 117544: fix remaining bug --- src/app/shared/btn-disabled.directive.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/btn-disabled.directive.ts b/src/app/shared/btn-disabled.directive.ts index ab37643c02..512aa87ede 100644 --- a/src/app/shared/btn-disabled.directive.ts +++ b/src/app/shared/btn-disabled.directive.ts @@ -12,7 +12,7 @@ import { Directive, Input, HostBinding, HostListener } from '@angular/core'; */ export class BtnDisabledDirective { - @Input() set dsDisabled(value: boolean) { + @Input() set dsBtnDisabled(value: boolean) { this.isDisabled = !!value; } From 83a44ba924fb20065f0bb62fc2a09a7aaec391bc Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Mon, 16 Sep 2024 15:46:07 +0200 Subject: [PATCH 489/875] 118220: Add live-region service and component --- config/config.example.yml | 16 ++- .../live-region/live-region.component.html | 3 + .../live-region/live-region.component.scss | 13 ++ .../live-region/live-region.component.ts | 25 ++++ .../shared/live-region/live-region.config.ts | 9 ++ .../shared/live-region/live-region.service.ts | 118 ++++++++++++++++++ src/app/shared/shared.module.ts | 4 +- src/config/app-config.interface.ts | 2 + src/config/default-app-config.ts | 7 ++ src/environments/environment.test.ts | 7 +- src/styles/_custom_variables.scss | 1 + 11 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 src/app/shared/live-region/live-region.component.html create mode 100644 src/app/shared/live-region/live-region.component.scss create mode 100644 src/app/shared/live-region/live-region.component.ts create mode 100644 src/app/shared/live-region/live-region.config.ts create mode 100644 src/app/shared/live-region/live-region.service.ts diff --git a/config/config.example.yml b/config/config.example.yml index ea38303fa3..58eb6ff33d 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -379,4 +379,18 @@ vocabularies: # Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query. comcolSelectionSort: sortField: 'dc.title' - sortDirection: 'ASC' \ No newline at end of file + sortDirection: 'ASC' + +# Live Region configuration +# Live Region as defined by w3c, https://www.w3.org/TR/wai-aria-1.1/#terms: +# Live regions are perceivable regions of a web page that are typically updated as a +# result of an external event when user focus may be elsewhere. +# +# The DSpace live region is a component present at the bottom of all pages that is invisible by default, but is useful +# for screen readers. Any message pushed to the live region will be announced by the screen reader. These messages +# usually contain information about changes on the page that might not be in focus. +liveRegion: + # The duration after which messages disappear from the live region in milliseconds + messageTimeOutDurationMs: 30000 + # The visibility of the live region. Setting this to true is only useful for debugging purposes. + isVisible: false diff --git a/src/app/shared/live-region/live-region.component.html b/src/app/shared/live-region/live-region.component.html new file mode 100644 index 0000000000..a48f3ad52e --- /dev/null +++ b/src/app/shared/live-region/live-region.component.html @@ -0,0 +1,3 @@ +
    +
    {{ message }}
    +
    diff --git a/src/app/shared/live-region/live-region.component.scss b/src/app/shared/live-region/live-region.component.scss new file mode 100644 index 0000000000..69844a93e1 --- /dev/null +++ b/src/app/shared/live-region/live-region.component.scss @@ -0,0 +1,13 @@ +.live-region { + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding-left: 60px; + height: 90px; + line-height: 18px; + color: var(--bs-white); + background-color: var(--bs-dark); + opacity: 0.94; + z-index: var(--ds-live-region-z-index); +} diff --git a/src/app/shared/live-region/live-region.component.ts b/src/app/shared/live-region/live-region.component.ts new file mode 100644 index 0000000000..d7bd5eb806 --- /dev/null +++ b/src/app/shared/live-region/live-region.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; +import { LiveRegionService } from './live-region.service'; +import { Observable } from 'rxjs'; + +@Component({ + selector: `ds-live-region`, + templateUrl: './live-region.component.html', + styleUrls: ['./live-region.component.scss'], +}) +export class LiveRegionComponent implements OnInit { + + protected isVisible: boolean; + + protected messages$: Observable; + + constructor( + protected liveRegionService: LiveRegionService, + ) { + } + + ngOnInit() { + this.isVisible = this.liveRegionService.getLiveRegionVisibility(); + this.messages$ = this.liveRegionService.getMessages$(); + } +} diff --git a/src/app/shared/live-region/live-region.config.ts b/src/app/shared/live-region/live-region.config.ts new file mode 100644 index 0000000000..e545bfd254 --- /dev/null +++ b/src/app/shared/live-region/live-region.config.ts @@ -0,0 +1,9 @@ +import { Config } from '../../../config/config.interface'; + +/** + * Configuration interface used by the LiveRegionService + */ +export class LiveRegionConfig implements Config { + messageTimeOutDurationMs: number; + isVisible: boolean; +} diff --git a/src/app/shared/live-region/live-region.service.ts b/src/app/shared/live-region/live-region.service.ts new file mode 100644 index 0000000000..482d1ca1bb --- /dev/null +++ b/src/app/shared/live-region/live-region.service.ts @@ -0,0 +1,118 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { environment } from '../../../environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class LiveRegionService { + + /** + * The duration after which the messages disappear in milliseconds + * @protected + */ + protected messageTimeOutDurationMs: number = environment.liveRegion.messageTimeOutDurationMs; + + /** + * Array containing the messages that should be shown in the live region + * @protected + */ + protected messages: string[] = []; + + /** + * BehaviorSubject emitting the array with messages every time the array updates + * @protected + */ + protected messages$: BehaviorSubject = new BehaviorSubject([]); + + /** + * Whether the live region should be visible + * @protected + */ + protected liveRegionIsVisible: boolean = environment.liveRegion.isVisible; + + /** + * Returns a copy of the array with the current live region messages + */ + getMessages() { + return [...this.messages]; + } + + /** + * Returns the BehaviorSubject emitting the array with messages every time the array updates + */ + getMessages$() { + return this.messages$; + } + + /** + * Adds a message to the live-region messages array + * @param message + */ + addMessage(message: string) { + this.messages.push(message); + this.emitCurrentMessages(); + + // Clear the message once the timeOut has passed + setTimeout(() => this.pop(), this.messageTimeOutDurationMs); + } + + /** + * Clears the live-region messages array + */ + clear() { + this.messages = []; + this.emitCurrentMessages(); + } + + /** + * Removes the longest living message from the array. + * @protected + */ + protected pop() { + if (this.messages.length > 0) { + this.messages.shift(); + this.emitCurrentMessages(); + } + } + + /** + * Makes the messages$ BehaviorSubject emit the current messages array + * @protected + */ + protected emitCurrentMessages() { + this.messages$.next(this.getMessages()); + } + + /** + * Returns a boolean specifying whether the live region should be visible. + * Returns 'true' if the region should be visible and false otherwise. + */ + getLiveRegionVisibility(): boolean { + return this.liveRegionIsVisible; + } + + /** + * Sets the visibility of the live region. + * Setting this to true will make the live region visible which is useful for debugging purposes. + * @param isVisible + */ + setLiveRegionVisibility(isVisible: boolean) { + this.liveRegionIsVisible = isVisible; + } + + /** + * Gets the current message timeOut duration in milliseconds + */ + getMessageTimeOutMs(): number { + return this.messageTimeOutDurationMs; + } + + /** + * Sets the message timeOut duration + * @param timeOutMs the message timeOut duration in milliseconds + */ + setMessageTimeOutMs(timeOutMs: number) { + this.messageTimeOutDurationMs = timeOutMs; + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 0f7871f7f9..db5b778722 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -284,6 +284,7 @@ import { } from '../item-page/simple/field-components/specific-field/title/themed-item-page-field.component'; import { BitstreamListItemComponent } from './object-list/bitstream-list-item/bitstream-list-item.component'; import { NgxPaginationModule } from 'ngx-pagination'; +import { LiveRegionComponent } from './live-region/live-region.component'; const MODULES = [ CommonModule, @@ -465,7 +466,8 @@ const ENTRY_COMPONENTS = [ AdvancedClaimedTaskActionRatingComponent, EpersonGroupListComponent, EpersonSearchBoxComponent, - GroupSearchBoxComponent + GroupSearchBoxComponent, + LiveRegionComponent, ]; const PROVIDERS = [ diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index 84a30549a7..aa3033ecec 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -22,6 +22,7 @@ import { HomeConfig } from './homepage-config.interface'; import { MarkdownConfig } from './markdown-config.interface'; import { FilterVocabularyConfig } from './filter-vocabulary-config'; import { DiscoverySortConfig } from './discovery-sort.config'; +import { LiveRegionConfig } from '../app/shared/live-region/live-region.config'; interface AppConfig extends Config { ui: UIServerConfig; @@ -48,6 +49,7 @@ interface AppConfig extends Config { markdown: MarkdownConfig; vocabularies: FilterVocabularyConfig[]; comcolSelectionSort: DiscoverySortConfig; + liveRegion: LiveRegionConfig; } /** diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index a6e9e092e4..1c0f88cf47 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -22,6 +22,7 @@ import { HomeConfig } from './homepage-config.interface'; import { MarkdownConfig } from './markdown-config.interface'; import { FilterVocabularyConfig } from './filter-vocabulary-config'; import { DiscoverySortConfig } from './discovery-sort.config'; +import { LiveRegionConfig } from '../app/shared/live-region/live-region.config'; export class DefaultAppConfig implements AppConfig { production = false; @@ -432,4 +433,10 @@ export class DefaultAppConfig implements AppConfig { sortField:'dc.title', sortDirection:'ASC', }; + + // Live Region configuration, used by the LiveRegionService + liveRegion: LiveRegionConfig = { + messageTimeOutDurationMs: 30000, + isVisible: false, + }; } diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index cb9d2c7130..498799a454 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -313,5 +313,10 @@ export const environment: BuildConfig = { vocabulary: 'srsc', enabled: true } - ] + ], + + liveRegion: { + messageTimeOutDurationMs: 30000, + isVisible: false, + }, }; diff --git a/src/styles/_custom_variables.scss b/src/styles/_custom_variables.scss index ddf490c7a7..09267d15ae 100644 --- a/src/styles/_custom_variables.scss +++ b/src/styles/_custom_variables.scss @@ -13,6 +13,7 @@ --ds-login-logo-width:72px; --ds-submission-header-z-index: 1001; --ds-submission-footer-z-index: 999; + --ds-live-region-z-index: 1030; --ds-main-z-index: 1; --ds-nav-z-index: 10; From e987c35450f2a03e73fe5a9951ec412fb9675e33 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Tue, 17 Sep 2024 09:07:02 +0200 Subject: [PATCH 490/875] 118220: Add liveRegionComponent & Service tests --- .../live-region/live-region.component.spec.ts | 57 +++++++ .../live-region/live-region.service.spec.ts | 140 ++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 src/app/shared/live-region/live-region.component.spec.ts create mode 100644 src/app/shared/live-region/live-region.service.spec.ts diff --git a/src/app/shared/live-region/live-region.component.spec.ts b/src/app/shared/live-region/live-region.component.spec.ts new file mode 100644 index 0000000000..2e9797f9c3 --- /dev/null +++ b/src/app/shared/live-region/live-region.component.spec.ts @@ -0,0 +1,57 @@ +import { LiveRegionComponent } from './live-region.component'; +import { ComponentFixture, waitForAsync, TestBed } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { LiveRegionService } from './live-region.service'; +import { of } from 'rxjs'; +import { By } from '@angular/platform-browser'; + +describe('liveRegionComponent', () => { + let fixture: ComponentFixture; + let liveRegionService: LiveRegionService; + + beforeEach(waitForAsync(() => { + liveRegionService = jasmine.createSpyObj('liveRegionService', { + getMessages$: of(['message1', 'message2']), + getLiveRegionVisibility: false, + setLiveRegionVisibility: undefined, + }); + + void TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), + ], + declarations: [LiveRegionComponent], + providers: [ + { provide: LiveRegionService, useValue: liveRegionService }, + ], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LiveRegionComponent); + fixture.detectChanges(); + }); + + it('should contain the current live region messages', () => { + const messages = fixture.debugElement.queryAll(By.css('.live-region-message')); + + expect(messages.length).toEqual(2); + expect(messages[0].nativeElement.textContent).toEqual('message1'); + expect(messages[1].nativeElement.textContent).toEqual('message2'); + }); + + it('should respect the live region visibility', () => { + const liveRegion = fixture.debugElement.query(By.css('.live-region')); + expect(liveRegion).toBeDefined(); + + const liveRegionHidden = fixture.debugElement.query(By.css('.visually-hidden')); + expect(liveRegionHidden).toBeDefined(); + + liveRegionService.getLiveRegionVisibility = jasmine.createSpy('getLiveRegionVisibility').and.returnValue(true); + fixture = TestBed.createComponent(LiveRegionComponent); + fixture.detectChanges(); + + const liveRegionVisible = fixture.debugElement.query(By.css('.visually-hidden')); + expect(liveRegionVisible).toBeNull(); + }); +}); diff --git a/src/app/shared/live-region/live-region.service.spec.ts b/src/app/shared/live-region/live-region.service.spec.ts new file mode 100644 index 0000000000..fe5e8b8d8c --- /dev/null +++ b/src/app/shared/live-region/live-region.service.spec.ts @@ -0,0 +1,140 @@ +import { LiveRegionService } from './live-region.service'; +import { fakeAsync, tick, flush } from '@angular/core/testing'; + +describe('liveRegionService', () => { + let service: LiveRegionService; + + + beforeEach(() => { + service = new LiveRegionService(); + }); + + describe('addMessage', () => { + it('should correctly add messages', () => { + expect(service.getMessages().length).toEqual(0); + + service.addMessage('Message One'); + expect(service.getMessages().length).toEqual(1); + expect(service.getMessages()[0]).toEqual('Message One'); + + service.addMessage('Message Two'); + expect(service.getMessages().length).toEqual(2); + expect(service.getMessages()[1]).toEqual('Message Two'); + }); + }); + + describe('clearMessages', () => { + it('should clear the messages', () => { + expect(service.getMessages().length).toEqual(0); + + service.addMessage('Message One'); + service.addMessage('Message Two'); + expect(service.getMessages().length).toEqual(2); + + service.clear(); + expect(service.getMessages().length).toEqual(0); + }); + }); + + describe('messages$', () => { + it('should emit when a message is added and when a message is removed after the timeOut', fakeAsync(() => { + const results: string[][] = []; + + service.getMessages$().subscribe((messages) => { + results.push(messages); + }); + + expect(results.length).toEqual(1); + expect(results[0]).toEqual([]); + + service.addMessage('message'); + + tick(); + + expect(results.length).toEqual(2); + expect(results[1]).toEqual(['message']); + + tick(service.getMessageTimeOutMs()); + + expect(results.length).toEqual(3); + expect(results[2]).toEqual([]); + })); + + it('should only emit once when the messages are cleared', fakeAsync(() => { + const results: string[][] = []; + + service.getMessages$().subscribe((messages) => { + results.push(messages); + }); + + expect(results.length).toEqual(1); + expect(results[0]).toEqual([]); + + service.addMessage('Message One'); + service.addMessage('Message Two'); + + tick(); + + expect(results.length).toEqual(3); + expect(results[2]).toEqual(['Message One', 'Message Two']); + + service.clear(); + flush(); + + expect(results.length).toEqual(4); + expect(results[3]).toEqual([]); + })); + + it('should respect configured timeOut', fakeAsync(() => { + const results: string[][] = []; + + service.getMessages$().subscribe((messages) => { + results.push(messages); + }); + + expect(results.length).toEqual(1); + expect(results[0]).toEqual([]); + + const timeOutMs = 500; + service.setMessageTimeOutMs(timeOutMs); + + service.addMessage('Message One'); + tick(timeOutMs - 1); + + expect(results.length).toEqual(2); + expect(results[1]).toEqual(['Message One']); + + tick(1); + + expect(results.length).toEqual(3); + expect(results[2]).toEqual([]); + + const timeOutMsTwo = 50000; + service.setMessageTimeOutMs(timeOutMsTwo); + + service.addMessage('Message Two'); + tick(timeOutMsTwo - 1); + + expect(results.length).toEqual(4); + expect(results[3]).toEqual(['Message Two']); + + tick(1); + + expect(results.length).toEqual(5); + expect(results[4]).toEqual([]); + })); + }); + + describe('liveRegionVisibility', () => { + it('should be false by default', () => { + expect(service.getLiveRegionVisibility()).toBeFalse(); + }); + + it('should correctly update', () => { + service.setLiveRegionVisibility(true); + expect(service.getLiveRegionVisibility()).toBeTrue(); + service.setLiveRegionVisibility(false); + expect(service.getLiveRegionVisibility()).toBeFalse(); + }); + }); +}); From 35d29c84258e1656ed17c53e6e7054c69b7878d1 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Fri, 20 Sep 2024 09:38:31 +0200 Subject: [PATCH 491/875] 118220: Add LiveRegion to RootComponent --- src/app/root/root.component.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/root/root.component.html b/src/app/root/root.component.html index bf49e507c0..d59bea1db4 100644 --- a/src/app/root/root.component.html +++ b/src/app/root/root.component.html @@ -27,3 +27,5 @@
    + + From cd51baa5f1ee5d8fa66e8ee94beb3168f93c5c01 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Mon, 23 Sep 2024 18:15:39 +0200 Subject: [PATCH 492/875] [CST-14904] add orcid icon with tooltip --- ...-item-metadata-list-element.component.html | 5 ++ .../orcid-badge-and-tooltip.component.html | 11 +++ .../orcid-badge-and-tooltip.component.scss | 11 +++ .../orcid-badge-and-tooltip.component.spec.ts | 71 +++++++++++++++++++ .../orcid-badge-and-tooltip.component.ts | 56 +++++++++++++++ src/app/shared/shared.module.ts | 2 + src/assets/i18n/en.json5 | 4 ++ 7 files changed, 160 insertions(+) create mode 100644 src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.html create mode 100644 src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.scss create mode 100644 src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.spec.ts create mode 100644 src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.ts diff --git a/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.html b/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.html index 6f56056781..f61c14d3ba 100644 --- a/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.html +++ b/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.html @@ -12,4 +12,9 @@ + + diff --git a/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.html b/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.html new file mode 100644 index 0000000000..fc34aee970 --- /dev/null +++ b/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.html @@ -0,0 +1,11 @@ +orcid-logo + + + {{ orcidTooltip }} + diff --git a/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.scss b/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.scss new file mode 100644 index 0000000000..6a1c259e18 --- /dev/null +++ b/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.scss @@ -0,0 +1,11 @@ +:host { + display: inline-block; +} + +.orcid-icon { + height: 1.2rem; + + &.not-authenticated { + filter: grayscale(100%); + } +} diff --git a/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.spec.ts b/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.spec.ts new file mode 100644 index 0000000000..adb3c91f94 --- /dev/null +++ b/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.spec.ts @@ -0,0 +1,71 @@ +import { + NgClass, + NgIf, +} from '@angular/common'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateService } from '@ngx-translate/core'; + +import { MetadataValue } from '../../core/shared/metadata.models'; +import { OrcidBadgeAndTooltipComponent } from './orcid-badge-and-tooltip.component'; + +describe('OrcidBadgeAndTooltipComponent', () => { + let component: OrcidBadgeAndTooltipComponent; + let fixture: ComponentFixture; + let translateService: TranslateService; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [OrcidBadgeAndTooltipComponent], + imports: [ + NgbTooltipModule, + NgClass, + NgIf, + ], + providers: [ + { provide: TranslateService, useValue: { instant: (key: string) => key } }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(OrcidBadgeAndTooltipComponent); + component = fixture.componentInstance; + translateService = TestBed.inject(TranslateService); + + component.orcid = { value: '0000-0002-1825-0097' } as MetadataValue; + component.authenticatedTimestamp = { value: '2023-10-01' } as MetadataValue; + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should set orcidTooltip when authenticatedTimestamp is provided', () => { + component.ngOnInit(); + expect(component.orcidTooltip).toBe('person.orcid-tooltip.authenticated'); + }); + + it('should set orcidTooltip when authenticatedTimestamp is not provided', () => { + component.authenticatedTimestamp = null; + component.ngOnInit(); + expect(component.orcidTooltip).toBe('person.orcid-tooltip.not-authenticated'); + }); + + it('should display the ORCID icon', () => { + const badgeIcon = fixture.debugElement.query(By.css('img[data-test="orcidIcon"]')); + expect(badgeIcon).toBeTruthy(); + }); + + it('should display the ORCID icon in greyscale if there is no authenticated timestamp', () => { + component.authenticatedTimestamp = null; + fixture.detectChanges(); + const badgeIcon = fixture.debugElement.query(By.css('img[data-test="orcidIcon"]')); + expect(badgeIcon.nativeElement.classList).toContain('not-authenticated'); + }); + +}); diff --git a/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.ts b/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.ts new file mode 100644 index 0000000000..1939bad57f --- /dev/null +++ b/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.ts @@ -0,0 +1,56 @@ + + +import { + Component, + Input, + OnInit, +} from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; + +import { MetadataValue } from '../../core/shared/metadata.models'; + +/** + * Component to display an ORCID badge with a tooltip. + * The tooltip text changes based on whether the ORCID is authenticated. + */ +@Component({ + selector: 'ds-orcid-badge-and-tooltip', + templateUrl: './orcid-badge-and-tooltip.component.html', + styleUrls: ['./orcid-badge-and-tooltip.component.scss'], +}) +export class OrcidBadgeAndTooltipComponent implements OnInit { + + /** + * The ORCID value to be displayed. + */ + @Input() orcid: MetadataValue; + + /** + * The timestamp indicating when the ORCID was authenticated. + */ + @Input() authenticatedTimestamp: MetadataValue; + + /** + * The tooltip text to be displayed. + */ + orcidTooltip: string; + + /** + * Constructor to inject the TranslateService. + * @param translateService - Service for translation. + */ + constructor( + private translateService: TranslateService, + ) { } + + /** + * Initializes the component. + * Sets the tooltip text based on the presence of the authenticated timestamp. + */ + ngOnInit() { + this.orcidTooltip = this.authenticatedTimestamp ? + this.translateService.instant('person.orcid-tooltip.authenticated', { orcid: this.orcid.value }) : + this.translateService.instant('person.orcid-tooltip.not-authenticated', { orcid: this.orcid.value }); + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 9f05b1d370..d6b6e861ce 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -284,6 +284,7 @@ import { BitstreamListItemComponent } from './object-list/bitstream-list-item/bi import { NgxPaginationModule } from 'ngx-pagination'; import { ThemedLangSwitchComponent } from './lang-switch/themed-lang-switch.component'; import {ThemedUserMenuComponent} from './auth-nav-menu/user-menu/themed-user-menu.component'; +import { OrcidBadgeAndTooltipComponent } from './orcid-badge-and-tooltip/orcid-badge-and-tooltip.component'; const MODULES = [ CommonModule, @@ -404,6 +405,7 @@ const COMPONENTS = [ EpersonSearchBoxComponent, GroupSearchBoxComponent, ThemedItemPageTitleFieldComponent, + OrcidBadgeAndTooltipComponent, ]; const ENTRY_COMPONENTS = [ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 0a1804fa5c..3f85e8b687 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -5248,6 +5248,10 @@ "person.orcid.registry.auth": "ORCID Authorizations", + "person.orcid-tooltip.authenticated": "{{orcid}}", + + "person.orcid-tooltip.not-authenticated": "{{orcid}} (unconfirmed)", + "home.recent-submissions.head": "Recent Submissions", "listable-notification-object.default-message": "This object couldn't be retrieved", From 35946dcf7cefdaafb70a2ee5ee02a2b53c7e8817 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 25 Sep 2024 08:13:21 +0300 Subject: [PATCH 493/875] Update isbot dependency Note that the minimum supported Node.js version is now v18, as v16 is now end of life. --- package.json | 2 +- server.ts | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 86959196d0..78f0cc9887 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "filesize": "^6.1.0", "http-proxy-middleware": "^1.0.5", "http-terminator": "^3.2.0", - "isbot": "^3.6.10", + "isbot": "^5.1.17", "js-cookie": "2.2.1", "js-yaml": "^4.1.0", "json5": "^2.2.3", diff --git a/server.ts b/server.ts index 93f3e86876..23d29723c6 100644 --- a/server.ts +++ b/server.ts @@ -28,7 +28,7 @@ import * as expressStaticGzip from 'express-static-gzip'; /* eslint-enable import/no-namespace */ import axios from 'axios'; import LRU from 'lru-cache'; -import isbot from 'isbot'; +import { isbot } from 'isbot'; import { createCertificate } from 'pem'; import { createServer } from 'https'; import { json } from 'body-parser'; diff --git a/yarn.lock b/yarn.lock index efc65983c6..2a4fd40b5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7188,10 +7188,10 @@ isbinaryfile@^4.0.8: resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz" integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== -isbot@^3.6.10: - version "3.6.10" - resolved "https://registry.yarnpkg.com/isbot/-/isbot-3.6.10.tgz#7b66334e81794f0461794debb567975cf08eaf2b" - integrity sha512-+I+2998oyP4oW9+OTQD8TS1r9P6wv10yejukj+Ksj3+UR5pUhsZN3f8W7ysq0p1qxpOVNbl5mCuv0bCaF8y5iQ== +isbot@^5.1.17: + version "5.1.17" + resolved "https://registry.yarnpkg.com/isbot/-/isbot-5.1.17.tgz#ad7da5690a61bbb19056a069975c9a73182682a0" + integrity sha512-/wch8pRKZE+aoVhRX/hYPY1C7dMCeeMyhkQLNLNlYAbGQn9bkvMB8fOUXNnk5I0m4vDYbBJ9ciVtkr9zfBJ7qA== isexe@^2.0.0: version "2.0.0" From 29c1b510733a6081fb5bfa09a5c8f7c0c25de300 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 25 Sep 2024 08:34:04 +0300 Subject: [PATCH 494/875] .github/workflows/build.yml: use Node.js v18 and v20 Node.js v16 LTS is end of life since October, 2023. See: https://nodejs.org/en/about/previous-releases --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 50b260b59e..f6ffa5e004 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,7 @@ jobs: strategy: # Create a matrix of Node versions to test against (in parallel) matrix: - node-version: [16.x, 18.x] + node-version: [18.x, 20.x] # Do NOT exit immediately if one matrix job fails fail-fast: false # These are the actual CI steps to perform per job From c1fa52ee64e7c50f38c0e671aa13757bc7a4c024 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 25 Sep 2024 09:50:33 +0200 Subject: [PATCH 495/875] 118220: Store messages with ID so clears can be targeted --- .../live-region/live-region.service.spec.ts | 34 +++++++++++++++- .../shared/live-region/live-region.service.ts | 39 ++++++++++++------- 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/app/shared/live-region/live-region.service.spec.ts b/src/app/shared/live-region/live-region.service.spec.ts index fe5e8b8d8c..858ef88313 100644 --- a/src/app/shared/live-region/live-region.service.spec.ts +++ b/src/app/shared/live-region/live-region.service.spec.ts @@ -1,12 +1,14 @@ import { LiveRegionService } from './live-region.service'; import { fakeAsync, tick, flush } from '@angular/core/testing'; +import { UUIDService } from '../../core/shared/uuid.service'; describe('liveRegionService', () => { let service: LiveRegionService; - beforeEach(() => { - service = new LiveRegionService(); + service = new LiveRegionService( + new UUIDService(), + ); }); describe('addMessage', () => { @@ -85,6 +87,34 @@ describe('liveRegionService', () => { expect(results[3]).toEqual([]); })); + it('should not pop messages added after clearing within timeOut period', fakeAsync(() => { + const results: string[][] = []; + + service.getMessages$().subscribe((messages) => { + results.push(messages); + }); + + expect(results.length).toEqual(1); + expect(results[0]).toEqual([]); + + service.addMessage('Message One'); + tick(10000); + service.clear(); + tick(15000); + service.addMessage('Message Two'); + + // Message Two should not be cleared after 5 more seconds + tick(5000); + + expect(results.length).toEqual(4); + expect(results[3]).toEqual(['Message Two']); + + // But should be cleared 30 seconds after it was added + tick(25000); + expect(results.length).toEqual(5); + expect(results[4]).toEqual([]); + })); + it('should respect configured timeOut', fakeAsync(() => { const results: string[][] = []; diff --git a/src/app/shared/live-region/live-region.service.ts b/src/app/shared/live-region/live-region.service.ts index 482d1ca1bb..d89d72a967 100644 --- a/src/app/shared/live-region/live-region.service.ts +++ b/src/app/shared/live-region/live-region.service.ts @@ -1,12 +1,18 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { environment } from '../../../environments/environment'; +import { UUIDService } from '../../core/shared/uuid.service'; @Injectable({ providedIn: 'root', }) export class LiveRegionService { + constructor( + protected uuidService: UUIDService, + ) { + } + /** * The duration after which the messages disappear in milliseconds * @protected @@ -14,10 +20,11 @@ export class LiveRegionService { protected messageTimeOutDurationMs: number = environment.liveRegion.messageTimeOutDurationMs; /** - * Array containing the messages that should be shown in the live region + * Array containing the messages that should be shown in the live region, + * together with a uuid, so they can be uniquely identified * @protected */ - protected messages: string[] = []; + protected messages: { message: string, uuid: string }[] = []; /** * BehaviorSubject emitting the array with messages every time the array updates @@ -34,27 +41,28 @@ export class LiveRegionService { /** * Returns a copy of the array with the current live region messages */ - getMessages() { - return [...this.messages]; + getMessages(): string[] { + return this.messages.map(messageObj => messageObj.message); } /** * Returns the BehaviorSubject emitting the array with messages every time the array updates */ - getMessages$() { + getMessages$(): BehaviorSubject { return this.messages$; } /** * Adds a message to the live-region messages array * @param message + * @return The uuid of the message */ - addMessage(message: string) { - this.messages.push(message); + addMessage(message: string): string { + const uuid = this.uuidService.generate(); + this.messages.push({ message, uuid }); + setTimeout(() => this.clearMessageByUUID(uuid), this.messageTimeOutDurationMs); this.emitCurrentMessages(); - - // Clear the message once the timeOut has passed - setTimeout(() => this.pop(), this.messageTimeOutDurationMs); + return uuid; } /** @@ -66,12 +74,15 @@ export class LiveRegionService { } /** - * Removes the longest living message from the array. + * Removes the message with the given UUID from the messages array + * @param uuid The uuid of the message to clear * @protected */ - protected pop() { - if (this.messages.length > 0) { - this.messages.shift(); + clearMessageByUUID(uuid: string) { + const index = this.messages.findIndex(messageObj => messageObj.uuid === uuid); + + if (index !== -1) { + this.messages.splice(index, 1); this.emitCurrentMessages(); } } From aed97c9dccf5460897f5c24ea9473fa642468c0c Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 26 Jun 2024 14:46:13 +0200 Subject: [PATCH 496/875] added missing German translations (500 error) (cherry picked from commit 5f6b6ef9849e27eaf87e5b8b0f94ccde271d8043) --- src/assets/i18n/de.json5 | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 8efda0390e..d1ac01fe2a 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -9,8 +9,6 @@ // "401.unauthorized": "unauthorized", "401.unauthorized": "unautorisiert", - - // "403.help": "You don't have permission to access this page. You can use the button below to get back to the home page.", "403.help": "Sie sind nicht berechtigt, auf diese Seite zuzugreifen. Über den Button unten auf der Seite gelangen Sie zurück zur Startseite.", @@ -20,8 +18,6 @@ // "403.forbidden": "forbidden", "403.forbidden": "verboten", - - // "404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ", "404.help": "Die Seite konnte nicht gefunden werden. Eventuell wurde sie verschoben oder gelöscht. Über den Button unten auf der Seite gelangen Sie zurück zur Startseite.", @@ -31,6 +27,16 @@ // "404.page-not-found": "page not found", "404.page-not-found": "Seite nicht gefunden", + // "500.page-internal-server-error": "Service unavailable", + "500.page-internal-server-error": "Dienst nicht verfügbar", + + // "500.help": "The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.", + "500.help": "Der Dienst steht momentan nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.", + + // "500.link.home-page": "Take me to the home page", + "500.link.home-page": "Zur Startseite", + + // "admin.access-control.epeople.breadcrumbs": "EPeople", "admin.access-control.epeople.breadcrumbs": "Personen suchen", From 48ec0cdc340ecf472688c6d606c3fb3643456663 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 9 Jul 2024 17:42:49 +0200 Subject: [PATCH 497/875] minor change: added missing periods in German translations (cherry picked from commit e26ab86dd33981ea4a9969b6043449619948a2b8) --- src/assets/i18n/de.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 8efda0390e..c154ae91c9 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -5143,10 +5143,10 @@ "submission.sections.general.deposit_error_notice": "Beim Einreichen des Items ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal.", // "submission.sections.general.deposit_success_notice": "Submission deposited successfully.", - "submission.sections.general.deposit_success_notice": "Veröffentlichung erfolgreich eingereicht", + "submission.sections.general.deposit_success_notice": "Veröffentlichung erfolgreich eingereicht.", // "submission.sections.general.discard_error_notice": "There was an issue when discarding the item, please try again later.", - "submission.sections.general.discard_error_notice": "Beim Verwerfen der Einreichung ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal", + "submission.sections.general.discard_error_notice": "Beim Verwerfen der Einreichung ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal.", // "submission.sections.general.discard_success_notice": "Submission discarded successfully.", "submission.sections.general.discard_success_notice": "Einreichung erfolgreich verworfen.", From edc738ae3964fada0c6628ce1732f047ae24a603 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 23 Sep 2024 15:53:55 -0400 Subject: [PATCH 498/875] Update en.json5 - fixed typo for occurred (cherry picked from commit ed5ac47f886fb0e2ad7ef29c2152abcbe414cca1) --- src/assets/i18n/en.json5 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 0a1804fa5c..d00e16caa0 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1240,7 +1240,7 @@ "community.edit.notifications.unauthorized": "You do not have privileges to make this change", - "community.edit.notifications.error": "An error occured while editing the community", + "community.edit.notifications.error": "An error occurred while editing the community", "community.edit.return": "Back", @@ -1448,7 +1448,7 @@ "curation.form.submit.error.head": "Running the curation task failed", - "curation.form.submit.error.content": "An error occured when trying to start the curation task.", + "curation.form.submit.error.content": "An error occurred when trying to start the curation task.", "curation.form.submit.error.invalid-handle": "Couldn't determine the handle for this object", @@ -1700,7 +1700,7 @@ "forgot-email.form.error.head": "Error when trying to reset password", - "forgot-email.form.error.content": "An error occured when attempting to reset the password for the account associated with the following email address: {{ email }}", + "forgot-email.form.error.content": "An error occurred when attempting to reset the password for the account associated with the following email address: {{ email }}", "forgot-password.title": "Forgot Password", @@ -3518,7 +3518,7 @@ "register-page.registration.error.head": "Error when trying to register email", - "register-page.registration.error.content": "An error occured when registering the following email address: {{ email }}", + "register-page.registration.error.content": "An error occurred when registering the following email address: {{ email }}", "register-page.registration.error.recaptcha": "Error when trying to authenticate with recaptcha", From fe7d2a8a7e05bcdb3b63943261ab84cfdd9614d5 Mon Sep 17 00:00:00 2001 From: Elvi Nemiz Date: Sat, 29 Jun 2024 08:14:10 +0800 Subject: [PATCH 499/875] Fix to Mobile navbar hamburger menu for base (custom) theme https://github.com/DSpace/dspace-angular/pull/2444 only fixes the DSpace theme, not the base (and custom) theme. (cherry picked from commit a3b6aef66a81e93f537bf35647be97d14f5b1472) --- src/app/navbar/navbar.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/navbar/navbar.component.scss b/src/app/navbar/navbar.component.scss index b74408f0f5..bbf5feec15 100644 --- a/src/app/navbar/navbar.component.scss +++ b/src/app/navbar/navbar.component.scss @@ -10,7 +10,7 @@ /** Mobile menu styling **/ @media screen and (max-width: map-get($grid-breakpoints, md)-0.02) { .navbar { - width: 100%; + width: 100vw; background-color: var(--bs-white); position: absolute; overflow: hidden; From 751d689ff65b15fa4f8b2f898d43ecaa682e8085 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Fri, 27 Sep 2024 10:02:26 +0200 Subject: [PATCH 500/875] 118220: Add additional TypeDocs --- src/app/shared/live-region/live-region.component.ts | 6 ++++++ src/app/shared/live-region/live-region.service.ts | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/app/shared/live-region/live-region.component.ts b/src/app/shared/live-region/live-region.component.ts index d7bd5eb806..8a25c4657c 100644 --- a/src/app/shared/live-region/live-region.component.ts +++ b/src/app/shared/live-region/live-region.component.ts @@ -2,6 +2,12 @@ import { Component, OnInit } from '@angular/core'; import { LiveRegionService } from './live-region.service'; import { Observable } from 'rxjs'; +/** + * The Live Region Component is an accessibility tool for screenreaders. When a change occurs on a page when the changed + * section is not in focus, a message should be displayed by this component so it can be announced by a screen reader. + * + * This component should not be used directly. Use the {@link LiveRegionService} to add messages. + */ @Component({ selector: `ds-live-region`, templateUrl: './live-region.component.html', diff --git a/src/app/shared/live-region/live-region.service.ts b/src/app/shared/live-region/live-region.service.ts index d89d72a967..72940c1a0e 100644 --- a/src/app/shared/live-region/live-region.service.ts +++ b/src/app/shared/live-region/live-region.service.ts @@ -3,6 +3,10 @@ import { BehaviorSubject } from 'rxjs'; import { environment } from '../../../environments/environment'; import { UUIDService } from '../../core/shared/uuid.service'; +/** + * The LiveRegionService is responsible for handling the messages that are shown by the {@link LiveRegionComponent}. + * Use this service to add or remove messages to the Live Region. + */ @Injectable({ providedIn: 'root', }) @@ -76,7 +80,6 @@ export class LiveRegionService { /** * Removes the message with the given UUID from the messages array * @param uuid The uuid of the message to clear - * @protected */ clearMessageByUUID(uuid: string) { const index = this.messages.findIndex(messageObj => messageObj.uuid === uuid); From cf54af2c22a8344d67573d7185401ade04cf5588 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Tue, 3 Sep 2024 13:43:02 +0200 Subject: [PATCH 501/875] 117803: Refactor Item Edit Bitstreams page to use HTML Table elements --- .../item-bitstreams.component.html | 11 +- .../item-bitstreams.component.scss | 20 +- .../item-edit-bitstream-bundle.component.html | 109 +++++++-- .../item-edit-bitstream-bundle.component.scss | 20 ++ .../item-edit-bitstream-bundle.component.ts | 216 +++++++++++++++++- ...-drag-and-drop-bitstream-list.component.ts | 4 + src/assets/i18n/en.json5 | 2 + 7 files changed, 336 insertions(+), 46 deletions(-) create mode 100644 src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html index 4cb9577fcb..70f6ca55e8 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html @@ -23,16 +23,7 @@
    -
    -
    -
    - - {{'item.edit.bitstreams.headers.name' | translate}} -
    -
    {{'item.edit.bitstreams.headers.description' | translate}}
    -
    {{'item.edit.bitstreams.headers.format' | translate}}
    -
    {{'item.edit.bitstreams.headers.actions' | translate}}
    -
    +
    -
    -
    - -
    - {{'item.edit.bitstreams.bundle.name' | translate:{ name: dsoNameService.getName(bundle) } }} -
    -
    -
    -
    - -
    -
    -
    - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + {{'item.edit.bitstreams.headers.name' | translate}} + + {{'item.edit.bitstreams.headers.description' | translate}} + + {{'item.edit.bitstreams.headers.format' | translate}} + + {{'item.edit.bitstreams.headers.actions' | translate}} +
    + {{'item.edit.bitstreams.bundle.name' | translate:{ name: bundleName } }} + + +
    + {{ entry.name }} + + {{ entry.description }} + + {{ (entry.format | async)?.shortDescription }} + +
    +
    + + + + + + +
    +
    +
    + +
    +
    + diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss new file mode 100644 index 0000000000..d344b1657a --- /dev/null +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss @@ -0,0 +1,20 @@ +.header-row { + color: var(--bs-table-dark-color); + background-color: var(--bs-table-dark-bg); + border-color: var(--bs-table-dark-border-color); +} + +.bundle-row { + color: var(--bs-table-head-color); + background-color: var(--bs-table-head-bg); + border-color: var(--bs-table-border-color); +} + +.row-element { + padding: 0.75em; + border-bottom: var(--bs-table-border-width) solid var(--bs-table-border-color); +} + +.bitstream-name { + font-weight: normal; +} diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index 70f4b63217..ad009e7cb7 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -5,10 +5,65 @@ import { ResponsiveColumnSizes } from '../../../../shared/responsive-table-sizes import { ResponsiveTableSizes } from '../../../../shared/responsive-table-sizes/responsive-table-sizes'; import { getItemPageRoute } from '../../../item-page-routing-paths'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { RemoteData } from 'src/app/core/data/remote-data'; +import { PaginatedList } from 'src/app/core/data/paginated-list.model'; +import { Bitstream } from 'src/app/core/shared/bitstream.model'; +import { Observable, BehaviorSubject, switchMap } from 'rxjs'; +import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; +import { FieldUpdates } from '../../../../core/data/object-updates/field-updates.model'; +import { PaginatedSearchOptions } from '../../../../shared/search/models/paginated-search-options.model'; +import { BundleDataService } from '../../../../core/data/bundle-data.service'; +import { followLink } from '../../../../shared/utils/follow-link-config.model'; +import { + getAllSucceededRemoteData, + paginatedListToArray, + getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteData +} from '../../../../core/shared/operators'; +import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; +import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; +import { map } from 'rxjs/operators'; +import { getBitstreamDownloadRoute } from '../../../../app-routing-paths'; +import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model'; +import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; + +/** + * Interface storing all the information necessary to create a row in the bitstream edit table + */ +export interface BitstreamTableEntry { + /** + * The bitstream + */ + bitstream: Bitstream, + /** + * The uuid of the Bitstream + */ + id: string, + /** + * The name of the Bitstream + */ + name: string, + /** + * The name of the Bitstream with all whitespace removed + */ + nameStripped: string, + /** + * The description of the Bitstream + */ + description: string, + /** + * Observable emitting the Format of the Bitstream + */ + format: Observable, + /** + * The download url of the Bitstream + */ + downloadUrl: string, +} @Component({ selector: 'ds-item-edit-bitstream-bundle', - styleUrls: ['../item-bitstreams.component.scss'], + styleUrls: ['../item-bitstreams.component.scss', './item-edit-bitstream-bundle.component.scss'], templateUrl: './item-edit-bitstream-bundle.component.html', }) /** @@ -17,6 +72,7 @@ import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; * (which means it'll be added to the parents html without a wrapping ds-item-edit-bitstream-bundle element) */ export class ItemEditBitstreamBundleComponent implements OnInit { + protected readonly FieldChangeType = FieldChangeType; /** * The view on the bundle information and bitstreams @@ -56,9 +112,48 @@ export class ItemEditBitstreamBundleComponent implements OnInit { */ itemPageRoute: string; + /** + * The name of the bundle + */ + bundleName: string; + + /** + * The bitstreams to show in the table + */ + bitstreamsRD$: Observable>>; + + /** + * The data to show in the table + */ + tableEntries$: Observable; + + /** + * The initial page options to use for fetching the bitstreams + */ + paginationOptions: PaginationComponentOptions; + + /** + * The current page options + */ + currentPaginationOptions$: BehaviorSubject; + + /** + * The self url of the bundle, also used when retrieving fieldUpdates + */ + bundleUrl: string; + + /** + * The updates to the current bitstreams + */ + updates$: Observable; + + constructor( protected viewContainerRef: ViewContainerRef, public dsoNameService: DSONameService, + protected bundleService: BundleDataService, + protected objectUpdatesService: ObjectUpdatesService, + protected paginationService: PaginationService, ) { } @@ -66,5 +161,124 @@ export class ItemEditBitstreamBundleComponent implements OnInit { this.bundleNameColumn = this.columnSizes.combineColumns(0, 2); this.viewContainerRef.createEmbeddedView(this.bundleView); this.itemPageRoute = getItemPageRoute(this.item); + this.bundleName = this.dsoNameService.getName(this.bundle); + this.bundleUrl = this.bundle.self; + + this.initializePagination(); + this.initializeBitstreams(); + + // this.bitstreamsRD = this. + } + + protected initializePagination() { + this.paginationOptions = Object.assign(new PaginationComponentOptions(),{ + id: this.bundleName, // This might behave unexpectedly if the item contains two bundles with the same name + currentPage: 1, + pageSize: 10 + }); + + this.currentPaginationOptions$ = new BehaviorSubject(this.paginationOptions); + + this.paginationService.getCurrentPagination(this.paginationOptions.id, this.paginationOptions) + .subscribe((pagination) => { + this.currentPaginationOptions$.next(pagination); + }); + } + + protected initializeBitstreams() { + this.bitstreamsRD$ = this.currentPaginationOptions$.pipe( + switchMap((page: PaginationComponentOptions) => { + const paginatedOptions = new PaginatedSearchOptions({ pagination: Object.assign({}, page) }); + return this.bundleService.getBitstreams(this.bundle.id, paginatedOptions, followLink('format')); + }), + ); + + this.bitstreamsRD$.pipe( + getFirstSucceededRemoteData(), + paginatedListToArray(), + ).subscribe((bitstreams) => { + this.objectUpdatesService.initialize(this.bundleUrl, bitstreams, new Date()); + }); + + this.updates$ = this.bitstreamsRD$.pipe( + getAllSucceededRemoteData(), + paginatedListToArray(), + switchMap((bitstreams) => this.objectUpdatesService.getFieldUpdatesExclusive(this.bundleUrl, bitstreams)) + ); + + this.tableEntries$ = this.bitstreamsRD$.pipe( + getAllSucceededRemoteData(), + paginatedListToArray(), + map((bitstreams) => { + return bitstreams.map((bitstream) => { + const name = this.dsoNameService.getName(bitstream); + + return { + bitstream: bitstream, + id: bitstream.uuid, + name: name, + nameStripped: this.stripWhiteSpace(name), + description: bitstream.firstMetadataValue('dc.description'), + format: bitstream.format.pipe(getFirstSucceededRemoteDataPayload()), + downloadUrl: getBitstreamDownloadRoute(bitstream), + }; + }); + }), + ); + } + + /** + * Check if a user should be allowed to remove this field + */ + canRemove(fieldUpdate: FieldUpdate): boolean { + return fieldUpdate.changeType !== FieldChangeType.REMOVE; + } + + /** + * Check if a user should be allowed to cancel the update to this field + */ + canUndo(fieldUpdate: FieldUpdate): boolean { + return fieldUpdate.changeType >= 0; + } + + /** + * Sends a new remove update for this field to the object updates service + */ + remove(bitstream: Bitstream): void { + this.objectUpdatesService.saveRemoveFieldUpdate(this.bundleUrl, bitstream); + } + + /** + * Cancels the current update for this field in the object updates service + */ + undo(bitstream: Bitstream): void { + this.objectUpdatesService.removeSingleFieldUpdate(this.bundleUrl, bitstream.uuid); + } + + getRowClass(update: FieldUpdate): string { + switch (update.changeType) { + case FieldChangeType.UPDATE: + return 'table-warning'; + case FieldChangeType.ADD: + return 'table-success'; + case FieldChangeType.REMOVE: + return 'table-danger'; + default: + return 'bg-white'; + } + } + + /** + * Returns a string equal to the input string with all whitespace removed. + * @param str + */ + // Whitespace is stripped from the Bitstream names for accessibility reasons. + // To make it clear which headers are relevant for a specific field in the table, the 'headers' attribute is used to + // refer to specific headers. The Bitstream's name is used as header ID for the row containing information regarding + // that bitstream. As the 'headers' attribute contains a space-separated string of header IDs, the Bitstream's header + // ID can not contain strings itself. + stripWhiteSpace(str: string): string { + // '/\s+/g' matches all occurrences of any amount of whitespace characters + return str.replace(/\s+/g, ''); } } diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts index 2c81a4e2cb..d5bb9eceea 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts @@ -24,6 +24,10 @@ import { PaginationComponentOptions } from '../../../../../shared/pagination/pag * bitstreams within the paginated list. To drag and drop a bitstream between two pages, drag the row on top of the * page number you want the bitstream to end up at. Doing so will add the bitstream to the top of that page. */ +// NOTE: +// This component was used by the item-edit-bitstream-bundle.component, but this is no longer the case. It is left here +// as a reference for the drag-and-drop functionality. This component (and the abstract version it extends) should be +// removed once this reference is no longer useful. export class PaginatedDragAndDropBitstreamListComponent extends AbstractPaginatedDragAndDropListComponent implements OnInit { /** * The bundle to display bitstreams for diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 6c91bae4c1..4d5ef9ee1b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1934,6 +1934,8 @@ "item.edit.bitstreams.bundle.name": "BUNDLE: {{ name }}", + "item.edit.bitstreams.bundle.table.aria-label": "Bitstreams in the {{ bundle }} Bundle", + "item.edit.bitstreams.discard-button": "Discard", "item.edit.bitstreams.edit.buttons.download": "Download", From a11bfc80ad5b087aab9c6e283a08f0db2f87d5ed Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Tue, 3 Sep 2024 14:24:31 +0200 Subject: [PATCH 502/875] 117803: Hide table headers for subsequent bundle tables --- .../item-bitstreams/item-bitstreams.component.html | 3 ++- .../item-edit-bitstream-bundle.component.html | 14 +++++++++----- .../item-edit-bitstream-bundle.component.ts | 5 +++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html index 70f6ca55e8..f22dbe6a0e 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html @@ -24,10 +24,11 @@
    -
    diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html index c11035e305..a95a7921a4 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html @@ -10,7 +10,7 @@ - + - - - - diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss index d344b1657a..725d329936 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss @@ -18,3 +18,12 @@ .bitstream-name { font-weight: normal; } + +.pagination-control-container { + display: flex; +} + +.pagination-control { + padding: 0 0.1rem; + vertical-align: center; +} diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index ae6bc0876c..2a84c00c02 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -26,6 +26,8 @@ import { getBitstreamDownloadRoute } from '../../../../app-routing-paths'; import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model'; import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model'; import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { SortDirection } from '../../../../core/cache/models/sort-options.model'; +import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; /** * Interface storing all the information necessary to create a row in the bitstream edit table @@ -79,6 +81,8 @@ export class ItemEditBitstreamBundleComponent implements OnInit { */ @ViewChild('bundleView', {static: true}) bundleView; + @ViewChild(PaginationComponent) paginationComponent: PaginationComponent; + /** * The bundle to display bitstreams for */ @@ -142,6 +146,16 @@ export class ItemEditBitstreamBundleComponent implements OnInit { */ currentPaginationOptions$: BehaviorSubject; + /** + * The available page size options + */ + pageSizeOptions: number[]; + + /** + * The currently selected page size + */ + pageSize$: BehaviorSubject; + /** * The self url of the bundle, also used when retrieving fieldUpdates */ @@ -182,11 +196,15 @@ export class ItemEditBitstreamBundleComponent implements OnInit { pageSize: 10 }); + this.pageSizeOptions = this.paginationOptions.pageSizeOptions; + this.currentPaginationOptions$ = new BehaviorSubject(this.paginationOptions); + this.pageSize$ = new BehaviorSubject(this.paginationOptions.pageSize); this.paginationService.getCurrentPagination(this.paginationOptions.id, this.paginationOptions) .subscribe((pagination) => { this.currentPaginationOptions$.next(pagination); + this.pageSize$.next(pagination.pageSize); }); } @@ -286,4 +304,9 @@ export class ItemEditBitstreamBundleComponent implements OnInit { // '/\s+/g' matches all occurrences of any amount of whitespace characters return str.replace(/\s+/g, ''); } + + public doPageSizeChange(pageSize: number) { + this.paginationComponent.doPageSizeChange(pageSize); + } + } From d85124c121db1ef88f3015cfdc333bfe1b8c9ce3 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 4 Sep 2024 14:32:29 +0200 Subject: [PATCH 504/875] 117803: Fix deleted bitstreams not being removed from list --- .../item-edit-bitstream-bundle.component.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index 2a84c00c02..6ef15397b0 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -26,8 +26,8 @@ import { getBitstreamDownloadRoute } from '../../../../app-routing-paths'; import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model'; import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model'; import { PaginationService } from '../../../../core/pagination/pagination.service'; -import { SortDirection } from '../../../../core/cache/models/sort-options.model'; import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; +import { RequestService } from '../../../../core/data/request.service'; /** * Interface storing all the information necessary to create a row in the bitstream edit table @@ -173,6 +173,7 @@ export class ItemEditBitstreamBundleComponent implements OnInit { protected bundleService: BundleDataService, protected objectUpdatesService: ObjectUpdatesService, protected paginationService: PaginationService, + protected requestService: RequestService, ) { } @@ -212,7 +213,14 @@ export class ItemEditBitstreamBundleComponent implements OnInit { this.bitstreamsRD$ = this.currentPaginationOptions$.pipe( switchMap((page: PaginationComponentOptions) => { const paginatedOptions = new PaginatedSearchOptions({ pagination: Object.assign({}, page) }); - return this.bundleService.getBitstreams(this.bundle.id, paginatedOptions, followLink('format')); + return this.bundleService.getBitstreamsEndpoint(this.bundle.id, paginatedOptions).pipe( + switchMap((href) => this.requestService.hasByHref$(href)), + switchMap(() => this.bundleService.getBitstreams( + this.bundle.id, + paginatedOptions, + followLink('format') + )) + ); }), ); From 374a9ae14eb036c1bf91eec2891aa722722545ed Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 4 Sep 2024 15:46:04 +0200 Subject: [PATCH 505/875] 117803: Add item-bitstream service --- .../item-bitstreams.component.ts | 82 ++------- .../item-bitstreams.service.ts | 158 ++++++++++++++++++ .../item-edit-bitstream-bundle.component.ts | 41 +---- 3 files changed, 176 insertions(+), 105 deletions(-) create mode 100644 src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts 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 ee53bd919c..9f27cf11b3 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 @@ -8,23 +8,19 @@ import { ActivatedRoute, Router } from '@angular/router'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; -import { hasValue, isNotEmpty } from '../../../shared/empty.util'; +import { hasValue } from '../../../shared/empty.util'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { RequestService } from '../../../core/data/request.service'; import { getFirstSucceededRemoteData, getRemoteDataPayload } from '../../../core/shared/operators'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { Bundle } from '../../../core/shared/bundle.model'; -import { Bitstream } from '../../../core/shared/bitstream.model'; import { BundleDataService } from '../../../core/data/bundle-data.service'; import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model'; -import { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-sizes'; import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes'; import { NoContent } from '../../../core/shared/NoContent.model'; import { Operation } from 'fast-json-patch'; -import { FieldUpdate } from '../../../core/data/object-updates/field-update.model'; -import { FieldUpdates } from '../../../core/data/object-updates/field-updates.model'; -import { FieldChangeType } from '../../../core/data/object-updates/field-change-type.model'; +import { ItemBitstreamsService } from './item-bitstreams.service'; @Component({ selector: 'ds-item-bitstreams', @@ -41,28 +37,10 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme */ bundles$: Observable; - /** - * The page options to use for fetching the bundles - */ - bundlesOptions = { - id: 'bundles-pagination-options', - currentPage: 1, - pageSize: 9999 - } as any; - /** * The bootstrap sizes used for the columns within this table */ - columnSizes = new ResponsiveTableSizes([ - // Name column - new ResponsiveColumnSizes(2, 2, 3, 4, 4), - // Description column - new ResponsiveColumnSizes(2, 3, 3, 3, 3), - // Format column - new ResponsiveColumnSizes(2, 2, 2, 2, 2), - // Actions column - new ResponsiveColumnSizes(6, 5, 4, 3, 3) - ]); + columnSizes: ResponsiveTableSizes; /** * Are we currently submitting the changes? @@ -88,16 +66,21 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme public requestService: RequestService, public cdRef: ChangeDetectorRef, public bundleService: BundleDataService, - public zone: NgZone + public zone: NgZone, + public itemBitstreamsService: ItemBitstreamsService, ) { super(itemService, objectUpdatesService, router, notificationsService, translateService, route); + + this.columnSizes = this.itemBitstreamsService.getColumnSizes(); } /** * Actions to perform after the item has been initialized */ postItemInit(): void { - this.bundles$ = this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({pagination: this.bundlesOptions})).pipe( + const bundlesOptions = this.itemBitstreamsService.getInitialBundlesPaginationOptions(); + + this. bundles$ = this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({pagination: bundlesOptions})).pipe( getFirstSucceededRemoteData(), getRemoteDataPayload(), map((bundlePage: PaginatedList) => bundlePage.page) @@ -119,30 +102,12 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme */ submit() { this.submitting = true; - const bundlesOnce$ = this.bundles$.pipe(take(1)); - // Fetch all removed bitstreams from the object update service - const removedBitstreams$ = bundlesOnce$.pipe( - switchMap((bundles: Bundle[]) => observableZip( - ...bundles.map((bundle: Bundle) => this.objectUpdatesService.getFieldUpdates(bundle.self, [], true)) - )), - map((fieldUpdates: FieldUpdates[]) => ([] as FieldUpdate[]).concat( - ...fieldUpdates.map((updates: FieldUpdates) => Object.values(updates).filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.REMOVE)) - )), - map((fieldUpdates: FieldUpdate[]) => fieldUpdates.map((fieldUpdate: FieldUpdate) => fieldUpdate.field)) - ); - - // Send out delete requests for all deleted bitstreams - const removedResponses$: Observable> = removedBitstreams$.pipe( - take(1), - switchMap((removedBitstreams: Bitstream[]) => { - return this.bitstreamService.removeMultiple(removedBitstreams); - }) - ); + const removedResponses$ = this.itemBitstreamsService.removeMarkedBitstreams(this.bundles$); // Perform the setup actions from above in order and display notifications removedResponses$.subscribe((responses: RemoteData) => { - this.displayNotifications('item.edit.bitstreams.notifications.remove', [responses]); + this.itemBitstreamsService.displayNotifications('item.edit.bitstreams.notifications.remove', [responses]); this.submitting = false; }); } @@ -164,7 +129,7 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme } as Operation; this.bundleService.patch(bundle, [moveOperation]).pipe(take(1)).subscribe((response: RemoteData) => { this.zone.run(() => { - this.displayNotifications('item.edit.bitstreams.notifications.move', [response]); + this.itemBitstreamsService.displayNotifications('item.edit.bitstreams.notifications.move', [response]); // Remove all cached requests from this bundle and call the event's callback when the requests are cleared this.requestService.removeByHrefSubstring(bundle.self).pipe( filter((isCached) => isCached), @@ -176,27 +141,6 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme }); } - /** - * Display notifications - * - Error notification for each failed response with their message - * - Success notification in case there's at least one successful response - * @param key The i18n key for the notification messages - * @param responses The returned responses to display notifications for - */ - displayNotifications(key: string, responses: RemoteData[]) { - if (isNotEmpty(responses)) { - const failedResponses = responses.filter((response: RemoteData) => hasValue(response) && response.hasFailed); - const successfulResponses = responses.filter((response: RemoteData) => hasValue(response) && response.hasSucceeded); - - failedResponses.forEach((response: RemoteData) => { - this.notificationsService.error(this.translateService.instant(`${key}.failed.title`), response.errorMessage); - }); - if (successfulResponses.length > 0) { - this.notificationsService.success(this.translateService.instant(`${key}.saved.title`), this.translateService.instant(`${key}.saved.content`)); - } - } - } - /** * Request the object updates service to discard all current changes to this item * Shows a notification to remind the user that they can undo this diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts new file mode 100644 index 0000000000..487df77b28 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts @@ -0,0 +1,158 @@ +import { Injectable } from '@angular/core'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes'; +import { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-sizes'; +import { RemoteData } from '../../../core/data/remote-data'; +import { isNotEmpty, hasValue } from '../../../shared/empty.util'; +import { Bundle } from '../../../core/shared/bundle.model'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, zip as observableZip } from 'rxjs'; +import { NoContent } from '../../../core/shared/NoContent.model'; +import { take, switchMap, map } from 'rxjs/operators'; +import { FieldUpdates } from '../../../core/data/object-updates/field-updates.model'; +import { FieldUpdate } from '../../../core/data/object-updates/field-update.model'; +import { FieldChangeType } from '../../../core/data/object-updates/field-change-type.model'; +import { Bitstream } from '../../../core/shared/bitstream.model'; +import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; +import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; +import { BitstreamTableEntry } from './item-edit-bitstream-bundle/item-edit-bitstream-bundle.component'; +import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; +import { getBitstreamDownloadRoute } from '../../../app-routing-paths'; +import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; + +@Injectable( + { providedIn: 'root' }, +) +export class ItemBitstreamsService { + + constructor( + protected notificationsService: NotificationsService, + protected translateService: TranslateService, + protected objectUpdatesService: ObjectUpdatesService, + protected bitstreamService: BitstreamDataService, + protected dsoNameService: DSONameService, + ) { + } + + /** + * Returns the pagination options to use when fetching the bundles + */ + getInitialBundlesPaginationOptions(): PaginationComponentOptions { + return Object.assign(new PaginationComponentOptions(), { + id: 'bundles-pagination-options', + currentPage: 1, + pageSize: 9999 + }); + } + + getInitialBitstreamsPaginationOptions(bundleName: string): PaginationComponentOptions { + return Object.assign(new PaginationComponentOptions(),{ + id: bundleName, // This might behave unexpectedly if the item contains two bundles with the same name + currentPage: 1, + pageSize: 10 + }); + } + + /** + * Returns the {@link ResponsiveTableSizes} for use in the columns of the edit bitstreams table + */ + getColumnSizes(): ResponsiveTableSizes { + return new ResponsiveTableSizes([ + // Name column + new ResponsiveColumnSizes(2, 2, 3, 4, 4), + // Description column + new ResponsiveColumnSizes(2, 3, 3, 3, 3), + // Format column + new ResponsiveColumnSizes(2, 2, 2, 2, 2), + // Actions column + new ResponsiveColumnSizes(6, 5, 4, 3, 3) + ]); + } + + /** + * Display notifications + * - Error notification for each failed response with their message + * - Success notification in case there's at least one successful response + * @param key The i18n key for the notification messages + * @param responses The returned responses to display notifications for + */ + displayNotifications(key: string, responses: RemoteData[]) { + if (isNotEmpty(responses)) { + const failedResponses = responses.filter((response: RemoteData) => hasValue(response) && response.hasFailed); + const successfulResponses = responses.filter((response: RemoteData) => hasValue(response) && response.hasSucceeded); + + failedResponses.forEach((response: RemoteData) => { + this.notificationsService.error(this.translateService.instant(`${key}.failed.title`), response.errorMessage); + }); + if (successfulResponses.length > 0) { + this.notificationsService.success(this.translateService.instant(`${key}.saved.title`), this.translateService.instant(`${key}.saved.content`)); + } + } + } + + /** + * Removes the bitstreams marked for deletion from the Bundles emitted by the provided observable. + * @param bundles$ + */ + removeMarkedBitstreams(bundles$: Observable): Observable> { + const bundlesOnce$ = bundles$.pipe(take(1)); + + // Fetch all removed bitstreams from the object update service + const removedBitstreams$ = bundlesOnce$.pipe( + switchMap((bundles: Bundle[]) => observableZip( + ...bundles.map((bundle: Bundle) => this.objectUpdatesService.getFieldUpdates(bundle.self, [], true)) + )), + map((fieldUpdates: FieldUpdates[]) => ([] as FieldUpdate[]).concat( + ...fieldUpdates.map((updates: FieldUpdates) => Object.values(updates).filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.REMOVE)) + )), + map((fieldUpdates: FieldUpdate[]) => fieldUpdates.map((fieldUpdate: FieldUpdate) => fieldUpdate.field)) + ); + + // Send out delete requests for all deleted bitstreams + return removedBitstreams$.pipe( + take(1), + switchMap((removedBitstreams: Bitstream[]) => { + return this.bitstreamService.removeMultiple(removedBitstreams); + }) + ); + } + + mapBitstreamsToTableEntries(bitstreams: Bitstream[]): BitstreamTableEntry[] { + return bitstreams.map((bitstream) => { + const name = this.dsoNameService.getName(bitstream); + + return { + bitstream: bitstream, + id: bitstream.uuid, + name: name, + nameStripped: this.nameToHeader(name), + description: bitstream.firstMetadataValue('dc.description'), + format: bitstream.format.pipe(getFirstSucceededRemoteDataPayload()), + downloadUrl: getBitstreamDownloadRoute(bitstream), + }; + }); + } + + /** + * Returns a string appropriate to be used as header ID + * @param name + */ + nameToHeader(name: string): string { + // Whitespace is stripped from the Bitstream names for accessibility reasons. + // To make it clear which headers are relevant for a specific field in the table, the 'headers' attribute is used to + // refer to specific headers. The Bitstream's name is used as header ID for the row containing information regarding + // that bitstream. As the 'headers' attribute contains a space-separated string of header IDs, the Bitstream's header + // ID can not contain strings itself. + return this.stripWhiteSpace(name); + } + + /** + * Returns a string equal to the input string with all whitespace removed. + * @param str + */ + stripWhiteSpace(str: string): string { + // '/\s+/g' matches all occurrences of any amount of whitespace characters + return str.replace(/\s+/g, ''); + } +} diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index 6ef15397b0..9c62fe06e7 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -17,17 +17,17 @@ import { followLink } from '../../../../shared/utils/follow-link-config.model'; import { getAllSucceededRemoteData, paginatedListToArray, - getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteData + getFirstSucceededRemoteData } from '../../../../core/shared/operators'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; import { map } from 'rxjs/operators'; -import { getBitstreamDownloadRoute } from '../../../../app-routing-paths'; import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model'; import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model'; import { PaginationService } from '../../../../core/pagination/pagination.service'; import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; import { RequestService } from '../../../../core/data/request.service'; +import { ItemBitstreamsService } from '../item-bitstreams.service'; /** * Interface storing all the information necessary to create a row in the bitstream edit table @@ -174,6 +174,7 @@ export class ItemEditBitstreamBundleComponent implements OnInit { protected objectUpdatesService: ObjectUpdatesService, protected paginationService: PaginationService, protected requestService: RequestService, + protected itemBitstreamsService: ItemBitstreamsService, ) { } @@ -191,11 +192,7 @@ export class ItemEditBitstreamBundleComponent implements OnInit { } protected initializePagination() { - this.paginationOptions = Object.assign(new PaginationComponentOptions(),{ - id: this.bundleName, // This might behave unexpectedly if the item contains two bundles with the same name - currentPage: 1, - pageSize: 10 - }); + this.paginationOptions = this.itemBitstreamsService.getInitialBitstreamsPaginationOptions(this.bundleName); this.pageSizeOptions = this.paginationOptions.pageSizeOptions; @@ -240,21 +237,7 @@ export class ItemEditBitstreamBundleComponent implements OnInit { this.tableEntries$ = this.bitstreamsRD$.pipe( getAllSucceededRemoteData(), paginatedListToArray(), - map((bitstreams) => { - return bitstreams.map((bitstream) => { - const name = this.dsoNameService.getName(bitstream); - - return { - bitstream: bitstream, - id: bitstream.uuid, - name: name, - nameStripped: this.stripWhiteSpace(name), - description: bitstream.firstMetadataValue('dc.description'), - format: bitstream.format.pipe(getFirstSucceededRemoteDataPayload()), - downloadUrl: getBitstreamDownloadRoute(bitstream), - }; - }); - }), + map((bitstreams) => this.itemBitstreamsService.mapBitstreamsToTableEntries(bitstreams)), ); } @@ -299,20 +282,6 @@ export class ItemEditBitstreamBundleComponent implements OnInit { } } - /** - * Returns a string equal to the input string with all whitespace removed. - * @param str - */ - // Whitespace is stripped from the Bitstream names for accessibility reasons. - // To make it clear which headers are relevant for a specific field in the table, the 'headers' attribute is used to - // refer to specific headers. The Bitstream's name is used as header ID for the row containing information regarding - // that bitstream. As the 'headers' attribute contains a space-separated string of header IDs, the Bitstream's header - // ID can not contain strings itself. - stripWhiteSpace(str: string): string { - // '/\s+/g' matches all occurrences of any amount of whitespace characters - return str.replace(/\s+/g, ''); - } - public doPageSizeChange(pageSize: number) { this.paginationComponent.doPageSizeChange(pageSize); } From 3f4bf7ce0fdf73494bcb28045ef6d7cb8a4634dd Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 4 Sep 2024 16:32:30 +0200 Subject: [PATCH 506/875] 117803: Fix existing tests --- ...em-edit-bitstream-bundle.component.spec.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts index c26f99eb8f..1502ad2311 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts @@ -6,6 +6,15 @@ import { Item } from '../../../../core/shared/item.model'; import { Bundle } from '../../../../core/shared/bundle.model'; import { ResponsiveTableSizes } from '../../../../shared/responsive-table-sizes/responsive-table-sizes'; import { ResponsiveColumnSizes } from '../../../../shared/responsive-table-sizes/responsive-column-sizes'; +import { BundleDataService } from '../../../../core/data/bundle-data.service'; +import { of as observableOf } from 'rxjs'; +import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; +import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; +import { RequestService } from '../../../../core/data/request.service'; +import { getMockRequestService } from '../../../../shared/mocks/request.service.mock'; +import { ItemBitstreamsService } from '../item-bitstreams.service'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; describe('ItemEditBitstreamBundleComponent', () => { let comp: ItemEditBitstreamBundleComponent; @@ -31,10 +40,37 @@ describe('ItemEditBitstreamBundleComponent', () => { } }); + const restEndpoint = 'fake-rest-endpoint'; + const bundleService = jasmine.createSpyObj('bundleService', { + getBitstreamsEndpoint: observableOf(restEndpoint), + getBitstreams: null, + }); + + const objectUpdatesService = { + initialize: () => { + // do nothing + }, + }; + + const itemBitstreamsService = jasmine.createSpyObj('itemBitstreamsService', { + getInitialBitstreamsPaginationOptions: Object.assign(new PaginationComponentOptions(), { + id: 'bundles-pagination-options', + currentPage: 1, + pageSize: 9999 + }), + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], declarations: [ItemEditBitstreamBundleComponent], + providers: [ + { provide: BundleDataService, useValue: bundleService }, + { provide: ObjectUpdatesService, useValue: objectUpdatesService }, + { provide: PaginationService, useValue: new PaginationServiceStub() }, + { provide: RequestService, useValue: getMockRequestService() }, + { provide: ItemBitstreamsService, useValue: itemBitstreamsService }, + ], schemas: [ NO_ERRORS_SCHEMA ] From d8b426d7458da47320447a85e3c2fb4c7028f20c Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Thu, 5 Sep 2024 10:52:02 +0200 Subject: [PATCH 507/875] 117803: Add ItemBitsreamsService tests --- .../object-updates.service.stub.ts | 28 ++++ .../item-bitstreams.service.spec.ts | 129 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 src/app/core/data/object-updates/object-updates.service.stub.ts create mode 100644 src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts diff --git a/src/app/core/data/object-updates/object-updates.service.stub.ts b/src/app/core/data/object-updates/object-updates.service.stub.ts new file mode 100644 index 0000000000..c41728a338 --- /dev/null +++ b/src/app/core/data/object-updates/object-updates.service.stub.ts @@ -0,0 +1,28 @@ +export class ObjectUpdatesServiceStub { + + initialize = jasmine.createSpy('initialize'); + saveFieldUpdate = jasmine.createSpy('saveFieldUpdate'); + getObjectEntry = jasmine.createSpy('getObjectEntry'); + getFieldState = jasmine.createSpy('getFieldState'); + getFieldUpdates = jasmine.createSpy('getFieldUpdates'); + getFieldUpdatesExclusive = jasmine.createSpy('getFieldUpdatesExclusive'); + isValid = jasmine.createSpy('isValid'); + isValidPage = jasmine.createSpy('isValidPage'); + saveAddFieldUpdate = jasmine.createSpy('saveAddFieldUpdate'); + saveRemoveFieldUpdate = jasmine.createSpy('saveRemoveFieldUpdate'); + saveChangeFieldUpdate = jasmine.createSpy('saveChangeFieldUpdate'); + isSelectedVirtualMetadata = jasmine.createSpy('isSelectedVirtualMetadata'); + setSelectedVirtualMetadata = jasmine.createSpy('setSelectedVirtualMetadata'); + setEditableFieldUpdate = jasmine.createSpy('setEditableFieldUpdate'); + setValidFieldUpdate = jasmine.createSpy('setValidFieldUpdate'); + discardFieldUpdates = jasmine.createSpy('discardFieldUpdates'); + discardAllFieldUpdates = jasmine.createSpy('discardAllFieldUpdates'); + reinstateFieldUpdates = jasmine.createSpy('reinstateFieldUpdates'); + removeSingleFieldUpdate = jasmine.createSpy('removeSingleFieldUpdate'); + getUpdateFields = jasmine.createSpy('getUpdateFields'); + hasUpdates = jasmine.createSpy('hasUpdates'); + isReinstatable = jasmine.createSpy('isReinstatable'); + getLastModified = jasmine.createSpy('getLastModified'); + createPatch = jasmine.createSpy('getPatch'); + +} diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts new file mode 100644 index 0000000000..89ecfb518f --- /dev/null +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts @@ -0,0 +1,129 @@ +import { ItemBitstreamsService } from './item-bitstreams.service'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; +import { getMockTranslateService } from '../../../shared/mocks/translate.service.mock'; +import { ObjectUpdatesServiceStub } from '../../../core/data/object-updates/object-updates.service.stub'; +import { BitstreamDataServiceStub } from '../../../shared/testing/bitstream-data-service.stub'; +import { DSONameServiceMock } from '../../../shared/mocks/dso-name.service.mock'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; +import { TranslateService } from '@ngx-translate/core'; +import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; +import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; +import { Bitstream } from '../../../core/shared/bitstream.model'; +import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; +import { + createSuccessfulRemoteDataObject$, + createFailedRemoteDataObject, + createSuccessfulRemoteDataObject +} from '../../../shared/remote-data.utils'; + +describe('ItemBitstreamsService', () => { + let service: ItemBitstreamsService; + let notificationsService: NotificationsService; + let translateService: TranslateService; + let objectUpdatesService: ObjectUpdatesService; + let bitstreamDataService: BitstreamDataService; + let dsoNameService: DSONameService; + + beforeEach(() => { + notificationsService = new NotificationsServiceStub() as any; + translateService = getMockTranslateService(); + objectUpdatesService = new ObjectUpdatesServiceStub() as any; + bitstreamDataService = new BitstreamDataServiceStub() as any; + dsoNameService = new DSONameServiceMock() as any; + + service = new ItemBitstreamsService( + notificationsService, + translateService, + objectUpdatesService, + bitstreamDataService, + dsoNameService, + ); + }); + + describe('displayNotifications', () => { + it('should display an error notification if a response failed', () => { + const responses = [ + createFailedRemoteDataObject(), + ]; + + const key = 'some.key'; + + service.displayNotifications(key, responses); + + expect(notificationsService.success).not.toHaveBeenCalled(); + expect(notificationsService.error).toHaveBeenCalled(); + expect(translateService.instant).toHaveBeenCalledWith('some.key.failed.title'); + }); + + it('should display a success notification if a response succeeded', () => { + const responses = [ + createSuccessfulRemoteDataObject(undefined), + ]; + + const key = 'some.key'; + + service.displayNotifications(key, responses); + + expect(notificationsService.success).toHaveBeenCalled(); + expect(notificationsService.error).not.toHaveBeenCalled(); + expect(translateService.instant).toHaveBeenCalledWith('some.key.saved.title'); + }); + + it('should display both notifications if some failed and some succeeded', () => { + const responses = [ + createFailedRemoteDataObject(), + createSuccessfulRemoteDataObject(undefined), + ]; + + const key = 'some.key'; + + service.displayNotifications(key, responses); + + expect(notificationsService.success).toHaveBeenCalled(); + expect(notificationsService.error).toHaveBeenCalled(); + expect(translateService.instant).toHaveBeenCalledWith('some.key.saved.title'); + expect(translateService.instant).toHaveBeenCalledWith('some.key.saved.title'); + }); + }); + + describe('mapBitstreamsToTableEntries', () => { + it('should correctly map a Bitstream to a BitstreamTableEntry', () => { + const format: BitstreamFormat = new BitstreamFormat(); + + const bitstream: Bitstream = Object.assign(new Bitstream(), { + uuid: 'testUUID', + format: createSuccessfulRemoteDataObject$(format), + }); + + spyOn(dsoNameService, 'getName').and.returnValue('Test Name'); + spyOn(bitstream, 'firstMetadataValue').and.returnValue('description'); + + const tableEntry = service.mapBitstreamsToTableEntries([bitstream])[0]; + + expect(tableEntry.name).toEqual('Test Name'); + expect(tableEntry.nameStripped).toEqual('TestName'); + expect(tableEntry.bitstream).toBe(bitstream); + expect(tableEntry.id).toEqual('testUUID'); + expect(tableEntry.description).toEqual('description'); + expect(tableEntry.downloadUrl).toEqual('/bitstreams/testUUID/download'); + }); + }); + + describe('nameToHeader', () => { + it('should correctly transform a string to an appropriate header ID', () => { + const stringA = 'Test String'; + const stringAResult = 'TestString'; + expect(service.nameToHeader(stringA)).toEqual(stringAResult); + + const stringB = 'Test String Two'; + const stringBResult = 'TestStringTwo'; + expect(service.nameToHeader(stringB)).toEqual(stringBResult); + + const stringC = 'Test String Three'; + const stringCResult = 'TestStringThree'; + expect(service.nameToHeader(stringC)).toEqual(stringCResult); + }); + }); + +}); From cc5b841a65ee396a2622ca3eceb1f2d9bbce07c7 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Tue, 10 Sep 2024 13:36:27 +0200 Subject: [PATCH 508/875] 117803: Only visually hide header rows --- .../item-edit-bitstream-bundle.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html index 6a505b9274..2eb1a151b7 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html @@ -10,7 +10,7 @@
    {{'item.edit.bitstreams.headers.name' | translate}} @@ -45,16 +45,20 @@
    + {{ entry.name }} + {{ entry.description }} + {{ (entry.format | async)?.shortDescription }} + - + +
    + +
    + + + +
    +
    +
    - +
    {{'item.edit.bitstreams.headers.name' | translate}} From 8481604b1eb63acdc152fc9b7061bb9cd464294e Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Tue, 10 Sep 2024 14:14:58 +0200 Subject: [PATCH 509/875] 117803: Fix table & pagination margin --- .../item-bitstreams/item-bitstreams.component.scss | 4 ++++ .../item-edit-bitstream-bundle.component.scss | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.scss b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.scss index 480bf56cc7..662c999461 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.scss +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.scss @@ -42,3 +42,7 @@ .table-border { border: 1px solid #dee2e6; } + +:host ::ng-deep .pagination { + padding-top: 0.5rem; +} diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss index 725d329936..ae4eac8d52 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss @@ -27,3 +27,7 @@ padding: 0 0.1rem; vertical-align: center; } + +.table { + margin-bottom: 0; +} From 79f3a3116e2e358d5d7b778324d15c4977c7d55f Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Tue, 10 Sep 2024 14:35:05 +0200 Subject: [PATCH 510/875] 117803: Set header border to background color --- .../item-edit-bitstream-bundle.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss index ae4eac8d52..7088c3978e 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss @@ -1,7 +1,7 @@ .header-row { color: var(--bs-table-dark-color); background-color: var(--bs-table-dark-bg); - border-color: var(--bs-table-dark-border-color); + border-color: var(--bs-table-dark-bg); } .bundle-row { From 6a8095d456167a139d320c4ad5c3a3d8a20a365e Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Tue, 10 Sep 2024 15:00:18 +0200 Subject: [PATCH 511/875] 117803: Change pagination settings styling --- .../item-edit-bitstream-bundle.component.html | 4 ++-- .../item-edit-bitstream-bundle.component.scss | 9 --------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html index 2eb1a151b7..f434bf0f8f 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html @@ -41,8 +41,8 @@ title="{{'item.edit.bitstreams.bundle.edit.buttons.upload' | translate}}"> -
    - diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss index 7088c3978e..bbd4e1e75c 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.scss @@ -19,15 +19,6 @@ font-weight: normal; } -.pagination-control-container { - display: flex; -} - -.pagination-control { - padding: 0 0.1rem; - vertical-align: center; -} - .table { margin-bottom: 0; } From a230eee76d931a5b55046587c0fe06fcdd383a90 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Tue, 10 Sep 2024 16:06:44 +0200 Subject: [PATCH 512/875] 117803: Add negative top-margin to subsequent tables --- .../item-bitstreams/item-bitstreams.component.html | 2 +- .../item-edit-bitstream-bundle.component.html | 4 ++-- .../item-edit-bitstream-bundle.component.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html index f22dbe6a0e..3527f2f5b8 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html @@ -28,7 +28,7 @@ [bundle]="bundle" [item]="item" [columnSizes]="columnSizes" - [hideHeader]="!isFirst" + [isFirstTable]="isFirst" (dropObject)="dropBitstream(bundle, $event)">
    diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html index f434bf0f8f..3d6ee4fa47 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html @@ -8,9 +8,9 @@ [collectionSize]="bitstreamsList.totalElements"> - - + - + - + - + - + + (cdkDragStarted)="dragStart(entry.name)" (cdkDragEnded)="dragEnd(entry.name)">
    {{'item.edit.bitstreams.headers.name' | translate}} diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index 9c62fe06e7..0974da3f8c 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -99,9 +99,9 @@ export class ItemEditBitstreamBundleComponent implements OnInit { @Input() columnSizes: ResponsiveTableSizes; /** - * Whether to hide the table headers + * Whether this is the first in a series of bundle tables */ - @Input() hideHeader = false; + @Input() isFirstTable = false; /** * Send an event when the user drops an object on the pagination From be99cc5c23763240869697798b95608e4ff1e319 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 11 Sep 2024 10:09:03 +0200 Subject: [PATCH 513/875] 118219: Allow dragging of table rows --- .../item-bitstreams/item-bitstreams.component.scss | 7 ------- .../item-edit-bitstream-bundle.component.html | 7 +++++-- .../item-edit-bitstream-bundle.component.ts | 5 +++++ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.scss b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.scss index 662c999461..0ee56fe67e 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.scss +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.scss @@ -1,7 +1,4 @@ - - .drag-handle { - visibility: hidden; &:hover { cursor: move; } @@ -11,10 +8,6 @@ cursor: move; } -:host ::ng-deep .bitstream-row:hover .drag-handle, :host ::ng-deep .bitstream-row-drag-handle:focus .drag-handle { - visibility: visible !important; -} - .cdk-drag-preview { margin-left: 0; box-sizing: border-box; diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html index 3d6ee4fa47..b79518a763 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html @@ -27,7 +27,7 @@
    {{'item.edit.bitstreams.bundle.name' | translate:{ name: bundleName } }} @@ -65,10 +65,13 @@
    +
    + +
    {{ entry.name }}
    ) { + console.log('dropEvent:', event); + } + } From eadbcdbe14108b6c47f9e623e8b99c10d03ff5bd Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 11 Sep 2024 16:25:42 +0200 Subject: [PATCH 514/875] 118219: Store result of drag & dropping bitstream --- .../item-bitstreams.component.ts | 15 +++-- .../item-edit-bitstream-bundle.component.ts | 60 ++++++++++++++++--- 2 files changed, 62 insertions(+), 13 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 9f27cf11b3..e7c846b5da 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 @@ -1,6 +1,6 @@ import { ChangeDetectorRef, Component, NgZone, OnDestroy } from '@angular/core'; import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component'; -import { filter, map, switchMap, take } from 'rxjs/operators'; +import { map, switchMap, take } from 'rxjs/operators'; import { Observable, Subscription, zip as observableZip } from 'rxjs'; import { ItemDataService } from '../../../core/data/item-data.service'; import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; @@ -11,7 +11,11 @@ import { BitstreamDataService } from '../../../core/data/bitstream-data.service' import { hasValue } from '../../../shared/empty.util'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { RequestService } from '../../../core/data/request.service'; -import { getFirstSucceededRemoteData, getRemoteDataPayload } from '../../../core/shared/operators'; +import { + getFirstSucceededRemoteData, + getRemoteDataPayload, + getFirstCompletedRemoteData +} from '../../../core/shared/operators'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { Bundle } from '../../../core/shared/bundle.model'; @@ -127,12 +131,13 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme from: `/_links/bitstreams/${event.fromIndex}/href`, path: `/_links/bitstreams/${event.toIndex}/href` } as Operation; - this.bundleService.patch(bundle, [moveOperation]).pipe(take(1)).subscribe((response: RemoteData) => { + this.bundleService.patch(bundle, [moveOperation]).pipe( + getFirstCompletedRemoteData(), + ).subscribe((response: RemoteData) => { this.zone.run(() => { this.itemBitstreamsService.displayNotifications('item.edit.bitstreams.notifications.move', [response]); // Remove all cached requests from this bundle and call the event's callback when the requests are cleared - this.requestService.removeByHrefSubstring(bundle.self).pipe( - filter((isCached) => isCached), + this.requestService.setStaleByHrefSubstring(bundle.self).pipe( take(1) ).subscribe(() => event.finish()); }); diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index c0c4e30231..0293a1daea 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -28,7 +28,8 @@ import { PaginationService } from '../../../../core/pagination/pagination.servic import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; import { RequestService } from '../../../../core/data/request.service'; import { ItemBitstreamsService } from '../item-bitstreams.service'; -import { CdkDragDrop } from '@angular/cdk/drag-drop'; +import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; +import { hasValue } from '../../../../shared/empty.util'; /** * Interface storing all the information necessary to create a row in the bitstream edit table @@ -135,7 +136,7 @@ export class ItemEditBitstreamBundleComponent implements OnInit { /** * The data to show in the table */ - tableEntries$: Observable; + tableEntries$: BehaviorSubject = new BehaviorSubject(null); /** * The initial page options to use for fetching the bitstreams @@ -165,7 +166,7 @@ export class ItemEditBitstreamBundleComponent implements OnInit { /** * The updates to the current bitstreams */ - updates$: Observable; + updates$: BehaviorSubject = new BehaviorSubject(null); constructor( @@ -229,17 +230,17 @@ export class ItemEditBitstreamBundleComponent implements OnInit { this.objectUpdatesService.initialize(this.bundleUrl, bitstreams, new Date()); }); - this.updates$ = this.bitstreamsRD$.pipe( + this.bitstreamsRD$.pipe( getAllSucceededRemoteData(), paginatedListToArray(), switchMap((bitstreams) => this.objectUpdatesService.getFieldUpdatesExclusive(this.bundleUrl, bitstreams)) - ); + ).subscribe((updates) => this.updates$.next(updates)); - this.tableEntries$ = this.bitstreamsRD$.pipe( + this.bitstreamsRD$.pipe( getAllSucceededRemoteData(), paginatedListToArray(), map((bitstreams) => this.itemBitstreamsService.mapBitstreamsToTableEntries(bitstreams)), - ); + ).subscribe((tableEntries) => this.tableEntries$.next(tableEntries)); } /** @@ -288,7 +289,50 @@ export class ItemEditBitstreamBundleComponent implements OnInit { } drop(event: CdkDragDrop) { - console.log('dropEvent:', event); + const dragIndex = event.previousIndex; + let dropIndex = event.currentIndex; + const dragPage = this.currentPaginationOptions$.value.currentPage - 1; + let dropPage = this.currentPaginationOptions$.value.currentPage - 1; + + // Check if the user is hovering over any of the pagination's pages at the time of dropping the object + const droppedOnElement = document.elementFromPoint(event.dropPoint.x, event.dropPoint.y); + if (hasValue(droppedOnElement) && hasValue(droppedOnElement.textContent) && droppedOnElement.classList.contains('page-link')) { + // The user is hovering over a page, fetch the page's number from the element + const droppedPage = Number(droppedOnElement.textContent); + if (hasValue(droppedPage) && !Number.isNaN(droppedPage)) { + dropPage = droppedPage - 1; + dropIndex = 0; + } + } + + const isNewPage = dragPage !== dropPage; + // Move the object in the custom order array if the drop happened within the same page + // This allows us to instantly display a change in the order, instead of waiting for the REST API's response first + if (!isNewPage && dragIndex !== dropIndex) { + const currentEntries = [...this.tableEntries$.value]; + moveItemInArray(currentEntries, dragIndex, dropIndex); + this.tableEntries$.next(currentEntries); + } + + const pageSize = this.currentPaginationOptions$.value.pageSize; + const redirectPage = dropPage + 1; + const fromIndex = (dragPage * pageSize) + dragIndex; + const toIndex = (dropPage * pageSize) + dropIndex; + // Send out a drop event (and navigate to the new page) when the "from" and "to" indexes are different from each other + if (fromIndex !== toIndex) { + // if (isNewPage) { + // this.loading$.next(true); + // } + this.dropObject.emit(Object.assign({ + fromIndex, + toIndex, + finish: () => { + if (isNewPage) { + this.paginationComponent.doPageChange(redirectPage); + } + } + })); + } } } From 6a2c7d09d69e6275d5e29d06a2672ce9b79e52fb Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Thu, 12 Sep 2024 10:01:55 +0200 Subject: [PATCH 515/875] 118219: Add dragging tooltip explaining how to drag to other page --- .../item-edit-bitstream-bundle.component.html | 7 +++-- .../item-edit-bitstream-bundle.component.ts | 30 ++++++++++++++++++- src/assets/i18n/en.json5 | 2 ++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html index b79518a763..0338055df5 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html @@ -27,7 +27,9 @@
    {{'item.edit.bitstreams.bundle.name' | translate:{ name: bundleName } }} @@ -65,7 +67,8 @@
    diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index 0293a1daea..bd99fc1a09 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -21,7 +21,7 @@ import { } from '../../../../core/shared/operators'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; -import { map } from 'rxjs/operators'; +import { map, take, filter } from 'rxjs/operators'; import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model'; import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model'; import { PaginationService } from '../../../../core/pagination/pagination.service'; @@ -83,8 +83,16 @@ export class ItemEditBitstreamBundleComponent implements OnInit { */ @ViewChild('bundleView', {static: true}) bundleView; + /** + * The view on the pagination component + */ @ViewChild(PaginationComponent) paginationComponent: PaginationComponent; + /** + * The view on the drag tooltip + */ + @ViewChild('dragTooltip') dragTooltip; + /** * The bundle to display bitstreams for */ @@ -158,6 +166,11 @@ export class ItemEditBitstreamBundleComponent implements OnInit { */ pageSize$: BehaviorSubject; + /** + * Whether the table has multiple pages + */ + hasMultiplePages = false; + /** * The self url of the bundle, also used when retrieving fieldUpdates */ @@ -288,6 +301,21 @@ export class ItemEditBitstreamBundleComponent implements OnInit { this.paginationComponent.doPageSizeChange(pageSize); } + dragStart() { + // Only open the drag tooltip when there are multiple pages + this.paginationComponent.shouldShowBottomPager.pipe( + take(1), + filter((hasMultiplePages) => hasMultiplePages), + ).subscribe(() => { + this.dragTooltip.open(); + }); + } + + dragEnd() { + this.dragTooltip.close(); + } + + drop(event: CdkDragDrop) { const dragIndex = event.previousIndex; let dropIndex = event.currentIndex; diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 4d5ef9ee1b..f2dbf9b2d1 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1936,6 +1936,8 @@ "item.edit.bitstreams.bundle.table.aria-label": "Bitstreams in the {{ bundle }} Bundle", + "item.edit.bitstreams.bundle.tooltip": "You can move a bitstream to a different page by dropping it on the page number.", + "item.edit.bitstreams.discard-button": "Discard", "item.edit.bitstreams.edit.buttons.download": "Download", From 8a16597b6958f99f949a413028ee2d5dee2905dc Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Thu, 12 Sep 2024 10:33:21 +0200 Subject: [PATCH 516/875] 118219: Fix tests --- .../item-bitstreams.component.spec.ts | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts index 10e1812131..6ce7394473 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts @@ -18,7 +18,6 @@ import { ObjectValuesPipe } from '../../../shared/utils/object-values-pipe'; import { VarDirective } from '../../../shared/utils/var.directive'; import { BundleDataService } from '../../../core/data/bundle-data.service'; import { Bundle } from '../../../core/shared/bundle.model'; -import { RestResponse } from '../../../core/cache/response.models'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; import { RouterStub } from '../../../shared/testing/router.stub'; import { getMockRequestService } from '../../../shared/mocks/request.service.mock'; @@ -145,7 +144,7 @@ describe('ItemBitstreamsComponent', () => { url: url }); bundleService = jasmine.createSpyObj('bundleService', { - patch: observableOf(new RestResponse(true, 200, 'OK')) + patch: createSuccessfulRemoteDataObject$({}), }); TestBed.configureTestingModule({ @@ -191,20 +190,6 @@ describe('ItemBitstreamsComponent', () => { }); }); - describe('when dropBitstream is called', () => { - const event = { - fromIndex: 0, - toIndex: 50, - // eslint-disable-next-line no-empty,@typescript-eslint/no-empty-function - finish: () => { - } - }; - - beforeEach(() => { - comp.dropBitstream(bundle, event); - }); - }); - describe('when dropBitstream is called', () => { beforeEach((done) => { comp.dropBitstream(bundle, { From a207fb51e9159c0076bb8a76cd5a73944a11655b Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Thu, 12 Sep 2024 10:38:38 +0200 Subject: [PATCH 517/875] 118219: Remove unused paginated-drag-and-drop components --- .../edit-item-page/edit-item-page.module.ts | 4 - ...rag-and-drop-bitstream-list.component.html | 33 --- ...-and-drop-bitstream-list.component.spec.ts | 150 ----------- ...-drag-and-drop-bitstream-list.component.ts | 80 ------ ...-edit-bitstream-drag-handle.component.html | 5 - ...em-edit-bitstream-drag-handle.component.ts | 26 -- ...nated-drag-and-drop-list.component.spec.ts | 136 ---------- ...-paginated-drag-and-drop-list.component.ts | 239 ------------------ 8 files changed, 673 deletions(-) delete mode 100644 src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html delete mode 100644 src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.spec.ts delete mode 100644 src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts delete mode 100644 src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component.html delete mode 100644 src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component.ts delete mode 100644 src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts delete mode 100644 src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts diff --git a/src/app/item-page/edit-item-page/edit-item-page.module.ts b/src/app/item-page/edit-item-page/edit-item-page.module.ts index 0a75394ddd..4ae5ebe666 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.module.ts @@ -26,8 +26,6 @@ import { ItemMoveComponent } from './item-move/item-move.component'; import { ItemEditBitstreamBundleComponent } from './item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component'; import { BundleDataService } from '../../core/data/bundle-data.service'; import { DragDropModule } from '@angular/cdk/drag-drop'; -import { ItemEditBitstreamDragHandleComponent } from './item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component'; -import { PaginatedDragAndDropBitstreamListComponent } from './item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component'; import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component'; import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component'; import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component'; @@ -82,12 +80,10 @@ import { ItemVersionHistoryComponent, ItemEditBitstreamComponent, ItemEditBitstreamBundleComponent, - PaginatedDragAndDropBitstreamListComponent, EditRelationshipComponent, EditRelationshipListComponent, ItemCollectionMapperComponent, ItemMoveComponent, - ItemEditBitstreamDragHandleComponent, VirtualMetadataComponent, ItemAuthorizationsComponent, IdentifierDataComponent, diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html deleted file mode 100644 index f54aa73597..0000000000 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.html +++ /dev/null @@ -1,33 +0,0 @@ - - -
    - -
    - -
    - -
    -
    -
    -
    -
    -
    - -
    diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.spec.ts deleted file mode 100644 index 7317eb93be..0000000000 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.spec.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { Bundle } from '../../../../../core/shared/bundle.model'; -import { TranslateModule } from '@ngx-translate/core'; -import { PaginatedDragAndDropBitstreamListComponent } from './paginated-drag-and-drop-bitstream-list.component'; -import { VarDirective } from '../../../../../shared/utils/var.directive'; -import { ObjectValuesPipe } from '../../../../../shared/utils/object-values-pipe'; -import { ObjectUpdatesService } from '../../../../../core/data/object-updates/object-updates.service'; -import { BundleDataService } from '../../../../../core/data/bundle-data.service'; -import { Bitstream } from '../../../../../core/shared/bitstream.model'; -import { BitstreamFormat } from '../../../../../core/shared/bitstream-format.model'; -import { of as observableOf } from 'rxjs'; -import { take } from 'rxjs/operators'; -import { ResponsiveTableSizes } from '../../../../../shared/responsive-table-sizes/responsive-table-sizes'; -import { ResponsiveColumnSizes } from '../../../../../shared/responsive-table-sizes/responsive-column-sizes'; -import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; -import { createPaginatedList } from '../../../../../shared/testing/utils.test'; -import { RequestService } from '../../../../../core/data/request.service'; -import { PaginationService } from '../../../../../core/pagination/pagination.service'; -import { PaginationServiceStub } from '../../../../../shared/testing/pagination-service.stub'; - -describe('PaginatedDragAndDropBitstreamListComponent', () => { - let comp: PaginatedDragAndDropBitstreamListComponent; - let fixture: ComponentFixture; - let objectUpdatesService: ObjectUpdatesService; - let bundleService: BundleDataService; - let objectValuesPipe: ObjectValuesPipe; - let requestService: RequestService; - let paginationService; - - const columnSizes = new ResponsiveTableSizes([ - new ResponsiveColumnSizes(2, 2, 3, 4, 4), - new ResponsiveColumnSizes(2, 3, 3, 3, 3), - new ResponsiveColumnSizes(2, 2, 2, 2, 2), - new ResponsiveColumnSizes(6, 5, 4, 3, 3) - ]); - - const bundle = Object.assign(new Bundle(), { - id: 'bundle-1', - uuid: 'bundle-1', - _links: { - self: { href: 'bundle-1-selflink' } - } - }); - const date = new Date(); - const format = Object.assign(new BitstreamFormat(), { - shortDescription: 'PDF' - }); - const bitstream1 = Object.assign(new Bitstream(), { - uuid: 'bitstreamUUID1', - name: 'Fake Bitstream 1', - bundleName: 'ORIGINAL', - description: 'Description', - format: createSuccessfulRemoteDataObject$(format) - }); - const fieldUpdate1 = { - field: bitstream1, - changeType: undefined - }; - const bitstream2 = Object.assign(new Bitstream(), { - uuid: 'bitstreamUUID2', - name: 'Fake Bitstream 2', - bundleName: 'ORIGINAL', - description: 'Description', - format: createSuccessfulRemoteDataObject$(format) - }); - const fieldUpdate2 = { - field: bitstream2, - changeType: undefined - }; - - beforeEach(waitForAsync(() => { - objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', - { - getFieldUpdates: observableOf({ - [bitstream1.uuid]: fieldUpdate1, - [bitstream2.uuid]: fieldUpdate2, - }), - getFieldUpdatesExclusive: observableOf({ - [bitstream1.uuid]: fieldUpdate1, - [bitstream2.uuid]: fieldUpdate2, - }), - getFieldUpdatesByCustomOrder: observableOf({ - [bitstream1.uuid]: fieldUpdate1, - [bitstream2.uuid]: fieldUpdate2, - }), - saveMoveFieldUpdate: {}, - saveRemoveFieldUpdate: {}, - removeSingleFieldUpdate: {}, - saveAddFieldUpdate: {}, - discardFieldUpdates: {}, - reinstateFieldUpdates: observableOf(true), - initialize: {}, - getUpdatedFields: observableOf([bitstream1, bitstream2]), - getLastModified: observableOf(date), - hasUpdates: observableOf(true), - isReinstatable: observableOf(false), - isValidPage: observableOf(true), - initializeWithCustomOrder: {}, - addPageToCustomOrder: {} - } - ); - - bundleService = jasmine.createSpyObj('bundleService', { - getBitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([bitstream1, bitstream2])), - getBitstreamsEndpoint: observableOf('') - }); - - objectValuesPipe = new ObjectValuesPipe(); - - requestService = jasmine.createSpyObj('requestService', { - hasByHref$: observableOf(true) - }); - - paginationService = new PaginationServiceStub(); - - TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], - declarations: [PaginatedDragAndDropBitstreamListComponent, VarDirective], - providers: [ - { provide: ObjectUpdatesService, useValue: objectUpdatesService }, - { provide: BundleDataService, useValue: bundleService }, - { provide: ObjectValuesPipe, useValue: objectValuesPipe }, - { provide: RequestService, useValue: requestService }, - { provide: PaginationService, useValue: paginationService } - ], schemas: [ - NO_ERRORS_SCHEMA - ] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(PaginatedDragAndDropBitstreamListComponent); - comp = fixture.componentInstance; - comp.bundle = bundle; - comp.columnSizes = columnSizes; - fixture.detectChanges(); - }); - - it('should initialize the objectsRD$', (done) => { - comp.objectsRD$.pipe(take(1)).subscribe((objects) => { - expect(objects.payload.page).toEqual([bitstream1, bitstream2]); - done(); - }); - }); - - it('should initialize the URL', () => { - expect(comp.url).toEqual(bundle.self); - }); -}); diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts deleted file mode 100644 index d5bb9eceea..0000000000 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { AbstractPaginatedDragAndDropListComponent } from '../../../../../shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component'; -import { Component, ElementRef, Input, OnInit } from '@angular/core'; -import { Bundle } from '../../../../../core/shared/bundle.model'; -import { Bitstream } from '../../../../../core/shared/bitstream.model'; -import { ObjectUpdatesService } from '../../../../../core/data/object-updates/object-updates.service'; -import { BundleDataService } from '../../../../../core/data/bundle-data.service'; -import { switchMap } from 'rxjs/operators'; -import { PaginatedSearchOptions } from '../../../../../shared/search/models/paginated-search-options.model'; -import { ResponsiveTableSizes } from '../../../../../shared/responsive-table-sizes/responsive-table-sizes'; -import { followLink } from '../../../../../shared/utils/follow-link-config.model'; -import { ObjectValuesPipe } from '../../../../../shared/utils/object-values-pipe'; -import { RequestService } from '../../../../../core/data/request.service'; -import { PaginationService } from '../../../../../core/pagination/pagination.service'; -import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; - -@Component({ - selector: 'ds-paginated-drag-and-drop-bitstream-list', - styleUrls: ['../../item-bitstreams.component.scss'], - templateUrl: './paginated-drag-and-drop-bitstream-list.component.html', -}) -/** - * A component listing edit-bitstream rows for each bitstream within the given bundle. - * This component makes use of the AbstractPaginatedDragAndDropListComponent, allowing for users to drag and drop - * bitstreams within the paginated list. To drag and drop a bitstream between two pages, drag the row on top of the - * page number you want the bitstream to end up at. Doing so will add the bitstream to the top of that page. - */ -// NOTE: -// This component was used by the item-edit-bitstream-bundle.component, but this is no longer the case. It is left here -// as a reference for the drag-and-drop functionality. This component (and the abstract version it extends) should be -// removed once this reference is no longer useful. -export class PaginatedDragAndDropBitstreamListComponent extends AbstractPaginatedDragAndDropListComponent implements OnInit { - /** - * The bundle to display bitstreams for - */ - @Input() bundle: Bundle; - - /** - * The bootstrap sizes used for the columns within this table - */ - @Input() columnSizes: ResponsiveTableSizes; - - constructor(protected objectUpdatesService: ObjectUpdatesService, - protected elRef: ElementRef, - protected objectValuesPipe: ObjectValuesPipe, - protected bundleService: BundleDataService, - protected paginationService: PaginationService, - protected requestService: RequestService) { - super(objectUpdatesService, elRef, objectValuesPipe, paginationService); - } - - ngOnInit() { - super.ngOnInit(); - } - - /** - * Initialize the bitstreams observable depending on currentPage$ - */ - initializeObjectsRD(): void { - this.objectsRD$ = this.currentPage$.pipe( - switchMap((page: PaginationComponentOptions) => { - const paginatedOptions = new PaginatedSearchOptions({pagination: Object.assign({}, page)}); - return this.bundleService.getBitstreamsEndpoint(this.bundle.id, paginatedOptions).pipe( - switchMap((href) => this.requestService.hasByHref$(href)), - switchMap(() => this.bundleService.getBitstreams( - this.bundle.id, - paginatedOptions, - followLink('format') - )) - ); - }) - ); - } - - /** - * Initialize the URL used for the field-update store, in this case the bundle's self-link - */ - initializeURL(): void { - this.url = this.bundle.self; - } -} diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component.html deleted file mode 100644 index 1bce8667ee..0000000000 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component.html +++ /dev/null @@ -1,5 +0,0 @@ - -
    - -
    -
    diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component.ts deleted file mode 100644 index e5cb9ba403..0000000000 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; - -@Component({ - selector: 'ds-item-edit-bitstream-drag-handle', - styleUrls: ['../item-bitstreams.component.scss'], - templateUrl: './item-edit-bitstream-drag-handle.component.html', -}) -/** - * Component displaying a drag handle for the item-edit-bitstream page - * Creates an embedded view of the contents - * (which means it'll be added to the parents html without a wrapping ds-item-edit-bitstream-drag-handle element) - */ -export class ItemEditBitstreamDragHandleComponent implements OnInit { - /** - * The view on the drag-handle - */ - @ViewChild('handleView', {static: true}) handleView; - - constructor(private viewContainerRef: ViewContainerRef) { - } - - ngOnInit(): void { - this.viewContainerRef.createEmbeddedView(this.handleView); - } - -} diff --git a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts deleted file mode 100644 index bac6b89583..0000000000 --- a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { AbstractPaginatedDragAndDropListComponent } from './abstract-paginated-drag-and-drop-list.component'; -import { DSpaceObject } from '../../core/shared/dspace-object.model'; -import { ObjectUpdatesService } from '../../core/data/object-updates/object-updates.service'; -import { Component, ElementRef } from '@angular/core'; -import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; -import { PaginatedList } from '../../core/data/paginated-list.model'; -import { RemoteData } from '../../core/data/remote-data'; -import { take } from 'rxjs/operators'; -import { PaginationComponent } from '../pagination/pagination.component'; -import { createSuccessfulRemoteDataObject } from '../remote-data.utils'; -import { createPaginatedList } from '../testing/utils.test'; -import { ObjectValuesPipe } from '../utils/object-values-pipe'; -import { PaginationService } from '../../core/pagination/pagination.service'; -import { PaginationServiceStub } from '../testing/pagination-service.stub'; -import { FieldUpdates } from '../../core/data/object-updates/field-updates.model'; - -@Component({ - selector: 'ds-mock-paginated-drag-drop-abstract', - template: '' -}) -class MockAbstractPaginatedDragAndDropListComponent extends AbstractPaginatedDragAndDropListComponent { - - constructor(protected objectUpdatesService: ObjectUpdatesService, - protected elRef: ElementRef, - protected objectValuesPipe: ObjectValuesPipe, - protected mockUrl: string, - protected paginationService: PaginationService, - protected mockObjectsRD$: Observable>>) { - super(objectUpdatesService, elRef, objectValuesPipe, paginationService); - } - - initializeObjectsRD(): void { - this.objectsRD$ = this.mockObjectsRD$; - } - - initializeURL(): void { - this.url = this.mockUrl; - } -} - -describe('AbstractPaginatedDragAndDropListComponent', () => { - let component: MockAbstractPaginatedDragAndDropListComponent; - let objectUpdatesService: ObjectUpdatesService; - let elRef: ElementRef; - let objectValuesPipe: ObjectValuesPipe; - - const url = 'mock-abstract-paginated-drag-and-drop-list-component'; - - - const object1 = Object.assign(new DSpaceObject(), { uuid: 'object-1' }); - const object2 = Object.assign(new DSpaceObject(), { uuid: 'object-2' }); - const objectsRD = createSuccessfulRemoteDataObject(createPaginatedList([object1, object2])); - let objectsRD$: BehaviorSubject>>; - let paginationService; - - const updates = { - [object1.uuid]: { field: object1, changeType: undefined }, - [object2.uuid]: { field: object2, changeType: undefined } - } as FieldUpdates; - - let paginationComponent: PaginationComponent; - - beforeEach(() => { - objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', { - initialize: {}, - getFieldUpdatesExclusive: observableOf(updates) - }); - elRef = { - nativeElement: jasmine.createSpyObj('nativeElement', { - querySelector: {} - }) - }; - objectValuesPipe = new ObjectValuesPipe(); - paginationComponent = jasmine.createSpyObj('paginationComponent', { - doPageChange: {} - }); - paginationService = new PaginationServiceStub(); - objectsRD$ = new BehaviorSubject(objectsRD); - component = new MockAbstractPaginatedDragAndDropListComponent(objectUpdatesService, elRef, objectValuesPipe, url, paginationService, objectsRD$); - component.paginationComponent = paginationComponent; - component.ngOnInit(); - }); - - it('should call initialize to initialize the objects in the store', () => { - expect(objectUpdatesService.initialize).toHaveBeenCalled(); - }); - - it('should initialize the updates correctly', (done) => { - component.updates$.pipe(take(1)).subscribe((fieldUpdates) => { - expect(fieldUpdates).toEqual(updates); - done(); - }); - }); - - describe('drop', () => { - const event = { - previousIndex: 0, - currentIndex: 1, - item: { element: { nativeElement: { id: object1.uuid } } } - } as any; - - describe('when the user is hovering over a new page', () => { - const hoverPage = 3; - const hoverElement = { textContent: '' + hoverPage }; - - beforeEach(() => { - elRef.nativeElement.querySelector.and.returnValue(hoverElement); - spyOn(component.dropObject, 'emit'); - component.drop(event); - }); - - it('should send out a dropObject event with the expected processed paginated indexes', () => { - expect(component.dropObject.emit).toHaveBeenCalledWith(Object.assign({ - fromIndex: ((component.currentPage$.value.currentPage - 1) * component.pageSize) + event.previousIndex, - toIndex: ((hoverPage - 1) * component.pageSize), - finish: jasmine.anything() - })); - }); - }); - - describe('when the user is not hovering over a new page', () => { - beforeEach(() => { - spyOn(component.dropObject, 'emit'); - component.drop(event); - }); - - it('should send out a dropObject event with the expected properties', () => { - expect(component.dropObject.emit).toHaveBeenCalledWith(Object.assign({ - fromIndex: event.previousIndex, - toIndex: event.currentIndex, - finish: jasmine.anything() - })); - }); - }); - }); -}); diff --git a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts b/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts deleted file mode 100644 index 8dba47566f..0000000000 --- a/src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { BehaviorSubject, Observable, Subscription } from 'rxjs'; -import { RemoteData } from '../../core/data/remote-data'; -import { PaginatedList } from '../../core/data/paginated-list.model'; -import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; -import { ObjectUpdatesService } from '../../core/data/object-updates/object-updates.service'; -import { distinctUntilChanged, map, switchMap } from 'rxjs/operators'; -import { hasValue } from '../empty.util'; -import { - paginatedListToArray, - getFirstSucceededRemoteData, - getAllSucceededRemoteData -} from '../../core/shared/operators'; -import { DSpaceObject } from '../../core/shared/dspace-object.model'; -import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; -import { Component, ElementRef, EventEmitter, OnDestroy, Output, ViewChild } from '@angular/core'; -import { PaginationComponent } from '../pagination/pagination.component'; -import { ObjectValuesPipe } from '../utils/object-values-pipe'; -import { compareArraysUsing } from '../../item-page/simple/item-types/shared/item-relationships-utils'; -import { PaginationService } from '../../core/pagination/pagination.service'; -import { FieldUpdate } from '../../core/data/object-updates/field-update.model'; -import { FieldUpdates } from '../../core/data/object-updates/field-updates.model'; - -/** - * Operator used for comparing {@link FieldUpdate}s by their field's UUID - */ -export const compareArraysUsingFieldUuids = () => - compareArraysUsing((fieldUpdate: FieldUpdate) => (hasValue(fieldUpdate) && hasValue(fieldUpdate.field)) ? fieldUpdate.field.uuid : undefined); - -/** - * An abstract component containing general methods and logic to be able to drag and drop objects within a paginated - * list. This implementation supports being able to drag and drop objects between pages. - * Dragging an object on top of a page number will automatically detect the page it's being dropped on and send a - * dropObject event to the parent component containing detailed information about the indexes the object was dropped from - * and to. - * - * To extend this component, it is important to make sure to: - * - Initialize objectsRD$ within the initializeObjectsRD() method - * - Initialize a unique URL for this component/page within the initializeURL() method - * - Add (cdkDropListDropped)="drop($event)" to the cdkDropList element in your template - * - Add (pageChange)="switchPage($event)" to the ds-pagination element in your template - * - Use the updates$ observable for building your list of cdkDrag elements in your template - * - * An example component extending from this abstract component: PaginatedDragAndDropBitstreamListComponent - */ -@Component({ - selector: 'ds-paginated-drag-drop-abstract', - template: '' -}) -export abstract class AbstractPaginatedDragAndDropListComponent implements OnDestroy { - /** - * A view on the child pagination component - */ - @ViewChild(PaginationComponent) paginationComponent: PaginationComponent; - - /** - * Send an event when the user drops an object on the pagination - * The event contains details about the index the object came from and is dropped to (across the entirety of the list, - * not just within a single page) - */ - @Output() dropObject: EventEmitter = new EventEmitter(); - - /** - * The URL to use for accessing the object updates from this list - */ - url: string; - - /** - * The objects to retrieve data for and transform into field updates - */ - objectsRD$: Observable>>; - - /** - * The updates to the current list - */ - updates$: Observable; - - /** - * A list of object UUIDs - * This is the order the objects will be displayed in - */ - customOrder: string[]; - - /** - * The amount of objects to display per page - */ - pageSize = 10; - - /** - * The page options to use for fetching the objects - * Start at page 1 and always use the set page size - */ - options = Object.assign(new PaginationComponentOptions(),{ - id: 'dad', - currentPage: 1, - pageSize: this.pageSize - }); - - /** - * The current page being displayed - */ - currentPage$ = new BehaviorSubject(this.options); - - /** - * Whether or not we should display a loading animation - * This is used to display a loading page when the user drops a bitstream onto a new page. The loading animation - * should stop once the bitstream has moved to the new page and the new page's response has loaded and contains the - * dropped object on top (see this.stopLoadingWhenFirstIs below) - */ - loading$: BehaviorSubject = new BehaviorSubject(false); - - /** - * List of subscriptions - */ - subs: Subscription[] = []; - - protected constructor(protected objectUpdatesService: ObjectUpdatesService, - protected elRef: ElementRef, - protected objectValuesPipe: ObjectValuesPipe, - protected paginationService: PaginationService - ) { - } - - /** - * Initialize the observables - */ - ngOnInit() { - this.initializeObjectsRD(); - this.initializeURL(); - this.initializeUpdates(); - this.initializePagination(); - } - - /** - * Overwrite this method to define how the list of objects is initialized and updated - */ - abstract initializeObjectsRD(): void; - - /** - * Overwrite this method to define how the URL is set - */ - abstract initializeURL(): void; - - /** - * Initialize the current pagination retrieval from the paginationService and push to the currentPage$ - */ - initializePagination() { - this.paginationService.getCurrentPagination(this.options.id, this.options).subscribe((currentPagination) => { - this.currentPage$.next(currentPagination); - }); - } - - /** - * Initialize the field-updates in the store - */ - initializeUpdates(): void { - this.objectsRD$.pipe( - getFirstSucceededRemoteData(), - paginatedListToArray(), - ).subscribe((objects: T[]) => { - this.objectUpdatesService.initialize(this.url, objects, new Date()); - }); - this.updates$ = this.objectsRD$.pipe( - getAllSucceededRemoteData(), - paginatedListToArray(), - switchMap((objects: T[]) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, objects)) - ); - this.subs.push( - this.updates$.pipe( - map((fieldUpdates) => this.objectValuesPipe.transform(fieldUpdates)), - distinctUntilChanged(compareArraysUsingFieldUuids()) - ).subscribe((updateValues) => { - this.customOrder = updateValues.map((fieldUpdate) => fieldUpdate.field.uuid); - // We received new values, stop displaying a loading indicator if it's present - this.loading$.next(false); - }), - // Disable the pagination when objects are loading - this.loading$.subscribe((loading) => this.options.disabled = loading) - ); - } - - /** - * An object was moved, send updates to the dropObject EventEmitter - * When the object is dropped on a page within the pagination of this component, the object moves to the top of that - * page and the pagination automatically loads and switches the view to that page (this is done by calling the event's - * finish() method after sending patch requests to the REST API) - * @param event - */ - drop(event: CdkDragDrop) { - const dragIndex = event.previousIndex; - let dropIndex = event.currentIndex; - const dragPage = this.currentPage$.value.currentPage - 1; - let dropPage = this.currentPage$.value.currentPage - 1; - - // Check if the user is hovering over any of the pagination's pages at the time of dropping the object - const droppedOnElement = this.elRef.nativeElement.querySelector('.page-item:hover'); - if (hasValue(droppedOnElement) && hasValue(droppedOnElement.textContent)) { - // The user is hovering over a page, fetch the page's number from the element - const droppedPage = Number(droppedOnElement.textContent); - if (hasValue(droppedPage) && !Number.isNaN(droppedPage)) { - dropPage = droppedPage - 1; - dropIndex = 0; - } - } - - const isNewPage = dragPage !== dropPage; - // Move the object in the custom order array if the drop happened within the same page - // This allows us to instantly display a change in the order, instead of waiting for the REST API's response first - if (!isNewPage && dragIndex !== dropIndex) { - moveItemInArray(this.customOrder, dragIndex, dropIndex); - } - - const redirectPage = dropPage + 1; - const fromIndex = (dragPage * this.pageSize) + dragIndex; - const toIndex = (dropPage * this.pageSize) + dropIndex; - // Send out a drop event (and navigate to the new page) when the "from" and "to" indexes are different from each other - if (fromIndex !== toIndex) { - if (isNewPage) { - this.loading$.next(true); - } - this.dropObject.emit(Object.assign({ - fromIndex, - toIndex, - finish: () => { - if (isNewPage) { - this.paginationComponent.doPageChange(redirectPage); - } - } - })); - } - } - - /** - * unsub all subscriptions - */ - ngOnDestroy(): void { - this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); - this.paginationService.clearPagination(this.options.id); - } -} From 876d94e124cf11477a5548f11f095bc129c4b313 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 2 Oct 2024 09:23:26 +0200 Subject: [PATCH 518/875] 115284: Add tests for isRepeatable --- .../edit-relationship-list.component.spec.ts | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts index 312f2936ac..69a2340fd5 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts @@ -78,7 +78,7 @@ describe('EditRelationshipListComponent', () => { fixture.detectChanges(); }; - function init(leftType: string, rightType: string): void { + function init(leftType: string, rightType: string, leftMaxCardinality?: number, rightMaxCardinality?: number): void { entityTypeLeft = Object.assign(new ItemType(), { id: leftType, uuid: leftType, @@ -98,6 +98,8 @@ describe('EditRelationshipListComponent', () => { rightType: createSuccessfulRemoteDataObject$(entityTypeRight), leftwardType: `is${rightType}Of${leftType}`, rightwardType: `is${leftType}Of${rightType}`, + leftMaxCardinality: leftMaxCardinality, + rightMaxCardinality: rightMaxCardinality, }); paginationOptions = Object.assign(new PaginationComponentOptions(), { @@ -367,4 +369,31 @@ describe('EditRelationshipListComponent', () => { })); }); }); + + describe('Is repeatable relationship', () => { + beforeEach(waitForAsync(() => { + currentItemIsLeftItem$ = new BehaviorSubject(true); + })); + describe('when max cardinality is 1', () => { + beforeEach(waitForAsync(() => init('Publication', 'OrgUnit', 1, undefined))); + it('should return false', () => { + const result = (comp as any).isRepeatable(); + expect(result).toBeFalse(); + }); + }); + describe('when max cardinality is 2', () => { + beforeEach(waitForAsync(() => init('Publication', 'OrgUnit', 2, undefined))); + it('should return true', () => { + const result = (comp as any).isRepeatable(); + expect(result).toBeTrue(); + }); + }); + describe('when max cardinality is undefined', () => { + beforeEach(waitForAsync(() => init('Publication', 'OrgUnit', undefined, undefined))); + it('should return true', () => { + const result = (comp as any).isRepeatable(); + expect(result).toBeTrue(); + }); + }); + }); }); From 4bd607107146f4149ce004aba749dbccef7eba71 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 2 Oct 2024 17:02:12 -0500 Subject: [PATCH 519/875] Squashed commit of changes in #3148 from @Andrea-Guevara Co-authored-by: @Andrea-Guevara --- .../item-operation/item-operation.component.html | 6 +++--- .../item-status/item-status.component.html | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-operation/item-operation.component.html b/src/app/item-page/edit-item-page/item-operation/item-operation.component.html index 85c6a2cca1..5364d5b9aa 100644 --- a/src/app/item-page/edit-item-page/item-operation/item-operation.component.html +++ b/src/app/item-page/edit-item-page/item-operation/item-operation.component.html @@ -1,9 +1,9 @@ -
    - +
    + {{'item.edit.tabs.status.buttons.' + operation.operationKey + '.label' | translate}}
    -
    +
    diff --git a/src/styles/_global-styles.scss b/src/styles/_global-styles.scss index 765b50ae86..b3120c08cd 100644 --- a/src/styles/_global-styles.scss +++ b/src/styles/_global-styles.scss @@ -466,3 +466,11 @@ ngb-accordion { .mt-ncs { margin-top: calc(var(--ds-content-spacing) * -1); } .mb-ncs { margin-bottom: calc(var(--ds-content-spacing) * -1); } .my-ncs { margin-top: calc(var(--ds-content-spacing) * -1); margin-bottom: calc(var(--ds-content-spacing) * -1); } + + +.link-contrast { + // Rules for accessibility to meet minimum contrast and have an identifiable link between other texts + color: darken($link-color, 5%); + // We use underline to discern link from text as we can't make color lighter on a white bg + text-decoration: underline; +} From db891f2f16103203a8b1cdab4bf363028bd4a820 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Tue, 23 Jul 2024 17:13:20 +0200 Subject: [PATCH 535/875] [CST-15592] improve tests, add attributes for testing, fix wrong references --- cypress/e2e/admin-add-new-modals.cy.ts | 6 ++-- cypress/e2e/admin-edit-modals.cy.ts | 6 ++-- cypress/e2e/admin-export-modals.cy.ts | 4 +-- cypress/e2e/admin-search-page.cy.ts | 3 ++ cypress/e2e/admin-workflow-page.cy.ts | 3 ++ cypress/e2e/bulk-access.cy.ts | 7 +++++ cypress/e2e/health-page.cy.ts | 31 +++++++++++++++---- .../health-page/health-page.component.html | 4 +-- .../onclick-menu-item.component.html | 3 +- 9 files changed, 50 insertions(+), 17 deletions(-) diff --git a/cypress/e2e/admin-add-new-modals.cy.ts b/cypress/e2e/admin-add-new-modals.cy.ts index 6e2e8e6970..565ae154f1 100644 --- a/cypress/e2e/admin-add-new-modals.cy.ts +++ b/cypress/e2e/admin-add-new-modals.cy.ts @@ -14,7 +14,7 @@ describe('Admin Add New Modals', () => { // Click on entry of menu cy.get('#admin-menu-section-new-title').click(); - cy.get('a[title="menu.section.new_community"]').click(); + cy.get('a[data-test="menu.section.new_community"]').click(); // Analyze for accessibility testA11y('ds-create-community-parent-selector'); @@ -27,7 +27,7 @@ describe('Admin Add New Modals', () => { // Click on entry of menu cy.get('#admin-menu-section-new-title').click(); - cy.get('a[title="menu.section.new_collection"]').click(); + cy.get('a[data-test="menu.section.new_collection"]').click(); // Analyze for accessibility testA11y('ds-create-collection-parent-selector'); @@ -40,7 +40,7 @@ describe('Admin Add New Modals', () => { // Click on entry of menu cy.get('#admin-menu-section-new-title').click(); - cy.get('a[title="menu.section.new_item"]').click(); + cy.get('a[data-test="menu.section.new_item"]').click(); // Analyze for accessibility testA11y('ds-create-item-parent-selector'); diff --git a/cypress/e2e/admin-edit-modals.cy.ts b/cypress/e2e/admin-edit-modals.cy.ts index 256a6d89cb..e96d6ce898 100644 --- a/cypress/e2e/admin-edit-modals.cy.ts +++ b/cypress/e2e/admin-edit-modals.cy.ts @@ -14,7 +14,7 @@ describe('Admin Edit Modals', () => { // Click on entry of menu cy.get('#admin-menu-section-edit-title').click(); - cy.get('a[title="menu.section.edit_community"]').click(); + cy.get('a[data-test="menu.section.edit_community"]').click(); // Analyze for accessibility testA11y('ds-edit-community-selector'); @@ -27,7 +27,7 @@ describe('Admin Edit Modals', () => { // Click on entry of menu cy.get('#admin-menu-section-edit-title').click(); - cy.get('a[title="menu.section.edit_collection"]').click(); + cy.get('a[data-test="menu.section.edit_collection"]').click(); // Analyze for accessibility testA11y('ds-edit-collection-selector'); @@ -40,7 +40,7 @@ describe('Admin Edit Modals', () => { // Click on entry of menu cy.get('#admin-menu-section-edit-title').click(); - cy.get('a[title="menu.section.edit_item"]').click(); + cy.get('a[data-test="menu.section.edit_item"]').click(); // Analyze for accessibility testA11y('ds-edit-item-selector'); diff --git a/cypress/e2e/admin-export-modals.cy.ts b/cypress/e2e/admin-export-modals.cy.ts index b611bb8fd5..9f69764d19 100644 --- a/cypress/e2e/admin-export-modals.cy.ts +++ b/cypress/e2e/admin-export-modals.cy.ts @@ -14,7 +14,7 @@ describe('Admin Export Modals', () => { // Click on entry of menu cy.get('#admin-menu-section-export-title').click(); - cy.get('a[title="menu.section.export_metadata"]').click(); + cy.get('a[data-test="menu.section.export_metadata"]').click(); // Analyze for accessibility testA11y('ds-export-metadata-selector'); @@ -27,7 +27,7 @@ describe('Admin Export Modals', () => { // Click on entry of menu cy.get('#admin-menu-section-export-title').click(); - cy.get('a[title="menu.section.export_batch"]').click(); + cy.get('a[data-test="menu.section.export_batch"]').click(); // Analyze for accessibility testA11y('ds-export-batch-selector'); diff --git a/cypress/e2e/admin-search-page.cy.ts b/cypress/e2e/admin-search-page.cy.ts index 2e1d13aa13..4fbf8939fe 100644 --- a/cypress/e2e/admin-search-page.cy.ts +++ b/cypress/e2e/admin-search-page.cy.ts @@ -12,6 +12,9 @@ describe('Admin Search Page', () => { cy.get('ds-admin-search-page').should('be.visible'); // At least one search result should be displayed cy.get('[data-test="list-object"]').should('be.visible'); + // Click each filter toggle to open *every* filter + // (As we want to scan filter section for accessibility issues as well) + cy.get('[data-test="filter-toggle"]').click({ multiple: true }); // Analyze for accessibility issues testA11y('ds-admin-search-page'); }); diff --git a/cypress/e2e/admin-workflow-page.cy.ts b/cypress/e2e/admin-workflow-page.cy.ts index cd2275f584..c3c235e346 100644 --- a/cypress/e2e/admin-workflow-page.cy.ts +++ b/cypress/e2e/admin-workflow-page.cy.ts @@ -12,6 +12,9 @@ describe('Admin Workflow Page', () => { cy.get('ds-admin-workflow-page').should('be.visible'); // At least one search result should be displayed cy.get('[data-test="list-object"]').should('be.visible'); + // Click each filter toggle to open *every* filter + // (As we want to scan filter section for accessibility issues as well) + cy.get('[data-test="filter-toggle"]').click({ multiple: true }); // Analyze for accessibility issues testA11y('ds-admin-workflow-page'); }); diff --git a/cypress/e2e/bulk-access.cy.ts b/cypress/e2e/bulk-access.cy.ts index 4d199f53f9..87033e13e4 100644 --- a/cypress/e2e/bulk-access.cy.ts +++ b/cypress/e2e/bulk-access.cy.ts @@ -11,6 +11,11 @@ describe('Bulk Access', () => { it('should pass accessibility tests', () => { // Page must first be visible cy.get('ds-bulk-access').should('be.visible'); + // At least one search result should be displayed + cy.get('[data-test="list-object"]').should('be.visible'); + // Click each filter toggle to open *every* filter + // (As we want to scan filter section for accessibility issues as well) + cy.get('[data-test="filter-toggle"]').click({ multiple: true }); // Analyze for accessibility issues testA11y('ds-bulk-access', { rules: { @@ -18,6 +23,8 @@ describe('Bulk Access', () => { // Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216 'aria-required-children': { enabled: false }, 'nested-interactive': { enabled: false }, + // Card titles fail this test currently + 'heading-order': { enabled: false }, }, } as Options); }); diff --git a/cypress/e2e/health-page.cy.ts b/cypress/e2e/health-page.cy.ts index 91c68638ea..79ebf4bc04 100644 --- a/cypress/e2e/health-page.cy.ts +++ b/cypress/e2e/health-page.cy.ts @@ -1,13 +1,14 @@ import { testA11y } from 'cypress/support/utils'; import { Options } from 'cypress-axe'; -describe('Health Page', () => { - beforeEach(() => { - // Must login as an Admin to see the page - cy.visit('/health'); - cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD')); - }); +beforeEach(() => { + // Must login as an Admin to see the page + cy.visit('/health'); + cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD')); +}); + +describe('Health Page > Status Tab', () => { it('should pass accessibility tests', () => { // Page must first be visible cy.get('ds-health-page').should('be.visible'); @@ -22,3 +23,21 @@ describe('Health Page', () => { } as Options); }); }); + +describe('Health Page > Info Tab', () => { + it('should pass accessibility tests', () => { + // Page must first be visible + cy.get('ds-health-page').should('be.visible'); + cy.get('a[data-test="health-page.info-tab"]').click(); + + // Analyze for accessibility issues + testA11y('ds-health-page', { + rules: { + // All panels are accordians & fail "aria-required-children" and "nested-interactive". + // Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216 + 'aria-required-children': { enabled: false }, + 'nested-interactive': { enabled: false }, + }, + } as Options); + }); +}); diff --git a/src/app/health-page/health-page.component.html b/src/app/health-page/health-page.component.html index 14c577a8ee..209fd230ae 100644 --- a/src/app/health-page/health-page.component.html +++ b/src/app/health-page/health-page.component.html @@ -3,7 +3,7 @@
    diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index bd99fc1a09..24319f4a83 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -30,6 +30,8 @@ import { RequestService } from '../../../../core/data/request.service'; import { ItemBitstreamsService } from '../item-bitstreams.service'; import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { hasValue } from '../../../../shared/empty.util'; +import { LiveRegionService } from '../../../../shared/live-region/live-region.service'; +import { TranslateService } from '@ngx-translate/core'; /** * Interface storing all the information necessary to create a row in the bitstream edit table @@ -166,11 +168,6 @@ export class ItemEditBitstreamBundleComponent implements OnInit { */ pageSize$: BehaviorSubject; - /** - * Whether the table has multiple pages - */ - hasMultiplePages = false; - /** * The self url of the bundle, also used when retrieving fieldUpdates */ @@ -190,6 +187,8 @@ export class ItemEditBitstreamBundleComponent implements OnInit { protected paginationService: PaginationService, protected requestService: RequestService, protected itemBitstreamsService: ItemBitstreamsService, + protected liveRegionService: LiveRegionService, + protected translateService: TranslateService, ) { } @@ -301,7 +300,7 @@ export class ItemEditBitstreamBundleComponent implements OnInit { this.paginationComponent.doPageSizeChange(pageSize); } - dragStart() { + dragStart(bitstreamName: string) { // Only open the drag tooltip when there are multiple pages this.paginationComponent.shouldShowBottomPager.pipe( take(1), @@ -309,10 +308,18 @@ export class ItemEditBitstreamBundleComponent implements OnInit { ).subscribe(() => { this.dragTooltip.open(); }); + + const message = this.translateService.instant('item.edit.bitstreams.edit.live.drag', + { bitstream: bitstreamName }); + this.liveRegionService.addMessage(message); } - dragEnd() { + dragEnd(bitstreamName: string) { this.dragTooltip.close(); + + const message = this.translateService.instant('item.edit.bitstreams.edit.live.drop', + { bitstream: bitstreamName }); + this.liveRegionService.addMessage(message); } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index f2dbf9b2d1..48fd581bdf 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1950,6 +1950,10 @@ "item.edit.bitstreams.edit.buttons.undo": "Undo changes", + "item.edit.bitstreams.edit.live.drag": "{{ bitstream }} grabbed", + + "item.edit.bitstreams.edit.live.drop": "{{ bitstream }} dropped", + "item.edit.bitstreams.empty": "This item doesn't contain any bitstreams. Click the upload button to create one.", "item.edit.bitstreams.headers.actions": "Actions", From 1f909dc6ea0d84859fbeef690c9bd212417c7a99 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Tue, 17 Sep 2024 13:34:08 +0200 Subject: [PATCH 556/875] 118223: Add instructive alert to item-bitstreams edit page --- .../item-bitstreams/item-bitstreams.component.html | 7 ++++++- .../item-bitstreams/item-bitstreams.component.ts | 4 ++++ src/assets/i18n/en.json5 | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html index 3527f2f5b8..1c13154bfa 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html @@ -1,4 +1,8 @@
    +
    + +
    +
    Date: Wed, 18 Sep 2024 11:57:41 +0200 Subject: [PATCH 557/875] 118223: Implement bitstream reordering with keyboard --- .../item-bitstreams.component.html | 1 - .../item-bitstreams.component.spec.ts | 43 +- .../item-bitstreams.component.ts | 87 ++-- .../item-bitstreams.service.spec.ts | 19 + .../item-bitstreams.service.ts | 303 ++++++++++++- .../item-edit-bitstream-bundle.component.html | 33 +- ...em-edit-bitstream-bundle.component.spec.ts | 1 + .../item-edit-bitstream-bundle.component.ts | 405 ++++++++++++------ .../live-region/live-region.service.stub.ts | 30 ++ src/assets/i18n/en.json5 | 8 +- 10 files changed, 727 insertions(+), 203 deletions(-) create mode 100644 src/app/shared/live-region/live-region.service.stub.ts diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html index 1c13154bfa..b9af2a7d18 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html @@ -33,7 +33,6 @@ [item]="item" [columnSizes]="columnSizes" [isFirstTable]="isFirst" - (dropObject)="dropBitstream(bundle, $event)" aria-describedby="reorder-description">
    diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts index 6ce7394473..a5549a6ba0 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts @@ -25,6 +25,10 @@ import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } f import { createPaginatedList } from '../../../shared/testing/utils.test'; import { FieldChangeType } from '../../../core/data/object-updates/field-change-type.model'; import { BitstreamDataServiceStub } from '../../../shared/testing/bitstream-data-service.stub'; +import { ItemBitstreamsService } from './item-bitstreams.service'; +import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes'; +import { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-sizes'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; let comp: ItemBitstreamsComponent; let fixture: ComponentFixture; @@ -76,6 +80,7 @@ let objectCache: ObjectCacheService; let requestService: RequestService; let searchConfig: SearchConfigurationService; let bundleService: BundleDataService; +let itemBitstreamsService: ItemBitstreamsService; describe('ItemBitstreamsComponent', () => { beforeEach(waitForAsync(() => { @@ -147,6 +152,19 @@ describe('ItemBitstreamsComponent', () => { patch: createSuccessfulRemoteDataObject$({}), }); + itemBitstreamsService = jasmine.createSpyObj('itemBitstreamsService', { + getColumnSizes: new ResponsiveTableSizes([ + new ResponsiveColumnSizes(2, 2, 3, 4, 4), + new ResponsiveColumnSizes(2, 3, 3, 3, 3), + new ResponsiveColumnSizes(2, 2, 2, 2, 2), + new ResponsiveColumnSizes(6, 5, 4, 3, 3) + ]), + getSelectedBitstream$: observableOf({}), + getInitialBundlesPaginationOptions: new PaginationComponentOptions(), + removeMarkedBitstreams: createSuccessfulRemoteDataObject$({}), + displayNotifications: undefined, + }); + TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], declarations: [ItemBitstreamsComponent, ObjectValuesPipe, VarDirective], @@ -161,6 +179,7 @@ describe('ItemBitstreamsComponent', () => { { provide: RequestService, useValue: requestService }, { provide: SearchConfigurationService, useValue: searchConfig }, { provide: BundleDataService, useValue: bundleService }, + { provide: ItemBitstreamsService, useValue: itemBitstreamsService }, ChangeDetectorRef ], schemas: [ NO_ERRORS_SCHEMA @@ -181,28 +200,8 @@ describe('ItemBitstreamsComponent', () => { comp.submit(); }); - it('should call removeMultiple on the bitstreamService for the marked field', () => { - expect(bitstreamService.removeMultiple).toHaveBeenCalledWith([bitstream2]); - }); - - it('should not call removeMultiple on the bitstreamService for the unmarked field', () => { - expect(bitstreamService.removeMultiple).not.toHaveBeenCalledWith([bitstream1]); - }); - }); - - describe('when dropBitstream is called', () => { - beforeEach((done) => { - comp.dropBitstream(bundle, { - fromIndex: 0, - toIndex: 50, - finish: () => { - done(); - } - }); - }); - - it('should send out a patch for the move operation', () => { - expect(bundleService.patch).toHaveBeenCalled(); + it('should call removeMarkedBitstreams on the itemBitstreamsService', () => { + expect(itemBitstreamsService.removeMarkedBitstreams).toHaveBeenCalled(); }); }); 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 7757170f4e..4ced3dd649 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 @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, NgZone, OnDestroy } from '@angular/core'; +import { ChangeDetectorRef, Component, NgZone, OnDestroy, HostListener } from '@angular/core'; import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component'; import { map, switchMap, take } from 'rxjs/operators'; import { Observable, Subscription, zip as observableZip } from 'rxjs'; @@ -8,13 +8,11 @@ import { ActivatedRoute, Router } from '@angular/router'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; -import { hasValue } from '../../../shared/empty.util'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { RequestService } from '../../../core/data/request.service'; import { getFirstSucceededRemoteData, getRemoteDataPayload, - getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list.model'; @@ -23,7 +21,6 @@ import { BundleDataService } from '../../../core/data/bundle-data.service'; import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model'; import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes'; import { NoContent } from '../../../core/shared/NoContent.model'; -import { Operation } from 'fast-json-patch'; import { ItemBitstreamsService } from './item-bitstreams.service'; import { AlertType } from '../../../shared/alert/aletr-type'; @@ -88,13 +85,63 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme postItemInit(): void { const bundlesOptions = this.itemBitstreamsService.getInitialBundlesPaginationOptions(); - this. bundles$ = this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({pagination: bundlesOptions})).pipe( + this.bundles$ = this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({pagination: bundlesOptions})).pipe( getFirstSucceededRemoteData(), getRemoteDataPayload(), map((bundlePage: PaginatedList) => bundlePage.page) ); } + /** + * Handles keyboard events that should move the currently selected bitstream up + */ + @HostListener('document:keydown.arrowUp', ['$event']) + moveUp(event: KeyboardEvent) { + if (this.itemBitstreamsService.hasSelectedBitstream()) { + event.preventDefault(); + this.itemBitstreamsService.moveSelectedBitstreamUp(); + } + } + + /** + * Handles keyboard events that should move the currently selected bitstream down + */ + @HostListener('document:keydown.arrowDown', ['$event']) + moveDown(event: KeyboardEvent) { + if (this.itemBitstreamsService.hasSelectedBitstream()) { + event.preventDefault(); + this.itemBitstreamsService.moveSelectedBitstreamDown(); + } + } + + /** + * Handles keyboard events that should cancel the currently selected bitstream. + * A cancel means that the selected bitstream is returned to its original position and is no longer selected. + * @param event + */ + @HostListener('document:keyup.escape', ['$event']) + cancelSelection(event: KeyboardEvent) { + if (this.itemBitstreamsService.hasSelectedBitstream()) { + event.preventDefault(); + this.itemBitstreamsService.cancelSelection(); + } + } + + /** + * Handles keyboard events that should clear the currently selected bitstream. + * A clear means that the selected bitstream remains in its current position but is no longer selected. + */ + @HostListener('document:keydown.enter', ['$event']) + @HostListener('document:keydown.space', ['$event']) + clearSelection(event: KeyboardEvent) { + // Only when no specific element is in focus do we want to clear the currently selected bitstream + // Otherwise we might clear the selection when a different action was intended, e.g. clicking a button or selecting + // a different bitstream. + if (event.target instanceof Element && event.target.tagName === 'BODY') { + this.itemBitstreamsService.clearSelection(); + } + } + /** * Initialize the notification messages prefix */ @@ -120,36 +167,6 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme }); } - /** - * A bitstream was dropped in a new location. Send out a Move Patch request to the REST API, display notifications, - * refresh the bundle's cache (so the lists can properly reload) and call the event's callback function (which will - * navigate the user to the correct page) - * @param bundle The bundle to send patch requests to - * @param event The event containing the index the bitstream came from and was dropped to - */ - dropBitstream(bundle: Bundle, event: any) { - this.zone.runOutsideAngular(() => { - if (hasValue(event) && hasValue(event.fromIndex) && hasValue(event.toIndex) && hasValue(event.finish)) { - const moveOperation = { - op: 'move', - from: `/_links/bitstreams/${event.fromIndex}/href`, - path: `/_links/bitstreams/${event.toIndex}/href` - } as Operation; - this.bundleService.patch(bundle, [moveOperation]).pipe( - getFirstCompletedRemoteData(), - ).subscribe((response: RemoteData) => { - this.zone.run(() => { - this.itemBitstreamsService.displayNotifications('item.edit.bitstreams.notifications.move', [response]); - // Remove all cached requests from this bundle and call the event's callback when the requests are cleared - this.requestService.setStaleByHrefSubstring(bundle.self).pipe( - take(1) - ).subscribe(() => event.finish()); - }); - }); - } - }); - } - /** * Request the object updates service to discard all current changes to this item * Shows a notification to remind the user that they can undo this diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts index 89ecfb518f..e144e81ec7 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts @@ -16,6 +16,12 @@ import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; +import { BundleDataService } from '../../../core/data/bundle-data.service'; +import { RequestService } from '../../../core/data/request.service'; +import { LiveRegionService } from '../../../shared/live-region/live-region.service'; +import { Bundle } from '../../../core/shared/bundle.model'; +import { of } from 'rxjs'; +import { getLiveRegionServiceStub } from '../../../shared/live-region/live-region.service.stub'; describe('ItemBitstreamsService', () => { let service: ItemBitstreamsService; @@ -23,21 +29,34 @@ describe('ItemBitstreamsService', () => { let translateService: TranslateService; let objectUpdatesService: ObjectUpdatesService; let bitstreamDataService: BitstreamDataService; + let bundleDataService: BundleDataService; let dsoNameService: DSONameService; + let requestService: RequestService; + let liveRegionService: LiveRegionService; beforeEach(() => { notificationsService = new NotificationsServiceStub() as any; translateService = getMockTranslateService(); objectUpdatesService = new ObjectUpdatesServiceStub() as any; bitstreamDataService = new BitstreamDataServiceStub() as any; + bundleDataService = jasmine.createSpyObj('bundleDataService', { + patch: createSuccessfulRemoteDataObject$(new Bundle()), + }); dsoNameService = new DSONameServiceMock() as any; + requestService = jasmine.createSpyObj('requestService', { + setStaleByHrefSubstring: of(true), + }); + liveRegionService = getLiveRegionServiceStub(); service = new ItemBitstreamsService( notificationsService, translateService, objectUpdatesService, bitstreamDataService, + bundleDataService, dsoNameService, + requestService, + liveRegionService, ); }); diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts index 487df77b28..21dc415198 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts @@ -3,38 +3,277 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes'; import { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-sizes'; import { RemoteData } from '../../../core/data/remote-data'; -import { isNotEmpty, hasValue } from '../../../shared/empty.util'; +import { isNotEmpty, hasValue, hasNoValue } from '../../../shared/empty.util'; import { Bundle } from '../../../core/shared/bundle.model'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; -import { Observable, zip as observableZip } from 'rxjs'; +import { Observable, zip as observableZip, BehaviorSubject } from 'rxjs'; import { NoContent } from '../../../core/shared/NoContent.model'; -import { take, switchMap, map } from 'rxjs/operators'; +import { take, switchMap, map, tap } from 'rxjs/operators'; import { FieldUpdates } from '../../../core/data/object-updates/field-updates.model'; import { FieldUpdate } from '../../../core/data/object-updates/field-update.model'; import { FieldChangeType } from '../../../core/data/object-updates/field-change-type.model'; import { Bitstream } from '../../../core/shared/bitstream.model'; import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; -import { BitstreamTableEntry } from './item-edit-bitstream-bundle/item-edit-bitstream-bundle.component'; -import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; +import { getFirstSucceededRemoteDataPayload, getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { getBitstreamDownloadRoute } from '../../../app-routing-paths'; import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; +import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; +import { MoveOperation } from 'fast-json-patch'; +import { BundleDataService } from '../../../core/data/bundle-data.service'; +import { RequestService } from '../../../core/data/request.service'; +import { LiveRegionService } from '../../../shared/live-region/live-region.service'; +/** + * Interface storing all the information necessary to create a row in the bitstream edit table + */ +export interface BitstreamTableEntry { + /** + * The bitstream + */ + bitstream: Bitstream, + /** + * The uuid of the Bitstream + */ + id: string, + /** + * The name of the Bitstream + */ + name: string, + /** + * The name of the Bitstream with all whitespace removed + */ + nameStripped: string, + /** + * The description of the Bitstream + */ + description: string, + /** + * Observable emitting the Format of the Bitstream + */ + format: Observable, + /** + * The download url of the Bitstream + */ + downloadUrl: string, +} + +/** + * Interface storing information necessary to highlight and reorder the selected bitstream entry + */ +export interface SelectedBitstreamTableEntry { + /** + * The selected entry + */ + bitstream: BitstreamTableEntry, + /** + * The bundle the bitstream belongs to + */ + bundle: Bundle, + /** + * The total number of bitstreams in the bundle + */ + bundleSize: number, + /** + * The original position of the bitstream within the bundle. + */ + originalPosition: number, + /** + * The current position of the bitstream within the bundle. + */ + currentPosition: number, +} + +/** + * This service handles the selection and updating of the bitstreams and their order on the + * 'Edit Item' -> 'Bitstreams' page. + */ @Injectable( { providedIn: 'root' }, ) export class ItemBitstreamsService { + /** + * BehaviorSubject which emits every time the selected bitstream changes. + */ + protected selectedBitstream$: BehaviorSubject = new BehaviorSubject(null); + + protected isPerformingMoveRequest = false; + constructor( protected notificationsService: NotificationsService, protected translateService: TranslateService, protected objectUpdatesService: ObjectUpdatesService, protected bitstreamService: BitstreamDataService, + protected bundleService: BundleDataService, protected dsoNameService: DSONameService, + protected requestService: RequestService, + protected liveRegionService: LiveRegionService, ) { } + /** + * Returns the observable emitting the currently selected bitstream + */ + getSelectedBitstream$(): Observable { + return this.selectedBitstream$; + } + + /** + * Returns a copy of the currently selected bitstream + */ + getSelectedBitstream(): SelectedBitstreamTableEntry { + const selected = this.selectedBitstream$.getValue(); + + if (hasNoValue(selected)) { + return selected; + } + + return Object.assign({}, selected); + } + + hasSelectedBitstream(): boolean { + return hasValue(this.getSelectedBitstream()); + } + + /** + * Select the provided entry + */ + selectBitstreamEntry(entry: SelectedBitstreamTableEntry) { + if (entry !== this.selectedBitstream$.getValue()) { + this.announceSelect(entry.bitstream.name); + this.updateSelectedBitstream(entry); + } + } + + /** + * Makes the {@link selectedBitstream$} observable emit the provided {@link SelectedBitstreamTableEntry}. + * @protected + */ + protected updateSelectedBitstream(entry: SelectedBitstreamTableEntry) { + this.selectedBitstream$.next(entry); + } + + /** + * Unselects the selected bitstream. Does nothing if no bitstream is selected. + */ + clearSelection() { + const selected = this.getSelectedBitstream(); + + if (hasValue(selected)) { + this.updateSelectedBitstream(null); + this.announceClear(selected.bitstream.name); + } + } + + /** + * Returns the currently selected bitstream to its original position and unselects the bitstream. + * Does nothing if no bitstream is selected. + */ + cancelSelection() { + const selected = this.getSelectedBitstream(); + + if (hasNoValue(selected) || this.isPerformingMoveRequest) { + return; + } + + this.selectedBitstream$.next(null); + + const originalPosition = selected.originalPosition; + const currentPosition = selected.currentPosition; + + // If the selected bitstream has not moved, there is no need to return it to its original position + if (currentPosition === originalPosition) { + this.announceClear(selected.bitstream.name); + } else { + this.announceCancel(selected.bitstream.name, originalPosition); + this.performBitstreamMoveRequest(selected.bundle, currentPosition, originalPosition); + } + } + + /** + * Moves the selected bitstream one position up in the bundle. Does nothing if no bitstream is selected or the + * selected bitstream already is at the beginning of the bundle. + */ + moveSelectedBitstreamUp() { + const selected = this.getSelectedBitstream(); + + if (hasNoValue(selected) || this.isPerformingMoveRequest) { + return; + } + + const originalPosition = selected.currentPosition; + if (originalPosition > 0) { + const newPosition = originalPosition - 1; + selected.currentPosition = newPosition; + + const onRequestCompleted = () => { + this.announceMove(selected.bitstream.name, newPosition); + }; + + this.performBitstreamMoveRequest(selected.bundle, originalPosition, newPosition, onRequestCompleted); + this.updateSelectedBitstream(selected); + } + } + + /** + * Moves the selected bitstream one position down in the bundle. Does nothing if no bitstream is selected or the + * selected bitstream already is at the end of the bundle. + */ + moveSelectedBitstreamDown() { + const selected = this.getSelectedBitstream(); + + if (hasNoValue(selected) || this.isPerformingMoveRequest) { + return; + } + + const originalPosition = selected.currentPosition; + if (originalPosition < selected.bundleSize - 1) { + const newPosition = originalPosition + 1; + selected.currentPosition = newPosition; + + const onRequestCompleted = () => { + this.announceMove(selected.bitstream.name, newPosition); + }; + + this.performBitstreamMoveRequest(selected.bundle, originalPosition, newPosition, onRequestCompleted); + this.updateSelectedBitstream(selected); + } + } + + /** + * Sends out a Move Patch request to the REST API, display notifications, + * refresh the bundle's cache (so the lists can properly reload) + * @param bundle The bundle to send patch requests to + * @param fromIndex The index to move from + * @param toIndex The index to move to + * @param finish Optional: Function to execute once the response has been received + */ + performBitstreamMoveRequest(bundle: Bundle, fromIndex: number, toIndex: number, finish?: () => void) { + if (this.isPerformingMoveRequest) { + console.warn('Attempted to perform move request while previous request has not completed yet'); + return; + } + + const moveOperation: MoveOperation = { + op: 'move', + from: `/_links/bitstreams/${fromIndex}/href`, + path: `/_links/bitstreams/${toIndex}/href`, + }; + + this.isPerformingMoveRequest = true; + this.bundleService.patch(bundle, [moveOperation]).pipe( + getFirstCompletedRemoteData(), + tap((response: RemoteData) => this.displayNotifications('item.edit.bitstreams.notifications.move', [response])), + switchMap(() => this.requestService.setStaleByHrefSubstring(bundle.self)), + take(1), + ).subscribe(() => { + this.isPerformingMoveRequest = false; + finish?.(); + }); + } + /** * Returns the pagination options to use when fetching the bundles */ @@ -46,6 +285,10 @@ export class ItemBitstreamsService { }); } + /** + * Returns the initial pagination options to use when fetching the bitstreams + * @param bundleName The name of the bundle, will be as pagination id. + */ getInitialBitstreamsPaginationOptions(bundleName: string): PaginationComponentOptions { return Object.assign(new PaginationComponentOptions(),{ id: bundleName, // This might behave unexpectedly if the item contains two bundles with the same name @@ -118,6 +361,10 @@ export class ItemBitstreamsService { ); } + /** + * Creates an array of {@link BitstreamTableEntry}s from an array of {@link Bitstream}s + * @param bitstreams The bitstreams array to map to table entries + */ mapBitstreamsToTableEntries(bitstreams: Bitstream[]): BitstreamTableEntry[] { return bitstreams.map((bitstream) => { const name = this.dsoNameService.getName(bitstream); @@ -143,7 +390,7 @@ export class ItemBitstreamsService { // To make it clear which headers are relevant for a specific field in the table, the 'headers' attribute is used to // refer to specific headers. The Bitstream's name is used as header ID for the row containing information regarding // that bitstream. As the 'headers' attribute contains a space-separated string of header IDs, the Bitstream's header - // ID can not contain strings itself. + // ID can not contain spaces itself. return this.stripWhiteSpace(name); } @@ -155,4 +402,48 @@ export class ItemBitstreamsService { // '/\s+/g' matches all occurrences of any amount of whitespace characters return str.replace(/\s+/g, ''); } + + /** + * Adds a message to the live region mentioning that the bitstream with the provided name was selected. + * @param bitstreamName The name of the bitstream that was selected. + */ + announceSelect(bitstreamName: string) { + const message = this.translateService.instant('item.edit.bitstreams.edit.live.select', + { bitstream: bitstreamName }); + this.liveRegionService.addMessage(message); + } + + /** + * Adds a message to the live region mentioning that the bitstream with the provided name was moved to the provided + * position. + * @param bitstreamName The name of the bitstream that moved. + * @param toPosition The zero-indexed position that the bitstream moved to. + */ + announceMove(bitstreamName: string, toPosition: number) { + const message = this.translateService.instant('item.edit.bitstreams.edit.live.move', + { bitstream: bitstreamName, toIndex: toPosition + 1 }); + this.liveRegionService.addMessage(message); + } + + /** + * Adds a message to the live region mentioning that the bitstream with the provided name is no longer selected and + * was returned to the provided position. + * @param bitstreamName The name of the bitstream that is no longer selected + * @param toPosition The zero-indexed position the bitstream returned to. + */ + announceCancel(bitstreamName: string, toPosition: number) { + const message = this.translateService.instant('item.edit.bitstreams.edit.live.cancel', + { bitstream: bitstreamName, toIndex: toPosition + 1 }); + this.liveRegionService.addMessage(message); + } + + /** + * Adds a message to the live region mentioning that the bitstream with the provided name is no longer selected. + * @param bitstreamName The name of the bitstream that is no longer selected. + */ + announceClear(bitstreamName: string) { + const message = this.translateService.instant('item.edit.bitstreams.edit.live.clear', + { bitstream: bitstreamName }); + this.liveRegionService.addMessage(message); + } } diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html index d530fb38d1..06fb571ce4 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html @@ -5,7 +5,8 @@ [hidePagerWhenSinglePage]="true" [hidePaginationDetail]="true" [paginationOptions]="paginationOptions" - [collectionSize]="bitstreamsList.totalElements"> + [collectionSize]="bitstreamsList.totalElements" + [retainScrollPosition]="true"> -
    - -
    + diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts index 1502ad2311..25274b8941 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts @@ -58,6 +58,7 @@ describe('ItemEditBitstreamBundleComponent', () => { currentPage: 1, pageSize: 9999 }), + getSelectedBitstream$: observableOf({}), }); beforeEach(waitForAsync(() => { diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index 24319f4a83..7d2a519baf 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -1,4 +1,10 @@ -import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core'; +import { + Component, + Input, + OnInit, + ViewChild, + ViewContainerRef, OnDestroy, +} from '@angular/core'; import { Bundle } from '../../../../core/shared/bundle.model'; import { Item } from '../../../../core/shared/item.model'; import { ResponsiveColumnSizes } from '../../../../shared/responsive-table-sizes/responsive-column-sizes'; @@ -8,7 +14,7 @@ import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { RemoteData } from 'src/app/core/data/remote-data'; import { PaginatedList } from 'src/app/core/data/paginated-list.model'; import { Bitstream } from 'src/app/core/shared/bitstream.model'; -import { Observable, BehaviorSubject, switchMap } from 'rxjs'; +import { Observable, BehaviorSubject, switchMap, shareReplay, Subscription } from 'rxjs'; import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; import { FieldUpdates } from '../../../../core/data/object-updates/field-updates.model'; import { PaginatedSearchOptions } from '../../../../shared/search/models/paginated-search-options.model'; @@ -17,55 +23,17 @@ import { followLink } from '../../../../shared/utils/follow-link-config.model'; import { getAllSucceededRemoteData, paginatedListToArray, - getFirstSucceededRemoteData } from '../../../../core/shared/operators'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; -import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; -import { map, take, filter } from 'rxjs/operators'; +import { map, take, filter, tap, pairwise } from 'rxjs/operators'; import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model'; import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model'; import { PaginationService } from '../../../../core/pagination/pagination.service'; import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; import { RequestService } from '../../../../core/data/request.service'; -import { ItemBitstreamsService } from '../item-bitstreams.service'; -import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; -import { hasValue } from '../../../../shared/empty.util'; -import { LiveRegionService } from '../../../../shared/live-region/live-region.service'; -import { TranslateService } from '@ngx-translate/core'; - -/** - * Interface storing all the information necessary to create a row in the bitstream edit table - */ -export interface BitstreamTableEntry { - /** - * The bitstream - */ - bitstream: Bitstream, - /** - * The uuid of the Bitstream - */ - id: string, - /** - * The name of the Bitstream - */ - name: string, - /** - * The name of the Bitstream with all whitespace removed - */ - nameStripped: string, - /** - * The description of the Bitstream - */ - description: string, - /** - * Observable emitting the Format of the Bitstream - */ - format: Observable, - /** - * The download url of the Bitstream - */ - downloadUrl: string, -} +import { ItemBitstreamsService, BitstreamTableEntry, SelectedBitstreamTableEntry } from '../item-bitstreams.service'; +import { CdkDragDrop } from '@angular/cdk/drag-drop'; +import { hasValue, hasNoValue } from '../../../../shared/empty.util'; @Component({ selector: 'ds-item-edit-bitstream-bundle', @@ -77,7 +45,7 @@ export interface BitstreamTableEntry { * Creates an embedded view of the contents. This is to ensure the table structure won't break. * (which means it'll be added to the parents html without a wrapping ds-item-edit-bitstream-bundle element) */ -export class ItemEditBitstreamBundleComponent implements OnInit { +export class ItemEditBitstreamBundleComponent implements OnInit, OnDestroy { protected readonly FieldChangeType = FieldChangeType; /** @@ -115,13 +83,6 @@ export class ItemEditBitstreamBundleComponent implements OnInit { */ @Input() isFirstTable = false; - /** - * Send an event when the user drops an object on the pagination - * The event contains details about the index the object came from and is dropped to (across the entirety of the list, - * not just within a single page) - */ - @Output() dropObject: EventEmitter = new EventEmitter(); - /** * The bootstrap sizes used for the Bundle Name column * This column stretches over the first 3 columns and thus is a combination of their sizes processed in ngOnInit @@ -138,6 +99,11 @@ export class ItemEditBitstreamBundleComponent implements OnInit { */ bundleName: string; + /** + * The number of bitstreams in the bundle + */ + bundleSize: number; + /** * The bitstreams to show in the table */ @@ -146,7 +112,7 @@ export class ItemEditBitstreamBundleComponent implements OnInit { /** * The data to show in the table */ - tableEntries$: BehaviorSubject = new BehaviorSubject(null); + tableEntries$: BehaviorSubject = new BehaviorSubject([]); /** * The initial page options to use for fetching the bitstreams @@ -158,11 +124,6 @@ export class ItemEditBitstreamBundleComponent implements OnInit { */ currentPaginationOptions$: BehaviorSubject; - /** - * The available page size options - */ - pageSizeOptions: number[]; - /** * The currently selected page size */ @@ -178,6 +139,11 @@ export class ItemEditBitstreamBundleComponent implements OnInit { */ updates$: BehaviorSubject = new BehaviorSubject(null); + /** + * Array containing all subscriptions created by this component + */ + subscriptions: Subscription[] = []; + constructor( protected viewContainerRef: ViewContainerRef, @@ -187,8 +153,6 @@ export class ItemEditBitstreamBundleComponent implements OnInit { protected paginationService: PaginationService, protected requestService: RequestService, protected itemBitstreamsService: ItemBitstreamsService, - protected liveRegionService: LiveRegionService, - protected translateService: TranslateService, ) { } @@ -201,23 +165,27 @@ export class ItemEditBitstreamBundleComponent implements OnInit { this.initializePagination(); this.initializeBitstreams(); + this.initializeSelectionActions(); + } - // this.bitstreamsRD = this. + ngOnDestroy() { + this.subscriptions.forEach(sub => sub?.unsubscribe()); } protected initializePagination() { this.paginationOptions = this.itemBitstreamsService.getInitialBitstreamsPaginationOptions(this.bundleName); - this.pageSizeOptions = this.paginationOptions.pageSizeOptions; - this.currentPaginationOptions$ = new BehaviorSubject(this.paginationOptions); this.pageSize$ = new BehaviorSubject(this.paginationOptions.pageSize); - this.paginationService.getCurrentPagination(this.paginationOptions.id, this.paginationOptions) - .subscribe((pagination) => { - this.currentPaginationOptions$.next(pagination); - this.pageSize$.next(pagination.pageSize); - }); + this.subscriptions.push( + this.paginationService.getCurrentPagination(this.paginationOptions.id, this.paginationOptions) + .subscribe((pagination) => { + this.currentPaginationOptions$.next(pagination); + this.pageSize$.next(pagination.pageSize); + }) + ); + } protected initializeBitstreams() { @@ -233,26 +201,88 @@ export class ItemEditBitstreamBundleComponent implements OnInit { )) ); }), + getAllSucceededRemoteData(), + shareReplay(1), ); - this.bitstreamsRD$.pipe( - getFirstSucceededRemoteData(), - paginatedListToArray(), - ).subscribe((bitstreams) => { - this.objectUpdatesService.initialize(this.bundleUrl, bitstreams, new Date()); - }); + this.subscriptions.push( + this.bitstreamsRD$.pipe( + take(1), + tap(bitstreamsRD => this.bundleSize = bitstreamsRD.payload.totalElements), + paginatedListToArray(), + ).subscribe((bitstreams) => { + this.objectUpdatesService.initialize(this.bundleUrl, bitstreams, new Date()); + }), - this.bitstreamsRD$.pipe( - getAllSucceededRemoteData(), - paginatedListToArray(), - switchMap((bitstreams) => this.objectUpdatesService.getFieldUpdatesExclusive(this.bundleUrl, bitstreams)) - ).subscribe((updates) => this.updates$.next(updates)); + this.bitstreamsRD$.pipe( + paginatedListToArray(), + switchMap((bitstreams) => this.objectUpdatesService.getFieldUpdatesExclusive(this.bundleUrl, bitstreams)) + ).subscribe((updates) => this.updates$.next(updates)), - this.bitstreamsRD$.pipe( - getAllSucceededRemoteData(), - paginatedListToArray(), - map((bitstreams) => this.itemBitstreamsService.mapBitstreamsToTableEntries(bitstreams)), - ).subscribe((tableEntries) => this.tableEntries$.next(tableEntries)); + this.bitstreamsRD$.pipe( + paginatedListToArray(), + map((bitstreams) => this.itemBitstreamsService.mapBitstreamsToTableEntries(bitstreams)), + ).subscribe((tableEntries) => this.tableEntries$.next(tableEntries)), + ); + } + + protected initializeSelectionActions() { + this.subscriptions.push( + this.itemBitstreamsService.getSelectedBitstream$().pipe(pairwise()).subscribe( + ([previousSelection, currentSelection]) => + this.handleSelectedEntryChange(previousSelection, currentSelection)) + ); + } + + /** + * Handles a change in selected bitstream by changing the pagination if the change happened on a different page + * @param previousSelectedEntry The previously selected entry + * @param currentSelectedEntry The currently selected entry + * @protected + */ + protected handleSelectedEntryChange( + previousSelectedEntry: SelectedBitstreamTableEntry, + currentSelectedEntry: SelectedBitstreamTableEntry + ) { + if (hasValue(currentSelectedEntry) && currentSelectedEntry.bundle === this.bundle) { + // If the currently selected bitstream belongs to this bundle, it has possibly moved to a different page. + // In that case we want to change the pagination to the new page. + this.redirectToCurrentPage(currentSelectedEntry); + } + + // If the selection is cancelled or cleared, it is possible the selected bitstream is currently on a different page + // In that case we want to change the pagination to the place where the bitstream was returned to + if (hasNoValue(currentSelectedEntry) && hasValue(previousSelectedEntry) && previousSelectedEntry.bundle === this.bundle) { + this.redirectToOriginalPage(previousSelectedEntry); + } + } + + /** + * Redirect the user to the current page of the provided bitstream if it is on a different page. + * @param bitstreamEntry The entry that the current position will be taken from to determine the page to move to + * @protected + */ + protected redirectToCurrentPage(bitstreamEntry: SelectedBitstreamTableEntry) { + const currentPage = this.getCurrentPage(); + const selectedEntryPage = this.bundleIndexToPage(bitstreamEntry.currentPosition); + + if (currentPage !== selectedEntryPage) { + this.changeToPage(selectedEntryPage); + } + } + + /** + * Redirect the user to the original page of the provided bitstream if it is on a different page. + * @param bitstreamEntry The entry that the original position will be taken from to determine the page to move to + * @protected + */ + protected redirectToOriginalPage(bitstreamEntry: SelectedBitstreamTableEntry) { + const currentPage = this.getCurrentPage(); + const originPage = this.bundleIndexToPage(bitstreamEntry.originalPosition); + + if (currentPage !== originPage) { + this.changeToPage(originPage); + } } /** @@ -283,7 +313,18 @@ export class ItemEditBitstreamBundleComponent implements OnInit { this.objectUpdatesService.removeSingleFieldUpdate(this.bundleUrl, bitstream.uuid); } - getRowClass(update: FieldUpdate): string { + /** + * Returns the css class for a table row depending on the state of the table entry. + * @param update + * @param bitstream + */ + getRowClass(update: FieldUpdate, bitstream: BitstreamTableEntry): string { + const selected = this.itemBitstreamsService.getSelectedBitstream(); + + if (hasValue(selected) && bitstream.id === selected.bitstream.id) { + return 'table-info'; + } + switch (update.changeType) { case FieldChangeType.UPDATE: return 'table-warning'; @@ -296,11 +337,19 @@ export class ItemEditBitstreamBundleComponent implements OnInit { } } + /** + * Changes the page size to the provided page size. + * @param pageSize + */ public doPageSizeChange(pageSize: number) { this.paginationComponent.doPageSizeChange(pageSize); } - dragStart(bitstreamName: string) { + /** + * Handles start of dragging by opening the tooltip mentioning that it is possible to drag a bitstream to a different + * page by dropping it on the page number, only if there are multiple pages. + */ + dragStart() { // Only open the drag tooltip when there are multiple pages this.paginationComponent.shouldShowBottomPager.pipe( take(1), @@ -308,66 +357,170 @@ export class ItemEditBitstreamBundleComponent implements OnInit { ).subscribe(() => { this.dragTooltip.open(); }); - - const message = this.translateService.instant('item.edit.bitstreams.edit.live.drag', - { bitstream: bitstreamName }); - this.liveRegionService.addMessage(message); } - dragEnd(bitstreamName: string) { + /** + * Handles end of dragging by closing the tooltip. + */ + dragEnd() { this.dragTooltip.close(); - - const message = this.translateService.instant('item.edit.bitstreams.edit.live.drop', - { bitstream: bitstreamName }); - this.liveRegionService.addMessage(message); } - + /** + * Handles dropping by calculation the target position, and changing the page if the bitstream was dropped on a + * different page. + * @param event + */ drop(event: CdkDragDrop) { const dragIndex = event.previousIndex; let dropIndex = event.currentIndex; - const dragPage = this.currentPaginationOptions$.value.currentPage - 1; - let dropPage = this.currentPaginationOptions$.value.currentPage - 1; + const dragPage = this.getCurrentPage(); + let dropPage = this.getCurrentPage(); // Check if the user is hovering over any of the pagination's pages at the time of dropping the object const droppedOnElement = document.elementFromPoint(event.dropPoint.x, event.dropPoint.y); if (hasValue(droppedOnElement) && hasValue(droppedOnElement.textContent) && droppedOnElement.classList.contains('page-link')) { // The user is hovering over a page, fetch the page's number from the element - const droppedPage = Number(droppedOnElement.textContent); + let droppedPage = Number(droppedOnElement.textContent); if (hasValue(droppedPage) && !Number.isNaN(droppedPage)) { - dropPage = droppedPage - 1; - dropIndex = 0; + droppedPage -= 1; + + if (droppedPage !== dragPage) { + dropPage = droppedPage; + + if (dropPage > dragPage) { + // When moving to later page, place bitstream at the top + dropIndex = 0; + } else { + // When moving to earlier page, place bitstream at the bottom + dropIndex = this.getCurrentPageSize() - 1; + } + } } } - const isNewPage = dragPage !== dropPage; - // Move the object in the custom order array if the drop happened within the same page - // This allows us to instantly display a change in the order, instead of waiting for the REST API's response first - if (!isNewPage && dragIndex !== dropIndex) { - const currentEntries = [...this.tableEntries$.value]; - moveItemInArray(currentEntries, dragIndex, dropIndex); - this.tableEntries$.next(currentEntries); + const fromIndex = this.pageIndexToBundleIndex(dragIndex, dragPage); + const toIndex = this.pageIndexToBundleIndex(dropIndex, dropPage); + + if (fromIndex === toIndex) { + return; } - const pageSize = this.currentPaginationOptions$.value.pageSize; - const redirectPage = dropPage + 1; - const fromIndex = (dragPage * pageSize) + dragIndex; - const toIndex = (dropPage * pageSize) + dropIndex; - // Send out a drop event (and navigate to the new page) when the "from" and "to" indexes are different from each other - if (fromIndex !== toIndex) { - // if (isNewPage) { - // this.loading$.next(true); - // } - this.dropObject.emit(Object.assign({ - fromIndex, - toIndex, - finish: () => { - if (isNewPage) { - this.paginationComponent.doPageChange(redirectPage); - } - } - })); + const selectedBitstream = this.tableEntries$.value[dragIndex]; + + const finish = () => { + this.itemBitstreamsService.announceMove(selectedBitstream.name, toIndex); + + if (dropPage !== this.getCurrentPage()) { + this.changeToPage(dropPage); + } + }; + + this.itemBitstreamsService.performBitstreamMoveRequest(this.bundle, fromIndex, toIndex, finish); + } + + /** + * Handles a select action for the provided bitstream entry. + * If the selected bitstream is currently selected, the selection is cleared. + * If no, or a different bitstream, is selected, the provided bitstream becomes the selected bitstream. + * @param bitstream + */ + select(bitstream: BitstreamTableEntry) { + const selectedBitstream = this.itemBitstreamsService.getSelectedBitstream(); + + if (hasValue(selectedBitstream) && selectedBitstream.bitstream === bitstream) { + this.itemBitstreamsService.cancelSelection(); + } else { + const selectionObject = this.createBitstreamSelectionObject(bitstream); + + if (hasNoValue(selectionObject)) { + console.warn('Failed to create selection object'); + return; + } + + this.itemBitstreamsService.selectBitstreamEntry(selectionObject); } } + /** + * Creates a {@link SelectedBitstreamTableEntry} from the provided {@link BitstreamTableEntry} so it can be given + * to the {@link ItemBitstreamsService} to select the table entry. + * @param bitstream The table entry to create the selection object from. + * @protected + */ + protected createBitstreamSelectionObject(bitstream: BitstreamTableEntry): SelectedBitstreamTableEntry { + const pageIndex = this.findBitstreamPageIndex(bitstream); + + if (pageIndex === -1) { + return null; + } + + const position = this.pageIndexToBundleIndex(pageIndex, this.getCurrentPage()); + + return { + bitstream: bitstream, + bundle: this.bundle, + bundleSize: this.bundleSize, + currentPosition: position, + originalPosition: position, + }; + } + + /** + * Returns the index of the provided {@link BitstreamTableEntry} relative to the current page + * If the current page size is 10, it will return a value from 0 to 9 (inclusive) + * Returns -1 if the provided bitstream could not be found + * @protected + */ + protected findBitstreamPageIndex(bitstream: BitstreamTableEntry): number { + const entries = this.tableEntries$.value; + return entries.findIndex(entry => entry === bitstream); + } + + /** + * Returns the current zero-indexed page + * @protected + */ + protected getCurrentPage(): number { + // The pagination component uses one-based numbering while zero-based numbering is more convenient for calculations + return this.currentPaginationOptions$.value.currentPage - 1; + } + + /** + * Returns the current page size + * @protected + */ + protected getCurrentPageSize(): number { + return this.currentPaginationOptions$.value.pageSize; + } + + /** + * Converts an index relative to the page to an index relative to the bundle + * @param index The index relative to the page + * @param page The zero-indexed page number + * @protected + */ + protected pageIndexToBundleIndex(index: number, page: number) { + return page * this.getCurrentPageSize() + index; + } + + /** + * Calculates the zero-indexed page number from the index relative to the bundle + * @param index The index relative to the bundle + * @protected + */ + protected bundleIndexToPage(index: number) { + return Math.floor(index / this.getCurrentPageSize()); + } + + /** + * Change the pagination for this bundle to the provided zero-indexed page + * @param page The zero-indexed page to change to + * @protected + */ + protected changeToPage(page: number) { + // Increments page by one because zero-indexing is way easier for calculations but the pagination component + // uses one-indexing. + this.paginationComponent.doPageChange(page + 1); + } } diff --git a/src/app/shared/live-region/live-region.service.stub.ts b/src/app/shared/live-region/live-region.service.stub.ts new file mode 100644 index 0000000000..4f10b46a4c --- /dev/null +++ b/src/app/shared/live-region/live-region.service.stub.ts @@ -0,0 +1,30 @@ +import { of } from 'rxjs'; +import { LiveRegionService } from './live-region.service'; + +export function getLiveRegionServiceStub(): LiveRegionService { + return new LiveRegionServiceStub() as unknown as LiveRegionService; +} + +export class LiveRegionServiceStub { + getMessages = jasmine.createSpy('getMessages').and.returnValue( + ['Message One', 'Message Two'] + ); + + getMessages$ = jasmine.createSpy('getMessages$').and.returnValue( + of(['Message One', 'Message Two']) + ); + + addMessage = jasmine.createSpy('addMessage').and.returnValue('messageId'); + + clear = jasmine.createSpy('clear'); + + clearMessageByUUID = jasmine.createSpy('clearMessageByUUID'); + + getLiveRegionVisibility = jasmine.createSpy('getLiveRegionVisibility').and.returnValue(false); + + setLiveRegionVisibility = jasmine.createSpy('setLiveRegionVisibility'); + + getMessageTimeOutMs = jasmine.createSpy('getMessageTimeOutMs').and.returnValue(30000); + + setMessageTimeOutMs = jasmine.createSpy('setMessageTimeOutMs'); +} diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index ab6f3792ca..519189ed69 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1950,9 +1950,13 @@ "item.edit.bitstreams.edit.buttons.undo": "Undo changes", - "item.edit.bitstreams.edit.live.drag": "{{ bitstream }} grabbed", + "item.edit.bitstreams.edit.live.cancel": "{{ bitstream }} was returned to position {{ toIndex }} and is no longer selected.", - "item.edit.bitstreams.edit.live.drop": "{{ bitstream }} dropped", + "item.edit.bitstreams.edit.live.clear": "{{ bitstream }} is no longer selected.", + + "item.edit.bitstreams.edit.live.select": "{{ bitstream }} is selected.", + + "item.edit.bitstreams.edit.live.move": "{{ bitstream }} is now in position {{ toIndex }}.", "item.edit.bitstreams.empty": "This item doesn't contain any bitstreams. Click the upload button to create one.", From 2e1b1489b65c6c6715774ef482242e8f14d990f0 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Fri, 4 Oct 2024 09:40:20 +0200 Subject: [PATCH 558/875] 118223: Stop space from scrolling down page --- .../item-bitstreams/item-bitstreams.component.ts | 1 + .../item-edit-bitstream-bundle.component.html | 2 +- .../item-edit-bitstream-bundle.component.ts | 12 ++++++++++-- 3 files changed, 12 insertions(+), 3 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 4ced3dd649..f77eda02fb 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 @@ -138,6 +138,7 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme // Otherwise we might clear the selection when a different action was intended, e.g. clicking a button or selecting // a different bitstream. if (event.target instanceof Element && event.target.tagName === 'BODY') { + event.preventDefault(); this.itemBitstreamsService.clearSelection(); } } diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html index 06fb571ce4..9afdd2d41c 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html @@ -82,7 +82,7 @@
    -
    - +
    +
    {{ entry.name }}
    + (keydown.enter)="select($event, entry)" (keydown.space)="select($event, entry)" (click)="select($event, entry)">
    diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index 7d2a519baf..e2dff2f018 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -423,9 +423,17 @@ export class ItemEditBitstreamBundleComponent implements OnInit, OnDestroy { * Handles a select action for the provided bitstream entry. * If the selected bitstream is currently selected, the selection is cleared. * If no, or a different bitstream, is selected, the provided bitstream becomes the selected bitstream. - * @param bitstream + * @param event The event that triggered the select action + * @param bitstream The bitstream that is the target of the select action */ - select(bitstream: BitstreamTableEntry) { + select(event: UIEvent, bitstream: BitstreamTableEntry) { + event.preventDefault(); + + if (event instanceof KeyboardEvent && event.repeat) { + // Don't handle hold events, otherwise it would change rapidly between being selected and unselected + return; + } + const selectedBitstream = this.itemBitstreamsService.getSelectedBitstream(); if (hasValue(selectedBitstream) && selectedBitstream.bitstream === bitstream) { From 0920a218762a4aa547c8b9bf72e795e8321e73ff Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Fri, 4 Oct 2024 10:53:42 +0200 Subject: [PATCH 559/875] 118223: Stop sending success notificiations on every move --- .../item-bitstreams.service.ts | 60 +++++++++++++++---- .../item-edit-bitstream-bundle.component.ts | 9 ++- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts index 21dc415198..f8091d616a 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts @@ -3,7 +3,7 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes'; import { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-sizes'; import { RemoteData } from '../../../core/data/remote-data'; -import { isNotEmpty, hasValue, hasNoValue } from '../../../shared/empty.util'; +import { hasValue, hasNoValue } from '../../../shared/empty.util'; import { Bundle } from '../../../core/shared/bundle.model'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; @@ -25,6 +25,8 @@ import { BundleDataService } from '../../../core/data/bundle-data.service'; import { RequestService } from '../../../core/data/request.service'; import { LiveRegionService } from '../../../shared/live-region/live-region.service'; +export const MOVE_KEY = 'item.edit.bitstreams.notifications.move'; + /** * Interface storing all the information necessary to create a row in the bitstream edit table */ @@ -164,6 +166,10 @@ export class ItemBitstreamsService { if (hasValue(selected)) { this.updateSelectedBitstream(null); this.announceClear(selected.bitstream.name); + + if (selected.currentPosition !== selected.originalPosition) { + this.displaySuccessNotification(MOVE_KEY); + } } } @@ -265,7 +271,7 @@ export class ItemBitstreamsService { this.isPerformingMoveRequest = true; this.bundleService.patch(bundle, [moveOperation]).pipe( getFirstCompletedRemoteData(), - tap((response: RemoteData) => this.displayNotifications('item.edit.bitstreams.notifications.move', [response])), + tap((response: RemoteData) => this.displayFailedResponseNotifications(MOVE_KEY, [response])), switchMap(() => this.requestService.setStaleByHrefSubstring(bundle.self)), take(1), ).subscribe(() => { @@ -321,19 +327,51 @@ export class ItemBitstreamsService { * @param responses The returned responses to display notifications for */ displayNotifications(key: string, responses: RemoteData[]) { - if (isNotEmpty(responses)) { - const failedResponses = responses.filter((response: RemoteData) => hasValue(response) && response.hasFailed); - const successfulResponses = responses.filter((response: RemoteData) => hasValue(response) && response.hasSucceeded); + this.displayFailedResponseNotifications(key, responses); + this.displaySuccessFulResponseNotifications(key, responses); + } - failedResponses.forEach((response: RemoteData) => { - this.notificationsService.error(this.translateService.instant(`${key}.failed.title`), response.errorMessage); - }); - if (successfulResponses.length > 0) { - this.notificationsService.success(this.translateService.instant(`${key}.saved.title`), this.translateService.instant(`${key}.saved.content`)); - } + /** + * Display an error notification for each failed response with their message + * @param key The i18n key for the notification messages + * @param responses The returned responses to display notifications for + */ + displayFailedResponseNotifications(key: string, responses: RemoteData[]) { + const failedResponses = responses.filter((response: RemoteData) => hasValue(response) && response.hasFailed); + failedResponses.forEach((response: RemoteData) => { + this.displayErrorNotification(key, response.errorMessage); + }); + } + + /** + * Display an error notification with the provided key and message + * @param key The i18n key for the notification messages + * @param errorMessage The error message to display + */ + displayErrorNotification(key: string, errorMessage: string) { + this.notificationsService.error(this.translateService.instant(`${key}.failed.title`), errorMessage); + } + + /** + * Display a success notification in case there's at least one successful response + * @param key The i18n key for the notification messages + * @param responses The returned responses to display notifications for + */ + displaySuccessFulResponseNotifications(key: string, responses: RemoteData[]) { + const successfulResponses = responses.filter((response: RemoteData) => hasValue(response) && response.hasSucceeded); + if (successfulResponses.length > 0) { + this.displaySuccessNotification(key); } } + /** + * Display a success notification with the provided key + * @param key The i18n key for the notification messages + */ + displaySuccessNotification(key: string) { + this.notificationsService.success(this.translateService.instant(`${key}.saved.title`), this.translateService.instant(`${key}.saved.content`)); + } + /** * Removes the bitstreams marked for deletion from the Bundles emitted by the provided observable. * @param bundles$ diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index e2dff2f018..4079ad225b 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -31,7 +31,12 @@ import { FieldUpdate } from '../../../../core/data/object-updates/field-update.m import { PaginationService } from '../../../../core/pagination/pagination.service'; import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; import { RequestService } from '../../../../core/data/request.service'; -import { ItemBitstreamsService, BitstreamTableEntry, SelectedBitstreamTableEntry } from '../item-bitstreams.service'; +import { + ItemBitstreamsService, + BitstreamTableEntry, + SelectedBitstreamTableEntry, + MOVE_KEY +} from '../item-bitstreams.service'; import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { hasValue, hasNoValue } from '../../../../shared/empty.util'; @@ -414,6 +419,8 @@ export class ItemEditBitstreamBundleComponent implements OnInit, OnDestroy { if (dropPage !== this.getCurrentPage()) { this.changeToPage(dropPage); } + + this.itemBitstreamsService.displaySuccessNotification(MOVE_KEY); }; this.itemBitstreamsService.performBitstreamMoveRequest(this.bundle, fromIndex, toIndex, finish); From b158c5c2a275ff64108229c37083da7e7f78720e Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Fri, 4 Oct 2024 10:58:52 +0200 Subject: [PATCH 560/875] 118223: Move drag tooltip to center of pagination numbers --- .../item-edit-bitstream-bundle.component.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html index 9afdd2d41c..efbdd8c69b 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html @@ -6,7 +6,9 @@ [hidePaginationDetail]="true" [paginationOptions]="paginationOptions" [collectionSize]="bitstreamsList.totalElements" - [retainScrollPosition]="true"> + [retainScrollPosition]="true" + [ngbTooltip]="'item.edit.bitstreams.bundle.tooltip' | translate" placement="bottom" + [autoClose]="false" triggers="manual" #dragTooltip="ngbTooltip"> - + +
    {{'item.edit.bitstreams.bundle.name' | translate:{ name: bundleName } }} From 1dcc5d1ec5a21667e90322fb2a6dc4528d8f5c0b Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Fri, 4 Oct 2024 15:16:16 +0200 Subject: [PATCH 561/875] 118223: Add ItemBitstreams service tests --- .../item-bitstreams.service.spec.ts | 443 +++++++++++++++++- .../item-bitstreams.service.ts | 4 +- 2 files changed, 444 insertions(+), 3 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts index e144e81ec7..94adb5f23a 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts @@ -1,4 +1,4 @@ -import { ItemBitstreamsService } from './item-bitstreams.service'; +import { ItemBitstreamsService, SelectedBitstreamTableEntry } from './item-bitstreams.service'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { getMockTranslateService } from '../../../shared/mocks/translate.service.mock'; import { ObjectUpdatesServiceStub } from '../../../core/data/object-updates/object-updates.service.stub'; @@ -22,6 +22,9 @@ import { LiveRegionService } from '../../../shared/live-region/live-region.servi import { Bundle } from '../../../core/shared/bundle.model'; import { of } from 'rxjs'; import { getLiveRegionServiceStub } from '../../../shared/live-region/live-region.service.stub'; +import { fakeAsync, tick } from '@angular/core/testing'; +import createSpy = jasmine.createSpy; +import { MoveOperation } from 'fast-json-patch'; describe('ItemBitstreamsService', () => { let service: ItemBitstreamsService; @@ -60,6 +63,444 @@ describe('ItemBitstreamsService', () => { ); }); + const defaultEntry: SelectedBitstreamTableEntry = { + bitstream: { + name: 'bitstream name', + } as any, + bundle: Object.assign(new Bundle(), { + _links: { self: { href: 'self_link' }}, + }), + bundleSize: 10, + currentPosition: 0, + originalPosition: 0, + }; + + describe('selectBitstreamEntry', () => { + it('should correctly make getSelectedBitstream$ emit', fakeAsync(() => { + const emittedEntries = []; + + service.getSelectedBitstream$().subscribe(selected => emittedEntries.push(selected)); + + expect(emittedEntries.length).toBe(1); + expect(emittedEntries[0]).toBeNull(); + + const entry = Object.assign({}, defaultEntry); + + service.selectBitstreamEntry(entry); + tick(); + + expect(emittedEntries.length).toBe(2); + expect(emittedEntries[1]).toEqual(entry); + })); + + it('should correctly make getSelectedBitstream return the bitstream', () => { + expect(service.getSelectedBitstream()).toBeNull(); + + const entry = Object.assign({}, defaultEntry); + + service.selectBitstreamEntry(entry); + expect(service.getSelectedBitstream()).toEqual(entry); + }); + + it('should correctly make hasSelectedBitstream return', () => { + expect(service.hasSelectedBitstream()).toBeFalse(); + + const entry = Object.assign({}, defaultEntry); + + service.selectBitstreamEntry(entry); + expect(service.hasSelectedBitstream()).toBeTrue(); + }); + + it('should do nothing if no entry was provided', fakeAsync(() => { + const emittedEntries = []; + + service.getSelectedBitstream$().subscribe(selected => emittedEntries.push(selected)); + + expect(emittedEntries.length).toBe(1); + expect(emittedEntries[0]).toBeNull(); + + const entry = Object.assign({}, defaultEntry); + + service.selectBitstreamEntry(entry); + tick(); + + expect(emittedEntries.length).toBe(2); + expect(emittedEntries[1]).toEqual(entry); + + service.selectBitstreamEntry(null); + tick(); + + expect(emittedEntries.length).toBe(2); + expect(emittedEntries[1]).toEqual(entry); + })); + + it('should announce the selected bitstream', () => { + const entry = Object.assign({}, defaultEntry); + + spyOn(service, 'announceSelect'); + + service.selectBitstreamEntry(entry); + expect(service.announceSelect).toHaveBeenCalledWith(entry.bitstream.name); + }); + }); + + describe('clearSelection', () => { + it('should clear the selected bitstream', fakeAsync(() => { + const emittedEntries = []; + + service.getSelectedBitstream$().subscribe(selected => emittedEntries.push(selected)); + + expect(emittedEntries.length).toBe(1); + expect(emittedEntries[0]).toBeNull(); + + const entry = Object.assign({}, defaultEntry); + + service.selectBitstreamEntry(entry); + tick(); + + expect(emittedEntries.length).toBe(2); + expect(emittedEntries[1]).toEqual(entry); + + service.clearSelection(); + tick(); + + expect(emittedEntries.length).toBe(3); + expect(emittedEntries[2]).toBeNull(); + })); + + it('should not do anything if there is no selected bitstream', fakeAsync(() => { + const emittedEntries = []; + + service.getSelectedBitstream$().subscribe(selected => emittedEntries.push(selected)); + + expect(emittedEntries.length).toBe(1); + expect(emittedEntries[0]).toBeNull(); + + service.clearSelection(); + tick(); + + expect(emittedEntries.length).toBe(1); + expect(emittedEntries[0]).toBeNull(); + })); + + it('should announce the cleared bitstream', () => { + const entry = Object.assign({}, defaultEntry); + + spyOn(service, 'announceClear'); + service.selectBitstreamEntry(entry); + service.clearSelection(); + + expect(service.announceClear).toHaveBeenCalledWith(entry.bitstream.name); + }); + + it('should display a notification if the selected bitstream was moved', () => { + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: 7, + } + ); + + spyOn(service, 'displaySuccessNotification'); + service.selectBitstreamEntry(entry); + service.clearSelection(); + + expect(service.displaySuccessNotification).toHaveBeenCalled(); + }); + + it('should not display a notification if the selected bitstream is in its original position', () => { + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 7, + currentPosition: 7, + } + ); + + spyOn(service, 'displaySuccessNotification'); + service.selectBitstreamEntry(entry); + service.clearSelection(); + + expect(service.displaySuccessNotification).not.toHaveBeenCalled(); + }); + }); + + describe('cancelSelection', () => { + it('should clear the selected bitstream', fakeAsync(() => { + const emittedEntries = []; + + service.getSelectedBitstream$().subscribe(selected => emittedEntries.push(selected)); + + expect(emittedEntries.length).toBe(1); + expect(emittedEntries[0]).toBeNull(); + + const entry = Object.assign({}, defaultEntry); + + service.selectBitstreamEntry(entry); + tick(); + + expect(emittedEntries.length).toBe(2); + expect(emittedEntries[1]).toEqual(entry); + + service.cancelSelection(); + tick(); + + expect(emittedEntries.length).toBe(3); + expect(emittedEntries[2]).toBeNull(); + })); + + it('should announce a clear if the bitstream has not moved', () => { + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 7, + currentPosition: 7, + } + ); + + spyOn(service, 'announceClear'); + spyOn(service, 'announceCancel'); + + service.selectBitstreamEntry(entry); + service.cancelSelection(); + + expect(service.announceClear).toHaveBeenCalledWith(entry.bitstream.name); + expect(service.announceCancel).not.toHaveBeenCalled(); + }); + + it('should announce a cancel if the bitstream has moved', () => { + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: 7, + } + ); + + spyOn(service, 'announceClear'); + spyOn(service, 'announceCancel'); + + service.selectBitstreamEntry(entry); + service.cancelSelection(); + + expect(service.announceClear).not.toHaveBeenCalled(); + expect(service.announceCancel).toHaveBeenCalledWith(entry.bitstream.name, entry.originalPosition); + }); + + it('should return the bitstream to its original position if it has moved', () => { + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: 7, + } + ); + + spyOn(service, 'performBitstreamMoveRequest'); + + service.selectBitstreamEntry(entry); + service.cancelSelection(); + + expect(service.performBitstreamMoveRequest).toHaveBeenCalledWith(entry.bundle, entry.currentPosition, entry.originalPosition); + }); + + it('should not move the bitstream if it has not moved', () => { + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 7, + currentPosition: 7, + } + ); + + spyOn(service, 'performBitstreamMoveRequest'); + + service.selectBitstreamEntry(entry); + service.cancelSelection(); + + expect(service.performBitstreamMoveRequest).not.toHaveBeenCalled(); + }); + + it('should not do anything if there is no selected bitstream', () => { + spyOn(service, 'announceClear'); + spyOn(service, 'announceCancel'); + spyOn(service, 'performBitstreamMoveRequest'); + + service.cancelSelection(); + + expect(service.announceClear).not.toHaveBeenCalled(); + expect(service.announceCancel).not.toHaveBeenCalled(); + expect(service.performBitstreamMoveRequest).not.toHaveBeenCalled(); + }); + }); + + describe('moveSelectedBitstream', () => { + beforeEach(() => { + spyOn(service, 'performBitstreamMoveRequest').and.callThrough(); + }); + + describe('up', () => { + it('should move the selected bitstream one position up', () => { + const startPosition = 7; + const endPosition = startPosition - 1; + + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: startPosition, + } + ); + + const movedEntry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: endPosition, + } + ); + + service.selectBitstreamEntry(entry); + service.moveSelectedBitstreamUp(); + expect(service.performBitstreamMoveRequest).toHaveBeenCalledWith(entry.bundle, startPosition, endPosition, jasmine.any(Function)); + expect(service.getSelectedBitstream()).toEqual(movedEntry); + }); + + it('should announce the move', () => { + const startPosition = 7; + const endPosition = startPosition - 1; + + spyOn(service, 'announceMove'); + + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: startPosition, + } + ); + + service.selectBitstreamEntry(entry); + service.moveSelectedBitstreamUp(); + + expect(service.announceMove).toHaveBeenCalledWith(entry.bitstream.name, endPosition); + }); + + it('should not do anything if the bitstream is already at the top', () => { + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: 0, + } + ); + + service.selectBitstreamEntry(entry); + service.moveSelectedBitstreamUp(); + + expect(service.performBitstreamMoveRequest).not.toHaveBeenCalled(); + }); + + it('should not do anything if there is no selected bitstream', () => { + service.moveSelectedBitstreamUp(); + + expect(service.performBitstreamMoveRequest).not.toHaveBeenCalled(); + }); + }); + + describe('down', () => { + it('should move the selected bitstream one position down', () => { + const startPosition = 7; + const endPosition = startPosition + 1; + + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: startPosition, + } + ); + + const movedEntry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: endPosition, + } + ); + + service.selectBitstreamEntry(entry); + service.moveSelectedBitstreamDown(); + expect(service.performBitstreamMoveRequest).toHaveBeenCalledWith(entry.bundle, startPosition, endPosition, jasmine.any(Function)); + expect(service.getSelectedBitstream()).toEqual(movedEntry); + }); + + it('should announce the move', () => { + const startPosition = 7; + const endPosition = startPosition + 1; + + spyOn(service, 'announceMove'); + + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: startPosition, + } + ); + + service.selectBitstreamEntry(entry); + service.moveSelectedBitstreamDown(); + + expect(service.announceMove).toHaveBeenCalledWith(entry.bitstream.name, endPosition); + }); + + it('should not do anything if the bitstream is already at the bottom of the bundle', () => { + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: 9, + } + ); + + service.selectBitstreamEntry(entry); + service.moveSelectedBitstreamDown(); + + expect(service.performBitstreamMoveRequest).not.toHaveBeenCalled(); + }); + + it('should not do anything if there is no selected bitstream', () => { + service.moveSelectedBitstreamDown(); + + expect(service.performBitstreamMoveRequest).not.toHaveBeenCalled(); + }); + }); + }); + + describe('performBitstreamMoveRequest', () => { + const bundle: Bundle = defaultEntry.bundle; + const from = 5; + const to = 7; + const callback = createSpy('callbackFunction'); + + console.log('bundle:', bundle); + + it('should correctly create the Move request', () => { + const expectedOperation: MoveOperation = { + op: 'move', + from: `/_links/bitstreams/${from}/href`, + path: `/_links/bitstreams/${to}/href`, + }; + + service.performBitstreamMoveRequest(bundle, from, to, callback); + expect(bundleDataService.patch).toHaveBeenCalledWith(bundle, [expectedOperation]); + }); + + it('should correctly make the bundle\'s self link stale', () => { + service.performBitstreamMoveRequest(bundle, from, to, callback); + expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(bundle._links.self.href); + }); + + it('should attempt to show a message should the request have failed', () => { + spyOn(service, 'displayFailedResponseNotifications'); + service.performBitstreamMoveRequest(bundle, from, to, callback); + expect(service.displayFailedResponseNotifications).toHaveBeenCalled(); + }); + + it('should correctly call the provided function once the request has finished', () => { + service.performBitstreamMoveRequest(bundle, from, to, callback); + expect(callback).toHaveBeenCalled(); + }); + }); + describe('displayNotifications', () => { it('should display an error notification if a response failed', () => { const responses = [ diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts index f8091d616a..5b5fb7d63c 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts @@ -143,7 +143,7 @@ export class ItemBitstreamsService { * Select the provided entry */ selectBitstreamEntry(entry: SelectedBitstreamTableEntry) { - if (entry !== this.selectedBitstream$.getValue()) { + if (hasValue(entry) && entry !== this.selectedBitstream$.getValue()) { this.announceSelect(entry.bitstream.name); this.updateSelectedBitstream(entry); } @@ -184,7 +184,7 @@ export class ItemBitstreamsService { return; } - this.selectedBitstream$.next(null); + this.updateSelectedBitstream(null); const originalPosition = selected.originalPosition; const currentPosition = selected.currentPosition; From 0bdb5742e064ca35325aa03530506012e36b1dc4 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Fri, 4 Oct 2024 15:22:43 +0200 Subject: [PATCH 562/875] 118223: Remove unused item-edit-bitstream component --- .../edit-item-page/edit-item-page.module.ts | 2 - .../item-edit-bitstream.component.html | 51 ------ .../item-edit-bitstream.component.spec.ts | 145 ------------------ .../item-edit-bitstream.component.ts | 117 -------------- 4 files changed, 315 deletions(-) delete mode 100644 src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html delete mode 100644 src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.spec.ts delete mode 100644 src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts diff --git a/src/app/item-page/edit-item-page/edit-item-page.module.ts b/src/app/item-page/edit-item-page/edit-item-page.module.ts index 4ae5ebe666..c38b480622 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.module.ts @@ -15,7 +15,6 @@ import { ItemPrivateComponent } from './item-private/item-private.component'; import { ItemPublicComponent } from './item-public/item-public.component'; import { ItemDeleteComponent } from './item-delete/item-delete.component'; import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component'; -import { ItemEditBitstreamComponent } from './item-bitstreams/item-edit-bitstream/item-edit-bitstream.component'; import { SearchPageModule } from '../../search-page/search-page.module'; import { ItemCollectionMapperComponent } from './item-collection-mapper/item-collection-mapper.component'; import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component'; @@ -78,7 +77,6 @@ import { ItemRelationshipsComponent, ItemBitstreamsComponent, ItemVersionHistoryComponent, - ItemEditBitstreamComponent, ItemEditBitstreamBundleComponent, EditRelationshipComponent, EditRelationshipListComponent, diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html deleted file mode 100644 index 0f0fad2199..0000000000 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.html +++ /dev/null @@ -1,51 +0,0 @@ - -
    - -
    - - {{ bitstreamName }} - -
    -
    -
    -
    -
    - {{ bitstream?.firstMetadataValue('dc.description') }} -
    -
    -
    -
    -
    - - {{ (format$ | async)?.shortDescription }} - -
    -
    -
    -
    -
    - - - - - - -
    -
    -
    -
    diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.spec.ts deleted file mode 100644 index aafa5a4fe4..0000000000 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.spec.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { ItemEditBitstreamComponent } from './item-edit-bitstream.component'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; -import { of as observableOf } from 'rxjs'; -import { Bitstream } from '../../../../core/shared/bitstream.model'; -import { TranslateModule } from '@ngx-translate/core'; -import { VarDirective } from '../../../../shared/utils/var.directive'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; -import { ResponsiveTableSizes } from '../../../../shared/responsive-table-sizes/responsive-table-sizes'; -import { ResponsiveColumnSizes } from '../../../../shared/responsive-table-sizes/responsive-column-sizes'; -import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; -import { getBitstreamDownloadRoute } from '../../../../app-routing-paths'; -import { By } from '@angular/platform-browser'; -import { BrowserOnlyMockPipe } from '../../../../shared/testing/browser-only-mock.pipe'; - -let comp: ItemEditBitstreamComponent; -let fixture: ComponentFixture; - -const columnSizes = new ResponsiveTableSizes([ - new ResponsiveColumnSizes(2, 2, 3, 4, 4), - new ResponsiveColumnSizes(2, 3, 3, 3, 3), - new ResponsiveColumnSizes(2, 2, 2, 2, 2), - new ResponsiveColumnSizes(6, 5, 4, 3, 3) -]); - -const format = Object.assign(new BitstreamFormat(), { - shortDescription: 'PDF' -}); -const bitstream = Object.assign(new Bitstream(), { - uuid: 'bitstreamUUID', - name: 'Fake Bitstream', - bundleName: 'ORIGINAL', - description: 'Description', - _links: { - content: { href: 'content-link' } - }, - - format: createSuccessfulRemoteDataObject$(format) -}); -const fieldUpdate = { - field: bitstream, - changeType: undefined -}; -const date = new Date(); -const url = 'thisUrl'; - -let objectUpdatesService: ObjectUpdatesService; - -describe('ItemEditBitstreamComponent', () => { - beforeEach(waitForAsync(() => { - objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', - { - getFieldUpdates: observableOf({ - [bitstream.uuid]: fieldUpdate, - }), - getFieldUpdatesExclusive: observableOf({ - [bitstream.uuid]: fieldUpdate, - }), - saveRemoveFieldUpdate: {}, - removeSingleFieldUpdate: {}, - saveAddFieldUpdate: {}, - discardFieldUpdates: {}, - reinstateFieldUpdates: observableOf(true), - initialize: {}, - getUpdatedFields: observableOf([bitstream]), - getLastModified: observableOf(date), - hasUpdates: observableOf(true), - isReinstatable: observableOf(false), - isValidPage: observableOf(true) - } - ); - - TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], - declarations: [ - ItemEditBitstreamComponent, - VarDirective, - BrowserOnlyMockPipe, - ], - providers: [ - { provide: ObjectUpdatesService, useValue: objectUpdatesService } - ], schemas: [ - NO_ERRORS_SCHEMA - ] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ItemEditBitstreamComponent); - comp = fixture.componentInstance; - comp.fieldUpdate = fieldUpdate; - comp.bundleUrl = url; - comp.columnSizes = columnSizes; - comp.ngOnChanges(undefined); - fixture.detectChanges(); - }); - - describe('when remove is called', () => { - beforeEach(() => { - comp.remove(); - }); - - it('should call saveRemoveFieldUpdate on objectUpdatesService', () => { - expect(objectUpdatesService.saveRemoveFieldUpdate).toHaveBeenCalledWith(url, bitstream); - }); - }); - - describe('when undo is called', () => { - beforeEach(() => { - comp.undo(); - }); - - it('should call removeSingleFieldUpdate on objectUpdatesService', () => { - expect(objectUpdatesService.removeSingleFieldUpdate).toHaveBeenCalledWith(url, bitstream.uuid); - }); - }); - - describe('when canRemove is called', () => { - it('should return true', () => { - expect(comp.canRemove()).toEqual(true); - }); - }); - - describe('when canUndo is called', () => { - it('should return false', () => { - expect(comp.canUndo()).toEqual(false); - }); - }); - - describe('when the component loads', () => { - it('should contain download button with a valid link to the bitstreams download page', () => { - fixture.detectChanges(); - const downloadBtnHref = fixture.debugElement.query(By.css('[data-test="download-button"]')).nativeElement.getAttribute('href'); - expect(downloadBtnHref).toEqual(comp.bitstreamDownloadUrl); - }); - }); - - describe('when the bitstreamDownloadUrl property gets populated', () => { - it('should contain the bitstream download page route', () => { - expect(comp.bitstreamDownloadUrl).not.toEqual(bitstream._links.content.href); - expect(comp.bitstreamDownloadUrl).toEqual(getBitstreamDownloadRoute(bitstream)); - }); - }); -}); diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts deleted file mode 100644 index fcb5c706ac..0000000000 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream/item-edit-bitstream.component.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild, ViewContainerRef } from '@angular/core'; -import { Bitstream } from '../../../../core/shared/bitstream.model'; -import cloneDeep from 'lodash/cloneDeep'; -import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; -import { Observable } from 'rxjs'; -import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; -import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../../core/shared/operators'; -import { ResponsiveTableSizes } from '../../../../shared/responsive-table-sizes/responsive-table-sizes'; -import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; -import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model'; -import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model'; -import { getBitstreamDownloadRoute } from '../../../../app-routing-paths'; - -@Component({ - selector: 'ds-item-edit-bitstream', - styleUrls: ['../item-bitstreams.component.scss'], - templateUrl: './item-edit-bitstream.component.html', -}) -/** - * Component that displays a single bitstream of an item on the edit page - * Creates an embedded view of the contents - * (which means it'll be added to the parents html without a wrapping ds-item-edit-bitstream element) - */ -export class ItemEditBitstreamComponent implements OnChanges, OnInit { - - /** - * The view on the bitstream - */ - @ViewChild('bitstreamView', {static: true}) bitstreamView; - - /** - * The current field, value and state of the bitstream - */ - @Input() fieldUpdate: FieldUpdate; - - /** - * The url of the bundle - */ - @Input() bundleUrl: string; - - /** - * The bootstrap sizes used for the columns within this table - */ - @Input() columnSizes: ResponsiveTableSizes; - - /** - * The bitstream of this field - */ - bitstream: Bitstream; - - /** - * The bitstream's name - */ - bitstreamName: string; - - /** - * The bitstream's download url - */ - bitstreamDownloadUrl: string; - - /** - * The format of the bitstream - */ - format$: Observable; - - constructor(private objectUpdatesService: ObjectUpdatesService, - private dsoNameService: DSONameService, - private viewContainerRef: ViewContainerRef) { - } - - ngOnInit(): void { - this.viewContainerRef.createEmbeddedView(this.bitstreamView); - } - - /** - * Update the current bitstream and its format on changes - * @param changes - */ - ngOnChanges(changes: SimpleChanges): void { - this.bitstream = cloneDeep(this.fieldUpdate.field) as Bitstream; - this.bitstreamName = this.dsoNameService.getName(this.bitstream); - this.bitstreamDownloadUrl = getBitstreamDownloadRoute(this.bitstream); - this.format$ = this.bitstream.format.pipe( - getFirstSucceededRemoteData(), - getRemoteDataPayload() - ); - } - - /** - * Sends a new remove update for this field to the object updates service - */ - remove(): void { - this.objectUpdatesService.saveRemoveFieldUpdate(this.bundleUrl, this.bitstream); - } - - /** - * Cancels the current update for this field in the object updates service - */ - undo(): void { - this.objectUpdatesService.removeSingleFieldUpdate(this.bundleUrl, this.bitstream.uuid); - } - - /** - * Check if a user should be allowed to remove this field - */ - canRemove(): boolean { - return this.fieldUpdate.changeType !== FieldChangeType.REMOVE; - } - - /** - * Check if a user should be allowed to cancel the update to this field - */ - canUndo(): boolean { - return this.fieldUpdate.changeType >= 0; - } - -} From e8379db987317c5cb892989a21297ab5edce8669 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Mon, 7 Oct 2024 11:49:54 +0200 Subject: [PATCH 563/875] 118223: Add item-bitstreams component tests --- .../item-bitstreams.component.spec.ts | 129 +++++++++++++++--- .../item-bitstreams.component.ts | 6 +- .../item-bitstreams.service.stub.ts | 74 ++++++++++ 3 files changed, 192 insertions(+), 17 deletions(-) create mode 100644 src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.stub.ts diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts index a5549a6ba0..d26f815316 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts @@ -26,9 +26,7 @@ import { createPaginatedList } from '../../../shared/testing/utils.test'; import { FieldChangeType } from '../../../core/data/object-updates/field-change-type.model'; import { BitstreamDataServiceStub } from '../../../shared/testing/bitstream-data-service.stub'; import { ItemBitstreamsService } from './item-bitstreams.service'; -import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes'; -import { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-sizes'; -import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { getItemBitstreamsServiceStub, ItemBitstreamsServiceStub } from './item-bitstreams.service.stub'; let comp: ItemBitstreamsComponent; let fixture: ComponentFixture; @@ -80,7 +78,7 @@ let objectCache: ObjectCacheService; let requestService: RequestService; let searchConfig: SearchConfigurationService; let bundleService: BundleDataService; -let itemBitstreamsService: ItemBitstreamsService; +let itemBitstreamsService: ItemBitstreamsServiceStub; describe('ItemBitstreamsComponent', () => { beforeEach(waitForAsync(() => { @@ -152,18 +150,7 @@ describe('ItemBitstreamsComponent', () => { patch: createSuccessfulRemoteDataObject$({}), }); - itemBitstreamsService = jasmine.createSpyObj('itemBitstreamsService', { - getColumnSizes: new ResponsiveTableSizes([ - new ResponsiveColumnSizes(2, 2, 3, 4, 4), - new ResponsiveColumnSizes(2, 3, 3, 3, 3), - new ResponsiveColumnSizes(2, 2, 2, 2, 2), - new ResponsiveColumnSizes(6, 5, 4, 3, 3) - ]), - getSelectedBitstream$: observableOf({}), - getInitialBundlesPaginationOptions: new PaginationComponentOptions(), - removeMarkedBitstreams: createSuccessfulRemoteDataObject$({}), - displayNotifications: undefined, - }); + itemBitstreamsService = getItemBitstreamsServiceStub(); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], @@ -218,4 +205,114 @@ describe('ItemBitstreamsComponent', () => { expect(objectUpdatesService.reinstateFieldUpdates).toHaveBeenCalledWith(bundle.self); }); }); + + describe('moveUp', () => { + it('should move the selected bitstream up', () => { + itemBitstreamsService.hasSelectedBitstream.and.returnValue(true); + + const event = { + preventDefault: () => {/* Intentionally empty */}, + } as KeyboardEvent; + comp.moveUp(event); + + expect(itemBitstreamsService.moveSelectedBitstreamUp).toHaveBeenCalled(); + }); + + it('should not do anything if no bitstream is selected', () => { + itemBitstreamsService.hasSelectedBitstream.and.returnValue(false); + + const event = { + preventDefault: () => {/* Intentionally empty */}, + } as KeyboardEvent; + comp.moveUp(event); + + expect(itemBitstreamsService.moveSelectedBitstreamUp).not.toHaveBeenCalled(); + }); + }); + + describe('moveDown', () => { + it('should move the selected bitstream down', () => { + itemBitstreamsService.hasSelectedBitstream.and.returnValue(true); + + const event = { + preventDefault: () => {/* Intentionally empty */}, + } as KeyboardEvent; + comp.moveDown(event); + + expect(itemBitstreamsService.moveSelectedBitstreamDown).toHaveBeenCalled(); + }); + + it('should not do anything if no bitstream is selected', () => { + itemBitstreamsService.hasSelectedBitstream.and.returnValue(false); + + const event = { + preventDefault: () => {/* Intentionally empty */}, + } as KeyboardEvent; + comp.moveDown(event); + + expect(itemBitstreamsService.moveSelectedBitstreamDown).not.toHaveBeenCalled(); + }); + }); + + describe('cancelSelection', () => { + it('should cancel the selection', () => { + itemBitstreamsService.hasSelectedBitstream.and.returnValue(true); + + const event = { + preventDefault: () => {/* Intentionally empty */}, + } as KeyboardEvent; + comp.cancelSelection(event); + + expect(itemBitstreamsService.cancelSelection).toHaveBeenCalled(); + }); + + it('should not do anything if no bitstream is selected', () => { + itemBitstreamsService.hasSelectedBitstream.and.returnValue(false); + + const event = { + preventDefault: () => {/* Intentionally empty */}, + } as KeyboardEvent; + comp.cancelSelection(event); + + expect(itemBitstreamsService.cancelSelection).not.toHaveBeenCalled(); + }); + }); + + describe('clearSelection', () => { + it('should clear the selection', () => { + itemBitstreamsService.hasSelectedBitstream.and.returnValue(true); + + const event = { + target: document.createElement('BODY'), + preventDefault: () => {/* Intentionally empty */}, + } as unknown as KeyboardEvent; + comp.clearSelection(event); + + expect(itemBitstreamsService.clearSelection).toHaveBeenCalled(); + }); + + it('should not do anything if no bitstream is selected', () => { + itemBitstreamsService.hasSelectedBitstream.and.returnValue(false); + + const event = { + target: document.createElement('BODY'), + preventDefault: () => {/* Intentionally empty */}, + } as unknown as KeyboardEvent; + comp.clearSelection(event); + + expect(itemBitstreamsService.clearSelection).not.toHaveBeenCalled(); + }); + + it('should not do anything if the event target is not \'BODY\'', () => { + itemBitstreamsService.hasSelectedBitstream.and.returnValue(true); + + const event = { + target: document.createElement('NOT-BODY'), + preventDefault: () => {/* Intentionally empty */}, + } as unknown as KeyboardEvent; + comp.clearSelection(event); + + expect(itemBitstreamsService.clearSelection).not.toHaveBeenCalled(); + }); + }); }); 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 f77eda02fb..6ee5dcb545 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 @@ -137,7 +137,11 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme // Only when no specific element is in focus do we want to clear the currently selected bitstream // Otherwise we might clear the selection when a different action was intended, e.g. clicking a button or selecting // a different bitstream. - if (event.target instanceof Element && event.target.tagName === 'BODY') { + if ( + this.itemBitstreamsService.hasSelectedBitstream() && + event.target instanceof Element && + event.target.tagName === 'BODY' + ) { event.preventDefault(); this.itemBitstreamsService.clearSelection(); } diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.stub.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.stub.ts new file mode 100644 index 0000000000..0521bf47f6 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.stub.ts @@ -0,0 +1,74 @@ +import { of } from 'rxjs'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes'; +import { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-sizes'; +import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; + +export function getItemBitstreamsServiceStub(): ItemBitstreamsServiceStub { + return new ItemBitstreamsServiceStub(); +} + +export class ItemBitstreamsServiceStub { + getSelectedBitstream$ = jasmine.createSpy('getSelectedBitstream$').and + .returnValue(of(null)); + + getSelectedBitstream = jasmine.createSpy('getSelectedBitstream').and + .returnValue(null); + + hasSelectedBitstream = jasmine.createSpy('hasSelectedBitstream').and + .returnValue(false); + + selectBitstreamEntry = jasmine.createSpy('selectBitstreamEntry'); + + clearSelection = jasmine.createSpy('clearSelection'); + + cancelSelection = jasmine.createSpy('cancelSelection'); + + moveSelectedBitstreamUp = jasmine.createSpy('moveSelectedBitstreamUp'); + + moveSelectedBitstreamDown = jasmine.createSpy('moveSelectedBitstreamDown'); + + performBitstreamMoveRequest = jasmine.createSpy('performBitstreamMoveRequest'); + + getInitialBundlesPaginationOptions = jasmine.createSpy('getInitialBundlesPaginationOptions').and + .returnValue(new PaginationComponentOptions()); + + getInitialBitstreamsPaginationOptions = jasmine.createSpy('getInitialBitstreamsPaginationOptions').and + .returnValue(new PaginationComponentOptions()); + + getColumnSizes = jasmine.createSpy('getColumnSizes').and + .returnValue( + new ResponsiveTableSizes([ + new ResponsiveColumnSizes(2, 2, 3, 4, 4), + new ResponsiveColumnSizes(2, 3, 3, 3, 3), + new ResponsiveColumnSizes(2, 2, 2, 2, 2), + new ResponsiveColumnSizes(6, 5, 4, 3, 3) + ]) + ); + + displayNotifications = jasmine.createSpy('displayNotifications'); + + displayFailedResponseNotifications = jasmine.createSpy('displayFailedResponseNotifications'); + + displayErrorNotification = jasmine.createSpy('displayErrorNotification'); + + displaySuccessFulResponseNotifications = jasmine.createSpy('displaySuccessFulResponseNotifications'); + + displaySuccessNotification = jasmine.createSpy('displaySuccessNotification'); + + removeMarkedBitstreams = jasmine.createSpy('removeMarkedBitstreams').and + .returnValue(createSuccessfulRemoteDataObject$({})); + + mapBitstreamsToTableEntries = jasmine.createSpy('mapBitstreamsToTableEntries').and + .returnValue([]); + + nameToHeader = jasmine.createSpy('nameToHeader').and.returnValue('header'); + + stripWhiteSpace = jasmine.createSpy('stripWhiteSpace').and.returnValue('string'); + + announceSelect = jasmine.createSpy('announceSelect'); + + announceMove = jasmine.createSpy('announceMove'); + + announceCancel = jasmine.createSpy('announceCancel'); +} From 7fb4755abaa1759f175ea2d2cff6d93af00d946e Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Tue, 8 Oct 2024 15:51:00 +0200 Subject: [PATCH 564/875] 118223: Add item-edit-bitstream-bundle component tests --- ...em-edit-bitstream-bundle.component.spec.ts | 302 ++++++++++++++++-- .../item-edit-bitstream-bundle.component.ts | 3 +- 2 files changed, 285 insertions(+), 20 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts index 25274b8941..6008b5431f 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts @@ -7,14 +7,19 @@ import { Bundle } from '../../../../core/shared/bundle.model'; import { ResponsiveTableSizes } from '../../../../shared/responsive-table-sizes/responsive-table-sizes'; import { ResponsiveColumnSizes } from '../../../../shared/responsive-table-sizes/responsive-column-sizes'; import { BundleDataService } from '../../../../core/data/bundle-data.service'; -import { of as observableOf } from 'rxjs'; +import { of as observableOf, of, Subject } from 'rxjs'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; import { RequestService } from '../../../../core/data/request.service'; import { getMockRequestService } from '../../../../shared/mocks/request.service.mock'; -import { ItemBitstreamsService } from '../item-bitstreams.service'; +import { ItemBitstreamsService, BitstreamTableEntry, SelectedBitstreamTableEntry } from '../item-bitstreams.service'; import { PaginationService } from '../../../../core/pagination/pagination.service'; -import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; +import { getItemBitstreamsServiceStub, ItemBitstreamsServiceStub } from '../item-bitstreams.service.stub'; +import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model'; +import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model'; +import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; +import { createPaginatedList } from '../../../../shared/testing/utils.test'; +import { CdkDragDrop } from '@angular/cdk/drag-drop'; describe('ItemEditBitstreamBundleComponent', () => { let comp: ItemEditBitstreamBundleComponent; @@ -43,25 +48,20 @@ describe('ItemEditBitstreamBundleComponent', () => { const restEndpoint = 'fake-rest-endpoint'; const bundleService = jasmine.createSpyObj('bundleService', { getBitstreamsEndpoint: observableOf(restEndpoint), - getBitstreams: null, + getBitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([])), }); - const objectUpdatesService = { - initialize: () => { - // do nothing - }, - }; - - const itemBitstreamsService = jasmine.createSpyObj('itemBitstreamsService', { - getInitialBitstreamsPaginationOptions: Object.assign(new PaginationComponentOptions(), { - id: 'bundles-pagination-options', - currentPage: 1, - pageSize: 9999 - }), - getSelectedBitstream$: observableOf({}), - }); + let objectUpdatesService: any; + let itemBitstreamsService: ItemBitstreamsServiceStub; beforeEach(waitForAsync(() => { + objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', { + initialize: undefined, + getFieldUpdatesExclusive: of(null), + }); + + itemBitstreamsService = getItemBitstreamsServiceStub(); + TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], declarations: [ItemEditBitstreamBundleComponent], @@ -92,4 +92,270 @@ describe('ItemEditBitstreamBundleComponent', () => { it('should create an embedded view of the component', () => { expect(viewContainerRef.createEmbeddedView).toHaveBeenCalled(); }); + + describe('on selected entry change', () => { + let paginationComponent: any; + let testSubject: Subject = new Subject(); + + beforeEach(() => { + paginationComponent = jasmine.createSpyObj('paginationComponent', { + doPageChange: undefined, + }); + comp.paginationComponent = paginationComponent; + + spyOn(comp, 'getCurrentPageSize').and.returnValue(2); + }); + + it('should move to the page the selected entry is on if were not on that page', () => { + const selectedA: SelectedBitstreamTableEntry = { + bitstream: null, + bundle: bundle, + bundleSize: 5, + originalPosition: 1, + currentPosition: 1, + }; + + const selectedB: SelectedBitstreamTableEntry = { + bitstream: null, + bundle: bundle, + bundleSize: 5, + originalPosition: 1, + currentPosition: 2, + }; + + comp.handleSelectedEntryChange(selectedA, selectedB); + expect(paginationComponent.doPageChange).toHaveBeenCalledWith(2); + }); + + it('should not change page when we are already on the correct page', () => { + const selectedA: SelectedBitstreamTableEntry = { + bitstream: null, + bundle: bundle, + bundleSize: 5, + originalPosition: 0, + currentPosition: 0, + }; + + const selectedB: SelectedBitstreamTableEntry = { + bitstream: null, + bundle: bundle, + bundleSize: 5, + originalPosition: 0, + currentPosition: 1, + }; + + comp.handleSelectedEntryChange(selectedA, selectedB); + expect(paginationComponent.doPageChange).not.toHaveBeenCalled(); + }); + + it('should change to the original page when cancelling', () => { + const selectedA: SelectedBitstreamTableEntry = { + bitstream: null, + bundle: bundle, + bundleSize: 5, + originalPosition: 3, + currentPosition: 0, + }; + + const selectedB = null; + + comp.handleSelectedEntryChange(selectedA, selectedB); + expect(paginationComponent.doPageChange).toHaveBeenCalledWith(2); + }); + + it('should not change page when we are already on the correct page when cancelling', () => { + const selectedA: SelectedBitstreamTableEntry = { + bitstream: null, + bundle: bundle, + bundleSize: 5, + originalPosition: 0, + currentPosition: 3, + }; + + const selectedB = null; + + comp.handleSelectedEntryChange(selectedA, selectedB); + expect(paginationComponent.doPageChange).not.toHaveBeenCalled(); + }); + }); + + describe('getRowClass', () => { + it('should return \'table-info\' when the bitstream is the selected bitstream', () => { + itemBitstreamsService.getSelectedBitstream.and.returnValue({ + bitstream: { id: 'bitstream-id'} + }); + + const bitstreamEntry = { + id: 'bitstream-id', + } as BitstreamTableEntry; + + expect(comp.getRowClass(undefined, bitstreamEntry)).toEqual('table-info'); + }); + + it('should return \'table-warning\' when the update is of type \'UPDATE\'', () => { + const update = { + changeType: FieldChangeType.UPDATE, + } as FieldUpdate; + + expect(comp.getRowClass(update, undefined)).toEqual('table-warning'); + }); + + it('should return \'table-success\' when the update is of type \'ADD\'', () => { + const update = { + changeType: FieldChangeType.ADD, + } as FieldUpdate; + + expect(comp.getRowClass(update, undefined)).toEqual('table-success'); + }); + + it('should return \'table-danger\' when the update is of type \'REMOVE\'', () => { + const update = { + changeType: FieldChangeType.REMOVE, + } as FieldUpdate; + + expect(comp.getRowClass(update, undefined)).toEqual('table-danger'); + }); + + it('should return \'bg-white\' in any other case', () => { + const update = { + changeType: undefined, + } as FieldUpdate; + + expect(comp.getRowClass(update, undefined)).toEqual('bg-white'); + }); + }); + + describe('drag', () => { + let dragTooltip; + let paginationComponent; + + beforeEach(() => { + dragTooltip = jasmine.createSpyObj('dragTooltip', { + open: undefined, + close: undefined, + }); + comp.dragTooltip = dragTooltip; + }); + + describe('Start', () => { + it('should open the tooltip when there are multiple pages', () => { + paginationComponent = jasmine.createSpyObj('paginationComponent', { + doPageChange: undefined, + }, { + shouldShowBottomPager: of(true), + }); + comp.paginationComponent = paginationComponent; + + comp.dragStart(); + expect(dragTooltip.open).toHaveBeenCalled(); + }); + + it('should not open the tooltip when there is only a single page', () => { + paginationComponent = jasmine.createSpyObj('paginationComponent', { + doPageChange: undefined, + }, { + shouldShowBottomPager: of(false), + }); + comp.paginationComponent = paginationComponent; + + comp.dragStart(); + expect(dragTooltip.open).not.toHaveBeenCalled(); + }); + }); + + describe('end', () => { + it('should always close the tooltip', () => { + paginationComponent = jasmine.createSpyObj('paginationComponent', { + doPageChange: undefined, + }, { + shouldShowBottomPager: of(false), + }); + comp.paginationComponent = paginationComponent; + + comp.dragEnd(); + expect(dragTooltip.close).toHaveBeenCalled(); + }); + }); + }); + + describe('drop', () => { + it('should correctly move the bitstream on drop', () => { + const event = { + previousIndex: 1, + currentIndex: 8, + dropPoint: { x: 100, y: 200 }, + } as CdkDragDrop; + + comp.drop(event); + expect(itemBitstreamsService.performBitstreamMoveRequest).toHaveBeenCalledWith(jasmine.any(Bundle), 1, 8, jasmine.any(Function)); + }); + + it('should not move the bitstream if dropped in the same place', () => { + const event = { + previousIndex: 1, + currentIndex: 1, + dropPoint: { x: 100, y: 200 }, + } as CdkDragDrop; + + comp.drop(event); + expect(itemBitstreamsService.performBitstreamMoveRequest).not.toHaveBeenCalled(); + }); + + it('should move to a different page if dropped on a page number', () => { + spyOn(document, 'elementFromPoint').and.returnValue({ + textContent: '2', + classList: { contains: (token: string) => true }, + } as Element); + + const event = { + previousIndex: 1, + currentIndex: 1, + dropPoint: { x: 100, y: 200 }, + } as CdkDragDrop; + + comp.drop(event); + expect(itemBitstreamsService.performBitstreamMoveRequest).toHaveBeenCalledWith(jasmine.any(Bundle), 1, 20, jasmine.any(Function)); + }); + }); + + describe('select', () => { + it('should select the bitstream', () => { + const event = new KeyboardEvent('keydown'); + spyOnProperty(event, 'repeat', 'get').and.returnValue(false); + + const entry = { } as BitstreamTableEntry; + comp.tableEntries$.next([entry]); + + comp.select(event, entry); + expect(itemBitstreamsService.selectBitstreamEntry).toHaveBeenCalledWith(jasmine.objectContaining({ bitstream: entry })); + }); + + it('should cancel the selection if the bitstream already is selected', () => { + const event = new KeyboardEvent('keydown'); + spyOnProperty(event, 'repeat', 'get').and.returnValue(false); + + const entry = { } as BitstreamTableEntry; + comp.tableEntries$.next([entry]); + + itemBitstreamsService.getSelectedBitstream.and.returnValue({ bitstream: entry }); + + comp.select(event, entry); + expect(itemBitstreamsService.selectBitstreamEntry).not.toHaveBeenCalled(); + expect(itemBitstreamsService.cancelSelection).toHaveBeenCalled(); + }); + + it('should not do anything if the user is holding down the select key', () => { + const event = new KeyboardEvent('keydown'); + spyOnProperty(event, 'repeat', 'get').and.returnValue(true); + + const entry = { } as BitstreamTableEntry; + comp.tableEntries$.next([entry]); + + itemBitstreamsService.getSelectedBitstream.and.returnValue({ bitstream: entry }); + + comp.select(event, entry); + expect(itemBitstreamsService.selectBitstreamEntry).not.toHaveBeenCalled(); + expect(itemBitstreamsService.cancelSelection).not.toHaveBeenCalled(); + }); + }); }); diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index 4079ad225b..7a70ba80dd 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -243,9 +243,8 @@ export class ItemEditBitstreamBundleComponent implements OnInit, OnDestroy { * Handles a change in selected bitstream by changing the pagination if the change happened on a different page * @param previousSelectedEntry The previously selected entry * @param currentSelectedEntry The currently selected entry - * @protected */ - protected handleSelectedEntryChange( + handleSelectedEntryChange( previousSelectedEntry: SelectedBitstreamTableEntry, currentSelectedEntry: SelectedBitstreamTableEntry ) { From 2b1b9d83d7d2e58a8f26b9843202f1705110fea5 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 9 Oct 2024 11:14:28 +0200 Subject: [PATCH 565/875] 118223: Include selection action with selection --- .../item-bitstreams.service.spec.ts | 176 ++++++++++++++---- .../item-bitstreams.service.stub.ts | 2 +- .../item-bitstreams.service.ts | 86 ++++++--- ...em-edit-bitstream-bundle.component.spec.ts | 35 +--- .../item-edit-bitstream-bundle.component.ts | 44 +++-- 5 files changed, 237 insertions(+), 106 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts index 94adb5f23a..f2af25f22f 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts @@ -77,20 +77,20 @@ describe('ItemBitstreamsService', () => { describe('selectBitstreamEntry', () => { it('should correctly make getSelectedBitstream$ emit', fakeAsync(() => { - const emittedEntries = []; + const emittedActions = []; - service.getSelectedBitstream$().subscribe(selected => emittedEntries.push(selected)); + service.getSelectionAction$().subscribe(selected => emittedActions.push(selected)); - expect(emittedEntries.length).toBe(1); - expect(emittedEntries[0]).toBeNull(); + expect(emittedActions.length).toBe(1); + expect(emittedActions[0]).toBeNull(); const entry = Object.assign({}, defaultEntry); service.selectBitstreamEntry(entry); tick(); - expect(emittedEntries.length).toBe(2); - expect(emittedEntries[1]).toEqual(entry); + expect(emittedActions.length).toBe(2); + expect(emittedActions[1]).toEqual({ action: 'Selected', selectedEntry: entry }); })); it('should correctly make getSelectedBitstream return the bitstream', () => { @@ -112,26 +112,26 @@ describe('ItemBitstreamsService', () => { }); it('should do nothing if no entry was provided', fakeAsync(() => { - const emittedEntries = []; + const emittedActions = []; - service.getSelectedBitstream$().subscribe(selected => emittedEntries.push(selected)); + service.getSelectionAction$().subscribe(selected => emittedActions.push(selected)); - expect(emittedEntries.length).toBe(1); - expect(emittedEntries[0]).toBeNull(); + expect(emittedActions.length).toBe(1); + expect(emittedActions[0]).toBeNull(); const entry = Object.assign({}, defaultEntry); service.selectBitstreamEntry(entry); tick(); - expect(emittedEntries.length).toBe(2); - expect(emittedEntries[1]).toEqual(entry); + expect(emittedActions.length).toBe(2); + expect(emittedActions[1]).toEqual({ action: 'Selected', selectedEntry: entry }); service.selectBitstreamEntry(null); tick(); - expect(emittedEntries.length).toBe(2); - expect(emittedEntries[1]).toEqual(entry); + expect(emittedActions.length).toBe(2); + expect(emittedActions[1]).toEqual({ action: 'Selected', selectedEntry: entry }); })); it('should announce the selected bitstream', () => { @@ -146,41 +146,41 @@ describe('ItemBitstreamsService', () => { describe('clearSelection', () => { it('should clear the selected bitstream', fakeAsync(() => { - const emittedEntries = []; + const emittedActions = []; - service.getSelectedBitstream$().subscribe(selected => emittedEntries.push(selected)); + service.getSelectionAction$().subscribe(selected => emittedActions.push(selected)); - expect(emittedEntries.length).toBe(1); - expect(emittedEntries[0]).toBeNull(); + expect(emittedActions.length).toBe(1); + expect(emittedActions[0]).toBeNull(); const entry = Object.assign({}, defaultEntry); service.selectBitstreamEntry(entry); tick(); - expect(emittedEntries.length).toBe(2); - expect(emittedEntries[1]).toEqual(entry); + expect(emittedActions.length).toBe(2); + expect(emittedActions[1]).toEqual({ action: 'Selected', selectedEntry: entry }); service.clearSelection(); tick(); - expect(emittedEntries.length).toBe(3); - expect(emittedEntries[2]).toBeNull(); + expect(emittedActions.length).toBe(3); + expect(emittedActions[2]).toEqual({ action: 'Cleared', selectedEntry: entry }); })); it('should not do anything if there is no selected bitstream', fakeAsync(() => { - const emittedEntries = []; + const emittedActions = []; - service.getSelectedBitstream$().subscribe(selected => emittedEntries.push(selected)); + service.getSelectionAction$().subscribe(selected => emittedActions.push(selected)); - expect(emittedEntries.length).toBe(1); - expect(emittedEntries[0]).toBeNull(); + expect(emittedActions.length).toBe(1); + expect(emittedActions[0]).toBeNull(); service.clearSelection(); tick(); - expect(emittedEntries.length).toBe(1); - expect(emittedEntries[0]).toBeNull(); + expect(emittedActions.length).toBe(1); + expect(emittedActions[0]).toBeNull(); })); it('should announce the cleared bitstream', () => { @@ -225,27 +225,53 @@ describe('ItemBitstreamsService', () => { }); describe('cancelSelection', () => { - it('should clear the selected bitstream', fakeAsync(() => { - const emittedEntries = []; + it('should clear the selected bitstream if it has not moved', fakeAsync(() => { + const emittedActions = []; - service.getSelectedBitstream$().subscribe(selected => emittedEntries.push(selected)); + service.getSelectionAction$().subscribe(selected => emittedActions.push(selected)); - expect(emittedEntries.length).toBe(1); - expect(emittedEntries[0]).toBeNull(); + expect(emittedActions.length).toBe(1); + expect(emittedActions[0]).toBeNull(); const entry = Object.assign({}, defaultEntry); service.selectBitstreamEntry(entry); tick(); - expect(emittedEntries.length).toBe(2); - expect(emittedEntries[1]).toEqual(entry); + expect(emittedActions.length).toBe(2); + expect(emittedActions[1]).toEqual({ action: 'Selected', selectedEntry: entry }); service.cancelSelection(); tick(); - expect(emittedEntries.length).toBe(3); - expect(emittedEntries[2]).toBeNull(); + expect(emittedActions.length).toBe(3); + expect(emittedActions[2]).toEqual({ action: 'Cleared', selectedEntry: entry }); + })); + + it('should cancel the selected bitstream if it has moved', fakeAsync(() => { + const emittedActions = []; + + service.getSelectionAction$().subscribe(selected => emittedActions.push(selected)); + + expect(emittedActions.length).toBe(1); + expect(emittedActions[0]).toBeNull(); + + const entry = Object.assign({}, defaultEntry, { + originalPosition: 0, + currentPosition: 3, + }); + + service.selectBitstreamEntry(entry); + tick(); + + expect(emittedActions.length).toBe(2); + expect(emittedActions[1]).toEqual({ action: 'Selected', selectedEntry: entry }); + + service.cancelSelection(); + tick(); + + expect(emittedActions.length).toBe(3); + expect(emittedActions[2]).toEqual({ action: 'Cancelled', selectedEntry: entry }); })); it('should announce a clear if the bitstream has not moved', () => { @@ -359,6 +385,44 @@ describe('ItemBitstreamsService', () => { expect(service.getSelectedBitstream()).toEqual(movedEntry); }); + it('should emit the move', fakeAsync(() => { + const emittedActions = []; + + service.getSelectionAction$().subscribe(selected => emittedActions.push(selected)); + + expect(emittedActions.length).toBe(1); + expect(emittedActions[0]).toBeNull(); + + const startPosition = 7; + const endPosition = startPosition - 1; + + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: startPosition, + } + ); + + const movedEntry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: endPosition, + } + ); + + service.selectBitstreamEntry(entry); + tick(); + + expect(emittedActions.length).toBe(2); + expect(emittedActions[1]).toEqual({ action: 'Selected', selectedEntry: entry }); + + service.moveSelectedBitstreamUp(); + tick(); + + expect(emittedActions.length).toBe(3); + expect(emittedActions[2]).toEqual({ action: 'Moved', selectedEntry: movedEntry }); + })); + it('should announce the move', () => { const startPosition = 7; const endPosition = startPosition - 1; @@ -424,6 +488,44 @@ describe('ItemBitstreamsService', () => { expect(service.getSelectedBitstream()).toEqual(movedEntry); }); + it('should emit the move', fakeAsync(() => { + const emittedActions = []; + + service.getSelectionAction$().subscribe(selected => emittedActions.push(selected)); + + expect(emittedActions.length).toBe(1); + expect(emittedActions[0]).toBeNull(); + + const startPosition = 7; + const endPosition = startPosition + 1; + + const entry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: startPosition, + } + ); + + const movedEntry = Object.assign({}, defaultEntry, + { + originalPosition: 5, + currentPosition: endPosition, + } + ); + + service.selectBitstreamEntry(entry); + tick(); + + expect(emittedActions.length).toBe(2); + expect(emittedActions[1]).toEqual({ action: 'Selected', selectedEntry: entry }); + + service.moveSelectedBitstreamDown(); + tick(); + + expect(emittedActions.length).toBe(3); + expect(emittedActions[2]).toEqual({ action: 'Moved', selectedEntry: movedEntry }); + })); + it('should announce the move', () => { const startPosition = 7; const endPosition = startPosition + 1; diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.stub.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.stub.ts index 0521bf47f6..7aac79fe69 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.stub.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.stub.ts @@ -9,7 +9,7 @@ export function getItemBitstreamsServiceStub(): ItemBitstreamsServiceStub { } export class ItemBitstreamsServiceStub { - getSelectedBitstream$ = jasmine.createSpy('getSelectedBitstream$').and + getSelectionAction$ = jasmine.createSpy('getSelectedBitstream$').and .returnValue(of(null)); getSelectedBitstream = jasmine.createSpy('getSelectedBitstream').and diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts index 5b5fb7d63c..2329107c29 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts @@ -87,6 +87,24 @@ export interface SelectedBitstreamTableEntry { currentPosition: number, } +/** + * Interface storing data regarding a change in selected bitstream + */ +export interface SelectionAction { + /** + * The different types of actions: + * - Selected: Bitstream was selected + * - Moved: Bitstream was moved + * - Cleared: Selection was cleared, bitstream remains at its current position + * - Cancelled: Selection was cancelled, bitstream returns to its original position + */ + action: 'Selected' | 'Moved' | 'Cleared' | 'Cancelled' + /** + * The table entry to which the selection action applies + */ + selectedEntry: SelectedBitstreamTableEntry, +} + /** * This service handles the selection and updating of the bitstreams and their order on the * 'Edit Item' -> 'Bitstreams' page. @@ -99,7 +117,7 @@ export class ItemBitstreamsService { /** * BehaviorSubject which emits every time the selected bitstream changes. */ - protected selectedBitstream$: BehaviorSubject = new BehaviorSubject(null); + protected selectionAction$: BehaviorSubject = new BehaviorSubject(null); protected isPerformingMoveRequest = false; @@ -116,45 +134,68 @@ export class ItemBitstreamsService { } /** - * Returns the observable emitting the currently selected bitstream + * Returns the observable emitting the selection actions */ - getSelectedBitstream$(): Observable { - return this.selectedBitstream$; + getSelectionAction$(): Observable { + return this.selectionAction$; + } + + /** + * Returns the latest selection action + */ + getSelectionAction(): SelectionAction { + const action = this.selectionAction$.value; + + if (hasNoValue(action)) { + return null; + } + + return Object.assign({}, action); + } + + /** + * Returns true if there currently is a selected bitstream + */ + hasSelectedBitstream(): boolean { + const selectionAction = this.getSelectionAction(); + + if (hasNoValue(selectionAction)) { + return false; + } + + const action = selectionAction.action; + + return action === 'Selected' || action === 'Moved'; } /** * Returns a copy of the currently selected bitstream */ getSelectedBitstream(): SelectedBitstreamTableEntry { - const selected = this.selectedBitstream$.getValue(); - - if (hasNoValue(selected)) { - return selected; + if (!this.hasSelectedBitstream()) { + return null; } - return Object.assign({}, selected); - } - - hasSelectedBitstream(): boolean { - return hasValue(this.getSelectedBitstream()); + const selectionAction = this.getSelectionAction(); + return Object.assign({}, selectionAction.selectedEntry); } /** * Select the provided entry */ selectBitstreamEntry(entry: SelectedBitstreamTableEntry) { - if (hasValue(entry) && entry !== this.selectedBitstream$.getValue()) { + if (hasValue(entry) && entry.bitstream !== this.getSelectedBitstream()?.bitstream) { this.announceSelect(entry.bitstream.name); - this.updateSelectedBitstream(entry); + this.updateSelectionAction({ action: 'Selected', selectedEntry: entry }); } } /** - * Makes the {@link selectedBitstream$} observable emit the provided {@link SelectedBitstreamTableEntry}. + * Makes the {@link selectionAction$} observable emit the provided {@link SelectedBitstreamTableEntry}. * @protected */ - protected updateSelectedBitstream(entry: SelectedBitstreamTableEntry) { - this.selectedBitstream$.next(entry); + protected updateSelectionAction(action: SelectionAction) { + this.selectionAction$.next(action); } /** @@ -164,7 +205,7 @@ export class ItemBitstreamsService { const selected = this.getSelectedBitstream(); if (hasValue(selected)) { - this.updateSelectedBitstream(null); + this.updateSelectionAction({ action: 'Cleared', selectedEntry: selected }); this.announceClear(selected.bitstream.name); if (selected.currentPosition !== selected.originalPosition) { @@ -184,7 +225,6 @@ export class ItemBitstreamsService { return; } - this.updateSelectedBitstream(null); const originalPosition = selected.originalPosition; const currentPosition = selected.currentPosition; @@ -192,9 +232,11 @@ export class ItemBitstreamsService { // If the selected bitstream has not moved, there is no need to return it to its original position if (currentPosition === originalPosition) { this.announceClear(selected.bitstream.name); + this.updateSelectionAction({ action: 'Cleared', selectedEntry: selected }); } else { this.announceCancel(selected.bitstream.name, originalPosition); this.performBitstreamMoveRequest(selected.bundle, currentPosition, originalPosition); + this.updateSelectionAction({ action: 'Cancelled', selectedEntry: selected }); } } @@ -219,7 +261,7 @@ export class ItemBitstreamsService { }; this.performBitstreamMoveRequest(selected.bundle, originalPosition, newPosition, onRequestCompleted); - this.updateSelectedBitstream(selected); + this.updateSelectionAction({ action: 'Moved', selectedEntry: selected }); } } @@ -244,7 +286,7 @@ export class ItemBitstreamsService { }; this.performBitstreamMoveRequest(selected.bundle, originalPosition, newPosition, onRequestCompleted); - this.updateSelectedBitstream(selected); + this.updateSelectionAction({ action: 'Moved', selectedEntry: selected }); } } diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts index 6008b5431f..26a1b0e913 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.spec.ts @@ -107,15 +107,8 @@ describe('ItemEditBitstreamBundleComponent', () => { }); it('should move to the page the selected entry is on if were not on that page', () => { - const selectedA: SelectedBitstreamTableEntry = { - bitstream: null, - bundle: bundle, - bundleSize: 5, - originalPosition: 1, - currentPosition: 1, - }; - const selectedB: SelectedBitstreamTableEntry = { + const entry: SelectedBitstreamTableEntry = { bitstream: null, bundle: bundle, bundleSize: 5, @@ -123,20 +116,12 @@ describe('ItemEditBitstreamBundleComponent', () => { currentPosition: 2, }; - comp.handleSelectedEntryChange(selectedA, selectedB); + comp.handleSelectionAction({ action: 'Moved', selectedEntry: entry }); expect(paginationComponent.doPageChange).toHaveBeenCalledWith(2); }); it('should not change page when we are already on the correct page', () => { - const selectedA: SelectedBitstreamTableEntry = { - bitstream: null, - bundle: bundle, - bundleSize: 5, - originalPosition: 0, - currentPosition: 0, - }; - - const selectedB: SelectedBitstreamTableEntry = { + const entry: SelectedBitstreamTableEntry = { bitstream: null, bundle: bundle, bundleSize: 5, @@ -144,12 +129,12 @@ describe('ItemEditBitstreamBundleComponent', () => { currentPosition: 1, }; - comp.handleSelectedEntryChange(selectedA, selectedB); + comp.handleSelectionAction({ action: 'Moved', selectedEntry: entry }); expect(paginationComponent.doPageChange).not.toHaveBeenCalled(); }); it('should change to the original page when cancelling', () => { - const selectedA: SelectedBitstreamTableEntry = { + const entry: SelectedBitstreamTableEntry = { bitstream: null, bundle: bundle, bundleSize: 5, @@ -157,14 +142,12 @@ describe('ItemEditBitstreamBundleComponent', () => { currentPosition: 0, }; - const selectedB = null; - - comp.handleSelectedEntryChange(selectedA, selectedB); + comp.handleSelectionAction({ action: 'Cancelled', selectedEntry: entry }); expect(paginationComponent.doPageChange).toHaveBeenCalledWith(2); }); it('should not change page when we are already on the correct page when cancelling', () => { - const selectedA: SelectedBitstreamTableEntry = { + const entry: SelectedBitstreamTableEntry = { bitstream: null, bundle: bundle, bundleSize: 5, @@ -172,9 +155,7 @@ describe('ItemEditBitstreamBundleComponent', () => { currentPosition: 3, }; - const selectedB = null; - - comp.handleSelectedEntryChange(selectedA, selectedB); + comp.handleSelectionAction({ action: 'Cancelled', selectedEntry: entry }); expect(paginationComponent.doPageChange).not.toHaveBeenCalled(); }); }); diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index 7a70ba80dd..2c7d8ca60f 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -25,7 +25,7 @@ import { paginatedListToArray, } from '../../../../core/shared/operators'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; -import { map, take, filter, tap, pairwise } from 'rxjs/operators'; +import { map, take, filter, tap } from 'rxjs/operators'; import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model'; import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model'; import { PaginationService } from '../../../../core/pagination/pagination.service'; @@ -35,7 +35,7 @@ import { ItemBitstreamsService, BitstreamTableEntry, SelectedBitstreamTableEntry, - MOVE_KEY + MOVE_KEY, SelectionAction } from '../item-bitstreams.service'; import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { hasValue, hasNoValue } from '../../../../shared/empty.util'; @@ -233,31 +233,37 @@ export class ItemEditBitstreamBundleComponent implements OnInit, OnDestroy { protected initializeSelectionActions() { this.subscriptions.push( - this.itemBitstreamsService.getSelectedBitstream$().pipe(pairwise()).subscribe( - ([previousSelection, currentSelection]) => - this.handleSelectedEntryChange(previousSelection, currentSelection)) + this.itemBitstreamsService.getSelectionAction$().subscribe( + selectionAction => this.handleSelectionAction(selectionAction)) ); } /** * Handles a change in selected bitstream by changing the pagination if the change happened on a different page - * @param previousSelectedEntry The previously selected entry - * @param currentSelectedEntry The currently selected entry + * @param selectionAction */ - handleSelectedEntryChange( - previousSelectedEntry: SelectedBitstreamTableEntry, - currentSelectedEntry: SelectedBitstreamTableEntry - ) { - if (hasValue(currentSelectedEntry) && currentSelectedEntry.bundle === this.bundle) { - // If the currently selected bitstream belongs to this bundle, it has possibly moved to a different page. - // In that case we want to change the pagination to the new page. - this.redirectToCurrentPage(currentSelectedEntry); + handleSelectionAction(selectionAction: SelectionAction) { + if (hasNoValue(selectionAction) || selectionAction.selectedEntry.bundle !== this.bundle) { + return; } - // If the selection is cancelled or cleared, it is possible the selected bitstream is currently on a different page - // In that case we want to change the pagination to the place where the bitstream was returned to - if (hasNoValue(currentSelectedEntry) && hasValue(previousSelectedEntry) && previousSelectedEntry.bundle === this.bundle) { - this.redirectToOriginalPage(previousSelectedEntry); + if (selectionAction.action === 'Moved') { + // If the currently selected bitstream belongs to this bundle, it has possibly moved to a different page. + // In that case we want to change the pagination to the new page. + this.redirectToCurrentPage(selectionAction.selectedEntry); + } + + if (selectionAction.action === 'Cancelled') { + // If the selection is cancelled (and returned to its original position), it is possible the previously selected + // bitstream is returned to a different page. In that case we want to change the pagination to the place where + // the bitstream was returned to. + this.redirectToOriginalPage(selectionAction.selectedEntry); + } + + if (selectionAction.action === 'Cleared') { + // If the selection is cleared, it is possible the previously selected bitstream is on a different page. In that + // case we want to change the pagination to the place where the bitstream is. + this.redirectToCurrentPage(selectionAction.selectedEntry); } } From 8d93f22767edbf18396edc49b16629967f04c287 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 9 Oct 2024 12:01:33 +0200 Subject: [PATCH 566/875] 119176: Make table horizontally scrollable For most screen sizes, the ResponsiveTableSizes is enough to resize the table columns. On very small screens, or when zoomed in a lot, even the smallest column sizes are too big. To make it possible to view the rest of the content even in these situations, the ability to scroll horizontally is added. --- .../item-bitstreams/item-bitstreams.component.html | 2 +- .../item-bitstreams/item-bitstreams.component.scss | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html index b9af2a7d18..7789b68278 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.html @@ -27,7 +27,7 @@ -
    +
    Date: Mon, 14 Oct 2024 11:30:48 +0200 Subject: [PATCH 567/875] 119176: Announce notification content in live region --- .../models/notification-options.model.ts | 12 +++- .../notifications-board.component.spec.ts | 49 ++++++++++++++- .../notifications-board.component.ts | 60 +++++++++++++------ 3 files changed, 97 insertions(+), 24 deletions(-) diff --git a/src/app/shared/notifications/models/notification-options.model.ts b/src/app/shared/notifications/models/notification-options.model.ts index 65011496b3..c891781d9d 100644 --- a/src/app/shared/notifications/models/notification-options.model.ts +++ b/src/app/shared/notifications/models/notification-options.model.ts @@ -4,19 +4,25 @@ export interface INotificationOptions { timeOut: number; clickToClose: boolean; animate: NotificationAnimationsType | string; + announceContentInLiveRegion: boolean; } export class NotificationOptions implements INotificationOptions { public timeOut: number; public clickToClose: boolean; public animate: any; + public announceContentInLiveRegion: boolean; - constructor(timeOut = 5000, - clickToClose = true, - animate: NotificationAnimationsType | string = NotificationAnimationsType.Scale) { + constructor( + timeOut = 5000, + clickToClose = true, + animate: NotificationAnimationsType | string = NotificationAnimationsType.Scale, + announceContentInLiveRegion: boolean = true, + ) { this.timeOut = timeOut; this.clickToClose = clickToClose; this.animate = animate; + this.announceContentInLiveRegion = announceContentInLiveRegion; } } diff --git a/src/app/shared/notifications/notifications-board/notifications-board.component.spec.ts b/src/app/shared/notifications/notifications-board/notifications-board.component.spec.ts index 08b9585a8c..73f4e6b1b1 100644 --- a/src/app/shared/notifications/notifications-board/notifications-board.component.spec.ts +++ b/src/app/shared/notifications/notifications-board/notifications-board.component.spec.ts @@ -1,4 +1,4 @@ -import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, inject, TestBed, waitForAsync, fakeAsync, flush } from '@angular/core/testing'; import { BrowserModule, By } from '@angular/platform-browser'; import { ChangeDetectorRef } from '@angular/core'; @@ -15,14 +15,20 @@ import uniqueId from 'lodash/uniqueId'; import { INotificationBoardOptions } from '../../../../config/notifications-config.interfaces'; import { NotificationsServiceStub } from '../../testing/notifications-service.stub'; import { cold } from 'jasmine-marbles'; +import { LiveRegionService } from '../../live-region/live-region.service'; +import { LiveRegionServiceStub } from '../../live-region/live-region.service.stub'; +import { NotificationOptions } from '../models/notification-options.model'; export const bools = { f: false, t: true }; describe('NotificationsBoardComponent', () => { let comp: NotificationsBoardComponent; let fixture: ComponentFixture; + let liveRegionService: LiveRegionServiceStub; beforeEach(waitForAsync(() => { + liveRegionService = new LiveRegionServiceStub(); + TestBed.configureTestingModule({ imports: [ BrowserModule, @@ -36,7 +42,9 @@ describe('NotificationsBoardComponent', () => { declarations: [NotificationsBoardComponent, NotificationComponent], // declare the test component providers: [ { provide: NotificationsService, useClass: NotificationsServiceStub }, - ChangeDetectorRef] + { provide: LiveRegionService, useValue: liveRegionService }, + ChangeDetectorRef, + ] }).compileComponents(); // compile template and css })); @@ -106,5 +114,42 @@ describe('NotificationsBoardComponent', () => { }); }); + describe('add', () => { + beforeEach(() => { + liveRegionService.addMessage.calls.reset(); + }); + + it('should announce content to the live region', fakeAsync(() => { + const notification = new Notification('id', NotificationType.Info, 'title', 'content'); + comp.add(notification); + + flush(); + + expect(liveRegionService.addMessage).toHaveBeenCalledWith('content'); + })); + + it('should not announce anything if there is no content', fakeAsync(() => { + const notification = new Notification('id', NotificationType.Info, 'title'); + comp.add(notification); + + flush(); + + expect(liveRegionService.addMessage).not.toHaveBeenCalled(); + })); + + it('should not announce the content if disabled', fakeAsync(() => { + const options = new NotificationOptions(); + options.announceContentInLiveRegion = false; + + const notification = new Notification('id', NotificationType.Info, 'title', 'content'); + notification.options = options; + comp.add(notification); + + flush(); + + expect(liveRegionService.addMessage).not.toHaveBeenCalled(); + })); + }); + }) ; diff --git a/src/app/shared/notifications/notifications-board/notifications-board.component.ts b/src/app/shared/notifications/notifications-board/notifications-board.component.ts index 97ae09c1a6..eaba659678 100644 --- a/src/app/shared/notifications/notifications-board/notifications-board.component.ts +++ b/src/app/shared/notifications/notifications-board/notifications-board.component.ts @@ -9,7 +9,7 @@ import { } from '@angular/core'; import { select, Store } from '@ngrx/store'; -import { BehaviorSubject, Subscription } from 'rxjs'; +import { BehaviorSubject, Subscription, of as observableOf } from 'rxjs'; import difference from 'lodash/difference'; import { NotificationsService } from '../notifications.service'; @@ -18,6 +18,9 @@ import { notificationsStateSelector } from '../selectors'; import { INotification } from '../models/notification.model'; import { NotificationsState } from '../notifications.reducers'; import { INotificationBoardOptions } from '../../../../config/notifications-config.interfaces'; +import { LiveRegionService } from '../../live-region/live-region.service'; +import { hasNoValue, isNotEmptyOperator } from '../../empty.util'; +import { take } from 'rxjs/operators'; @Component({ selector: 'ds-notifications-board', @@ -49,9 +52,12 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { */ public isPaused$: BehaviorSubject = new BehaviorSubject(false); - constructor(private service: NotificationsService, - private store: Store, - private cdr: ChangeDetectorRef) { + constructor( + private service: NotificationsService, + private store: Store, + private cdr: ChangeDetectorRef, + protected liveRegionService: LiveRegionService, + ) { } ngOnInit(): void { @@ -85,6 +91,7 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { this.notifications.splice(this.notifications.length - 1, 1); } this.notifications.splice(0, 0, item); + this.addContentToLiveRegion(item); } else { // Remove the notification from the store // This notification was in the store, but not in this.notifications @@ -93,29 +100,44 @@ export class NotificationsBoardComponent implements OnInit, OnDestroy { } } + /** + * Adds the content of the notification (if any) to the live region, so it can be announced by screen readers. + */ + private addContentToLiveRegion(item: INotification) { + let content = item.content; + + if (!item.options.announceContentInLiveRegion || hasNoValue(content)) { + return; + } + + if (typeof content === 'string') { + content = observableOf(content); + } + + content.pipe( + isNotEmptyOperator(), + take(1), + ).subscribe(contentStr => this.liveRegionService.addMessage(contentStr)); + } + + /** + * Whether to block the provided item because a duplicate notification with the exact same information already + * exists within the notifications array. + * @param item The item to check + * @return true if the notifications array already contains a notification with the exact same information as the + * provided item. false otherwise. + * @private + */ private block(item: INotification): boolean { const toCheck = item.html ? this.checkHtml : this.checkStandard; + this.notifications.forEach((notification) => { if (toCheck(notification, item)) { return true; } }); - if (this.notifications.length > 0) { - this.notifications.forEach((notification) => { - if (toCheck(notification, item)) { - return true; - } - }); - } - - let comp: INotification; - if (this.notifications.length > 0) { - comp = this.notifications[0]; - } else { - return false; - } - return toCheck(comp, item); + return false; } private checkStandard(checker: INotification, item: INotification): boolean { From 93f9341387755efc14d3272f7f9cca452ab91996 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 16 Oct 2024 14:11:01 +0200 Subject: [PATCH 568/875] 119176: Add aria-labels to buttons --- .../item-edit-bitstream-bundle.component.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html index efbdd8c69b..06201b1cbe 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html @@ -40,6 +40,7 @@
    -
    +
    + + diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.scss b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.scss index 985516ab12..7fd1f4b31e 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.scss +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.scss @@ -43,3 +43,13 @@ .scrollable-table { overflow-x: auto; } + +.disabled-overlay { + opacity: 0.6; +} + +.loading-overlay { + position: fixed; + top: 50%; + left: 50%; +} 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 6ee5dcb545..72f85675c9 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 @@ -59,6 +59,11 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme */ itemUpdateSubscription: Subscription; + /** + * An observable which emits a boolean which represents whether the service is currently handling a 'move' request + */ + isProcessingMoveRequest: Observable; + constructor( public itemService: ItemDataService, public objectUpdatesService: ObjectUpdatesService, @@ -84,6 +89,7 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme */ postItemInit(): void { const bundlesOptions = this.itemBitstreamsService.getInitialBundlesPaginationOptions(); + this.isProcessingMoveRequest = this.itemBitstreamsService.getPerformingMoveRequest$(); this.bundles$ = this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({pagination: bundlesOptions})).pipe( getFirstSucceededRemoteData(), diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts index f2af25f22f..a0277ef064 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.spec.ts @@ -573,8 +573,6 @@ describe('ItemBitstreamsService', () => { const to = 7; const callback = createSpy('callbackFunction'); - console.log('bundle:', bundle); - it('should correctly create the Move request', () => { const expectedOperation: MoveOperation = { op: 'move', @@ -601,6 +599,22 @@ describe('ItemBitstreamsService', () => { service.performBitstreamMoveRequest(bundle, from, to, callback); expect(callback).toHaveBeenCalled(); }); + + it('should emit at the start and end of the request', fakeAsync(() => { + const emittedActions = []; + + service.getPerformingMoveRequest$().subscribe(selected => emittedActions.push(selected)); + + expect(emittedActions.length).toBe(1); + expect(emittedActions[0]).toBeFalse(); + + service.performBitstreamMoveRequest(bundle, from, to, callback); + tick(); + + expect(emittedActions.length).toBe(3); + expect(emittedActions[1]).toBeTrue(); + expect(emittedActions[2]).toBeFalse(); + })); }); describe('displayNotifications', () => { diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.stub.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.stub.ts index 7aac79fe69..f60693f726 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.stub.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.stub.ts @@ -30,6 +30,10 @@ export class ItemBitstreamsServiceStub { performBitstreamMoveRequest = jasmine.createSpy('performBitstreamMoveRequest'); + getPerformingMoveRequest = jasmine.createSpy('getPerformingMoveRequest').and.returnValue(false); + + getPerformingMoveRequest$ = jasmine.createSpy('getPerformingMoveRequest$').and.returnValue(of(false)); + getInitialBundlesPaginationOptions = jasmine.createSpy('getInitialBundlesPaginationOptions').and .returnValue(new PaginationComponentOptions()); diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts index 2329107c29..9bbf380487 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts @@ -119,7 +119,7 @@ export class ItemBitstreamsService { */ protected selectionAction$: BehaviorSubject = new BehaviorSubject(null); - protected isPerformingMoveRequest = false; + protected isPerformingMoveRequest: BehaviorSubject = new BehaviorSubject(false); constructor( protected notificationsService: NotificationsService, @@ -221,7 +221,7 @@ export class ItemBitstreamsService { cancelSelection() { const selected = this.getSelectedBitstream(); - if (hasNoValue(selected) || this.isPerformingMoveRequest) { + if (hasNoValue(selected) || this.getPerformingMoveRequest()) { return; } @@ -247,7 +247,7 @@ export class ItemBitstreamsService { moveSelectedBitstreamUp() { const selected = this.getSelectedBitstream(); - if (hasNoValue(selected) || this.isPerformingMoveRequest) { + if (hasNoValue(selected) || this.getPerformingMoveRequest()) { return; } @@ -272,7 +272,7 @@ export class ItemBitstreamsService { moveSelectedBitstreamDown() { const selected = this.getSelectedBitstream(); - if (hasNoValue(selected) || this.isPerformingMoveRequest) { + if (hasNoValue(selected) || this.getPerformingMoveRequest()) { return; } @@ -299,7 +299,7 @@ export class ItemBitstreamsService { * @param finish Optional: Function to execute once the response has been received */ performBitstreamMoveRequest(bundle: Bundle, fromIndex: number, toIndex: number, finish?: () => void) { - if (this.isPerformingMoveRequest) { + if (this.getPerformingMoveRequest()) { console.warn('Attempted to perform move request while previous request has not completed yet'); return; } @@ -310,18 +310,34 @@ export class ItemBitstreamsService { path: `/_links/bitstreams/${toIndex}/href`, }; - this.isPerformingMoveRequest = true; + this.announceLoading(); + this.isPerformingMoveRequest.next(true); this.bundleService.patch(bundle, [moveOperation]).pipe( getFirstCompletedRemoteData(), tap((response: RemoteData) => this.displayFailedResponseNotifications(MOVE_KEY, [response])), switchMap(() => this.requestService.setStaleByHrefSubstring(bundle.self)), take(1), ).subscribe(() => { - this.isPerformingMoveRequest = false; + console.log('got here!'); + this.isPerformingMoveRequest.next(false); finish?.(); }); } + /** + * Whether the service currently is processing a 'move' request + */ + getPerformingMoveRequest(): boolean { + return this.isPerformingMoveRequest.value; + } + + /** + * Returns an observable which emits when the service starts, or ends, processing a 'move' request + */ + getPerformingMoveRequest$(): Observable { + return this.isPerformingMoveRequest; + } + /** * Returns the pagination options to use when fetching the bundles */ @@ -526,4 +542,12 @@ export class ItemBitstreamsService { { bitstream: bitstreamName }); this.liveRegionService.addMessage(message); } + + /** + * Adds a message to the live region mentioning that the + */ + announceLoading() { + const message = this.translateService.instant('item.edit.bitstreams.edit.live.loading'); + this.liveRegionService.addMessage(message); + } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 519189ed69..9007982a72 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1954,6 +1954,8 @@ "item.edit.bitstreams.edit.live.clear": "{{ bitstream }} is no longer selected.", + "item.edit.bitstreams.edit.live.loading": "Waiting for move to complete.", + "item.edit.bitstreams.edit.live.select": "{{ bitstream }} is selected.", "item.edit.bitstreams.edit.live.move": "{{ bitstream }} is now in position {{ toIndex }}.", From 9486ab5fa1d3d0fc11ceb9f5063bbda61a078924 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 18 Oct 2024 14:26:48 -0500 Subject: [PATCH 602/875] Fix code scanning alert no. 6: Incomplete string escaping or encoding Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> (cherry picked from commit 372444c50ac28a6c7f68b20695bea616a3ab8b7f) --- src/app/core/shared/metadata.utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/shared/metadata.utils.ts b/src/app/core/shared/metadata.utils.ts index 87a90b53a3..e48b2b0c44 100644 --- a/src/app/core/shared/metadata.utils.ts +++ b/src/app/core/shared/metadata.utils.ts @@ -157,7 +157,7 @@ export class Metadata { const outputKeys: string[] = []; for (const inputKey of inputKeys) { if (inputKey.includes('*')) { - const inputKeyRegex = new RegExp('^' + inputKey.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$'); + const inputKeyRegex = new RegExp('^' + inputKey.replace(/\\/g, '\\\\').replace(/\./g, '\\.').replace(/\*/g, '.*') + '$'); for (const mapKey of Object.keys(mdMap)) { if (!outputKeys.includes(mapKey) && inputKeyRegex.test(mapKey)) { outputKeys.push(mapKey); From 5c877f56e92e07633efecab036a1493b8a06c465 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 23 Oct 2024 14:11:53 -0500 Subject: [PATCH 603/875] Remove unused/unmaintained postcss-apply --- package.json | 1 - postcss.config.js | 1 - yarn.lock | 21 --------------------- 3 files changed, 23 deletions(-) diff --git a/package.json b/package.json index 5197b720dc..b3988ad5d0 100644 --- a/package.json +++ b/package.json @@ -185,7 +185,6 @@ "ngx-mask": "^13.1.7", "nodemon": "^2.0.22", "postcss": "^8.4", - "postcss-apply": "0.12.0", "postcss-import": "^14.0.0", "postcss-loader": "^4.0.3", "postcss-preset-env": "^7.4.2", diff --git a/postcss.config.js b/postcss.config.js index df092d1d39..14013bd4f8 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -2,7 +2,6 @@ module.exports = { plugins: [ require('postcss-import')(), require('postcss-preset-env')(), - require('postcss-apply')(), require('postcss-responsive-type')() ] }; diff --git a/yarn.lock b/yarn.lock index 3f6a555df3..3a1f482a96 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9279,11 +9279,6 @@ performance-now@^2.1.0: resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -picocolors@^0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz" - integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== - picocolors@^1.0.0, picocolors@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -9352,14 +9347,6 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== -postcss-apply@0.12.0: - version "0.12.0" - resolved "https://registry.npmjs.org/postcss-apply/-/postcss-apply-0.12.0.tgz" - integrity sha512-u8qZLyA9P86cD08IhqjSVV8tf1eGiKQ4fPvjcG3Ic/eOU65EAkDQClp8We7d15TG+RIWRVPSy9v7cJ2D9OReqw== - dependencies: - balanced-match "^1.0.0" - postcss "^7.0.14" - postcss-attribute-case-insensitive@^5.0.2: version "5.0.2" resolved "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz" @@ -9693,14 +9680,6 @@ postcss@^6.0.6: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^7.0.14: - version "7.0.39" - resolved "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz" - integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== - dependencies: - picocolors "^0.2.1" - source-map "^0.6.1" - postcss@^8.2.14, postcss@^8.3.11, postcss@^8.3.7, postcss@^8.4, postcss@^8.4.19: version "8.4.47" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" From 425078dc4e60aac89151c5ab516597faca2271f6 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 23 Oct 2024 14:13:33 -0500 Subject: [PATCH 604/875] Remove unused postcss-responsive-type --- package.json | 1 - postcss.config.js | 3 +-- yarn.lock | 20 ++------------------ 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index b3988ad5d0..aaf3c17656 100644 --- a/package.json +++ b/package.json @@ -188,7 +188,6 @@ "postcss-import": "^14.0.0", "postcss-loader": "^4.0.3", "postcss-preset-env": "^7.4.2", - "postcss-responsive-type": "1.0.0", "react": "^16.14.0", "react-dom": "^16.14.0", "rimraf": "^3.0.2", diff --git a/postcss.config.js b/postcss.config.js index 14013bd4f8..f8b9666b31 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,7 +1,6 @@ module.exports = { plugins: [ require('postcss-import')(), - require('postcss-preset-env')(), - require('postcss-responsive-type')() + require('postcss-preset-env')() ] }; diff --git a/yarn.lock b/yarn.lock index 3a1f482a96..b05cebf7cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3937,7 +3937,7 @@ chalk@^1.1.1: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -9635,13 +9635,6 @@ postcss-replace-overflow-wrap@^4.0.0: resolved "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz" integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== -postcss-responsive-type@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/postcss-responsive-type/-/postcss-responsive-type-1.0.0.tgz" - integrity sha512-O4kAKbc4RLnSkzcguJ6ojW67uOfeILaj+8xjsO0quLU94d8BKCqYwwFEUVRNbj0YcXA6d3uF/byhbaEATMRVig== - dependencies: - postcss "^6.0.6" - postcss-selector-not@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz" @@ -9671,15 +9664,6 @@ postcss@8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^6.0.6: - version "6.0.23" - resolved "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz" - integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== - dependencies: - chalk "^2.4.1" - source-map "^0.6.1" - supports-color "^5.4.0" - postcss@^8.2.14, postcss@^8.3.11, postcss@^8.3.7, postcss@^8.4, postcss@^8.4.19: version "8.4.47" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" @@ -11315,7 +11299,7 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== -supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== From 0ed8c05e1c40824d8d428a96c9fb5a0fddd5131a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 23 Oct 2024 15:01:54 -0500 Subject: [PATCH 605/875] Bump http-proxy-middleware from 1.0.5 to 2.0.7 --- package.json | 2 +- yarn.lock | 21 +++++---------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 5197b720dc..d9251da2b9 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "express-rate-limit": "^5.1.3", "fast-json-patch": "^3.1.1", "filesize": "^6.1.0", - "http-proxy-middleware": "^1.0.5", + "http-proxy-middleware": "^2.0.7", "http-terminator": "^3.2.0", "isbot": "^5.1.17", "js-cookie": "2.2.1", diff --git a/yarn.lock b/yarn.lock index 3f6a555df3..dd61f43ddb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2522,7 +2522,7 @@ "@types/react" "*" hoist-non-react-statics "^3.3.0" -"@types/http-proxy@^1.17.5", "@types/http-proxy@^1.17.8": +"@types/http-proxy@^1.17.8": version "1.17.10" resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.10.tgz" integrity sha512-Qs5aULi+zV1bwKAg5z1PWnDXWmsn+LxIvUGv6E2+OOMYhclZMO+OXd9pYVf2gLykf2I7IV2u7oTHwChPNsvJ7g== @@ -6629,21 +6629,10 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -http-proxy-middleware@^1.0.5: - version "1.3.1" - resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz" - integrity sha512-13eVVDYS4z79w7f1+NPllJtOQFx/FdUW4btIvVRMaRlUY9VGstAbo5MOhLEuUgZFRHn3x50ufn25zkj/boZnEg== - dependencies: - "@types/http-proxy" "^1.17.5" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - -http-proxy-middleware@^2.0.3, http-proxy-middleware@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== +http-proxy-middleware@^2.0.3, http-proxy-middleware@^2.0.6, http-proxy-middleware@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6" + integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA== dependencies: "@types/http-proxy" "^1.17.8" http-proxy "^1.18.1" From d4bb79ca18d20111786015bd4657c468a29985f2 Mon Sep 17 00:00:00 2001 From: andreaNeki Date: Tue, 22 Oct 2024 13:29:05 -0300 Subject: [PATCH 606/875] Issue 3426 - Aligning the browse button (cherry picked from commit ddafda33b8c4730a732a133147451390ee2c7ab7) --- src/app/shared/upload/uploader/uploader.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/upload/uploader/uploader.component.html b/src/app/shared/upload/uploader/uploader.component.html index b1fd8199d8..4c1db13d13 100644 --- a/src/app/shared/upload/uploader/uploader.component.html +++ b/src/app/shared/upload/uploader/uploader.component.html @@ -19,8 +19,8 @@ (fileOver)="fileOverBase($event)" class="well ds-base-drop-zone mt-1 mb-3 text-muted">
    - - + + {{dropMsg | translate}}{{'uploader.or' | translate}}
    {{ dsoNameService.getName(undefined) }} + {{ dsoNameService.getName((group.object | async)?.payload) }} +
    diff --git a/src/app/access-control/group-registry/group-form/group-form.component.ts b/src/app/access-control/group-registry/group-form/group-form.component.ts index 37ce30473f..c0ea034fbb 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.ts +++ b/src/app/access-control/group-registry/group-form/group-form.component.ts @@ -13,7 +13,7 @@ import { Observable, Subscription, combineLatest, } from 'rxjs'; -import { map, switchMap, take, debounceTime, startWith, filter } from 'rxjs/operators'; +import { map, switchMap, take, debounceTime } from 'rxjs/operators'; import { getCollectionEditRolesRoute } from '../../../collection-page/collection-page-routing-paths'; import { getCommunityEditRolesRoute } from '../../../community-page/community-page-routing-paths'; import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service'; @@ -35,7 +35,7 @@ import { } from '../../../core/shared/operators'; import { AlertType } from '../../../shared/alert/aletr-type'; import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component'; -import { hasValue, isNotEmpty, hasValueOperator, hasNoValue } from '../../../shared/empty.util'; +import { hasValue, isNotEmpty, hasValueOperator } from '../../../shared/empty.util'; import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { followLink } from '../../../shared/utils/follow-link-config.model'; @@ -164,11 +164,16 @@ export class GroupFormComponent implements OnInit, OnDestroy { this.activeGroupLinkedDSO$ = this.getActiveGroupLinkedDSO(); this.linkedEditRolesRoute$ = this.getLinkedEditRolesRoute(); this.canEdit$ = this.activeGroupLinkedDSO$.pipe( - filter((dso: DSpaceObject) => hasNoValue(dso)), - switchMap(() => this.activeGroup$), - hasValueOperator(), - switchMap((group: Group) => this.authorizationService.isAuthorized(FeatureID.CanDelete, group.self)), - startWith(false), + switchMap((dso: DSpaceObject) => { + if (hasValue(dso)) { + return [false]; + } else { + return this.activeGroup$.pipe( + hasValueOperator(), + switchMap((group: Group) => this.authorizationService.isAuthorized(FeatureID.CanDelete, group.self)), + ); + } + }), ); this.initialisePage(); } @@ -216,7 +221,7 @@ export class GroupFormComponent implements OnInit, OnDestroy { combineLatest([ this.activeGroup$, this.canEdit$, - this.activeGroupLinkedDSO$.pipe(take(1)), + this.activeGroupLinkedDSO$, ]).subscribe(([activeGroup, canEdit, linkedObject]) => { if (activeGroup != null) { @@ -224,25 +229,31 @@ export class GroupFormComponent implements OnInit, OnDestroy { // Disable group name exists validator this.formGroup.controls.groupName.clearAsyncValidators(); - if (linkedObject?.name) { - this.formBuilderService.insertFormGroupControl(1, this.formGroup, this.formModel, groupCommunityModel); - this.groupDescription = this.formGroup.get('groupCommunity'); + if (isNotEmpty(linkedObject?.name)) { + if (!this.formGroup.controls.groupCommunity) { + this.formBuilderService.insertFormGroupControl(1, this.formGroup, this.formModel, groupCommunityModel); + this.groupDescription = this.formGroup.get('groupCommunity'); + } this.formGroup.patchValue({ groupName: activeGroup.name, groupCommunity: linkedObject?.name ?? '', groupDescription: activeGroup.firstMetadataValue('dc.description'), }); } else { + this.formModel = [ + groupNameModel, + groupDescriptionModel, + ]; this.formGroup.patchValue({ groupName: activeGroup.name, groupDescription: activeGroup.firstMetadataValue('dc.description'), }); } - setTimeout(() => { - if (!canEdit || activeGroup.permanent) { - this.formGroup.disable(); - } - }, 200); + if (!canEdit || activeGroup.permanent) { + this.formGroup.disable(); + } else { + this.formGroup.enable(); + } } }) ); @@ -471,6 +482,7 @@ export class GroupFormComponent implements OnInit, OnDestroy { */ getLinkedEditRolesRoute(): Observable { return this.activeGroupLinkedDSO$.pipe( + hasValueOperator(), map((dso: DSpaceObject) => { switch ((dso as any).type) { case Community.type.value: @@ -478,7 +490,7 @@ export class GroupFormComponent implements OnInit, OnDestroy { case Collection.type.value: return getCollectionEditRolesRoute(dso.id); } - }) + }), ); } } From 585bbec5d53ebc8dedbafaf37f89c095addb883a Mon Sep 17 00:00:00 2001 From: andreaNeki Date: Tue, 27 Aug 2024 17:02:12 -0300 Subject: [PATCH 619/875] Improving accessibility on the new user registration page (cherry picked from commit 0eb2d5ce587ece5d3573cad7534bcbb8df5c6ed4) --- .../register-email-form.component.html | 9 ++++++--- src/assets/i18n/en.json5 | 2 ++ src/assets/i18n/es.json5 | 3 +++ src/assets/i18n/pt-BR.json5 | 3 +++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/app/register-email-form/register-email-form.component.html b/src/app/register-email-form/register-email-form.component.html index d4cf75b563..b47790d1cb 100644 --- a/src/app/register-email-form/register-email-form.component.html +++ b/src/app/register-email-form/register-email-form.component.html @@ -14,13 +14,16 @@ + type="text" id="email" formControlName="email" + [attr.aria-label]="'register-email.aria.label'|translate" + aria-describedby="email-errors-required email-error-not-valid" + [attr.aria-invalid]="form.get('email')?.invalid"/>
    - + {{ MESSAGE_PREFIX + '.email.error.required' | translate }} - + {{ MESSAGE_PREFIX + '.email.error.not-email-form' | translate }} {{ MESSAGE_PREFIX + '.email.error.not-valid-domain' | translate: { domains: validMailDomains.join(', ') } }} diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 475e6437d6..84fbb5db96 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -5369,4 +5369,6 @@ "process.overview.unknown.user": "Unknown", "browse.search-form.placeholder": "Search the repository", + + "register-email.aria.label": "Enter your e-mail address", } diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index 3bc98a9502..6373f91652 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -7831,5 +7831,8 @@ //"browse.search-form.placeholder": "Search the repository", "browse.search-form.placeholder": "Buscar en el repositorio", + // "register-email.aria.label": "Enter your e-mail address", + "register-email.aria.label": "Introduzca su dirección de correo electrónico", + } diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index aae51b3671..09c6cbf630 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -7857,4 +7857,7 @@ //"browse.search-form.placeholder": "Search the repository", "browse.search-form.placeholder": "Buscar no repositório", + + // "register-email.aria.label": "Enter your e-mail address", + "register-email.aria.label": "Digite seu e-mail", } From a6b45f777cbfdafd4bf104cff81c9ed8f3a7460c Mon Sep 17 00:00:00 2001 From: andreaNeki Date: Thu, 5 Sep 2024 09:35:55 -0300 Subject: [PATCH 620/875] Code refactoring - Accessibility on the new user registration and forgotten password forms (cherry picked from commit f00eae67602c3b1d12c5f8fcf43db774a7a70e11) --- .../register-email-form/register-email-form.component.html | 4 ++-- src/assets/i18n/en.json5 | 4 +++- src/assets/i18n/es.json5 | 7 +++++-- src/assets/i18n/pt-BR.json5 | 7 +++++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/app/register-email-form/register-email-form.component.html b/src/app/register-email-form/register-email-form.component.html index b47790d1cb..9192226b1e 100644 --- a/src/app/register-email-form/register-email-form.component.html +++ b/src/app/register-email-form/register-email-form.component.html @@ -15,9 +15,9 @@ for="email">{{MESSAGE_PREFIX + '.email' | translate}} + [attr.aria-invalid]="email.invalid"/>
    diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 84fbb5db96..56439dee43 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -5370,5 +5370,7 @@ "browse.search-form.placeholder": "Search the repository", - "register-email.aria.label": "Enter your e-mail address", + "register-page.registration.aria.label": "Enter your e-mail address", + + "forgot-email.form.aria.label": "Enter your e-mail address", } diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index 6373f91652..dfdf4ca628 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -7831,8 +7831,11 @@ //"browse.search-form.placeholder": "Search the repository", "browse.search-form.placeholder": "Buscar en el repositorio", - // "register-email.aria.label": "Enter your e-mail address", - "register-email.aria.label": "Introduzca su dirección de correo electrónico", + // "register-page.registration.aria.label": "Enter your e-mail address", + "register-page.registration.aria.label": "Introduzca su dirección de correo electrónico", + + // "forgot-email.form.aria.label": "Enter your e-mail address", + "forgot-email.form.aria.label": "Introduzca su dirección de correo electrónico", } diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index 09c6cbf630..eebf896889 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -7858,6 +7858,9 @@ //"browse.search-form.placeholder": "Search the repository", "browse.search-form.placeholder": "Buscar no repositório", - // "register-email.aria.label": "Enter your e-mail address", - "register-email.aria.label": "Digite seu e-mail", + // "register-page.registration.aria.label": "Enter your e-mail address", + "register-page.registration.aria.label": "Digite seu e-mail", + + // "forgot-email.form.aria.label": "Enter your e-mail address", + "forgot-email.form.aria.label": "Digite seu e-mail", } From 3b3fa4f643ca96e81216963018b826d8989545ec Mon Sep 17 00:00:00 2001 From: andreaNeki Date: Thu, 31 Oct 2024 11:27:10 -0300 Subject: [PATCH 621/875] Dynamic aria-describedby attribute (cherry picked from commit e629d9edf0d2177953950f5e145bf49ff1203f88) --- .../register-email-form.component.html | 2 +- .../register-email-form.component.spec.ts | 35 +++++++++++++++++++ .../register-email-form.component.ts | 26 ++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/app/register-email-form/register-email-form.component.html b/src/app/register-email-form/register-email-form.component.html index 9192226b1e..a3ccc4bce8 100644 --- a/src/app/register-email-form/register-email-form.component.html +++ b/src/app/register-email-form/register-email-form.component.html @@ -16,7 +16,7 @@
    diff --git a/src/app/register-email-form/register-email-form.component.spec.ts b/src/app/register-email-form/register-email-form.component.spec.ts index 9e852d9491..67b87a974c 100644 --- a/src/app/register-email-form/register-email-form.component.spec.ts +++ b/src/app/register-email-form/register-email-form.component.spec.ts @@ -192,4 +192,39 @@ describe('RegisterEmailFormComponent', () => { expect(router.navigate).not.toHaveBeenCalled(); })); }); + describe('ariaDescribedby', () => { + it('should have required error message when email is empty', () => { + comp.form.patchValue({ email: '' }); + comp.checkEmailValidity(); + + expect(comp.ariaDescribedby).toContain('email-errors-required'); + }); + + it('should have invalid email error message when email is invalid', () => { + comp.form.patchValue({ email: 'invalid-email' }); + comp.checkEmailValidity(); + + expect(comp.ariaDescribedby).toContain('email-error-not-valid'); + }); + + it('should clear ariaDescribedby when email is valid', () => { + comp.form.patchValue({ email: 'valid@email.com' }); + comp.checkEmailValidity(); + + expect(comp.ariaDescribedby).toBe(''); + }); + + it('should update ariaDescribedby on value changes', () => { + spyOn(comp, 'checkEmailValidity').and.callThrough(); + + comp.form.patchValue({ email: '' }); + expect(comp.ariaDescribedby).toContain('email-errors-required'); + + comp.form.patchValue({ email: 'invalid-email' }); + expect(comp.ariaDescribedby).toContain('email-error-not-valid'); + + comp.form.patchValue({ email: 'valid@email.com' }); + expect(comp.ariaDescribedby).toBe(''); + }); + }); }); diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index df7e9bea5e..0ba37a9090 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -66,6 +66,11 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit { subscriptions: Subscription[] = []; + /** + * Stores error messages related to the email field + */ + ariaDescribedby: string = ''; + captchaVersion(): Observable { return this.googleRecaptchaService.captchaVersion(); } @@ -135,6 +140,13 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit { this.disableUntilChecked = res; this.changeDetectorRef.detectChanges(); })); + + /** + * Subscription to email field value changes + */ + this.subscriptions.push(this.email.valueChanges.subscribe(() => { + this.checkEmailValidity(); + })); } /** @@ -248,4 +260,18 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit { } } + checkEmailValidity() { + const descriptions = []; + + if (this.email.errors?.required) { + descriptions.push('email-errors-required'); + } + + if (this.email.errors?.pattern || this.email.errors?.email) { + descriptions.push('email-error-not-valid'); + } + + this.ariaDescribedby = descriptions.join(' '); + } + } From c330e83095c844c3db61e4be47499f793c0c1773 Mon Sep 17 00:00:00 2001 From: andreaNeki Date: Thu, 31 Oct 2024 11:39:34 -0300 Subject: [PATCH 622/875] Resolving a wrongly declared variable error (cherry picked from commit 7b72a5f0c26514eb07f58faf3470c89fe9729d6f) --- src/app/register-email-form/register-email-form.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index 0ba37a9090..1d8a6d3474 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -69,7 +69,7 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit { /** * Stores error messages related to the email field */ - ariaDescribedby: string = ''; + ariaDescribedby = ''; captchaVersion(): Observable { return this.googleRecaptchaService.captchaVersion(); From 1fad3cffc7c78dd52380ca21006ea2aa17ada847 Mon Sep 17 00:00:00 2001 From: andreaNeki Date: Thu, 31 Oct 2024 15:01:54 -0300 Subject: [PATCH 623/875] Simplifying the implementation of dynamic aria-describedby (cherry picked from commit fa6e85d6db5a671038e8e27968701b46520154df) --- .../register-email-form.component.html | 2 +- .../register-email-form.component.spec.ts | 35 ------------------- .../register-email-form.component.ts | 28 +-------------- 3 files changed, 2 insertions(+), 63 deletions(-) diff --git a/src/app/register-email-form/register-email-form.component.html b/src/app/register-email-form/register-email-form.component.html index a3ccc4bce8..f6f4880ab6 100644 --- a/src/app/register-email-form/register-email-form.component.html +++ b/src/app/register-email-form/register-email-form.component.html @@ -16,7 +16,7 @@
    diff --git a/src/app/register-email-form/register-email-form.component.spec.ts b/src/app/register-email-form/register-email-form.component.spec.ts index 67b87a974c..9e852d9491 100644 --- a/src/app/register-email-form/register-email-form.component.spec.ts +++ b/src/app/register-email-form/register-email-form.component.spec.ts @@ -192,39 +192,4 @@ describe('RegisterEmailFormComponent', () => { expect(router.navigate).not.toHaveBeenCalled(); })); }); - describe('ariaDescribedby', () => { - it('should have required error message when email is empty', () => { - comp.form.patchValue({ email: '' }); - comp.checkEmailValidity(); - - expect(comp.ariaDescribedby).toContain('email-errors-required'); - }); - - it('should have invalid email error message when email is invalid', () => { - comp.form.patchValue({ email: 'invalid-email' }); - comp.checkEmailValidity(); - - expect(comp.ariaDescribedby).toContain('email-error-not-valid'); - }); - - it('should clear ariaDescribedby when email is valid', () => { - comp.form.patchValue({ email: 'valid@email.com' }); - comp.checkEmailValidity(); - - expect(comp.ariaDescribedby).toBe(''); - }); - - it('should update ariaDescribedby on value changes', () => { - spyOn(comp, 'checkEmailValidity').and.callThrough(); - - comp.form.patchValue({ email: '' }); - expect(comp.ariaDescribedby).toContain('email-errors-required'); - - comp.form.patchValue({ email: 'invalid-email' }); - expect(comp.ariaDescribedby).toContain('email-error-not-valid'); - - comp.form.patchValue({ email: 'valid@email.com' }); - expect(comp.ariaDescribedby).toBe(''); - }); - }); }); diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index 1d8a6d3474..42be43aab3 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -66,11 +66,6 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit { subscriptions: Subscription[] = []; - /** - * Stores error messages related to the email field - */ - ariaDescribedby = ''; - captchaVersion(): Observable { return this.googleRecaptchaService.captchaVersion(); } @@ -140,13 +135,6 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit { this.disableUntilChecked = res; this.changeDetectorRef.detectChanges(); })); - - /** - * Subscription to email field value changes - */ - this.subscriptions.push(this.email.valueChanges.subscribe(() => { - this.checkEmailValidity(); - })); } /** @@ -259,19 +247,5 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit { console.warn(`Unimplemented notification '${key}' from reCaptcha service`); } } - - checkEmailValidity() { - const descriptions = []; - - if (this.email.errors?.required) { - descriptions.push('email-errors-required'); - } - - if (this.email.errors?.pattern || this.email.errors?.email) { - descriptions.push('email-error-not-valid'); - } - - this.ariaDescribedby = descriptions.join(' '); - } - + } From 1011468273f2887b21e77bf687285acb240d3e53 Mon Sep 17 00:00:00 2001 From: andreaNeki Date: Thu, 31 Oct 2024 15:07:22 -0300 Subject: [PATCH 624/875] Adjusting spaces in ts (cherry picked from commit 009da08177f31d049c2094151d8485c157ee0ede) --- src/app/register-email-form/register-email-form.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index 42be43aab3..c8ca0cc710 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -247,5 +247,4 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit { console.warn(`Unimplemented notification '${key}' from reCaptcha service`); } } - } From 26f4d1d329d21f1661b6e10bd06362897c7521d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 02:46:18 +0000 Subject: [PATCH 625/875] Bump sass from 1.80.4 to 1.80.6 in the sass group Bumps the sass group with 1 update: [sass](https://github.com/sass/dart-sass). Updates `sass` from 1.80.4 to 1.80.6 - [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.80.4...1.80.6) --- updated-dependencies: - dependency-name: sass dependency-type: direct:development update-type: version-update:semver-patch dependency-group: sass ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index e2923712c0..67e416e6e1 100644 --- a/package.json +++ b/package.json @@ -186,7 +186,7 @@ "react-copy-to-clipboard": "^5.1.0", "react-dom": "^16.14.0", "rimraf": "^3.0.2", - "sass": "~1.80.4", + "sass": "~1.80.6", "sass-loader": "^12.6.0", "sass-resources-loader": "^2.2.5", "ts-node": "^8.10.2", diff --git a/yarn.lock b/yarn.lock index 21dcdd6fa5..69b50f026e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10234,15 +10234,16 @@ sass@1.58.1: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" -sass@^1.25.0, sass@~1.80.4: - version "1.80.4" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.80.4.tgz#bc0418fd796cad2f1a1309d8b4d7fe44b7027de0" - integrity sha512-rhMQ2tSF5CsuuspvC94nPM9rToiAFw2h3JTrLlgmNw1MH79v8Cr3DH6KF6o6r+8oofY3iYVPUf66KzC8yuVN1w== +sass@^1.25.0, sass@~1.80.6: + version "1.80.6" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.80.6.tgz#5d0aa55763984effe41e40019c9571ab73e6851f" + integrity sha512-ccZgdHNiBF1NHBsWvacvT5rju3y1d/Eu+8Ex6c21nHp2lZGLBEtuwc415QfiI1PJa1TpCo3iXwwSRjRpn2Ckjg== dependencies: - "@parcel/watcher" "^2.4.1" chokidar "^4.0.0" immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" + optionalDependencies: + "@parcel/watcher" "^2.4.1" sax@^1.2.4: version "1.2.4" From 2eb120909b6b917d3f82852382429e391ace7bfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 02:46:50 +0000 Subject: [PATCH 626/875] Bump compression from 1.7.4 to 1.7.5 Bumps [compression](https://github.com/expressjs/compression) from 1.7.4 to 1.7.5. - [Release notes](https://github.com/expressjs/compression/releases) - [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/compression/compare/1.7.4...1.7.5) --- updated-dependencies: - dependency-name: compression dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 36 ++++++++++++++++++------------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index e2923712c0..24c674d8d6 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "cerialize": "0.1.18", "cli-progress": "^3.12.0", "colors": "^1.4.0", - "compression": "^1.7.4", + "compression": "^1.7.5", "cookie-parser": "1.4.7", "core-js": "^3.38.1", "date-fns": "^2.30.0", diff --git a/yarn.lock b/yarn.lock index 21dcdd6fa5..0208ffe1a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2983,7 +2983,7 @@ abbrev@1, abbrev@^1.0.0: resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: +accepts@~1.3.4, accepts@~1.3.8: version "1.3.8" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -3710,11 +3710,6 @@ builtins@^5.0.0: dependencies: semver "^7.0.0" -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" - integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== - bytes@3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" @@ -4119,7 +4114,7 @@ commondir@^1.0.1: resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== -compressible@~2.0.16: +compressible@~2.0.18: version "2.0.18" resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz" integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== @@ -4134,17 +4129,17 @@ compression-webpack-plugin@^9.2.0: schema-utils "^4.0.0" serialize-javascript "^6.0.0" -compression@^1.7.4: - version "1.7.4" - resolved "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz" - integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== +compression@^1.7.4, compression@^1.7.5: + version "1.7.5" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.5.tgz#fdd256c0a642e39e314c478f6c2cd654edd74c93" + integrity sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q== dependencies: - accepts "~1.3.5" - bytes "3.0.0" - compressible "~2.0.16" + bytes "3.1.2" + compressible "~2.0.18" debug "2.6.9" + negotiator "~0.6.4" on-headers "~1.0.2" - safe-buffer "5.1.2" + safe-buffer "5.2.1" vary "~1.1.2" concat-map@0.0.1: @@ -8351,11 +8346,16 @@ needle@^3.1.0: iconv-lite "^0.6.3" sax "^1.2.4" -negotiator@0.6.3, negotiator@^0.6.3: +negotiator@0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +negotiator@^0.6.3, negotiator@~0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== + neo-async@^2.6.2: version "2.6.2" resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" @@ -10158,12 +10158,12 @@ safe-array-concat@^1.1.2: has-symbols "^1.0.3" isarray "^2.0.5" -safe-buffer@5.1.2, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== From 1b77530a3187f5e991a686876053b72a16d9ddcc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 02:47:05 +0000 Subject: [PATCH 627/875] Bump @types/lodash from 4.17.12 to 4.17.13 Bumps [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) from 4.17.12 to 4.17.13. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash) --- updated-dependencies: - dependency-name: "@types/lodash" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e2923712c0..6aef716bd5 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "@types/grecaptcha": "^3.0.9", "@types/jasmine": "~3.6.0", "@types/js-cookie": "2.2.6", - "@types/lodash": "^4.17.12", + "@types/lodash": "^4.17.13", "@types/node": "^14.18.63", "@types/sanitize-html": "^2.13.0", "@typescript-eslint/eslint-plugin": "^5.62.0", diff --git a/yarn.lock b/yarn.lock index 21dcdd6fa5..a44db790f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2539,10 +2539,10 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/lodash@^4.17.12": - version "4.17.12" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.12.tgz#25d71312bf66512105d71e55d42e22c36bcfc689" - integrity sha512-sviUmCE8AYdaF/KIHLDJBQgeYzPBI0vf/17NaYehBJfYD1j6/L95Slh07NlyK2iNyBNaEkb3En2jRt+a8y3xZQ== +"@types/lodash@^4.17.13": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.13.tgz#786e2d67cfd95e32862143abe7463a7f90c300eb" + integrity sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg== "@types/mime@*": version "3.0.1" From 2aaa32ae5f27594aaea2146c809352c7a8a02cc6 Mon Sep 17 00:00:00 2001 From: Pierre Lasou Date: Thu, 31 Oct 2024 11:12:29 -0400 Subject: [PATCH 628/875] Complete tag translation in french for ORCID Contains all french translations for ORCID and Researcher Profile. (cherry picked from commit ac720033dcca82c0bbd8c6a9f8ac8b1d07ffb37e) --- src/assets/i18n/fr.json5 | 366 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index a73baacf2d..9c0b85cfe4 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -6241,6 +6241,372 @@ // "idle-modal.extend-session": "Extend session", "idle-modal.extend-session": "Prolonger la session", + //"researcher.profile.action.processing": "Processing...", + "researcher.profile.action.processing": "En traitement...", + + //"researcher.profile.associated": "Researcher profile associated", + "researcher.profile.associated": "Profil du chercheur associé", + + //"researcher.profile.change-visibility.fail": "An unexpected error occurs while changing the profile visibility", + "researcher.profile.change-visibility.fail": "Une erreur inattendue s'est produite pendant le changement apporté à la visibilité du profil.", + + //"researcher.profile.create.new": "Create new", + "researcher.profile.create.new": "Créer un nouveau", + + //"researcher.profile.create.success": "Researcher profile created successfully", + "researcher.profile.create.success": "Profil créé avec succès", + + //"researcher.profile.create.fail": "An error occurs during the researcher profile creation", + "researcher.profile.create.fail": "Une erreur s'est produite lors de la création du profil", + + //"researcher.profile.delete": "Delete", + "researcher.profile.delete": "Supprimer", + + //"researcher.profile.expose": "Expose", + "researcher.profile.expose": "Exposer", + + //"researcher.profile.hide": "Hide", + "researcher.profile.hide": "Cacher", + + //"researcher.profile.not.associated": "Researcher profile not yet associated", + "researcher.profile.not.associated": "Profil non associé", + + //"researcher.profile.view": "View", + "researcher.profile.view": "Voir", + + //"researcher.profile.private.visibility": "PRIVATE", + "researcher.profile.private.visibility": "PRIVÉ", + + //"researcher.profile.public.visibility": "PUBLIC", + "researcher.profile.public.visibility": "PUBLIC", + + //"researcher.profile.status": "Status:", + "researcher.profile.status": "Statut:", + + //"researcherprofile.claim.not-authorized": "You are not authorized to claim this item. For more details contact the administrator(s).", + "researcherprofile.claim.not-authorized": "Vous n'êtes pas autorisé à réclamer cet item. Pour plus de détails contacter la personne administratrice.", + + //"researcherprofile.error.claim.body": "An error occurred while claiming the profile, please try again later", + "researcherprofile.error.claim.body": "Une erreur s'est produite lors de la réclamation du profil, veuillez réessayer plus tard.", + + //"researcherprofile.error.claim.title": "Error", + "researcherprofile.error.claim.title": "Erreur", + + //"researcherprofile.success.claim.body": "Profile claimed with success", + "researcherprofile.success.claim.body": "Profil réclamé avec succès", + + //"researcherprofile.success.claim.title": "Success", + "researcherprofile.success.claim.title": "Succès", + + //"person.page.orcid.create": "Create an ORCID ID", + "person.page.orcid.create": "Créer un identifiant ORCID", + + //"person.page.orcid.granted-authorizations": "Granted authorizations", + "person.page.orcid.granted-authorizations": "Autorisations accordées", + + //"person.page.orcid.grant-authorizations": "Grant authorizations", + "person.page.orcid.grant-authorizations": "Accorder des autorisations", + + //"person.page.orcid.link": "Connect to ORCID ID", + "person.page.orcid.link": "Se connecter à ORCID", + + //"person.page.orcid.link.processing": "Linking profile to ORCID...", + "person.page.orcid.link.processing": "Liaison du profil avec ORCID en cours...", + + //"person.page.orcid.link.error.message": "Something went wrong while connecting the profile with ORCID. If the problem persists, contact the administrator.", + "person.page.orcid.link.error.message": "Quelque chose a échoué lors de la connection du profil avec ORCID. Si le problème persiste, contacter la personne administratice.", + + //"person.page.orcid.orcid-not-linked-message": "The ORCID iD of this profile ({{ orcid }}) has not yet been connected to an account on the ORCID registry or the connection is expired.", + "person.page.orcid.orcid-not-linked-message": "L'identifiant ORCID du profil ({{ orcid }}) n'a pas encore été connecté à une compte du registre ORCID ou la connection a expiré. ", + + //"person.page.orcid.unlink": "Disconnect from ORCID", + "person.page.orcid.unlink": "Déconnecter d'ORCID", + + //"person.page.orcid.unlink.processing": "Processing...", + "person.page.orcid.unlink.processing": "Traitement en cours...", + + //"person.page.orcid.missing-authorizations": "Missing authorizations", + "person.page.orcid.missing-authorizations": "Autorisations manquantes", + + //"person.page.orcid.missing-authorizations-message": "The following authorizations are missing:", + "person.page.orcid.missing-authorizations-message": "Les autorisations suivantes sont manquantes :", + + //"person.page.orcid.no-missing-authorizations-message": "Great! This box is empty, so you have granted all access rights to use all functions offers by your institution.", + "person.page.orcid.no-missing-authorizations-message": "Cette boite est vide, vous avez autorisé l'utilisation de toutes les fonctions proposées par votre organisation.", + + //"person.page.orcid.no-orcid-message": "No ORCID iD associated yet. By clicking on the button below it is possible to link this profile with an ORCID account.", + "person.page.orcid.no-orcid-message": "Aucun identifiant ORCID n'est encore associé. En cliquant sur le bouton ci-dessous, il est possible de lier ce profil à un compte ORCID.", + + //"person.page.orcid.profile-preferences": "Profile preferences", + "person.page.orcid.profile-preferences": "Préférences du profil", + + //"person.page.orcid.funding-preferences": "Funding preferences", + "person.page.orcid.funding-preferences": "Préférence pour la section Financement", + + //"person.page.orcid.publications-preferences": "Publication preferences", + "person.page.orcid.publications-preferences": "Préférence pour la section Publication", + + //"person.page.orcid.remove-orcid-message": "If you need to remove your ORCID, please contact the repository administrator", + "person.page.orcid.remove-orcid-message": "Si vous avez besoin de retirer votre ORCID, contacter la personne administratrice du dépôt.", + + //"person.page.orcid.save.preference.changes": "Update settings", + "person.page.orcid.save.preference.changes": "Mettre à jour les configurations", + + //"person.page.orcid.sync-profile.affiliation": "Affiliation", + "person.page.orcid.sync-profile.affiliation": "Affiliation", + + //"person.page.orcid.sync-profile.biographical": "Biographical data", + "person.page.orcid.sync-profile.biographical": "Données biographiques", + + //"person.page.orcid.sync-profile.education": "Education", + "person.page.orcid.sync-profile.education": "Éducation", + + //"person.page.orcid.sync-profile.identifiers": "Identifiers", + "person.page.orcid.sync-profile.identifiers": "Identifiants", + + //"person.page.orcid.sync-fundings.all": "All fundings", + "person.page.orcid.sync-fundings.all": "Tous les financements", + + //"person.page.orcid.sync-fundings.mine": "My fundings", + "person.page.orcid.sync-fundings.mine": "Mes financements", + + //"person.page.orcid.sync-fundings.my_selected": "Selected fundings", + "person.page.orcid.sync-fundings.my_selected": "Financements sélectionnés", + + //"person.page.orcid.sync-fundings.disabled": "Disabled", + "person.page.orcid.sync-fundings.disabled": "Désactivé", + + //"person.page.orcid.sync-publications.all": "All publications", + "person.page.orcid.sync-publications.all": "Toutes les publications", + + //"person.page.orcid.sync-publications.mine": "My publications", + "person.page.orcid.sync-publications.mine": "Mes publications", + + //"person.page.orcid.sync-publications.my_selected": "Selected publications", + "person.page.orcid.sync-publications.my_selected": "Publications sélectionnées", + + //"person.page.orcid.sync-publications.disabled": "Disabled", + "person.page.orcid.sync-publications.disabled": "Désactivé", + + //"person.page.orcid.sync-queue.discard": "Discard the change and do not synchronize with the ORCID registry", + "person.page.orcid.sync-queue.discard": "Ignorer les changements et ne pas synchroniser avec le registre ORCID.", + + //"person.page.orcid.sync-queue.discard.error": "The discarding of the ORCID queue record failed", + "person.page.orcid.sync-queue.discard.error": "L'annulation de la file d'attente ORCID a échoué.", + + //"person.page.orcid.sync-queue.discard.success": "The ORCID queue record have been discarded successfully", + "person.page.orcid.sync-queue.discard.success": "La file d'attente ORCID a été annulée avec succès.", + + //"person.page.orcid.sync-queue.empty-message": "The ORCID queue registry is empty", + "person.page.orcid.sync-queue.empty-message": "La file d'attente ORCID est vide.", + + //"person.page.orcid.sync-queue.table.header.type": "Type", + "person.page.orcid.sync-queue.table.header.type": "Type", + + //"person.page.orcid.sync-queue.table.header.description": "Description", + "person.page.orcid.sync-queue.table.header.description": "Description", + + //"person.page.orcid.sync-queue.table.header.action": "Action", + "person.page.orcid.sync-queue.table.header.action": "Action", + + //"person.page.orcid.sync-queue.description.affiliation": "Affiliations", + "person.page.orcid.sync-queue.description.affiliation": "Affiliations", + + //"person.page.orcid.sync-queue.description.country": "Country", + "person.page.orcid.sync-queue.description.country": "Pays", + + //"person.page.orcid.sync-queue.description.education": "Educations", + "person.page.orcid.sync-queue.description.education": "Éducation", + + //"person.page.orcid.sync-queue.description.external_ids": "External ids", + "person.page.orcid.sync-queue.description.external_ids": "Identifiants externes", + + //"person.page.orcid.sync-queue.description.other_names": "Other names", + "person.page.orcid.sync-queue.description.other_names": "Autres noms", + + //"person.page.orcid.sync-queue.description.qualification": "Qualifications", + "person.page.orcid.sync-queue.description.qualification": "Qualifications", + + //"person.page.orcid.sync-queue.description.researcher_urls": "Researcher urls", + "person.page.orcid.sync-queue.description.researcher_urls": "URLs du chercheur", + + //"person.page.orcid.sync-queue.description.keywords": "Keywords", + "person.page.orcid.sync-queue.description.keywords": "Mots clés", + + //"person.page.orcid.sync-queue.tooltip.insert": "Add a new entry in the ORCID registry", + "person.page.orcid.sync-queue.tooltip.insert": "Ajouter une nouvelle entrée dans le registre ORCID", + + //"person.page.orcid.sync-queue.tooltip.update": "Update this entry on the ORCID registry", + "person.page.orcid.sync-queue.tooltip.update": "Mettre à jour cette entrée dans le registre ORCID", + + //"person.page.orcid.sync-queue.tooltip.delete": "Remove this entry from the ORCID registry", + "person.page.orcid.sync-queue.tooltip.delete": "Supprimer cette entrée du registre ORCID", + + //"person.page.orcid.sync-queue.tooltip.publication": "Publication", + "person.page.orcid.sync-queue.tooltip.publication": "Publication", + + //"person.page.orcid.sync-queue.tooltip.project": "Project", + "person.page.orcid.sync-queue.tooltip.project": "Projet", + + //"person.page.orcid.sync-queue.tooltip.affiliation": "Affiliation", + "person.page.orcid.sync-queue.tooltip.affiliation": "Affiliation", + + //"person.page.orcid.sync-queue.tooltip.education": "Education", + "person.page.orcid.sync-queue.tooltip.education": "Éducation", + + //"person.page.orcid.sync-queue.tooltip.qualification": "Qualification", + "person.page.orcid.sync-queue.tooltip.qualification": "Qualification", + + //"person.page.orcid.sync-queue.tooltip.other_names": "Other name", + "person.page.orcid.sync-queue.tooltip.other_names": "Autre nom", + + //"person.page.orcid.sync-queue.tooltip.country": "Country", + "person.page.orcid.sync-queue.tooltip.country": "Pays", + + //"person.page.orcid.sync-queue.tooltip.keywords": "Keyword", + "person.page.orcid.sync-queue.tooltip.keywords": "Mot clé", + + //"person.page.orcid.sync-queue.tooltip.external_ids": "External identifier", + "person.page.orcid.sync-queue.tooltip.external_ids": "Identifiant externe", + + //"person.page.orcid.sync-queue.tooltip.researcher_urls": "Researcher url", + "person.page.orcid.sync-queue.tooltip.researcher_urls": "URL du chercheur", + + //"person.page.orcid.sync-queue.send": "Synchronize with ORCID registry", + "person.page.orcid.sync-queue.send": "Synchroniser avec le registre ORCID", + + //"person.page.orcid.sync-queue.send.unauthorized-error.title": "The submission to ORCID failed for missing authorizations.", + "person.page.orcid.sync-queue.send.unauthorized-error.title": "La transmission à ORCID a échoué en raison d'autorisations manquantes.", + + //"person.page.orcid.sync-queue.send.unauthorized-error.content": "Click here to grant again the required permissions. If the problem persists, contact the administrator", + "person.page.orcid.sync-queue.send.unauthorized-error.content": "Cliquer ici afin d'accorder à nouveau les permissions nécessaires. Si le problème persiste, contacter l'administrateur.", + + //"person.page.orcid.sync-queue.send.bad-request-error": "The submission to ORCID failed because the resource sent to ORCID registry is not valid", + "person.page.orcid.sync-queue.send.bad-request-error": "La transmission à ORCID a échoué en raison du fait que la ressource envoyée n'est pas valide.", + + //"person.page.orcid.sync-queue.send.error": "The submission to ORCID failed", + "person.page.orcid.sync-queue.send.error": "La transmission à ORCID a échoué.", + + //"person.page.orcid.sync-queue.send.conflict-error": "The submission to ORCID failed because the resource is already present on the ORCID registry", + "person.page.orcid.sync-queue.send.conflict-error": "La transmission à ORCID a échoué en raison du fait que la ressource est déjà présente dans le registre ORCID.", + + //"person.page.orcid.sync-queue.send.not-found-warning": "The resource does not exists anymore on the ORCID registry.", + "person.page.orcid.sync-queue.send.not-found-warning": "La ressource n'existe plus dans le registre ORCID.", + + //"person.page.orcid.sync-queue.send.success": "The submission to ORCID was completed successfully", + "person.page.orcid.sync-queue.send.success": "La transmission à ORCID a été effectuée avec succès.", + + //"person.page.orcid.sync-queue.send.validation-error": "The data that you want to synchronize with ORCID is not valid", + "person.page.orcid.sync-queue.send.validation-error": "Les données que vous souhaitez synchroniser avec ORCID ne sont pas valides.", + + //"person.page.orcid.sync-queue.send.validation-error.amount-currency.required": "The amount's currency is required", + "person.page.orcid.sync-queue.send.validation-error.amount-currency.required": "La devise du montant est requise.", + + //"person.page.orcid.sync-queue.send.validation-error.external-id.required": "The resource to be sent requires at least one identifier", + "person.page.orcid.sync-queue.send.validation-error.external-id.required": "La ressource à transmettre doit avoir au moins un identifiant.", + + //"person.page.orcid.sync-queue.send.validation-error.title.required": "The title is required", + "person.page.orcid.sync-queue.send.validation-error.title.required": "Le titre est obligatoire.", + + //"person.page.orcid.sync-queue.send.validation-error.type.required": "The dc.type is required", + "person.page.orcid.sync-queue.send.validation-error.type.required": "Le type de document est obligatoire.", + + //"person.page.orcid.sync-queue.send.validation-error.start-date.required": "The start date is required", + "person.page.orcid.sync-queue.send.validation-error.start-date.required": "La date de début est obbligatoire.", + + //"person.page.orcid.sync-queue.send.validation-error.funder.required": "The funder is required", + "person.page.orcid.sync-queue.send.validation-error.funder.required": "L'organisme subventionnaire est obligatoire.", + + //"person.page.orcid.sync-queue.send.validation-error.country.invalid": "Invalid 2 digits ISO 3166 country", + "person.page.orcid.sync-queue.send.validation-error.country.invalid": "Code de pays ISO 3166 à 2 chiffres invalide", + + //"person.page.orcid.sync-queue.send.validation-error.organization.required": "The organization is required", + "person.page.orcid.sync-queue.send.validation-error.organization.required": "L'organisation est obligatoire.", + + //"person.page.orcid.sync-queue.send.validation-error.organization.name-required": "The organization's name is required", + "person.page.orcid.sync-queue.send.validation-error.organization.name-required": "Le nom de l'organisation est obligatoire.", + + //"person.page.orcid.sync-queue.send.validation-error.publication.date-invalid": "The publication date must be one year after 1900", + "person.page.orcid.sync-queue.send.validation-error.publication.date-invalid": "La date de publication doit être d'au moins un an après 1900.", + + //"person.page.orcid.sync-queue.send.validation-error.organization.address-required": "The organization to be sent requires an address", + "person.page.orcid.sync-queue.send.validation-error.organization.address-required": "L'organisation a transmettre doit avoir une adresse.", + + //"person.page.orcid.sync-queue.send.validation-error.organization.city-required": "The address of the organization to be sent requires a city", + "person.page.orcid.sync-queue.send.validation-error.organization.city-required": "L'adresse de l'organisation à transmettre doit mentionner une ville.", + + //"person.page.orcid.sync-queue.send.validation-error.organization.country-required": "The address of the organization to be sent requires a valid 2 digits ISO 3166 country", + "person.page.orcid.sync-queue.send.validation-error.organization.country-required": "L'adresse de l'organisation à transmettre doit avoir une code de pays ISO 3166 à 2 chiffres valide.", + + //"person.page.orcid.sync-queue.send.validation-error.disambiguated-organization.required": "An identifier to disambiguate organizations is required. Supported ids are GRID, Ringgold, Legal Entity identifiers (LEIs) and Crossref Funder Registry identifiers", + "person.page.orcid.sync-queue.send.validation-error.disambiguated-organization.required": "Un identifiant pour désambiguer l'orgnisation est obligatoire. Les identifiants possibles sont GRID, Ringgold, Legal Entity identifiers (LEIs) et Crossref Funder Registry identifiers", + + //"person.page.orcid.sync-queue.send.validation-error.disambiguated-organization.value-required": "The organization's identifiers requires a value", + "person.page.orcid.sync-queue.send.validation-error.disambiguated-organization.value-required": "Une valeur pour l'identifiant de l'organisation est obligatoire.", + + //"person.page.orcid.sync-queue.send.validation-error.disambiguation-source.required": "The organization's identifiers requires a source", + "person.page.orcid.sync-queue.send.validation-error.disambiguation-source.required": "La source de l'identifiant de l'organisation est obligatoire.", + + //"person.page.orcid.sync-queue.send.validation-error.disambiguation-source.invalid": "The source of one of the organization identifiers is invalid. Supported sources are RINGGOLD, GRID, LEI and FUNDREF", + "person.page.orcid.sync-queue.send.validation-error.disambiguation-source.invalid": "La source d'un des identifiants d'organisation est invalide. Les sources possibles sont RINGGOLD, GRID, LEI and FUNDREF", + + //"person.page.orcid.synchronization-mode": "Synchronization mode", + "person.page.orcid.synchronization-mode": "Mode de synchronisation", + + //"person.page.orcid.synchronization-mode.batch": "Batch", + "person.page.orcid.synchronization-mode.batch": "En lot", + + //"person.page.orcid.synchronization-mode.label": "Synchronization mode", + "person.page.orcid.synchronization-mode.label": "Mode de synchronisation", + + //"person.page.orcid.synchronization-mode-message": "Please select how you would like synchronization to ORCID to occur. The options include \"Manual\" (you must send your data to ORCID manually), or \"Batch\" (the system will send your data to ORCID via a scheduled script).", + "person.page.orcid.synchronization-mode-message": "Sélectionner le mode de synchronisation vers ORCID. Les options sont \"Manuel\" (vous devrez sélectionner les données à transmettre vers ORCID manuellement), ou \"En lot\" (le système transmettra vos données vers ORCID automatiquement).", + + //"person.page.orcid.synchronization-mode-funding-message": "Select whether to send your linked Project entities to your ORCID record's list of funding information.", + "person.page.orcid.synchronization-mode-funding-message": "Sélectionnez si vous souhaitez transmettre vos projets de recherche vers votre profil ORCID.", + + //"person.page.orcid.synchronization-mode-publication-message": "Select whether to send your linked Publication entities to your ORCID record's list of works.", + "person.page.orcid.synchronization-mode-publication-message": "Sélectionnez si vous souhaitez transmettre vos publications vers votre profil ORCID.", + + //"person.page.orcid.synchronization-mode-profile-message": "Select whether to send your biographical data or personal identifiers to your ORCID record.", + "person.page.orcid.synchronization-mode-profile-message": "Sélectionnez si vous souhaitez transmettre vos données biographiques vers votre profil ORCID.", + + //"person.page.orcid.synchronization-settings-update.success": "The synchronization settings have been updated successfully", + "person.page.orcid.synchronization-settings-update.success": "Les paramètres de synchronisation ont été mis à jour avec succès.", + + //"person.page.orcid.synchronization-settings-update.error": "The update of the synchronization settings failed", + "person.page.orcid.synchronization-settings-update.error": "La mise à jour des paramètres de synchronisation a échoué.", + + //"person.page.orcid.synchronization-mode.manual": "Manual", + "person.page.orcid.synchronization-mode.manual": "Manuel", + + //"person.page.orcid.scope.authenticate": "Get your ORCID iD", + "person.page.orcid.scope.authenticate": "Obtenez votre identifiant ORCID", + + //"person.page.orcid.scope.read-limited": "Read your information with visibility set to Trusted Parties", + "person.page.orcid.scope.read-limited": "Consultez vos informations ayant le paramètre de visibilité réglé sur Parties de confiance.", + + //"person.page.orcid.scope.activities-update": "Add/update your research activities", + "person.page.orcid.scope.activities-update": "Ajouter ou mettre à jour vos activités de recherche", + + //"person.page.orcid.scope.person-update": "Add/update other information about you", + "person.page.orcid.scope.person-update": "Ajouter ou mettre à jour d'autre information sur vous", + + //"person.page.orcid.unlink.success": "The disconnection between the profile and the ORCID registry was successful", + "person.page.orcid.unlink.success": "La déconnexion entre votre profil et le registre ORCID a été effectuée avec succès.", + + //"person.page.orcid.unlink.error": "An error occurred while disconnecting between the profile and the ORCID registry. Try again", + "person.page.orcid.unlink.error": "Une erreur s'est produite lors de la déconnexion entre votre profil et le registre ORCID. Veuillez réessayer.", + + //"person.orcid.sync.setting": "ORCID Synchronization settings", + "person.orcid.sync.setting": "Paramètres de synchronisation ORCID", + + //"person.orcid.registry.queue": "ORCID Registry Queue", + "person.orcid.registry.queue": "Liste d'attente pour le registre ORCID", + + //"person.orcid.registry.auth": "ORCID Authorizations", + "person.orcid.registry.auth": "Autorisation ORCID", + // "system-wide-alert-banner.retrieval.error": "Something went wrong retrieving the system-wide alert banner", "system-wide-alert-banner.retrieval.error": "Une erreur s'est produite lors de la récupération de la bannière du message d'avertissement", From 33262795babe9ada5d4d97f83fbaaf36cd8c626f Mon Sep 17 00:00:00 2001 From: Pierre Lasou Date: Fri, 1 Nov 2024 11:35:05 -0400 Subject: [PATCH 629/875] Correct small alignment errors (cherry picked from commit fde2db85e72e44e9606938d216c1205c8dabd253) --- src/assets/i18n/fr.json5 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 9c0b85cfe4..8122486a4e 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -6358,8 +6358,8 @@ //"person.page.orcid.sync-profile.biographical": "Biographical data", "person.page.orcid.sync-profile.biographical": "Données biographiques", - //"person.page.orcid.sync-profile.education": "Education", - "person.page.orcid.sync-profile.education": "Éducation", + //"person.page.orcid.sync-profile.education": "Education", + "person.page.orcid.sync-profile.education": "Éducation", //"person.page.orcid.sync-profile.identifiers": "Identifiers", "person.page.orcid.sync-profile.identifiers": "Identifiants", @@ -6490,8 +6490,8 @@ //"person.page.orcid.sync-queue.send.conflict-error": "The submission to ORCID failed because the resource is already present on the ORCID registry", "person.page.orcid.sync-queue.send.conflict-error": "La transmission à ORCID a échoué en raison du fait que la ressource est déjà présente dans le registre ORCID.", - //"person.page.orcid.sync-queue.send.not-found-warning": "The resource does not exists anymore on the ORCID registry.", - "person.page.orcid.sync-queue.send.not-found-warning": "La ressource n'existe plus dans le registre ORCID.", + //"person.page.orcid.sync-queue.send.not-found-warning": "The resource does not exists anymore on the ORCID registry.", + "person.page.orcid.sync-queue.send.not-found-warning": "La ressource n'existe plus dans le registre ORCID.", //"person.page.orcid.sync-queue.send.success": "The submission to ORCID was completed successfully", "person.page.orcid.sync-queue.send.success": "La transmission à ORCID a été effectuée avec succès.", @@ -6547,7 +6547,7 @@ //"person.page.orcid.sync-queue.send.validation-error.disambiguation-source.required": "The organization's identifiers requires a source", "person.page.orcid.sync-queue.send.validation-error.disambiguation-source.required": "La source de l'identifiant de l'organisation est obligatoire.", - //"person.page.orcid.sync-queue.send.validation-error.disambiguation-source.invalid": "The source of one of the organization identifiers is invalid. Supported sources are RINGGOLD, GRID, LEI and FUNDREF", + //"person.page.orcid.sync-queue.send.validation-error.disambiguation-source.invalid": "The source of one of the organization identifiers is invalid. Supported sources are RINGGOLD, GRID, LEI and FUNDREF", "person.page.orcid.sync-queue.send.validation-error.disambiguation-source.invalid": "La source d'un des identifiants d'organisation est invalide. Les sources possibles sont RINGGOLD, GRID, LEI and FUNDREF", //"person.page.orcid.synchronization-mode": "Synchronization mode", @@ -6589,7 +6589,7 @@ //"person.page.orcid.scope.activities-update": "Add/update your research activities", "person.page.orcid.scope.activities-update": "Ajouter ou mettre à jour vos activités de recherche", - //"person.page.orcid.scope.person-update": "Add/update other information about you", + //"person.page.orcid.scope.person-update": "Add/update other information about you", "person.page.orcid.scope.person-update": "Ajouter ou mettre à jour d'autre information sur vous", //"person.page.orcid.unlink.success": "The disconnection between the profile and the ORCID registry was successful", From 9c3363d4652c97279836e5b655a369eab18e76dc Mon Sep 17 00:00:00 2001 From: Pierre Lasou Date: Fri, 1 Nov 2024 14:02:00 -0400 Subject: [PATCH 630/875] Correction of 2 lint errors due to spacing. (cherry picked from commit 23dd7903ba36bd4140c3e15af2f9d33bfd2fd9a5) --- src/assets/i18n/fr.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 8122486a4e..0f5587626b 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -6314,7 +6314,7 @@ "person.page.orcid.link.processing": "Liaison du profil avec ORCID en cours...", //"person.page.orcid.link.error.message": "Something went wrong while connecting the profile with ORCID. If the problem persists, contact the administrator.", - "person.page.orcid.link.error.message": "Quelque chose a échoué lors de la connection du profil avec ORCID. Si le problème persiste, contacter la personne administratice.", + "person.page.orcid.link.error.message": "Quelque chose a échoué lors de la connection du profil avec ORCID. Si le problème persiste, contacter la personne administratice.", //"person.page.orcid.orcid-not-linked-message": "The ORCID iD of this profile ({{ orcid }}) has not yet been connected to an account on the ORCID registry or the connection is expired.", "person.page.orcid.orcid-not-linked-message": "L'identifiant ORCID du profil ({{ orcid }}) n'a pas encore été connecté à une compte du registre ORCID ou la connection a expiré. ", @@ -6567,7 +6567,7 @@ //"person.page.orcid.synchronization-mode-publication-message": "Select whether to send your linked Publication entities to your ORCID record's list of works.", "person.page.orcid.synchronization-mode-publication-message": "Sélectionnez si vous souhaitez transmettre vos publications vers votre profil ORCID.", - + //"person.page.orcid.synchronization-mode-profile-message": "Select whether to send your biographical data or personal identifiers to your ORCID record.", "person.page.orcid.synchronization-mode-profile-message": "Sélectionnez si vous souhaitez transmettre vos données biographiques vers votre profil ORCID.", From 87f5b502010581ad4d8f3152aa126dbc6c6bbe62 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 7 Nov 2024 19:58:01 +0100 Subject: [PATCH 631/875] update comment to correctly describe component's purpose (cherry picked from commit 33dddc697fb86d449998900ab82e4f876631eaac) --- .../bitstream-authorizations.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/bitstream-page/bitstream-authorizations/bitstream-authorizations.component.ts b/src/app/bitstream-page/bitstream-authorizations/bitstream-authorizations.component.ts index adc0638780..90b4151a9d 100644 --- a/src/app/bitstream-page/bitstream-authorizations/bitstream-authorizations.component.ts +++ b/src/app/bitstream-page/bitstream-authorizations/bitstream-authorizations.component.ts @@ -12,7 +12,7 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model'; templateUrl: './bitstream-authorizations.component.html', }) /** - * Component that handles the Collection Authorizations + * Component that handles the Bitstream Authorizations */ export class BitstreamAuthorizationsComponent implements OnInit { From 9f879d38501f68e068350b567dafbe9b0a858d92 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 7 Nov 2024 20:02:39 +0100 Subject: [PATCH 632/875] fix invalid selector (cherry picked from commit 752951ce3b05436a13d689fc492fa0b95563862e) --- .../bitstream-authorizations.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/bitstream-page/bitstream-authorizations/bitstream-authorizations.component.ts b/src/app/bitstream-page/bitstream-authorizations/bitstream-authorizations.component.ts index 90b4151a9d..72683f5d74 100644 --- a/src/app/bitstream-page/bitstream-authorizations/bitstream-authorizations.component.ts +++ b/src/app/bitstream-page/bitstream-authorizations/bitstream-authorizations.component.ts @@ -8,7 +8,7 @@ import { RemoteData } from '../../core/data/remote-data'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; @Component({ - selector: 'ds-collection-authorizations', + selector: 'ds-bitstream-authorizations', templateUrl: './bitstream-authorizations.component.html', }) /** From 4353c57cd045f912f27bf8c3dce2a73497ff7d42 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 29 Oct 2024 13:58:50 -0500 Subject: [PATCH 633/875] Fix Klaro translations by forcing Klaro to use a 'zy' language key which DSpace will translate (cherry picked from commit 6076423907e22707a4c31c7c96d1b74ca6b0d81c) --- .../cookies/browser-klaro.service.spec.ts | 6 +++--- .../shared/cookies/browser-klaro.service.ts | 8 +++---- src/app/shared/cookies/klaro-configuration.ts | 21 +++++++++++++------ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/app/shared/cookies/browser-klaro.service.spec.ts b/src/app/shared/cookies/browser-klaro.service.spec.ts index 7fd72b54b3..3da3a8b7a3 100644 --- a/src/app/shared/cookies/browser-klaro.service.spec.ts +++ b/src/app/shared/cookies/browser-klaro.service.spec.ts @@ -101,7 +101,7 @@ describe('BrowserKlaroService', () => { mockConfig = { translations: { - zz: { + zy: { purposes: {}, test: { testeritis: testKey @@ -159,8 +159,8 @@ describe('BrowserKlaroService', () => { it('addAppMessages', () => { service.addAppMessages(); - expect(mockConfig.translations.zz[appName]).toBeDefined(); - expect(mockConfig.translations.zz.purposes[purpose]).toBeDefined(); + expect(mockConfig.translations.zy[appName]).toBeDefined(); + expect(mockConfig.translations.zy.purposes[purpose]).toBeDefined(); }); it('translateConfiguration', () => { diff --git a/src/app/shared/cookies/browser-klaro.service.ts b/src/app/shared/cookies/browser-klaro.service.ts index 2b09c0bf15..adcb59e146 100644 --- a/src/app/shared/cookies/browser-klaro.service.ts +++ b/src/app/shared/cookies/browser-klaro.service.ts @@ -91,7 +91,7 @@ export class BrowserKlaroService extends KlaroService { initialize() { if (!environment.info.enablePrivacyStatement) { delete this.klaroConfig.privacyPolicy; - this.klaroConfig.translations.zz.consentNotice.description = 'cookies.consent.content-notice.description.no-privacy'; + this.klaroConfig.translations.zy.consentNotice.description = 'cookies.consent.content-notice.description.no-privacy'; } const hideGoogleAnalytics$ = this.configService.findByPropertyName(this.GOOGLE_ANALYTICS_KEY).pipe( @@ -238,12 +238,12 @@ export class BrowserKlaroService extends KlaroService { */ addAppMessages() { this.klaroConfig.services.forEach((app) => { - this.klaroConfig.translations.zz[app.name] = { + this.klaroConfig.translations.zy[app.name] = { title: this.getTitleTranslation(app.name), description: this.getDescriptionTranslation(app.name) }; app.purposes.forEach((purpose) => { - this.klaroConfig.translations.zz.purposes[purpose] = this.getPurposeTranslation(purpose); + this.klaroConfig.translations.zy.purposes[purpose] = this.getPurposeTranslation(purpose); }); }); } @@ -257,7 +257,7 @@ export class BrowserKlaroService extends KlaroService { */ this.translateService.setDefaultLang(environment.defaultLanguage); - this.translate(this.klaroConfig.translations.zz); + this.translate(this.klaroConfig.translations.zy); } /** diff --git a/src/app/shared/cookies/klaro-configuration.ts b/src/app/shared/cookies/klaro-configuration.ts index f527f7f096..6ec4855e28 100644 --- a/src/app/shared/cookies/klaro-configuration.ts +++ b/src/app/shared/cookies/klaro-configuration.ts @@ -17,7 +17,7 @@ export const GOOGLE_ANALYTICS_KLARO_KEY = 'google-analytics'; /** * Klaro configuration - * For more information see https://kiprotect.com/docs/klaro/annotated-config + * For more information see https://klaro.org/docs/integration/annotated-configuration */ export const klaroConfiguration: any = { storageName: ANONYMOUS_STORAGE_NAME_KLARO, @@ -47,21 +47,30 @@ export const klaroConfiguration: any = { htmlTexts: true, + /* + Force Klaro to use our custom "zy" lang configs defined below. + */ + lang: 'zy', + /* You can overwrite existing translations and add translations for your app descriptions and purposes. See `src/translations/` for a full list of translations that can be overwritten: - https://github.com/KIProtect/klaro/tree/master/src/translations + https://github.com/klaro-org/klaro-js/tree/master/src/translations */ translations: { /* - The `zz` key contains default translations that will be used as fallback values. - This can e.g. be useful for defining a fallback privacy policy URL. - FOR DSPACE: We use 'zz' to map to our own i18n translations for klaro, see + For DSpace we use this custom 'zy' key to map to our own i18n translations for klaro, see translateConfiguration() in browser-klaro.service.ts. All the below i18n keys are specified in your /src/assets/i18n/*.json5 translation pack. + This 'zy' key has no special meaning to Klaro & is not a valid language code. It just + allows DSpace to override Klaro's own translations in favor of DSpace's i18n keys. + NOTE: we do not use 'zz' as that has special meaning to Klaro and is ONLY used as a "fallback" + if no other translations can be found within Klaro. Currently, a bug in Klaro means that + 'zz' is never used as there's no way to exclude translations: + https://github.com/klaro-org/klaro-js/issues/515 */ - zz: { + zy: { acceptAll: 'cookies.consent.accept-all', acceptSelected: 'cookies.consent.accept-selected', close: 'cookies.consent.close', From 64628f7e0d92d8bbee3078719eabeea4aa4275a9 Mon Sep 17 00:00:00 2001 From: andreaNeki Date: Fri, 13 Sep 2024 16:34:16 -0300 Subject: [PATCH 634/875] DSpace#2668 - Adding and changing classes in global scss to make cookie settings more accessible --- src/styles/_custom_variables.scss | 1 + src/styles/_global-styles.scss | 34 +++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/styles/_custom_variables.scss b/src/styles/_custom_variables.scss index 7171aea689..aa67acac1c 100644 --- a/src/styles/_custom_variables.scss +++ b/src/styles/_custom_variables.scss @@ -137,4 +137,5 @@ --green1: #1FB300; // This variable represents the success color for the Klaro cookie banner --button-text-color-cookie: #333; // This variable represents the text color for buttons in the Klaro cookie banner + --very-dark-cyan: #215E50; // This variable represents the background color of the save cookies button } diff --git a/src/styles/_global-styles.scss b/src/styles/_global-styles.scss index b3120c08cd..99cc075dbe 100644 --- a/src/styles/_global-styles.scss +++ b/src/styles/_global-styles.scss @@ -43,17 +43,39 @@ body { .cm-btn.cm-btn-success { color: var(--button-text-color-cookie); background-color: var(--green1); - } - .cm-btn.cm-btn-success.cm-btn-accept-all { - color: var(--button-text-color-cookie); - background-color: var(--green1); + font-weight: 600; } } } -.klaro .cookie-modal a, .klaro .context-notice a, .klaro .cookie-notice a -{ +.klaro .cookie-modal .cm-btn.cm-btn-success.cm-btn-accept-all { + color: var(--button-text-color-cookie); + background-color: var(--green1); + font-weight: 600; +} + +.klaro .cookie-modal a, +.klaro .context-notice a, +.klaro .cookie-notice a { color: var(--green1); + text-decoration: underline !important; +} + +.klaro .cookie-modal .cm-modal .cm-body span, +.klaro + .cookie-modal + .cm-modal + .cm-body + ul.cm-purposes + li.cm-purpose + span.cm-required, +p.purposes, +.klaro .cookie-modal .cm-modal .cm-footer .cm-powered-by a { + color: var(--bs-light) !important; +} + +.klaro .cookie-modal .cm-btn.cm-btn-info { + background-color: var(--very-dark-cyan) !important; } .media-viewer From bc50efec5e4387bfbae6f1405717d9d2c5bed0eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 20:35:42 +0000 Subject: [PATCH 635/875] Bump core-js from 3.38.1 to 3.39.0 Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.38.1 to 3.39.0. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/commits/v3.39.0/packages/core-js) --- updated-dependencies: - dependency-name: core-js dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b3dbb92115..c4fad1e06a 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "colors": "^1.4.0", "compression": "^1.7.5", "cookie-parser": "1.4.7", - "core-js": "^3.38.1", + "core-js": "^3.39.0", "date-fns": "^2.30.0", "date-fns-tz": "^1.3.7", "deepmerge": "^4.3.1", diff --git a/yarn.lock b/yarn.lock index e6c67e0e80..eedca951f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4272,10 +4272,10 @@ core-js-compat@^3.25.1: dependencies: browserslist "^4.21.5" -core-js@^3.38.1: - version "3.38.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.38.1.tgz#aa375b79a286a670388a1a363363d53677c0383e" - integrity sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw== +core-js@^3.39.0: + version "3.39.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.39.0.tgz#57f7647f4d2d030c32a72ea23a0555b2eaa30f83" + integrity sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g== core-util-is@1.0.2: version "1.0.2" From 06783364c41f66dc237f606ad294ef67fc87c89f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 03:15:03 +0000 Subject: [PATCH 636/875] Bump express-static-gzip from 2.1.8 to 2.2.0 Bumps [express-static-gzip](https://github.com/tkoenig89/express-static-gzip) from 2.1.8 to 2.2.0. - [Release notes](https://github.com/tkoenig89/express-static-gzip/releases) - [Commits](https://github.com/tkoenig89/express-static-gzip/compare/v2.1.8...v2.2.0) --- updated-dependencies: - dependency-name: express-static-gzip dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index f8f725fafc..7381cdfa97 100644 --- a/package.json +++ b/package.json @@ -165,7 +165,7 @@ "eslint-plugin-jsonc": "^2.16.0", "eslint-plugin-lodash": "^7.4.0", "eslint-plugin-unused-imports": "^2.0.0", - "express-static-gzip": "^2.1.8", + "express-static-gzip": "^2.2.0", "jasmine-core": "^3.8.0", "jasmine-marbles": "0.9.2", "karma": "^6.4.4", diff --git a/yarn.lock b/yarn.lock index 0106427952..aa4fb3ed3c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5609,11 +5609,12 @@ express-rate-limit@^5.1.3: resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.5.1.tgz" integrity sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg== -express-static-gzip@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/express-static-gzip/-/express-static-gzip-2.1.8.tgz#f37f0fe9e8113e56cfac63a98c0197ee6bd6458f" - integrity sha512-g8tiJuI9Y9Ffy59ehVXvqb0hhP83JwZiLxzanobPaMbkB5qBWA8nuVgd+rcd5qzH3GkgogTALlc0BaADYwnMbQ== +express-static-gzip@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/express-static-gzip/-/express-static-gzip-2.2.0.tgz#7c3f7dd89da68e51c591edf02e6de6169c017f5f" + integrity sha512-4ZQ0pHX0CAauxmzry2/8XFLM6aZA4NBvg9QezSlsEO1zLnl7vMFa48/WIcjzdfOiEUS4S1npPPKP2NHHYAp6qg== dependencies: + parseurl "^1.3.3" serve-static "^1.16.2" express@^4.17.3, express@^4.18.2, express@^4.21.1: @@ -8988,7 +8989,7 @@ parse5@^7.0.0, parse5@^7.1.1, parse5@^7.1.2: dependencies: entities "^4.4.0" -parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@^1.3.3, parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== From 6b32d04aecc57586318b919fa5fadaeb032772a8 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Fri, 26 Jul 2024 15:32:57 +0200 Subject: [PATCH 637/875] Updated some messages following the lindat v5 and clarin-dspace v7 instance. (cherry picked from commit b10563ea5388e8c398ec8927dc562d6f841473db) --- src/assets/i18n/cs.json5 | 101 +++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 53 deletions(-) diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index 0b4168cca9..3824e9741a 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -1477,8 +1477,8 @@ // "bitstream.download.page": "Now downloading {{bitstream}}...", "bitstream.download.page": "Nyní se stahuje {{bitstream}}..." , - // "bitstream.download.page.back": "Back", - "bitstream.download.page.back": "Zpět" , + // "bitstream.download.page.back": "Back" , + "bitstream.download.page.back": "Zpět", // "bitstream.edit.authorizations.link": "Edit bitstream's Policies", "bitstream.edit.authorizations.link": "Upravit politiky souboru", @@ -1797,7 +1797,7 @@ // "claimed-declined-task-search-result-list-element.title": "Declined, sent back to Review Manager's workflow", "claimed-declined-task-search-result-list-element.title": "Zamítnuto, posláno zpět správci schvalovacího workflow", - // "collection.create.breadcrumbs": "Create collection", + // "collection.create.breadcrumbs": "Create collection", // TODO New key - Add a translation "collection.create.breadcrumbs": "Create collection", @@ -2210,8 +2210,7 @@ "collection.source.controls.harvest.last": "Naposledy harvestováno:", // "collection.source.controls.harvest.message": "Harvest info:", - "collection.source.controls.harvest.message": "Informace o harevstu:", - + "collection.source.controls.harvest.message": "Informace o harvestu:", // "collection.source.controls.harvest.no-information": "N/A", "collection.source.controls.harvest.no-information": "Není k dispozi", @@ -2703,7 +2702,7 @@ "dso-selector.create.community.head": "Nová komunita", // "dso-selector.create.community.or-divider": "or", - "dso-selector.create.community.or-divider": "or", + "dso-selector.create.community.or-divider": "nebo", // "dso-selector.create.community.sub-level": "Create a new community in", "dso-selector.create.community.sub-level": "Vytvořit novou komunitu v", @@ -2940,7 +2939,7 @@ "error.top-level-communities": "Chyba při načítání komunit nejvyšší úrovně", // "error.validation.license.notgranted": "You must grant this license to complete your submission. If you are unable to grant this license at this time you may save your work and return later or remove the submission.", - "error.validation.license.notgranted": "eslání. Pokud tuto licenci v tuto chvíli nemůžete udělit, můžete svou práci uložit a vrátit se k ní později nebo podání odstranit.", + "error.validation.license.notgranted": "Pro dokončení zaslání musíte udělit licenci. Pokud v tuto chvíli tuto licenci nemůžete udělit, můžete svou práci uložit a později se k svému příspěveku vrátit nebo jej smazat.", // "error.validation.pattern": "This input is restricted by the current pattern: {{ pattern }}.", "error.validation.pattern": "Toto zadání je omezeno aktuálním vzorcem: {{ pattern }}.", @@ -3252,7 +3251,7 @@ "grant-deny-request-copy.intro2": "Po výběru možnosti se zobrazí návrh e-mailové odpovědi, který můžete upravit.", // "grant-deny-request-copy.processed": "This request has already been processed. You can use the button below to get back to the home page.", - "grant-deny-request-copy.processed": " Tato žádost již byla zpracována. Pomocí níže uvedeného tlačítka se můžete vrátit na domovskou stránku.", + "grant-deny-request-copy.processed": "Tato žádost již byla zpracována. Pomocí níže uvedeného tlačítka se můžete vrátit na domovskou stránku.", // "grant-request-copy.email.subject": "Request copy of document", "grant-request-copy.email.subject": "Žádost o kopii dokumentu", @@ -3432,8 +3431,7 @@ "info.coar-notify-support.breadcrumbs": "COAR Notify Support", // "item.alerts.private": "This item is non-discoverable", - // TODO Source message changed - Revise the translation - "item.alerts.private": "Tento záznam je nezobrazitelný", + "item.alerts.private": "Tento záznam je nevyhledatelný", // "item.alerts.withdrawn": "This item has been withdrawn", "item.alerts.withdrawn": "Tento záznam byl stažen", @@ -4168,7 +4166,7 @@ "item.page.journal-title": "Název časopisu", // "item.page.publisher": "Publisher", - "item.page.publisher": "Vydavatel", + "item.page.publisher": "Nakladatel", // "item.page.titleprefix": "Item: ", "item.page.titleprefix": "Záznam: ", @@ -4268,7 +4266,7 @@ "item.page.abstract": "Abstrakt", // "item.page.author": "Authors", - "item.page.author": "Autor", + "item.page.author": "Autoři", // "item.page.citation": "Citation", "item.page.citation": "Citace", @@ -4310,10 +4308,10 @@ "item.page.journal.search.title": "Články v tomto časopise", // "item.page.link.full": "Full item page", - "item.page.link.full": "Úplný záznam", + "item.page.link.full": "Zobrazit celý záznam", // "item.page.link.simple": "Simple item page", - "item.page.link.simple": "Jednoduchý záznam", + "item.page.link.simple": "Zobrazit minimální záznam", // "item.page.orcid.title": "ORCID", "item.page.orcid.title": "ORCID", @@ -4346,7 +4344,7 @@ "item.page.subject": "Klíčová slova", // "item.page.uri": "URI", - "item.page.uri": "URI", + "item.page.uri": "Identifikátor", // "item.page.bitstreams.view-more": "Show more", "item.page.bitstreams.view-more": "Zobrazit více", @@ -4461,10 +4459,10 @@ "item.preview.oaire.awardNumber": "Identifikátor zdroje financování:", // "item.preview.dc.title.alternative": "Acronym:", - "item.preview.dc.title.alternative": "Akronym:", + "item.preview.dc.title.alternative": "Zkratka:", // "item.preview.dc.coverage.spatial": "Jurisdiction:", - "item.preview.dc.coverage.spatial": "Jurisdikce:", + "item.preview.dc.coverage.spatial": "Příslušnost:", // "item.preview.oaire.fundingStream": "Funding Stream:", "item.preview.oaire.fundingStream": "Tok finančních prostředků:", @@ -4841,7 +4839,7 @@ "journal.page.issn": "ISSN", // "journal.page.publisher": "Publisher", - "journal.page.publisher": "Vydavatel", + "journal.page.publisher": "Nakladatel", // "journal.page.titleprefix": "Journal: ", "journal.page.titleprefix": "Časopis: ", @@ -4936,7 +4934,7 @@ "iiif.page.doi": "Trvalý odkaz: ", // "iiif.page.issue": "Issue: ", - "iiif.page.issue": "Číslo: ", + "iiif.page.issue": "Problém:", // "iiif.page.description": "Description: ", "iiif.page.description": "Popis: ", @@ -5042,8 +5040,7 @@ "menu.header.nav.description": "Admin navigation bar", // "menu.header.admin": "Management", - // TODO Source message changed - Revise the translation - "menu.header.admin": "Management", + "menu.header.admin": "Admin", // "menu.header.image.logo": "Repository logo", "menu.header.image.logo": "Logo úložiště", @@ -5378,7 +5375,7 @@ "mydspace.new-submission-external-short": "Importovat metadata", // "mydspace.results.head": "Your submissions", - "mydspace.results.head": "Váš příspěvek", + "mydspace.results.head": "Vaše zázmany", // "mydspace.results.no-abstract": "No Abstract", "mydspace.results.no-abstract": "Žádný abstrakt", @@ -5390,7 +5387,7 @@ "mydspace.results.no-collections": "Žádné kolekce", // "mydspace.results.no-date": "No Date", - "mydspace.results.no-date": "Žádné datum", + "mydspace.results.no-date": "Žádny datum", // "mydspace.results.no-files": "No Files", "mydspace.results.no-files": "Žádné soubory", @@ -5412,9 +5409,9 @@ // TODO Source message changed - Revise the translation "mydspace.show.workflow": "Úlohy workflow", - // "mydspace.show.workspace": "Your submissions", + // "mydspace.show.workspace": "Your Submissions", // TODO Source message changed - Revise the translation - "mydspace.show.workspace": "Váš příspěvek", + "mydspace.show.workspace": "Vaše záznamy", // "mydspace.show.supervisedWorkspace": "Supervised items", "mydspace.show.supervisedWorkspace": "Zkontrolované záznamy", @@ -5488,7 +5485,7 @@ "nav.user-profile-menu-and-logout": "Menu uživatelského profilu a odhlášení", // "nav.logout": "Log Out", - "nav.logout": "Menu uživatelského profilu a odhlášení", + "nav.logout": "Odhlásit se", // "nav.main.description": "Main navigation bar", "nav.main.description": "Hlavní navigační panel", @@ -6289,7 +6286,7 @@ "publication.page.journal-title": "Název časopisu", // "publication.page.publisher": "Publisher", - "publication.page.publisher": "Vydavatel", + "publication.page.publisher": "Nakladatel", // "publication.page.titleprefix": "Publication: ", "publication.page.titleprefix": "Publikace: ", @@ -6913,7 +6910,7 @@ "search.filters.applied.f.subject": "Předmět", // "search.filters.applied.f.submitter": "Submitter", - "search.filters.applied.f.submitter": "Vkladatel", + "search.filters.applied.f.submitter": "Předkladatel", // "search.filters.applied.f.jobTitle": "Job Title", "search.filters.applied.f.jobTitle": "Název pracovní pozice", @@ -7019,10 +7016,10 @@ "search.filters.filter.creativeWorkKeywords.label": "Předmět hledání", // "search.filters.filter.creativeWorkPublisher.head": "Publisher", - "search.filters.filter.creativeWorkPublisher.head": "Vydavatel", + "search.filters.filter.creativeWorkPublisher.head": "Nakladatel", // "search.filters.filter.creativeWorkPublisher.placeholder": "Publisher", - "search.filters.filter.creativeWorkPublisher.placeholder": "Vydavatel", + "search.filters.filter.creativeWorkPublisher.placeholder": "Nakladatel", // "search.filters.filter.creativeWorkPublisher.label": "Search publisher", "search.filters.filter.creativeWorkPublisher.label": "Hledat vydavatele", @@ -7058,7 +7055,7 @@ "search.filters.filter.discoverable.head": "Nedohledatelné", // "search.filters.filter.withdrawn.head": "Withdrawn", - "search.filters.filter.withdrawn.head": "Zrušeno", + "search.filters.filter.withdrawn.head": "Vyřazeno", // "search.filters.filter.entityType.head": "Item Type", "search.filters.filter.entityType.head": "Typ záznamu", @@ -7142,7 +7139,7 @@ "search.filters.filter.objectpeople.placeholder": "Lidé", // "search.filters.filter.objectpeople.label": "Search people", - "search.filters.filter.objectpeople.label": "Hledat lidi", + "search.filters.filter.objectpeople.label": "Hledat osoby", // "search.filters.filter.organizationAddressCountry.head": "Country", "search.filters.filter.organizationAddressCountry.head": "Stát", @@ -7178,7 +7175,7 @@ "search.filters.filter.scope.placeholder": "Filtr rozsahu", // "search.filters.filter.scope.label": "Search scope filter", - "search.filters.filter.scope.label": "Hledat filtr rozsahu", + "search.filters.filter.scope.label": "Filtr rozsahu hledání", // "search.filters.filter.show-less": "Collapse", "search.filters.filter.show-less": "Sbalit", @@ -7196,13 +7193,13 @@ "search.filters.filter.subject.label": "Hledat předmět", // "search.filters.filter.submitter.head": "Submitter", - "search.filters.filter.submitter.head": "Vkladatel", + "search.filters.filter.submitter.head": "Předkladatel", // "search.filters.filter.submitter.placeholder": "Submitter", - "search.filters.filter.submitter.placeholder": "Vkladatel", + "search.filters.filter.submitter.placeholder": "Předkladatel", // "search.filters.filter.submitter.label": "Search submitter", - "search.filters.filter.submitter.label": "Hledat vkladatele", + "search.filters.filter.submitter.label": "Hledat předkladatele", // "search.filters.filter.show-tree": "Browse {{ name }} tree", "search.filters.filter.show-tree": "Procházet {{ name }} podle", @@ -7286,8 +7283,8 @@ // "search.filters.withdrawn.false": "No", "search.filters.withdrawn.false": "Ne", - // "search.filters.head": "Filters", - "search.filters.head": "Filtry", + // "search.filters.head": "Limit your search", + "search.filters.head": "Zúžit hledání", // "search.filters.reset": "Reset filters", "search.filters.reset": "Resetovat filtry", @@ -7526,7 +7523,7 @@ "submission.general.cannot_submit": "Nemáte oprávnění k vytvoření nového příspěvku.", // "submission.general.deposit": "Deposit", - "submission.general.deposit": "Vložit do repozitáře", + "submission.general.deposit": "Nahrát", // "submission.general.discard.confirm.cancel": "Cancel", "submission.general.discard.confirm.cancel": "Zrušit", @@ -7554,7 +7551,7 @@ "submission.general.info.pending-changes": "Neuložené změny", // "submission.general.save": "Save", - "submission.general.save": "Průběžně uložit záznam", + "submission.general.save": "Uložit", // "submission.general.save-later": "Save for later", "submission.general.save-later": "Uložit na později", @@ -7709,10 +7706,10 @@ "submission.import-external.preview.subtitle": "Níže uvedená metadata byla importována z externího zdroje. Budou předvyplněna při zahájení odesílání..", // "submission.import-external.preview.button.import": "Start submission", - "submission.import-external.preview.button.import": "Zahájit odesílání", + "submission.import-external.preview.button.import": "Začít nový příspěvek", // "submission.import-external.preview.error.import.title": "Submission error", - "submission.import-external.preview.error.import.title": "Chyba při odesílání", + "submission.import-external.preview.error.import.title": "Chyba při vytváření nového příspěvku", // "submission.import-external.preview.error.import.body": "An error occurs during the external source entry import process.", "submission.import-external.preview.error.import.body": "Během procesu importu externí zdrojového záznamu došlo k chybě.", @@ -7893,7 +7890,7 @@ "submission.sections.describe.relationship-lookup.search-tab.placeholder": "Vyhledávací dotaz", // "submission.sections.describe.relationship-lookup.search-tab.search": "Go", - "submission.sections.describe.relationship-lookup.search-tab.search": "Hledání", + "submission.sections.describe.relationship-lookup.search-tab.search": "Přejít na", // "submission.sections.describe.relationship-lookup.search-tab.search-form.placeholder": "Search...", "submission.sections.describe.relationship-lookup.search-tab.search-form.placeholder": "Hledání...", @@ -8080,7 +8077,7 @@ "submission.sections.describe.relationship-lookup.title.Funding Agency": "Financující agentura", // "submission.sections.describe.relationship-lookup.title.isFundingOfPublication": "Funding", - "submission.sections.describe.relationship-lookup.title.isFundingOfPublication": "Projekt, ke kterému publikace náleží", + "submission.sections.describe.relationship-lookup.title.isFundingOfPublication": "Financování", // "submission.sections.describe.relationship-lookup.title.isChildOrgUnitOf": "Parent Organizational Unit", "submission.sections.describe.relationship-lookup.title.isChildOrgUnitOf": "Nadřazená organizační jednotka", @@ -8311,7 +8308,7 @@ "submission.sections.submit.progressbar.describe.recycle": "Opětovně použít", // "submission.sections.submit.progressbar.describe.stepcustom": "Describe", - "submission.sections.submit.progressbar.describe.stepcustom": "Popsat", + "submission.sections.submit.progressbar.describe.stepcustom": "Popis", // "submission.sections.submit.progressbar.describe.stepone": "Describe", "submission.sections.submit.progressbar.describe.stepone": "Základní informace o dokumentu", @@ -8627,8 +8624,8 @@ "submission.submit.breadcrumbs": "Nově podaný záznam", // "submission.submit.title": "New submission", - // TODO Source message changed - Revise the translation - "submission.submit.title": "Nově podaný záznam", + "submission.submit.title": "Nový příspěvek", + // "submission.workflow.generic.delete": "Delete", "submission.workflow.generic.delete": "Smazat", @@ -8710,7 +8707,7 @@ "submission.workflow.tasks.generic.processing": "Zpracování...", // "submission.workflow.tasks.generic.submitter": "Submitter", - "submission.workflow.tasks.generic.submitter": "Zadavatel", + "submission.workflow.tasks.generic.submitter": "Předkladatel", // "submission.workflow.tasks.generic.success": "Operation successful", "submission.workflow.tasks.generic.success": "Operace proběhla úspěšně", @@ -9074,10 +9071,10 @@ "workflow-item.scorereviewaction.notification.error.content": "Nebylo možné zkontrolovat tento záznam", // "workflow-item.scorereviewaction.title": "Rate this item", - "workflow-item.scorereviewaction.title": "Zkontrolovat tento záznam", + "workflow-item.scorereviewaction.title": "Ohodnotit tento záznam", // "workflow-item.scorereviewaction.header": "Rate this item", - "workflow-item.scorereviewaction.header": "Zkontrolovat tento záznam", + "workflow-item.scorereviewaction.header": "Ohodnotit tento záznam", // "workflow-item.scorereviewaction.button.cancel": "Cancel", "workflow-item.scorereviewaction.button.cancel": "Zrušit", @@ -11034,6 +11031,4 @@ // "item.page.cc.license.disclaimer": "Except where otherwised noted, this item's license is described as", // TODO New key - Add a translation "item.page.cc.license.disclaimer": "Except where otherwised noted, this item's license is described as", - - -} \ No newline at end of file +} From e5ea435cbe58186e6dc42d56753147ade7ce3d5a Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Fri, 26 Jul 2024 15:49:37 +0200 Subject: [PATCH 638/875] Fixed linting error. (cherry picked from commit 7e864d27b45c58ef566d31c5d0e2a478a540ecdd) --- src/assets/i18n/cs.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index 3824e9741a..a957232a89 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -1797,7 +1797,7 @@ // "claimed-declined-task-search-result-list-element.title": "Declined, sent back to Review Manager's workflow", "claimed-declined-task-search-result-list-element.title": "Zamítnuto, posláno zpět správci schvalovacího workflow", - // "collection.create.breadcrumbs": "Create collection", + // "collection.create.breadcrumbs": "Create collection", // TODO New key - Add a translation "collection.create.breadcrumbs": "Create collection", From 865268e820fafcfe047a752d9effb9fcfd99c160 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Wed, 9 Oct 2024 15:08:31 +0200 Subject: [PATCH 639/875] Updated cs messages following review requirements (cherry picked from commit 813d644b32a28685f8a4205bc383268b2502afdb) --- src/assets/i18n/cs.json5 | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index a957232a89..5e52f751ec 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -1475,9 +1475,9 @@ "auth.messages.token-refresh-failed": "Nepodařilo se obnovit váš přístupový token. Prosím přihlašte se znovu.", // "bitstream.download.page": "Now downloading {{bitstream}}...", - "bitstream.download.page": "Nyní se stahuje {{bitstream}}..." , + "bitstream.download.page": "Nyní se stahuje {{bitstream}}...", - // "bitstream.download.page.back": "Back" , + // "bitstream.download.page.back": "Back", "bitstream.download.page.back": "Zpět", // "bitstream.edit.authorizations.link": "Edit bitstream's Policies", @@ -2939,7 +2939,7 @@ "error.top-level-communities": "Chyba při načítání komunit nejvyšší úrovně", // "error.validation.license.notgranted": "You must grant this license to complete your submission. If you are unable to grant this license at this time you may save your work and return later or remove the submission.", - "error.validation.license.notgranted": "Pro dokončení zaslání musíte udělit licenci. Pokud v tuto chvíli tuto licenci nemůžete udělit, můžete svou práci uložit a později se k svému příspěveku vrátit nebo jej smazat.", + "error.validation.license.notgranted": "Bez udělení licence nelze záznam dokončit. Pokud v tuto chvíli nemůžete licenci udělit, uložte svou práci a vraťte se k příspěveku později nebo jej smažte.", // "error.validation.pattern": "This input is restricted by the current pattern: {{ pattern }}.", "error.validation.pattern": "Toto zadání je omezeno aktuálním vzorcem: {{ pattern }}.", @@ -4308,7 +4308,7 @@ "item.page.journal.search.title": "Články v tomto časopise", // "item.page.link.full": "Full item page", - "item.page.link.full": "Zobrazit celý záznam", + "item.page.link.full": "Zobrazit úplný záznam", // "item.page.link.simple": "Simple item page", "item.page.link.simple": "Zobrazit minimální záznam", @@ -4344,7 +4344,7 @@ "item.page.subject": "Klíčová slova", // "item.page.uri": "URI", - "item.page.uri": "Identifikátor", + "item.page.uri": "Permanentní identifikátor", // "item.page.bitstreams.view-more": "Show more", "item.page.bitstreams.view-more": "Zobrazit více", @@ -4934,7 +4934,7 @@ "iiif.page.doi": "Trvalý odkaz: ", // "iiif.page.issue": "Issue: ", - "iiif.page.issue": "Problém:", + "iiif.page.issue": "Číslo:", // "iiif.page.description": "Description: ", "iiif.page.description": "Popis: ", @@ -5375,7 +5375,7 @@ "mydspace.new-submission-external-short": "Importovat metadata", // "mydspace.results.head": "Your submissions", - "mydspace.results.head": "Vaše zázmany", + "mydspace.results.head": "Vaše záznamy", // "mydspace.results.no-abstract": "No Abstract", "mydspace.results.no-abstract": "Žádný abstrakt", @@ -5387,7 +5387,7 @@ "mydspace.results.no-collections": "Žádné kolekce", // "mydspace.results.no-date": "No Date", - "mydspace.results.no-date": "Žádny datum", + "mydspace.results.no-date": "Žádné datum", // "mydspace.results.no-files": "No Files", "mydspace.results.no-files": "Žádné soubory", @@ -6910,7 +6910,7 @@ "search.filters.applied.f.subject": "Předmět", // "search.filters.applied.f.submitter": "Submitter", - "search.filters.applied.f.submitter": "Předkladatel", + "search.filters.applied.f.submitter": "Odesílatel", // "search.filters.applied.f.jobTitle": "Job Title", "search.filters.applied.f.jobTitle": "Název pracovní pozice", @@ -7193,13 +7193,13 @@ "search.filters.filter.subject.label": "Hledat předmět", // "search.filters.filter.submitter.head": "Submitter", - "search.filters.filter.submitter.head": "Předkladatel", + "search.filters.filter.submitter.head": "Odesílatel", // "search.filters.filter.submitter.placeholder": "Submitter", - "search.filters.filter.submitter.placeholder": "Předkladatel", + "search.filters.filter.submitter.placeholder": "Odesílatel", // "search.filters.filter.submitter.label": "Search submitter", - "search.filters.filter.submitter.label": "Hledat předkladatele", + "search.filters.filter.submitter.label": "Hledat odesílatele", // "search.filters.filter.show-tree": "Browse {{ name }} tree", "search.filters.filter.show-tree": "Procházet {{ name }} podle", @@ -8626,7 +8626,6 @@ // "submission.submit.title": "New submission", "submission.submit.title": "Nový příspěvek", - // "submission.workflow.generic.delete": "Delete", "submission.workflow.generic.delete": "Smazat", @@ -8692,7 +8691,7 @@ "submission.workflow.tasks.claimed.reject.submit": "Odmítnout", // "submission.workflow.tasks.claimed.reject_help": "If you have reviewed the item and found it is not suitable for inclusion in the collection, select \"Reject\". You will then be asked to enter a message indicating why the item is unsuitable, and whether the submitter should change something and resubmit.", - "submission.workflow.tasks.claimed.reject_help": "Pokud jste záznam zkontrolovali a zjistili, že není vhodný pro zařazení do kolekce, vyberte \"Odmítnout\". Poté budete vyzváni k zadání zprávy, ve které uvedete, proč je záznam nevhodný, a zda by měl vkladatel něco změnit a znovu předložit.", + "submission.workflow.tasks.claimed.reject_help": "Pokud jste záznam zkontrolovali a zjistili, že není vhodný pro zařazení do kolekce, vyberte \"Odmítnout\". Poté budete vyzváni k zadání zprávy, ve které uvedete, proč je záznam nevhodný, a zda by měl vkladatel něco změnit a znovu odeslat.", // "submission.workflow.tasks.claimed.return": "Return to pool", "submission.workflow.tasks.claimed.return": "Vrátit do fondu", @@ -8707,7 +8706,7 @@ "submission.workflow.tasks.generic.processing": "Zpracování...", // "submission.workflow.tasks.generic.submitter": "Submitter", - "submission.workflow.tasks.generic.submitter": "Předkladatel", + "submission.workflow.tasks.generic.submitter": "Odesílatel", // "submission.workflow.tasks.generic.success": "Operation successful", "submission.workflow.tasks.generic.success": "Operace proběhla úspěšně", From 33bc8ba1a3fe5934c10f648c2a756e65a6c59e27 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Wed, 9 Oct 2024 16:09:51 +0200 Subject: [PATCH 640/875] Fixed messages following the PR from the UFAL - https://github.com/dataquest-dev/dspace-angular/pull/669/commits/f18d45ce23ea9c42778885f59e5f7d25e548b2e9 (cherry picked from commit 5e5c627b8b4dbafc5454d91e24797f3ef6ef76aa) --- src/assets/i18n/cs.json5 | 220 +++++++++++++++++++-------------------- 1 file changed, 110 insertions(+), 110 deletions(-) diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index 5e52f751ec..2cb95f10d3 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -13,7 +13,7 @@ "403.help": "Nemáte povolení k přístupu na tuto stránku. Pro návrat na domovskou stránku můžete použít tlačítko níže.", // "403.link.home-page": "Take me to the home page", - "403.link.home-page": "Přesměrujte mě na domovskou stránku", + "403.link.home-page": "Návrat na domovskou stránku", // "403.forbidden": "Forbidden", "403.forbidden": "Přístup zakázán", @@ -46,7 +46,7 @@ "error-page.description.500": "Služba je nedostupná", // "error-page.description.404": "Page not found", - "error-page.description.404": "Stránka nebyla nenalezena", + "error-page.description.404": "Stránka nebyla nalezena", // "error-page.orcid.generic-error": "An error occurred during login via ORCID. Make sure you have shared your ORCID account email address with DSpace. If the error persists, contact the administrator", "error-page.orcid.generic-error": "Při přihlašování přes ORCID došlo k chybě. Ujistěte se, že jste sdíleli e-mailovou adresu připojenou ke svému účtu ORCID s DSpace. Pokud chyba přetrvává, kontaktujte správce", @@ -67,13 +67,13 @@ "access-status.unknown.listelement.badge": "Status neznámý", // "admin.curation-tasks.breadcrumbs": "System curation tasks", - "admin.curation-tasks.breadcrumbs": "Kurátorská úloha systému", + "admin.curation-tasks.breadcrumbs": "Systémové úlohy správy", // "admin.curation-tasks.title": "System curation tasks", - "admin.curation-tasks.title": "Kurátorská úloha systému", + "admin.curation-tasks.title": "Systémové úlohy správy", // "admin.curation-tasks.header": "System curation tasks", - "admin.curation-tasks.header": "Kurátorská úloha systému", + "admin.curation-tasks.header": "Systémové úlohy správy", // "admin.registries.bitstream-formats.breadcrumbs": "Format registry", "admin.registries.bitstream-formats.breadcrumbs": "Registr formátů", @@ -172,7 +172,7 @@ "admin.registries.bitstream-formats.edit.supportLevel.label": "Úroveň podpory", // "admin.registries.bitstream-formats.head": "Bitstream Format Registry", - "admin.registries.bitstream-formats.head": "Registr formátu souboru", + "admin.registries.bitstream-formats.head": "Registr formátů souboru", // "admin.registries.bitstream-formats.no-items": "No bitstream formats to show.", "admin.registries.bitstream-formats.no-items": "Žádné formáty souboru k zobrazení.", @@ -373,7 +373,7 @@ "admin.access-control.bulk-access.breadcrumbs": "Hromadná správa přístupu", // "administrativeBulkAccess.search.results.head": "Search Results", - "administrativeBulkAccess.search.results.head": "Prohledávat výsledky", + "administrativeBulkAccess.search.results.head": "Výsledky vyhledávání", // "admin.access-control.bulk-access": "Bulk Access Management", "admin.access-control.bulk-access": "Hromadná správa přístupu", @@ -403,7 +403,7 @@ "admin.access-control.epeople.actions.reset": "Resetovat heslo", // "admin.access-control.epeople.actions.stop-impersonating": "Stop impersonating EPerson", - "admin.access-control.epeople.actions.stop-impersonating": "Přestat simulovat jiného uživatele", + "admin.access-control.epeople.actions.stop-impersonating": "Přestat vystupovat jako jiný uživatel", // "admin.access-control.epeople.breadcrumbs": "EPeople", "admin.access-control.epeople.breadcrumbs": "Uživatelé", @@ -612,7 +612,7 @@ "admin.access-control.groups.table.edit.buttons.remove": "Odstranit \"{{name}}\"", // "admin.access-control.groups.no-items": "No groups found with this in their name or this as UUID", - "admin.access-control.groups.no-items": "Nebyly nalezeny žádné skupiny, které by měly toto ve svém názvu nebo toto jako UUID,", + "admin.access-control.groups.no-items": "Nebyla nalezena žádná skupina s tímto v návzvu nebo UUID", // "admin.access-control.groups.notification.deleted.success": "Successfully deleted group \"{{name}}\"", "admin.access-control.groups.notification.deleted.success": "Úspěšně odstraněna skupina \"{{name}}\"", @@ -696,7 +696,7 @@ "admin.access-control.groups.form.members-list.button.see-all": "Procházet vše", // "admin.access-control.groups.form.members-list.headMembers": "Current Members", - "admin.access-control.groups.form.members-list.headMembers": "Aktuální členové", + "admin.access-control.groups.form.members-list.headMembers": "Současní členové", // "admin.access-control.groups.form.members-list.search.button": "Search", "admin.access-control.groups.form.members-list.search.button": "Hledat", @@ -738,13 +738,13 @@ "admin.access-control.groups.form.members-list.table.edit.buttons.add": "Přidat člena jménem \"{{name}}\"", // "admin.access-control.groups.form.members-list.notification.failure.noActiveGroup": "No current active group, submit a name first.", - "admin.access-control.groups.form.members-list.notification.failure.noActiveGroup": "Žádná aktuální aktivní skupina, nejprve zadejte jméno.", + "admin.access-control.groups.form.members-list.notification.failure.noActiveGroup": "Žádná aktuální aktivní skupina, nejprve zadejte název.", // "admin.access-control.groups.form.members-list.no-members-yet": "No members in group yet, search and add.", "admin.access-control.groups.form.members-list.no-members-yet": "Ve skupině zatím nejsou žádní členové, vyhledejte je a přidejte.", // "admin.access-control.groups.form.members-list.no-items": "No EPeople found in that search", - "admin.access-control.groups.form.members-list.no-items": "V tomto vyhledávání nebyly nalezeni žádní uživatelé", + "admin.access-control.groups.form.members-list.no-items": "V tomto vyhledávání nebyli nalezeni žádní uživatelé", // "admin.access-control.groups.form.subgroups-list.notification.failure": "Something went wrong: \"{{cause}}\"", "admin.access-control.groups.form.subgroups-list.notification.failure": "Neúspěch: Něco se pokazilo: \"{{cause}}\"", @@ -801,7 +801,7 @@ "admin.access-control.groups.form.subgroups-list.notification.failure.subgroupToAddIsActiveGroup": "Toto je aktuální skupina, nelze přidat.", // "admin.access-control.groups.form.subgroups-list.no-items": "No groups found with this in their name or this as UUID", - "admin.access-control.groups.form.subgroups-list.no-items": "Nebyly nalezeny žádné skupiny, které by měly toto ve svém názvu nebo toto UUID", + "admin.access-control.groups.form.subgroups-list.no-items": "Nebyla nalezena žádná skupina s tímto v návzvu nebo UUID", // "admin.access-control.groups.form.subgroups-list.no-subgroups-yet": "No subgroups in group yet.", "admin.access-control.groups.form.subgroups-list.no-subgroups-yet": "Ve skupině zatím nejsou žádné podskupiny.", @@ -831,7 +831,7 @@ "admin.notifications.source.breadcrumbs": "Quality Assurance", // "admin.access-control.groups.form.tooltip.editGroupPage": "On this page, you can modify the properties and members of a group. In the top section, you can edit the group name and description, unless this is an admin group for a collection or community, in which case the group name and description are auto-generated and cannot be edited. In the following sections, you can edit group membership. See [the wiki](https://wiki.lyrasis.org/display/DSDOC7x/Create+or+manage+a+user+group) for more details.", - "admin.access-control.groups.form.tooltip.editGroupPage": "Na této stránce můžete upravit vlastnosti a členy skupiny. V horní části můžete upravit název a popis skupiny, pokud se nejedá o skupinu admin pro kolekci nebo komunitu. V takovém případě jsou název a popis skupiny vygenerovány automaticky a nelze je upravovat. V následujících částech můžete upravovat členství ve skupině. Další podrobnosti naleznete v části [the wiki](https://wiki.lyrasis.org/display/DSDOC7x/Create+or+manage+a+user+group).", + "admin.access-control.groups.form.tooltip.editGroupPage": "Na této stránce můžete upravit vlastnosti a členy skupiny. V horní části můžete upravit název a popis skupiny, pokud se nejedá o skupinu admin pro kolekci nebo komunitu. V takovém případě jsou název a popis skupiny vygenerovány automaticky a nelze je upravovat. V následujících částech můžete upravovat členství ve skupině. Další podrobnosti naleznete v části [wiki](https://wiki.lyrasis.org/display/DSDOC7x/Create+or+manage+a+user+group).", // "admin.access-control.groups.form.tooltip.editGroup.addEpeople": "To add or remove an EPerson to/from this group, either click the 'Browse All' button or use the search bar below to search for users (use the dropdown to the left of the search bar to choose whether to search by metadata or by email). Then click the plus icon for each user you wish to add in the list below, or the trash can icon for each user you wish to remove. The list below may have several pages: use the page controls below the list to navigate to the next pages.", "admin.access-control.groups.form.tooltip.editGroup.addEpeople": "Pro přidání nebo odebrání uživatele z této skupiny, buď klikněte na „Prohledávat všechny“ nebo použijte vyhledávací okno pro vyhledání uživatele (použijte roletku na levé straně vyhledávacího okna a vyberte, zda chcete prohledávat pomocí metadat nebo e-mailu). Potom klikněte na tlačítko plus u každého uživatele, kterého chcete přidat, nebo na ikonu odpadkového koše u každého uživatele, kterého chcete odebrat. Seznam níže může obsahovat několik stránek: k přechodu na další stránku použijte ovládací prvky pod seznamem. Až budete hotovi, uložte změny pomocí tlačítka „Uložit“ v horní části.", @@ -1406,7 +1406,7 @@ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.button.see-all": "Prohledávat vše", // "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.headMembers": "Current Members", - "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.headMembers": "Aktuální členové", + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.headMembers": "Současní členové", // "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.button": "Search", "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.button": "Hledat", @@ -1448,7 +1448,7 @@ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.edit.buttons.add": "Přidat uživatele jménem \"{{name}}\"", // "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.notification.failure.noActiveGroup": "No current active group, submit a name first.", - "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.notification.failure.noActiveGroup": "Žádná aktivní skupina, nejdříve vložte název.", + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.notification.failure.noActiveGroup": "Žádná aktuální aktivní skupina, nejprve zadejte název.", // "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.no-members-yet": "No members in group yet, search and add.", "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.no-members-yet": "Žádní členové ve skupině, nejdříve vyhledejte a přidejte.", @@ -1792,7 +1792,7 @@ "claimed-approved-search-result-list-element.title": "Schváleno", // "claimed-declined-search-result-list-element.title": "Rejected, sent back to submitter", - "claimed-declined-search-result-list-element.title": "Zamítnuto, posláno zpět vkladateli", + "claimed-declined-search-result-list-element.title": "Zamítnuto, posláno zpět odesílateli", // "claimed-declined-task-search-result-list-element.title": "Declined, sent back to Review Manager's workflow", "claimed-declined-task-search-result-list-element.title": "Zamítnuto, posláno zpět správci schvalovacího workflow", @@ -1815,7 +1815,7 @@ "collection.create.sub-head": "Vytvořit kolekci pro komunitu {{ parent }}", // "collection.curate.header": "Curate Collection: {{collection}}", - "collection.curate.header": "Kurátorovat kolekci: {{collection}}", + "collection.curate.header": "Spravovat kolekci: {{collection}}", // "collection.delete.cancel": "Cancel", "collection.delete.cancel": "Zrušit", @@ -1923,7 +1923,7 @@ "collection.edit.logo.notifications.add.success": "Nahrání loga kolekce proběhlo úspěšně.", // "collection.edit.logo.notifications.delete.success.title": "Logo deleted", - "collection.edit.logo.notifications.delete.success.title": "Logo odstraněno", + "collection.edit.logo.notifications.delete.success.title": "Logo smazáno", // "collection.edit.logo.notifications.delete.success.content": "Successfully deleted the collection's logo", "collection.edit.logo.notifications.delete.success.content": "Úspěšně odstraněno logo kolekce", @@ -1948,10 +1948,10 @@ "collection.edit.tabs.access-control.title": "Úprava kolekce - Řízení přístupu", // "collection.edit.tabs.curate.head": "Curate", - "collection.edit.tabs.curate.head": "Kurátorovat", + "collection.edit.tabs.curate.head": "Spravovat", // "collection.edit.tabs.curate.title": "Collection Edit - Curate", - "collection.edit.tabs.curate.title": "Upravit kolekci - kurátorovat", + "collection.edit.tabs.curate.title": "Upravit kolekci - správa", // "collection.edit.tabs.authorizations.head": "Authorizations", "collection.edit.tabs.authorizations.head": "Oprávnění", @@ -2014,7 +2014,7 @@ "collection.edit.tabs.source.head": "Zdroj obsahu", // "collection.edit.tabs.source.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", - "collection.edit.tabs.source.notifications.discarded.content": "Vaše změny byly vyřazeny. Chcete-li své změny obnovit, klikněte na tlačítko Zpět", + "collection.edit.tabs.source.notifications.discarded.content": "Vaše změny byly zahozeny. Pro obnovení změn klikněte na tlačítko 'Vrátit změny'", // "collection.edit.tabs.source.notifications.discarded.title": "Changes discarded", // TODO Source message changed - Revise the translation @@ -2024,7 +2024,7 @@ "collection.edit.tabs.source.notifications.invalid.content": "Vaše změny nebyly uloženy. Před uložením se prosím ujistěte, že jsou všechna pole platná.", // "collection.edit.tabs.source.notifications.invalid.title": "Metadata invalid", - "collection.edit.tabs.source.notifications.invalid.title": "Metadata neplatná", + "collection.edit.tabs.source.notifications.invalid.title": "Neplatná metadata", // "collection.edit.tabs.source.notifications.saved.content": "Your changes to this collection's content source were saved.", "collection.edit.tabs.source.notifications.saved.content": "Vaše změny ve zdroji obsahu této kolekce byly uloženy.", @@ -2215,7 +2215,7 @@ "collection.source.controls.harvest.no-information": "Není k dispozi", // "collection.source.update.notifications.error.content": "The provided settings have been tested and didn't work.", - "collection.source.update.notifications.error.content": "Zadané nastavení bylo testováno a nefungovalo.", + "collection.source.update.notifications.error.content": "Zadané nastavení bylo otestováno a nefungovalo.", // "collection.source.update.notifications.error.title": "Server Error", "collection.source.update.notifications.error.title": "Chyba serveru", @@ -2262,7 +2262,7 @@ "community.create.sub-head": "Vytvořit dílčí komunitu pro komunitu {{ parent }}", // "community.curate.header": "Curate Community: {{community}}", - "community.curate.header": "Kurátorství komunity: {{ community }}", + "community.curate.header": "Spravovat komunitu: {{ community }}", // "community.delete.cancel": "Cancel", "community.delete.cancel": "Zrušit", @@ -2295,7 +2295,7 @@ "community.edit.breadcrumbs": "Upravit komunitu", // "community.edit.logo.delete.title": "Delete logo", - "community.edit.logo.delete.title": "Odstranit logo", + "community.edit.logo.delete.title": "Smazat logo", // "community.edit.logo.delete-undo.title": "Undo delete", "community.edit.logo.delete-undo.title": "Zrušit odstranění", @@ -2310,7 +2310,7 @@ "community.edit.logo.notifications.add.success": "Nahrání loga komunity proběhlo úspěšně.", // "community.edit.logo.notifications.delete.success.title": "Logo deleted", - "community.edit.logo.notifications.delete.success.title": "Logo odstraněno", + "community.edit.logo.notifications.delete.success.title": "Logo smazáno", // "community.edit.logo.notifications.delete.success.content": "Successfully deleted the community's logo", "community.edit.logo.notifications.delete.success.content": "Úspěšně odstraněno logo komunity", @@ -2335,10 +2335,10 @@ "community.edit.return": "Zpět", // "community.edit.tabs.curate.head": "Curate", - "community.edit.tabs.curate.head": "Kurátorovat", + "community.edit.tabs.curate.head": "Spravovat", // "community.edit.tabs.curate.title": "Community Edit - Curate", - "community.edit.tabs.curate.title": "Upravit komunitu - kurátorovat", + "community.edit.tabs.curate.title": "Upravit komunitu - správa", // "community.edit.tabs.access-control.head": "Access Control", "community.edit.tabs.access-control.head": "Řízení přístupu", @@ -2396,13 +2396,13 @@ "comcol-role.edit.collection-admin.name": "Správci", // "comcol-role.edit.community-admin.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", - "comcol-role.edit.community-admin.description": "Správci komunit mohou vytvářet dílčí komunity nebo kolekce a spravovat nebo přidělovat správu těmto dílčím komunitám nebo kolekcím. Kromě toho rozhodují o tom, kdo může odesílat záznamy do všech dílčích kolekcí, upravovat metadata záznamů (po odeslání) a přidávat (mapovat) existující záznamy z jiných kolekcí (na základě oprávnění).", + "comcol-role.edit.community-admin.description": "Administrátoři komunit mohou vytvářet dílčí komunity nebo kolekce a spravovat nebo přidělovat správu těmto dílčím komunitám nebo kolekcím. Kromě toho rozhodují o tom, kdo může odesílat záznamy do všech dílčích kolekcí, upravovat metadata záznamů (po odeslání) a přidávat (mapovat) existující záznamy z jiných kolekcí (na základě oprávnění).", // "comcol-role.edit.collection-admin.description": "Collection administrators decide who can submit items to the collection, edit item metadata (after submission), and add (map) existing items from other collections to this collection (subject to authorization for that collection).", "comcol-role.edit.collection-admin.description": "Správci kolekcí rozhodují o tom, kdo může do kolekce vkládat záznamy, upravovat metadata záznamů (po vložení) a přidávat (mapovat) existující záznamy z jiných kolekcí do této kolekce (podléhá autorizaci pro danou kolekci).", // "comcol-role.edit.submitters.name": "Submitters", - "comcol-role.edit.submitters.name": "Vkladatelé", + "comcol-role.edit.submitters.name": "Odesílatelé", // "comcol-role.edit.submitters.description": "The E-People and Groups that have permission to submit new items to this collection.", "comcol-role.edit.submitters.description": "Uživatelé a skupiny, které mají oprávnění ke vkládání nových záznamů do této kolekce.", @@ -2460,7 +2460,7 @@ "community.form.errors.title.required": "Zadejte název komunity", // "community.form.rights": "Copyright text (HTML)", - "community.form.rights": "Text autorských práv (HTML)", + "community.form.rights": "Copyright (HTML)", // "community.form.tableofcontents": "News (HTML)", "community.form.tableofcontents": "Novinky (HTML)", @@ -2640,16 +2640,16 @@ "curation.form.submit": "Start", // "curation.form.submit.success.head": "The curation task has been started successfully", - "curation.form.submit.success.head": "Kurátorská úloha byla úspěšně spuštěna", + "curation.form.submit.success.head": "Úloha správy byla úspěšně spuštěna", // "curation.form.submit.success.content": "You will be redirected to the corresponding process page.", "curation.form.submit.success.content": "Budete přesměrováni na příslušnou stránku procesu.", // "curation.form.submit.error.head": "Running the curation task failed", - "curation.form.submit.error.head": "Spuštění kurátorské úlohy se nezdařilo.", + "curation.form.submit.error.head": "Spuštění úlohy správy se nezdařilo.", // "curation.form.submit.error.content": "An error occured when trying to start the curation task.", - "curation.form.submit.error.content": "Při pokusu o spuštění kurátorské úlohy došlo k chybě.", + "curation.form.submit.error.content": "Při pokusu o spuštění úlohy správy došlo k chybě.", // "curation.form.submit.error.invalid-handle": "Couldn't determine the handle for this object", "curation.form.submit.error.invalid-handle": "Nepodařilo se určit handle pro tento objekt", @@ -3044,7 +3044,7 @@ "forgot-email.form.success.head": "E-mail pro obnovení hesla odeslán", // "forgot-email.form.success.content": "An email has been sent to {{ email }} containing a special URL and further instructions.", - "forgot-email.form.success.content": "Na adresu {{ email }} byl odeslán e-mail obsahující speciální adresu URL a další pokyny.", + "forgot-email.form.success.content": "Na adresu {{ email }} byl odeslán e-mail obsahující speciální URL a další instrukce.", // "forgot-email.form.error.head": "Error when trying to reset password", // TODO Source message changed - Revise the translation @@ -3242,7 +3242,7 @@ "grant-deny-request-copy.header": "Žádost o kopii dokumentu", // "grant-deny-request-copy.home-page": "Take me to the home page", - "grant-deny-request-copy.home-page": "Přesměrujte mne na domovskou stránku", + "grant-deny-request-copy.home-page": "Návrat na domovskou stránku", // "grant-deny-request-copy.intro1": "If you are one of the authors of the document {{ name }}, then please use one of the options below to respond to the user's request.", "grant-deny-request-copy.intro1": "Pokud jste jedním z autorů dokumentu {{ name }}, použijte prosím jednu z níže uvedených možností, abyste odpověděli na žádost uživatele.", @@ -3278,13 +3278,13 @@ "health-page.info-tab": "Informace", // "health-page.status-tab": "Status", - "health-page.status-tab": "Status", + "health-page.status-tab": "Stav", // "health-page.error.msg": "The health check service is temporarily unavailable", "health-page.error.msg": "Služba kontrolující stav systému je momentálně nedostupná.", // "health-page.property.status": "Status code", - "health-page.property.status": "Kód statusu", + "health-page.property.status": "Stavový kód", // "health-page.section.db.title": "Database", "health-page.section.db.title": "Databáze", @@ -3455,7 +3455,7 @@ "item.badge.private": "Nedohledatelné", // "item.badge.withdrawn": "Withdrawn", - "item.badge.withdrawn": "Zrušeno", + "item.badge.withdrawn": "Vyřazeno", // "item.bitstreams.upload.bundle": "Bundle", "item.bitstreams.upload.bundle": "Svazek", @@ -3543,10 +3543,10 @@ "item.edit.bitstreams.headers.name": "Název", // "item.edit.bitstreams.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", - "item.edit.bitstreams.notifications.discarded.content": "Vaše změny byly zahozeny. Pro obnovení změn klikněte na tlačítko 'Undo'", + "item.edit.bitstreams.notifications.discarded.content": "Vaše změny byly zahozeny. Pro obnovení změn klikněte na tlačítko 'Vrátit změny'", // "item.edit.bitstreams.notifications.discarded.title": "Changes discarded", - "item.edit.bitstreams.notifications.discarded.title": "Změny zahozeny", + "item.edit.bitstreams.notifications.discarded.title": "Zahozené změny", // "item.edit.bitstreams.notifications.move.failed.title": "Error moving bitstreams", "item.edit.bitstreams.notifications.move.failed.title": "Chyba při přesunu souborů", @@ -3558,7 +3558,7 @@ "item.edit.bitstreams.notifications.move.saved.title": "Změny uloženy", // "item.edit.bitstreams.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", - "item.edit.bitstreams.notifications.outdated.content": "Záznam, na které právě pracujete, byl změněn jiným uživatelem. Vaše aktuální změny jsou zahozeny, aby se zabránilo konfliktům", + "item.edit.bitstreams.notifications.outdated.content": "Záznam, na kterém právě pracujete, byl změněn jiným uživatelem. Vaše aktuální změny byly zahozeny, aby se zabránilo konfliktům", // "item.edit.bitstreams.notifications.outdated.title": "Changes outdated", "item.edit.bitstreams.notifications.outdated.title": "Změny jsou zastaralé", @@ -3651,13 +3651,13 @@ "item.edit.identifiers.doi.status.MINTED": "Vydáno (nezaregistrováno)", // "item.edit.tabs.status.buttons.register-doi.label": "Register a new or pending DOI", - "item.edit.tabs.status.buttons.register-doi.label": "Registrace nového nebo čekání na nové DOI", + "item.edit.tabs.status.buttons.register-doi.label": "Registrovat nové nebo čekající DOI", // "item.edit.tabs.status.buttons.register-doi.button": "Register DOI...", "item.edit.tabs.status.buttons.register-doi.button": "Zaregistrujte DOI...", // "item.edit.register-doi.header": "Register a new or pending DOI", - "item.edit.register-doi.header": "Zaregistrujte nové DOI nebo čekající k registraci", + "item.edit.register-doi.header": "Registrovat nové nebo čekající DOI", // "item.edit.register-doi.description": "Review any pending identifiers and item metadata below and click Confirm to proceed with DOI registration, or Cancel to back out", "item.edit.register-doi.description": "Níže zkontrolujte všechny identifikátory a metadata ve frontě. Zvolte \"Potvrdit\" pro pokračování v registraci DOI nebo \"Zrušit\" pro přerušení procesu.", @@ -3687,7 +3687,7 @@ "item.edit.item-mapper.cancel": "Zrušit", // "item.edit.item-mapper.description": "This is the item mapper tool that allows administrators to map this item to other collections. You can search for collections and map them, or browse the list of collections the item is currently mapped to.", - "item.edit.item-mapper.description": "Toto je nástroj pro mapování záznamů, který umožňuje správcům mapovat tuto záznam na jiné kolekce. Můžete vyhledávat kolekce a mapovat je nebo procházet seznam kolekcí, ke kterým je záznam aktuálně mapována.", + "item.edit.item-mapper.description": "Toto je nástroj pro mapování záznamů, který umožňuje správcům mapovat tento záznam do jiné kolekce. Můžete vyhledávat kolekce a mapovat je nebo procházet seznam kolekcí, ke kterým je záznam aktuálně mapována.", // "item.edit.item-mapper.head": "Item Mapper - Map Item to Collections", "item.edit.item-mapper.head": "Mapování do jiných kolekcí - mapovat záznam do kolekcí", @@ -3768,7 +3768,7 @@ "item.edit.metadata.edit.buttons.unedit": "Zastavit úpravy", // "item.edit.metadata.edit.buttons.virtual": "This is a virtual metadata value, i.e. a value inherited from a related entity. It can’t be modified directly. Add or remove the corresponding relationship in the \"Relationships\" tab", - "item.edit.metadata.edit.buttons.virtual": "Tato hodnota metadat je virtuální, tedy děděná z příbuzné entity. Nemůže být zděděna napřímo. Přidejte či odeberte příslušný vztah na záložce \"Vztahy\".", + "item.edit.metadata.edit.buttons.virtual": "Tato hodnota metadat je virtuální, tedy děděná z provázané entity. Nemůže být změněna napřímo. Přidejte či odeberte příslušný vztah na záložce \"Vztahy\".", // "item.edit.metadata.empty": "The item currently doesn't contain any metadata. Click Add to start adding a metadata value.", "item.edit.metadata.empty": "Záznam aktuálně neobsahuje žádná metadata. Kliknutím na tlačítko Přidat začněte přidávat hodnotu metadat.", @@ -3796,11 +3796,11 @@ "item.edit.metadata.metadatafield.invalid": "Prosím, vyberte platné metadatové pole", // "item.edit.metadata.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", - "item.edit.metadata.notifications.discarded.content": "Vaše změny byly vyřazeny. Pro obnovení změn klikněte na tlačítko 'Vrátit změny'", + "item.edit.metadata.notifications.discarded.content": "Vaše změny byly zahozeny. Pro obnovení změn klikněte na tlačítko 'Vrátit změny'", // "item.edit.metadata.notifications.discarded.title": "Changes discarded", // TODO Source message changed - Revise the translation - "item.edit.metadata.notifications.discarded.title": "Změny zahozeny", + "item.edit.metadata.notifications.discarded.title": "Zahozené změny", // "item.edit.metadata.notifications.error.title": "An error occurred", "item.edit.metadata.notifications.error.title": "Došlo k chybě", @@ -3809,17 +3809,17 @@ "item.edit.metadata.notifications.invalid.content": "Vaše změny nebyly uloženy. Před uložením se prosím ujistěte, že jsou všechna pole platná.", // "item.edit.metadata.notifications.invalid.title": "Metadata invalid", - "item.edit.metadata.notifications.invalid.title": "Metadata neplatná", + "item.edit.metadata.notifications.invalid.title": "Neplatná metadata", // "item.edit.metadata.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", - "item.edit.metadata.notifications.outdated.content": "Záznam, na které právě pracujete, byl změněna jiným uživatelem. Vaše aktuální změny jsou zahozeny, aby se zabránilo konfliktům", + "item.edit.metadata.notifications.outdated.content": "Záznam, na kterém právě pracujete, byl změněn jiným uživatelem. Vaše aktuální změny byly zahozeny, aby se zabránilo konfliktům", // "item.edit.metadata.notifications.outdated.title": "Changes outdated", // TODO Source message changed - Revise the translation "item.edit.metadata.notifications.outdated.title": "Změny jsou zastaralé", // "item.edit.metadata.notifications.saved.content": "Your changes to this item's metadata were saved.", - "item.edit.metadata.notifications.saved.content": "Vaše změny metadat této záznamu byly uloženy.", + "item.edit.metadata.notifications.saved.content": "Vaše změny metadat tohoto záznamu byly uloženy.", // "item.edit.metadata.notifications.saved.title": "Metadata saved", "item.edit.metadata.notifications.saved.title": "Metadata uložena", @@ -3976,16 +3976,16 @@ "item.edit.relationships.no-relationships": "Žádné vztahy", // "item.edit.relationships.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", - "item.edit.relationships.notifications.discarded.content": "Vaše změny byly vyřazeny. Pro obnovení změn klikněte na tlačítko 'Vrátit změny'", + "item.edit.relationships.notifications.discarded.content": "Vaše změny byly zahozeny. Pro obnovení změn klikněte na tlačítko 'Vrátit změny'", // "item.edit.relationships.notifications.discarded.title": "Changes discarded", - "item.edit.relationships.notifications.discarded.title": "Změny vyřazeny", + "item.edit.relationships.notifications.discarded.title": "Zahozené změny", // "item.edit.relationships.notifications.failed.title": "Error editing relationships", "item.edit.relationships.notifications.failed.title": "Chyba při úpravě vztahů", // "item.edit.relationships.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", - "item.edit.relationships.notifications.outdated.content": "Záznam, na které právě pracujete, byl změněna jiným uživatelem. Vaše aktuální změny jsou vyřazeny, aby se zabránilo konfliktům", + "item.edit.relationships.notifications.outdated.content": "Záznam, na kterém právě pracujete, byl změněn jiným uživatelem. Vaše aktuální změny byly zahozeny, aby se zabránilo konfliktům", // "item.edit.relationships.notifications.outdated.title": "Changes outdated", "item.edit.relationships.notifications.outdated.title": "Změny jsou zastaralé", @@ -4009,25 +4009,25 @@ "item.edit.return": "Zpět", // "item.edit.tabs.bitstreams.head": "Bitstreams", - "item.edit.tabs.bitstreams.head": "Soubor", + "item.edit.tabs.bitstreams.head": "Soubory", // "item.edit.tabs.bitstreams.title": "Item Edit - Bitstreams", "item.edit.tabs.bitstreams.title": "Úprava záznamu - soubor", // "item.edit.tabs.curate.head": "Curate", - "item.edit.tabs.curate.head": "Kurátorovat", + "item.edit.tabs.curate.head": "Spravovat", // "item.edit.tabs.curate.title": "Item Edit - Curate", - "item.edit.tabs.curate.title": "Úprava záznamu - Kurátorovat", + "item.edit.tabs.curate.title": "Úprava záznamu - spravovat", // "item.edit.curate.title": "Curate Item: {{item}}", - "item.edit.curate.title": "Kurátorovat záznam: {{item}}", + "item.edit.curate.title": "Spravovat záznam: {{item}}", // "item.edit.tabs.access-control.head": "Access Control", - "item.edit.tabs.access-control.head": "Kontrola přístupu", + "item.edit.tabs.access-control.head": "Řízení přístupu", // "item.edit.tabs.access-control.title": "Item Edit - Access Control", - "item.edit.tabs.access-control.title": "Úprava záznamu - Kontrola přístupu", + "item.edit.tabs.access-control.title": "Úprava záznamu - Řízení přístupu", // "item.edit.tabs.metadata.head": "Metadata", "item.edit.tabs.metadata.head": "Metadata", @@ -4275,7 +4275,7 @@ "item.page.collections": "Kolekce", // "item.page.collections.loading": "Loading...", - "item.page.collections.loading": "Načítání...", + "item.page.collections.loading": "Načítá se...", // "item.page.collections.load-more": "Load more", "item.page.collections.load-more": "Načíst další", @@ -4377,7 +4377,7 @@ "item.page.reinstate": "Request reinstatement", // "item.page.version.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history", - "item.page.version.hasDraft": "Nová verze nemůže být vytvořena, protože v historii verzí právě probíhá zadání nové verze.", + "item.page.version.hasDraft": "Nová verze nemůže být vytvořena, protože v historii verzí už další rozpracovaná verze existuje.", // "item.page.claim.button": "Claim", "item.page.claim.button": "Prohlásit", @@ -4585,7 +4585,7 @@ "item.version.history.table.action.deleteVersion": "Smazat verzi", // "item.version.history.table.action.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history", - "item.version.history.table.action.hasDraft": "Nová verze nemůže být vytvořena, protože v historii verzí probíhá odesílání.", + "item.version.history.table.action.hasDraft": "Nová verze nemůže být vytvořena, protože v historii verzí už další rozpracovaná verze existuje.", // "item.version.notice": "This is not the latest version of this item. The latest version can be found here.", "item.version.notice": "Toto není nejnovější verze tohoto záznamu. Nejnovější verzi naleznete zde.", @@ -4698,7 +4698,7 @@ "item.version.create.notification.failure": "Nová verze nebyla vytvořena", // "item.version.create.notification.inProgress": "A new version cannot be created because there is an inprogress submission in the version history", - "item.version.create.notification.inProgress": "Nová verze nemůže být vytvořena, protože v historii verzí probíhá odesílání.", + "item.version.create.notification.inProgress": "Nová verze nemůže být vytvořena, protože v historii verzí už další rozpracovaná verze existuje.", // "item.version.delete.modal.header": "Delete version", "item.version.delete.modal.header": "Smazat verzi", @@ -4788,7 +4788,7 @@ "itemtemplate.edit.metadata.metadatafield.invalid": "Prosím zvolte platné metadatové pole", // "itemtemplate.edit.metadata.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", - "itemtemplate.edit.metadata.notifications.discarded.content": "Vaše změny byly zahozeny. Chcete-li obnovit své změny, klikněte na tlačítko 'Zpět'.", + "itemtemplate.edit.metadata.notifications.discarded.content": "Vaše změny byly zahozeny. Pro obnovení změn klikněte na tlačítko 'Vrátit změny'", // "itemtemplate.edit.metadata.notifications.discarded.title": "Changes discarded", "itemtemplate.edit.metadata.notifications.discarded.title": "Změny byly zahozeny", @@ -4797,7 +4797,7 @@ "itemtemplate.edit.metadata.notifications.error.title": "Vyskytla se chyba", // "itemtemplate.edit.metadata.notifications.invalid.content": "Your changes were not saved. Please make sure all fields are valid before you save.", - "itemtemplate.edit.metadata.notifications.invalid.content": "Vaše změny nebyly uloženy. Před uložením se ujistěte, že všechna pole jsou platná.", + "itemtemplate.edit.metadata.notifications.invalid.content": "Vaše změny nebyly uloženy. Před uložením se prosím ujistěte, že jsou všechna pole platná.", // "itemtemplate.edit.metadata.notifications.invalid.title": "Metadata invalid", "itemtemplate.edit.metadata.notifications.invalid.title": "Neplatná metadata", @@ -4812,7 +4812,7 @@ "itemtemplate.edit.metadata.notifications.saved.content": "Vaše změny v šabloně metadat tohoto záznamu byly uloženy.", // "itemtemplate.edit.metadata.notifications.saved.title": "Metadata saved", - "itemtemplate.edit.metadata.notifications.saved.title": "Metadata uloženy", + "itemtemplate.edit.metadata.notifications.saved.title": "Metadata uložena", // "itemtemplate.edit.metadata.reinstate-button": "Undo", "itemtemplate.edit.metadata.reinstate-button": "Vrátit zpět", @@ -5043,7 +5043,7 @@ "menu.header.admin": "Admin", // "menu.header.image.logo": "Repository logo", - "menu.header.image.logo": "Logo úložiště", + "menu.header.image.logo": "Logo repozitáře", // "menu.header.admin.description": "Management menu", "menu.header.admin.description": "Management menu", @@ -5119,7 +5119,7 @@ "menu.section.control_panel": "Ovládací panel", // "menu.section.curation_task": "Curation Task", - "menu.section.curation_task": "Kurátorská úloha", + "menu.section.curation_task": "Úloha správy", // "menu.section.edit": "Edit", "menu.section.edit": "Upravit", @@ -5165,25 +5165,25 @@ "menu.section.icon.control_panel": "Sekce menu Ovládací panel", // "menu.section.icon.curation_tasks": "Curation Task menu section", - "menu.section.icon.curation_tasks": "Sekce menu Kurátorská úloha", + "menu.section.icon.curation_tasks": "Sekce menu úloha správy", // "menu.section.icon.edit": "Edit menu section", "menu.section.icon.edit": "Sekce menu Upravit", // "menu.section.icon.export": "Export menu section", - "menu.section.icon.export": "Exportovat sekci menu", + "menu.section.icon.export": "Sekce menu Exportovat", // "menu.section.icon.find": "Find menu section", - "menu.section.icon.find": "Najít sekci menu", + "menu.section.icon.find": "Sekce menu Najít", // "menu.section.icon.health": "Health check menu section", - "menu.section.icon.health": "Kontrola stavu sekce menu", + "menu.section.icon.health": "Sekce menu Kontrola stavu", // "menu.section.icon.import": "Import menu section", - "menu.section.icon.import": "Importovat sekci menu", + "menu.section.icon.import": "Sekce menu Importovat", // "menu.section.icon.new": "New menu section", - "menu.section.icon.new": "Nová sekce menu", + "menu.section.icon.new": "Sekce menu Nový", // "menu.section.icon.pin": "Pin sidebar", "menu.section.icon.pin": "Připnout postranní panel", @@ -5260,7 +5260,7 @@ "menu.section.health": "Stav systému", // "menu.section.registries": "Registries", - "menu.section.registries": "Rejstřík", + "menu.section.registries": "Registry", // "menu.section.registries_format": "Format", "menu.section.registries_format": "Formát", @@ -5285,7 +5285,7 @@ "menu.section.toggle.control_panel": "Přepnout sekci ovládacího panelu", // "menu.section.toggle.curation_task": "Toggle Curation Task section", - "menu.section.toggle.curation_task": "Přepnout sekci Kurátorská úloha", + "menu.section.toggle.curation_task": "Přepnout sekci úloha správy", // "menu.section.toggle.edit": "Toggle Edit section", "menu.section.toggle.edit": "Přepnout sekci Úpravy", @@ -5303,7 +5303,7 @@ "menu.section.toggle.new": "Přepnout sekci Nová", // "menu.section.toggle.registries": "Toggle Registries section", - "menu.section.toggle.registries": "Přepnout sekci Rejstříky", + "menu.section.toggle.registries": "Přepnout sekci Registry", // "menu.section.toggle.statistics_task": "Toggle Statistics Task section", "menu.section.toggle.statistics_task": "Přepnout sekci Statistická úloha", @@ -5327,7 +5327,7 @@ "mydspace.description": "", // "mydspace.messages.controller-help": "Select this option to send a message to item's submitter.", - "mydspace.messages.controller-help": "Zvolte tuto možnost, chcete-li odeslat zprávu vkladateli záznamu.", + "mydspace.messages.controller-help": "Zvolte tuto možnost, chcete-li odeslat zprávu odesílateli záznamu.", // "mydspace.messages.description-placeholder": "Insert your message here...", "mydspace.messages.description-placeholder": "Zde vložte svou zprávu...", @@ -5506,7 +5506,7 @@ "nav.statistics.header": "Statistika", // "nav.stop-impersonating": "Stop impersonating EPerson", - "nav.stop-impersonating": "Přestat simulovat jiného uživatele", + "nav.stop-impersonating": "Přestat vystupovat jako jiný uživatel", // "nav.subscriptions": "Subscriptions", "nav.subscriptions": "Nastavení notifikací", @@ -5810,7 +5810,7 @@ "orgunit.page.city": "Město", // "orgunit.page.country": "Country", - "orgunit.page.country": "Stát", + "orgunit.page.country": "Země", // "orgunit.page.dateestablished": "Date established", "orgunit.page.dateestablished": "Datum založení.", @@ -5892,7 +5892,7 @@ "person-relationships.search.results.head": "Výsledky vyhledávání osob", // "person.search.title": "Person Search", - "person.search.title": "Vyledatávání osob", + "person.search.title": "Vyhledávání osob", // "process.new.select-parameters": "Parameters", "process.new.select-parameters": "Parametry", @@ -6121,7 +6121,7 @@ "process.overview.delete.clear": "Zrušit výběr mazání", // "process.overview.delete.processing": "{{count}} process(es) are being deleted. Please wait for the deletion to fully complete. Note that this can take a while.", - "process.overview.delete.processing": "Smazává se {{count}} procesů. Počkejte prosím na úplné dokončení mazání. To může chvíli trvat.", + "process.overview.delete.processing": "Maže se {{count}} procesů. Počkejte prosím na úplné dokončení mazání. To může chvíli trvat.", // "process.overview.delete.body": "Are you sure you want to delete {{count}} process(es)?", "process.overview.delete.body": "Určitě chcete smazat {{count}} procesů", @@ -6512,7 +6512,7 @@ "register-page.registration.header": "Registrace nového uživatele", // "register-page.registration.info": "Register an account to subscribe to collections for email updates, and submit new items to DSpace.", - "register-page.registration.info": "Zaregistrujte si účet a přihlaste se k odběru sbírek pro e-mailové aktualizace a odesílejte nové záznamy do systému DSpace", + "register-page.registration.info": "Zaregistrujte si účet a přihlaste se k odběru kolekcí pro e-mailové aktualizace a odesílejte nové záznamy do systému DSpace", // "register-page.registration.email": "Email Address *", "register-page.registration.email": "E-mailová adresa *", @@ -6880,7 +6880,7 @@ "search.filters.applied.f.dateIssued.min": "Datum zahájení", // "search.filters.applied.f.dateSubmitted": "Date submitted", - "search.filters.applied.f.dateSubmitted": "Date vložení", + "search.filters.applied.f.dateSubmitted": "Datum vložení", // "search.filters.applied.f.discoverable": "Non-discoverable", // TODO Source message changed - Revise the translation @@ -6925,7 +6925,7 @@ "search.filters.applied.f.supervisedBy": "Zkontrolováno", // "search.filters.applied.f.withdrawn": "Withdrawn", - "search.filters.applied.f.withdrawn": "Zrušeno", + "search.filters.applied.f.withdrawn": "Vyřazeno", // "search.filters.applied.operator.equals": "", // TODO New key - Add a translation @@ -7142,10 +7142,10 @@ "search.filters.filter.objectpeople.label": "Hledat osoby", // "search.filters.filter.organizationAddressCountry.head": "Country", - "search.filters.filter.organizationAddressCountry.head": "Stát", + "search.filters.filter.organizationAddressCountry.head": "Země", // "search.filters.filter.organizationAddressCountry.placeholder": "Country", - "search.filters.filter.organizationAddressCountry.placeholder": "Stát", + "search.filters.filter.organizationAddressCountry.placeholder": "Země", // "search.filters.filter.organizationAddressCountry.label": "Search country", "search.filters.filter.organizationAddressCountry.label": "Hledat stát", @@ -7438,10 +7438,10 @@ "sorting.dc.date.issued.DESC": "Datum vydání sestupně", // "sorting.dc.date.accessioned.ASC": "Accessioned Date Ascending", - "sorting.dc.date.accessioned.ASC": "Datum přírůstku vzestupně", + "sorting.dc.date.accessioned.ASC": "Datum odeslání vzestupně", // "sorting.dc.date.accessioned.DESC": "Accessioned Date Descending", - "sorting.dc.date.accessioned.DESC": "Datum přírůstku sestupně", + "sorting.dc.date.accessioned.DESC": "Datum odeslání sestupně", // "sorting.lastModified.ASC": "Last modified Ascending", "sorting.lastModified.ASC": "Naposledy upraveno vzestupně", @@ -8284,7 +8284,7 @@ "submission.sections.identifiers.info": "Pro tento záznam budou vytvořeny následující identifikátory:", // "submission.sections.identifiers.no_handle": "No handles have been minted for this item.", - "submission.sections.identifiers.no_handle": "Tomuto záznamu nybyl přidělen žádný handle.", + "submission.sections.identifiers.no_handle": "Tomuto záznamu nebyl přidělen žádný handle.", // "submission.sections.identifiers.no_doi": "No DOIs have been minted for this item.", "submission.sections.identifiers.no_doi": "Tomuto záznamu nebylo přiděleno žádné DOI.", @@ -8446,10 +8446,10 @@ "submission.sections.upload.form.until-placeholder": "Až do", // "submission.sections.upload.header.policy.default.nolist": "Uploaded files in the {{collectionName}} collection will be accessible according to the following group(s):", - "submission.sections.upload.header.policy.default.nolist": "Nahrané soubory v kolekci {{collectionName}} budou přístupné podle následující skupiny (skupin):", + "submission.sections.upload.header.policy.default.nolist": "Nahrané soubory v kolekci {{collectionName}} budou přístupné podle následujících skupin:", // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", - "submission.sections.upload.header.policy.default.withlist": "Vezměte prosím na vědomí, že nahrané soubory v kolekci {{collectionName}} budou kromě toho, co je explicitně rozhodnuto pro jednotlivý soubor, přístupné podle následující skupiny (skupin):", + "submission.sections.upload.header.policy.default.withlist": "Vezměte prosím na vědomí, že nahrané soubory v kolekci {{collectionName}} budou kromě toho, co je explicitně rozhodnuto pro jednotlivý soubor, přístupné podle následujících skupin:", // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Zde najdete všechny soubory, které jsou aktuálně v záznamu. Můžete aktualizovat metadata souborů a podmínky přístupu nebo nahrát další soubory pouhým přetažením kdekoli na stránku.", @@ -8618,7 +8618,7 @@ "submission.sections.sherpa.record.information.uri": "URI", // "submission.sections.sherpa.error.message": "There was an error retrieving sherpa informations", - "submission.sections.sherpa.error.message": "Informace ze Sherpa Romeo se nepodařilo načíst.", + "submission.sections.sherpa.error.message": "Při načítání informací ze služby Sherpa došlo k chybě", // "submission.submit.breadcrumbs": "New submission", "submission.submit.breadcrumbs": "Nově podaný záznam", @@ -8676,7 +8676,7 @@ "submission.workflow.tasks.claimed.decline_help": "", // "submission.workflow.tasks.claimed.reject.reason.info": "Please enter your reason for rejecting the submission into the box below, indicating whether the submitter may fix a problem and resubmit.", - "submission.workflow.tasks.claimed.reject.reason.info": "Do níže uvedeného pole zadejte důvod odmítnutí podání a uveďte, zda může vkladatel problém odstranit a podání znovu odeslat.", + "submission.workflow.tasks.claimed.reject.reason.info": "Do níže uvedeného pole zadejte důvod odmítnutí podání a uveďte, zda může odesílatel problém odstranit a podání znovu odeslat.", // "submission.workflow.tasks.claimed.reject.reason.placeholder": "Describe the reason of reject", "submission.workflow.tasks.claimed.reject.reason.placeholder": "Popište důvod odmítnutí", @@ -8691,7 +8691,7 @@ "submission.workflow.tasks.claimed.reject.submit": "Odmítnout", // "submission.workflow.tasks.claimed.reject_help": "If you have reviewed the item and found it is not suitable for inclusion in the collection, select \"Reject\". You will then be asked to enter a message indicating why the item is unsuitable, and whether the submitter should change something and resubmit.", - "submission.workflow.tasks.claimed.reject_help": "Pokud jste záznam zkontrolovali a zjistili, že není vhodný pro zařazení do kolekce, vyberte \"Odmítnout\". Poté budete vyzváni k zadání zprávy, ve které uvedete, proč je záznam nevhodný, a zda by měl vkladatel něco změnit a znovu odeslat.", + "submission.workflow.tasks.claimed.reject_help": "Pokud jste záznam zkontrolovali a zjistili, že není vhodný pro zařazení do kolekce, vyberte \"Odmítnout\". Poté budete vyzváni k zadání zprávy, ve které uvedete, proč je záznam nevhodný, a zda by měl odesílatel něco změnit a znovu odeslat.", // "submission.workflow.tasks.claimed.return": "Return to pool", "submission.workflow.tasks.claimed.return": "Vrátit do fondu", @@ -8712,7 +8712,7 @@ "submission.workflow.tasks.generic.success": "Operace proběhla úspěšně", // "submission.workflow.tasks.pool.claim": "Claim", - "submission.workflow.tasks.pool.claim": "Vyžádat si", + "submission.workflow.tasks.pool.claim": "Převzít", // "submission.workflow.tasks.pool.claim_help": "Assign this task to yourself.", "submission.workflow.tasks.pool.claim_help": "Přiřaďte si tento úkol.", @@ -8909,7 +8909,7 @@ "uploader.or": ", nebo ", // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", - "uploader.processing": "(nyní je bezpečné tuto stránku zavřít)", + "uploader.processing": "Zpracovávám nahrané soubory... (nyní je bezpečné tuto stránku zavřít)", // "uploader.queue-length": "Queue length", "uploader.queue-length": "Délka fronty", @@ -8933,7 +8933,7 @@ "workflowAdmin.search.results.head": "Spravovat workflow", // "workflow.search.results.head": "Workflow tasks", - "workflow.search.results.head": "Kroky workflow", + "workflow.search.results.head": "Úlohy workflow", // "supervision.search.results.head": "Workflow and Workspace tasks", "supervision.search.results.head": "Úlohy Workflow a osobního pracovního prostoru", @@ -8973,22 +8973,22 @@ "workflow-item.delete.button.confirm": "Smazat", // "workflow-item.send-back.notification.success.title": "Sent back to submitter", - "workflow-item.send-back.notification.success.title": "Odesláno zpět vkladateli", + "workflow-item.send-back.notification.success.title": "Vráceno zpět odesílateli", // "workflow-item.send-back.notification.success.content": "This workflow item was successfully sent back to the submitter", - "workflow-item.send-back.notification.success.content": "Tento záznam ve workflow byl úspěšně odeslán zpět vkladateli", + "workflow-item.send-back.notification.success.content": "Tento záznam ve workflow byl úspěšně vrácen zpět odesílateli", // "workflow-item.send-back.notification.error.title": "Something went wrong", "workflow-item.send-back.notification.error.title": "Něco se pokazilo", // "workflow-item.send-back.notification.error.content": "The workflow item could not be sent back to the submitter", - "workflow-item.send-back.notification.error.content": "Záznam ve workflow se nepodařilo odeslat zpět vkladateli", + "workflow-item.send-back.notification.error.content": "Záznam ve workflow se nepodařilo vrátit zpět odesílateli", // "workflow-item.send-back.title": "Send workflow item back to submitter", - "workflow-item.send-back.title": "Odeslat záznam ve workflow zpět vladateli", + "workflow-item.send-back.title": "Vrátit záznam ve workflow zpět odesílateli", // "workflow-item.send-back.header": "Send workflow item back to submitter", - "workflow-item.send-back.header": "Odeslat záznam ve workflow zpět vladateli", + "workflow-item.send-back.header": "Vrátit záznam ve workflow zpět odesílateli", // "workflow-item.send-back.button.cancel": "Cancel", "workflow-item.send-back.button.cancel": "Zrušit", @@ -9004,7 +9004,7 @@ "workspace-item.view.breadcrumbs": "Zobrazení pracovního prostoru", // "workspace-item.view.title": "Workspace View", - "workspace-item.view.title": "Zobrazení pracovní prostoru", + "workspace-item.view.title": "Zobrazení pracovního prostoru", // "workspace-item.delete.breadcrumbs": "Workspace Delete", "workspace-item.delete.breadcrumbs": "Vymazat obsah pracovního prostoru", @@ -9337,7 +9337,7 @@ "person.page.orcid.sync-queue.send.bad-request-error": "Odeslání do ORCID se nezdařilo, protože zdroj odeslaný do registru ORCID není platný", // "person.page.orcid.sync-queue.send.error": "The submission to ORCID failed", - "person.page.orcid.sync-queue.send.error": "Podání do ORCID se nezdařilo", + "person.page.orcid.sync-queue.send.error": "Odeslání do ORCID se nezdařilo", // "person.page.orcid.sync-queue.send.conflict-error": "The submission to ORCID failed because the resource is already present on the ORCID registry", "person.page.orcid.sync-queue.send.conflict-error": "Odeslání do ORCID se nezdařilo, protože zdroj se již nachází v registru ORCID", @@ -9490,7 +9490,7 @@ "system-wide-alert-form.retrieval.error": "Něco se pokazilo při načítání systémových upozornění", // "system-wide-alert.form.cancel": "Cancel", - "system-wide-alert.form.cancel": "Zrušir", + "system-wide-alert.form.cancel": "Zrušit", // "system-wide-alert.form.save": "Save", "system-wide-alert.form.save": "Uložit", @@ -9508,7 +9508,7 @@ "system-wide-alert.form.label.message": "Obsah upozornění", // "system-wide-alert.form.label.countdownTo.enable": "Enable a countdown timer", - "system-wide-alert.form.label.countdownTo.enable": "Povolit odpočítávací měřič", + "system-wide-alert.form.label.countdownTo.enable": "Povolit odpočet", // "system-wide-alert.form.label.countdownTo.hint": "Hint: Set a countdown timer. When enabled, a date can be set in the future and the system-wide alert banner will perform a countdown to the set date. When this timer ends, it will disappear from the alert. The server will NOT be automatically stopped.", "system-wide-alert.form.label.countdownTo.hint": "Nápověda: Nastavte odpočítávací měřič. Je-li to povoleno, lze datum nastavit i v budoucnosti a systémový upozornění bude uvádět odpočítávání do nastaveného data. Ve chvíli, kdy skončí odpočítávání, zmizí i odpočítávač z upozornění. Server NEBUDE automaticky zastaven.", @@ -9643,7 +9643,7 @@ // "vocabulary-treeview.search.form.add": "Add", // TODO New key - Add a translation - "vocabulary-treeview.search.form.add": "Add", + "vocabulary-treeview.search.form.add": "Přidat", // "admin.notifications.publicationclaim.breadcrumbs": "Publication Claim", // TODO New key - Add a translation From d586a8450d5d0207035d4703b7545d8caf1b5dfa Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Wed, 9 Oct 2024 16:14:04 +0200 Subject: [PATCH 641/875] Updated cs localization for subcommunities and subcollections (cherry picked from commit 9badd4a4b6261a2abf5ed838f16bb000b1a127f0) --- src/assets/i18n/cs.json5 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index 2cb95f10d3..09dd023e8d 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -2259,7 +2259,7 @@ "community.create.notifications.success": "Úspěšně vytvořena komunita", // "community.create.sub-head": "Create a Sub-Community for Community {{ parent }}", - "community.create.sub-head": "Vytvořit dílčí komunitu pro komunitu {{ parent }}", + "community.create.sub-head": "Vytvořit podkomunitu v komunitě {{ parent }}", // "community.curate.header": "Curate Community: {{community}}", "community.curate.header": "Spravovat komunitu: {{ community }}", @@ -2396,7 +2396,7 @@ "comcol-role.edit.collection-admin.name": "Správci", // "comcol-role.edit.community-admin.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", - "comcol-role.edit.community-admin.description": "Administrátoři komunit mohou vytvářet dílčí komunity nebo kolekce a spravovat nebo přidělovat správu těmto dílčím komunitám nebo kolekcím. Kromě toho rozhodují o tom, kdo může odesílat záznamy do všech dílčích kolekcí, upravovat metadata záznamů (po odeslání) a přidávat (mapovat) existující záznamy z jiných kolekcí (na základě oprávnění).", + "comcol-role.edit.community-admin.description": "Administrátoři komunit mohou vytvářet podkomunity nebo kolekce a spravovat nebo přidělovat správu těmto podkomunitám nebo kolekcím. Kromě toho rozhodují o tom, kdo může odesílat záznamy do všech podkolekcí, upravovat metadata záznamů (po odeslání) a přidávat (mapovat) existující záznamy z jiných kolekcí (na základě oprávnění).", // "comcol-role.edit.collection-admin.description": "Collection administrators decide who can submit items to the collection, edit item metadata (after submission), and add (map) existing items from other collections to this collection (subject to authorization for that collection).", "comcol-role.edit.collection-admin.description": "Správci kolekcí rozhodují o tom, kdo může do kolekce vkládat záznamy, upravovat metadata záznamů (po vložení) a přidávat (mapovat) existující záznamy z jiných kolekcí do této kolekce (podléhá autorizaci pro danou kolekci).", @@ -2421,7 +2421,7 @@ // "comcol-role.edit.bitstream_read.description": "E-People and Groups that can read new bitstreams submitted to this collection. Changes to this role are not retroactive. Existing bitstreams in the system will still be viewable by those who had read access at the time of their addition.", // TODO Source message changed - Revise the translation - "comcol-role.edit.bitstream_read.description": "Správci komunit mohou vytvářet dílčí komunity nebo kolekce a spravovat nebo přidělovat správu těmto dílčím komunitám nebo kolekcím. Kromě toho rozhodují o tom, kdo může odesílat záznamy do libovolných dílčích kolekcí, upravovat metadata záznamů (po odeslání) a přidávat (mapovat) existující záznamy z jiných kolekcí (na základě oprávnění).", + "comcol-role.edit.bitstream_read.description": "Správci komunit mohou vytvářet podkomunity nebo kolekce a spravovat nebo přidělovat správu těmto podkomunitám nebo kolekcím. Kromě toho rozhodují o tom, kdo může odesílat záznamy do libovolných podkolekcí, upravovat metadata záznamů (po odeslání) a přidávat (mapovat) existující záznamy z jiných kolekcí (na základě oprávnění).", // "comcol-role.edit.bitstream_read.anonymous-group": "Default read for incoming bitstreams is currently set to Anonymous.", "comcol-role.edit.bitstream_read.anonymous-group": "Výchozí čtení pro příchozí soubory je v současné době nastaveno na hodnotu Anonymní.", @@ -2481,7 +2481,7 @@ "community.page.news": "Novinky", // "community.all-lists.head": "Subcommunities and Collections", - "community.all-lists.head": "Dílčí komunity a kolekce", + "community.all-lists.head": "Podkomunity a kolekce", // "community.search.results.head": "Search Results", // TODO New key - Add a translation @@ -2927,10 +2927,10 @@ "error.invalid-search-query": "Vyhledávací dotaz není platný. Další informace o této chybě naleznete v osvědčených postupech Solr query syntax.", // "error.sub-collections": "Error fetching sub-collections", - "error.sub-collections": "Chyba při načítání dílčích kolekcí", + "error.sub-collections": "Chyba při načítání podkolekcí", // "error.sub-communities": "Error fetching sub-communities", - "error.sub-communities": "Chyba při načítání dílčích komunit", + "error.sub-communities": "Chyba při načítání podkomunit", // "error.submission.sections.init-form-error": "An error occurred during section initialize, please check your input-form configuration. Details are below :

    ", "error.submission.sections.init-form-error": "Při inicializaci sekce došlo k chybě, zkontrolujte prosím konfiguraci vstupního formuláře. Podrobnosti jsou uvedeny níže :

    ", @@ -4985,10 +4985,10 @@ "loading.search-results": "Načítají se výsledky vyhledávání...", // "loading.sub-collections": "Loading sub-collections...", - "loading.sub-collections": "Načítají se dílčí kolekce...", + "loading.sub-collections": "Načítají se podkolekce...", // "loading.sub-communities": "Loading sub-communities...", - "loading.sub-communities": "Načítají se dílčí komunity...", + "loading.sub-communities": "Načítají se podkomunity...", // "loading.top-level-communities": "Loading top-level communities...", "loading.top-level-communities": "Načítají se komunity nejvyšší úrovně...", From 68b6cc9bd45e01a306990c05b464e94cacb1a4f2 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Fri, 11 Oct 2024 13:37:24 +0200 Subject: [PATCH 642/875] Fixed small cs localization mistakes (cherry picked from commit 680d6c94166266d6195f13b92e3be916917ae8c0) --- src/assets/i18n/cs.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index 09dd023e8d..466e3475bf 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -2939,7 +2939,7 @@ "error.top-level-communities": "Chyba při načítání komunit nejvyšší úrovně", // "error.validation.license.notgranted": "You must grant this license to complete your submission. If you are unable to grant this license at this time you may save your work and return later or remove the submission.", - "error.validation.license.notgranted": "Bez udělení licence nelze záznam dokončit. Pokud v tuto chvíli nemůžete licenci udělit, uložte svou práci a vraťte se k příspěveku později nebo jej smažte.", + "error.validation.license.notgranted": "Bez udělení licence nelze záznam dokončit. Pokud v tuto chvíli nemůžete licenci udělit, uložte svou práci a vraťte se k příspěvku později nebo jej smažte.", // "error.validation.pattern": "This input is restricted by the current pattern: {{ pattern }}.", "error.validation.pattern": "Toto zadání je omezeno aktuálním vzorcem: {{ pattern }}.", @@ -4934,7 +4934,7 @@ "iiif.page.doi": "Trvalý odkaz: ", // "iiif.page.issue": "Issue: ", - "iiif.page.issue": "Číslo:", + "iiif.page.issue": "Číslo: ", // "iiif.page.description": "Description: ", "iiif.page.description": "Popis: ", From b11efbbf0cde5cde73f3873a41539bec7588cf9c Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Mon, 4 Nov 2024 14:37:04 +0100 Subject: [PATCH 643/875] Updated messages for the 'supervised' and 'claim' sentenses (cherry picked from commit 1aef6ce1d623ac8f0a52dcf8086847996a2d0180) --- src/assets/i18n/cs.json5 | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index 466e3475bf..f06db668a3 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -4221,10 +4221,10 @@ "workflow-item.search.result.notification.deleted.failure": "Při mazání příkazu supervize \"{{name}}\" došlo k chybě", // "workflow-item.search.result.list.element.supervised-by": "Supervised by:", - "workflow-item.search.result.list.element.supervised-by": "Supervizován:", + "workflow-item.search.result.list.element.supervised-by": "Pod dohledem:", // "workflow-item.search.result.list.element.supervised.remove-tooltip": "Remove supervision group", - "workflow-item.search.result.list.element.supervised.remove-tooltip": "Odebrat skupinu supervize", + "workflow-item.search.result.list.element.supervised.remove-tooltip": "Odebrat skupinu dohledu", // "confidence.indicator.help-text.accepted": "This authority value has been confirmed as accurate by an interactive user", // TODO New key - Add a translation @@ -4380,10 +4380,10 @@ "item.page.version.hasDraft": "Nová verze nemůže být vytvořena, protože v historii verzí už další rozpracovaná verze existuje.", // "item.page.claim.button": "Claim", - "item.page.claim.button": "Prohlásit", + "item.page.claim.button": "Převzít", // "item.page.claim.tooltip": "Claim this item as profile", - "item.page.claim.tooltip": "Prohlásit tento záznam za profilovou", + "item.page.claim.tooltip": "Prohlásit tento záznam za profil", // "item.page.image.alt.ROR": "ROR logo", // TODO New key - Add a translation @@ -5414,7 +5414,7 @@ "mydspace.show.workspace": "Vaše záznamy", // "mydspace.show.supervisedWorkspace": "Supervised items", - "mydspace.show.supervisedWorkspace": "Zkontrolované záznamy", + "mydspace.show.supervisedWorkspace": "Záznamy pod dohledem", // "mydspace.status.mydspaceArchived": "Archived", "mydspace.status.mydspaceArchived": "Archivováno", @@ -6922,7 +6922,7 @@ "search.filters.applied.f.birthDate.min": "Datum narození od", // "search.filters.applied.f.supervisedBy": "Supervised by", - "search.filters.applied.f.supervisedBy": "Zkontrolováno", + "search.filters.applied.f.supervisedBy": "Pod dohledem", // "search.filters.applied.f.withdrawn": "Withdrawn", "search.filters.applied.f.withdrawn": "Vyřazeno", @@ -7221,8 +7221,7 @@ "search.filters.filter.supervisedBy.placeholder": "Supervised By", // "search.filters.filter.supervisedBy.label": "Search Supervised By", - // TODO New key - Add a translation - "search.filters.filter.supervisedBy.label": "Search Supervised By", + "search.filters.filter.supervisedBy.label": "Hledat pod dohledem", // "search.filters.entityType.JournalIssue": "Journal Issue", "search.filters.entityType.JournalIssue": "Číslo časopisu", @@ -8924,7 +8923,7 @@ "virtual-metadata.delete-relationship.modal-head": "Vyberte záznamy, pro které chcete uložit virtuální metadata jako skutečná metadata", // "supervisedWorkspace.search.results.head": "Supervised Items", - "supervisedWorkspace.search.results.head": "Zkontrolované záznamy", + "supervisedWorkspace.search.results.head": "Záznamy pod dohledem", // "workspace.search.results.head": "Your submissions", "workspace.search.results.head": "Moje záznamy", From bd638f03560015a5bdc31108227810ca1fdba540 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Fri, 8 Nov 2024 15:24:06 +0100 Subject: [PATCH 644/875] Updated supervised by messages following NTK suggestions (cherry picked from commit d819cf43968665c20870ee16a1620e744dfbd821) --- src/assets/i18n/cs.json5 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index f06db668a3..866660f513 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -4221,10 +4221,10 @@ "workflow-item.search.result.notification.deleted.failure": "Při mazání příkazu supervize \"{{name}}\" došlo k chybě", // "workflow-item.search.result.list.element.supervised-by": "Supervised by:", - "workflow-item.search.result.list.element.supervised-by": "Pod dohledem:", + "workflow-item.search.result.list.element.supervised-by": "Dohlížející autorita:", // "workflow-item.search.result.list.element.supervised.remove-tooltip": "Remove supervision group", - "workflow-item.search.result.list.element.supervised.remove-tooltip": "Odebrat skupinu dohledu", + "workflow-item.search.result.list.element.supervised.remove-tooltip": "Odebrat dohlížející autoritu", // "confidence.indicator.help-text.accepted": "This authority value has been confirmed as accurate by an interactive user", // TODO New key - Add a translation @@ -7221,7 +7221,7 @@ "search.filters.filter.supervisedBy.placeholder": "Supervised By", // "search.filters.filter.supervisedBy.label": "Search Supervised By", - "search.filters.filter.supervisedBy.label": "Hledat pod dohledem", + "search.filters.filter.supervisedBy.label": "Hledat dohlížející autoritu", // "search.filters.entityType.JournalIssue": "Journal Issue", "search.filters.entityType.JournalIssue": "Číslo časopisu", From 66dedc5b2e08de7c3691fb21a7238d1ac08be15c Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Fri, 19 Jul 2024 14:54:39 +0200 Subject: [PATCH 645/875] [CST-15591] Fixed headings by their rank --- .../create-collection-page.component.html | 2 +- .../create-community-page.component.html | 4 ++-- .../workspaceitem/workspaceitem-actions.component.html | 2 +- .../item-list-preview/item-list-preview.component.html | 2 +- .../scope-selector-modal.component.html | 6 +++--- .../search/search-results/search-results.component.html | 2 +- .../sections/upload/section-upload.component.html | 2 +- .../workspaceitems-delete-page.component.html | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/app/collection-page/create-collection-page/create-collection-page.component.html b/src/app/collection-page/create-collection-page/create-collection-page.component.html index f3f9785692..4a09d25aed 100644 --- a/src/app/collection-page/create-collection-page/create-collection-page.component.html +++ b/src/app/collection-page/create-collection-page/create-collection-page.component.html @@ -1,7 +1,7 @@
    -

    {{'collection.create.sub-head' | translate:{ parent: dsoNameService.getName((parentRD$| async)?.payload) } }}

    +

    {{'collection.create.sub-head' | translate:{ parent: dsoNameService.getName((parentRD$| async)?.payload) } }}

    - -

    {{ 'community.create.sub-head' | translate:{ parent: dsoNameService.getName(parent) } }}

    +

    {{ 'community.create.head' | translate }}

    +

    {{ 'community.create.sub-head' | translate:{ parent: dsoNameService.getName(parent) } }}

    diff --git a/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.html b/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.html index 6e958c7a8b..26066df4b5 100644 --- a/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.html +++ b/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.html @@ -30,7 +30,7 @@ -

    +

    diff --git a/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.html b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.html index 013274320b..74c71b1d12 100644 --- a/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.html +++ b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.html @@ -6,14 +6,14 @@
    -
    {{'dso-selector.' + action + '.' + objectType.toString().toLowerCase() + '.input-header' | translate}}
    +

    {{'dso-selector.' + action + '.' + objectType.toString().toLowerCase() + '.input-header' | 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 11c589254a..1f1e58ea10 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -1,5 +1,5 @@
    -

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

    +

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

    diff --git a/src/app/submission/sections/upload/section-upload.component.html b/src/app/submission/sections/upload/section-upload.component.html index b57b454288..3c4fbe49f6 100644 --- a/src/app/submission/sections/upload/section-upload.component.html +++ b/src/app/submission/sections/upload/section-upload.component.html @@ -5,7 +5,7 @@
    -

    {{'submission.sections.upload.no-file-uploaded' | translate}}

    +
    {{'submission.sections.upload.no-file-uploaded' | translate}}
    diff --git a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.html b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.html index a0f0a1711e..9d9bece197 100644 --- a/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.html +++ b/src/app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component.html @@ -1,5 +1,5 @@
    -

    {{ 'workspace-item.delete.header' | translate }}

    +

    {{ 'workspace-item.delete.header' | translate }}

    @@ -7,7 +7,7 @@ diff --git a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.spec.ts index 2d0a2a25de..d21714a5ef 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.spec.ts +++ b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.spec.ts @@ -8,6 +8,9 @@ import { Community } from '../../../../core/shared/community.model'; import { CreateCommunityParentSelectorComponent } from './create-community-parent-selector.component'; import { MetadataValue } from '../../../../core/shared/metadata.models'; import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; +import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; +import { By } from '@angular/platform-browser'; +import { of as observableOf } from 'rxjs'; describe('CreateCommunityParentSelectorComponent', () => { let component: CreateCommunityParentSelectorComponent; @@ -26,7 +29,9 @@ describe('CreateCommunityParentSelectorComponent', () => { const communityRD = createSuccessfulRemoteDataObject(community); const modalStub = jasmine.createSpyObj('modalStub', ['close']); const createPath = '/communities/create'; - + const mockAuthorizationDataService = jasmine.createSpyObj('authorizationService', { + isAuthorized: observableOf(true), + }); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], @@ -47,7 +52,8 @@ describe('CreateCommunityParentSelectorComponent', () => { }, { provide: Router, useValue: router - } + }, + { provide: AuthorizationDataService, useValue: mockAuthorizationDataService }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -70,4 +76,20 @@ describe('CreateCommunityParentSelectorComponent', () => { expect(router.navigate).toHaveBeenCalledWith([createPath], { queryParams: { parent: community.uuid } }); }); + it('should show the div when user is an admin', (waitForAsync(() => { + component.isAdmin$ = observableOf(true); + fixture.detectChanges(); + + const divElement = fixture.debugElement.query(By.css('div[data-test="admin-div"]')); + expect(divElement).toBeTruthy(); + }))); + + it('should hide the div when user is not an admin', (waitForAsync(() => { + component.isAdmin$ = observableOf(false); + fixture.detectChanges(); + + const divElement = fixture.debugElement.query(By.css('div[data-test="admin-div"]')); + expect(divElement).toBeFalsy(); + }))); + }); diff --git a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts index 77458d9802..e44a7450e6 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts @@ -14,6 +14,9 @@ import { } from '../../../../community-page/community-page-routing-paths'; import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; import { environment } from '../../../../../environments/environment'; +import { FeatureID } from '../../../../core/data/feature-authorization/feature-id'; +import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; +import { Observable } from 'rxjs'; /** * Component to wrap a button - for top communities - @@ -32,11 +35,16 @@ export class CreateCommunityParentSelectorComponent extends DSOSelectorModalWrap selectorTypes = [DSpaceObjectType.COMMUNITY]; action = SelectorActionType.CREATE; defaultSort = new SortOptions(environment.comcolSelectionSort.sortField, environment.comcolSelectionSort.sortDirection as SortDirection); + isAdmin$: Observable; - constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) { + constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router, protected authorizationService: AuthorizationDataService) { super(activeModal, route); } + ngOnInit() { + this.isAdmin$ = this.authorizationService.isAuthorized(FeatureID.AdministratorOf); + } + /** * Navigate to the community create page */ From 8849f140fbf47e8a15dda672ecec7770d49308b7 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 28 Nov 2024 22:39:08 +0100 Subject: [PATCH 652/875] 117287: Removed method calls returning observables from ItemDetailPreviewComponent --- .../item-detail-preview.component.html | 2 +- .../item-detail-preview.component.ts | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.html b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.html index bd00c9411f..c2f5299d4b 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.html +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.html @@ -8,7 +8,7 @@ - +
    + diff --git a/src/app/shared/form/form.service.ts b/src/app/shared/form/form.service.ts index bf316daaed..e6c3776a42 100644 --- a/src/app/shared/form/form.service.ts +++ b/src/app/shared/form/form.service.ts @@ -130,6 +130,9 @@ export class FormService { } public addControlErrors(field: AbstractControl, formId: string, fieldId: string, fieldIndex: number) { + if (field.errors === null) { + return; + } const errors: string[] = Object.keys(field.errors) .filter((errorKey) => field.errors[errorKey] === true) .map((errorKey) => `error.validation.${errorKey}`); From 987ea5104adda0f7887a4fef710a11b6fa165989 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 19 Nov 2024 16:35:45 +0100 Subject: [PATCH 657/875] 120109: Fixed BaseDataService not emitting when the request is too fast and the ResponsePending are not emitted (cherry picked from commit 0f4d71eb58d63c9c73af2e098126437da0f266c4) --- .../core/data/base/base-data.service.spec.ts | 69 +++++++++++++++---- src/app/core/data/base/base-data.service.ts | 6 +- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/app/core/data/base/base-data.service.spec.ts b/src/app/core/data/base/base-data.service.spec.ts index 098f075c10..9ec31b376e 100644 --- a/src/app/core/data/base/base-data.service.spec.ts +++ b/src/app/core/data/base/base-data.service.spec.ts @@ -50,6 +50,7 @@ describe('BaseDataService', () => { let selfLink; let linksToFollow; let testScheduler; + let remoteDataTimestamp: number; let remoteDataMocks; function initTestService(): TestService { @@ -86,19 +87,21 @@ describe('BaseDataService', () => { expect(actual).toEqual(expected); }); - const timeStamp = new Date().getTime(); + // 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; const msToLive = 15 * 60 * 1000; const payload = { foo: 'bar' }; const statusCodeSuccess = 200; const statusCodeError = 404; const errorMessage = 'not found'; remoteDataMocks = { - RequestPending: new RemoteData(undefined, msToLive, timeStamp, RequestEntryState.RequestPending, undefined, undefined, undefined), - ResponsePending: new RemoteData(undefined, msToLive, timeStamp, RequestEntryState.ResponsePending, undefined, undefined, undefined), - Success: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Success, undefined, payload, statusCodeSuccess), - SuccessStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.SuccessStale, undefined, payload, statusCodeSuccess), - Error: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Error, errorMessage, undefined, statusCodeError), - ErrorStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.ErrorStale, errorMessage, undefined, statusCodeError), + 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, payload, statusCodeSuccess), + SuccessStale: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.SuccessStale, undefined, payload, statusCodeSuccess), + Error: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.Error, errorMessage, undefined, statusCodeError), + ErrorStale: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.ErrorStale, errorMessage, undefined, statusCodeError), }; return new TestService( @@ -330,11 +333,15 @@ describe('BaseDataService', () => { spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source); }); - - it(`should not emit a cached completed RemoteData, but only start emitting after the state first changes to RequestPending`, () => { + it('should not emit a cached completed RemoteData', () => { + // Old cached value from 1 minute before the test started + const oldCachedSucceededData: RemoteData = Object.assign({}, remoteDataMocks.Success, { + timeCompleted: remoteDataTimestamp - 2 * 60 * 1000, + lastUpdated: remoteDataTimestamp - 2 * 60 * 1000, + } as RemoteData); testScheduler.run(({ cold, expectObservable }) => { spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', { - a: remoteDataMocks.Success, + a: oldCachedSucceededData, b: remoteDataMocks.RequestPending, c: remoteDataMocks.ResponsePending, d: remoteDataMocks.Success, @@ -352,6 +359,22 @@ describe('BaseDataService', () => { }); }); + it('should emit the first completed RemoteData since the request was made', () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(rdbService, 'buildSingle').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.findByHref(selfLink, false, true, ...linksToFollow)).toBe(expected, values); + }); + }); + it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => { testScheduler.run(({ cold, expectObservable }) => { spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', { @@ -514,11 +537,15 @@ describe('BaseDataService', () => { spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source); }); - - it(`should not emit a cached completed RemoteData, but only start emitting after the state first changes to RequestPending`, () => { + it('should not emit a cached completed RemoteData', () => { testScheduler.run(({ cold, expectObservable }) => { + // Old cached value from 1 minute before the test started + const oldCachedSucceededData: RemoteData = Object.assign({}, remoteDataMocks.Success, { + timeCompleted: remoteDataTimestamp - 2 * 60 * 1000, + lastUpdated: remoteDataTimestamp - 2 * 60 * 1000, + } as RemoteData); spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', { - a: remoteDataMocks.Success, + a: oldCachedSucceededData, b: remoteDataMocks.RequestPending, c: remoteDataMocks.ResponsePending, d: remoteDataMocks.Success, @@ -536,6 +563,22 @@ describe('BaseDataService', () => { }); }); + it('should emit the first completed RemoteData since the request was made', () => { + testScheduler.run(({ cold, expectObservable }) => { + spyOn(rdbService, 'buildList').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.findListByHref(selfLink, findListOptions, false, true, ...linksToFollow)).toBe(expected, values); + }); + }); + it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => { testScheduler.run(({ cold, expectObservable }) => { spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', { diff --git a/src/app/core/data/base/base-data.service.ts b/src/app/core/data/base/base-data.service.ts index edd6d9e2a4..18131dea63 100644 --- a/src/app/core/data/base/base-data.service.ts +++ b/src/app/core/data/base/base-data.service.ts @@ -266,6 +266,7 @@ export class BaseDataService implements HALDataServic map((href: string) => this.buildHrefFromFindOptions(href, {}, [], ...linksToFollow)), ); + const startTime: number = new Date().getTime(); this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable); return this.rdbService.buildSingle(requestHref$, ...linksToFollow).pipe( @@ -273,7 +274,7 @@ export class BaseDataService implements HALDataServic // 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) => useCachedVersionIfAvailable ? rd.isStale : rd.hasCompleted), + skipWhile((rd: RemoteData) => rd.isStale || (!useCachedVersionIfAvailable && rd.lastUpdated < startTime)), this.reRequestStaleRemoteData(reRequestOnStale, () => this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)), ); @@ -300,6 +301,7 @@ export class BaseDataService implements HALDataServic map((href: string) => this.buildHrefFromFindOptions(href, options, [], ...linksToFollow)), ); + const startTime: number = new Date().getTime(); this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable); return this.rdbService.buildList(requestHref$, ...linksToFollow).pipe( @@ -307,7 +309,7 @@ export class BaseDataService implements HALDataServic // 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>) => useCachedVersionIfAvailable ? rd.isStale : rd.hasCompleted), + skipWhile((rd: RemoteData>) => rd.isStale || (!useCachedVersionIfAvailable && rd.lastUpdated < startTime)), this.reRequestStaleRemoteData(reRequestOnStale, () => this.findListByHref(href$, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)), ); From 91acc957b13675ab8c1fdab141579b8794afd2d2 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 19 Nov 2024 18:14:12 +0100 Subject: [PATCH 658/875] 120109: Fixed "no elements in sequence" sometimes being thrown on the item bitstream & relationship tabs (cherry picked from commit decacec40437e891c672d1842598dfabd55f204a) --- .../abstract-item-update/abstract-item-update.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts b/src/app/item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts index 80002f614b..3745b972d6 100644 --- a/src/app/item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts +++ b/src/app/item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts @@ -6,7 +6,7 @@ import { ObjectUpdatesService } from '../../../core/data/object-updates/object-u import { ActivatedRoute, Router, Data } from '@angular/router'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; -import { first, map, switchMap, tap } from 'rxjs/operators'; +import { take, map, switchMap, tap } from 'rxjs/operators'; import { RemoteData } from '../../../core/data/remote-data'; import { AbstractTrackableComponent } from '../../../shared/trackable/abstract-trackable.component'; import { environment } from '../../../../environments/environment'; @@ -85,7 +85,7 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl if (this.url.indexOf('?') > 0) { this.url = this.url.substr(0, this.url.indexOf('?')); } - this.hasChanges().pipe(first()).subscribe((hasChanges) => { + this.hasChanges().pipe(take(1)).subscribe((hasChanges) => { if (!hasChanges) { this.initializeOriginalFields(); } else { @@ -170,7 +170,7 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl */ private checkLastModified() { const currentVersion = this.item.lastModified; - this.objectUpdatesService.getLastModified(this.url).pipe(first()).subscribe( + this.objectUpdatesService.getLastModified(this.url).pipe(take(1)).subscribe( (updateVersion: Date) => { if (updateVersion.getDate() !== currentVersion.getDate()) { this.notificationsService.warning(this.getNotificationTitle('outdated'), this.getNotificationContent('outdated')); From ce17d23c08c2e5eef9f318c4095e95abbe626108 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 19 Nov 2024 18:45:16 +0100 Subject: [PATCH 659/875] 120109: Updated the route configuration to only resolve the dsoEditMenuResolver on pages who use the DsoEditMenuComponent (cherry picked from commit 5c9f494f7692ae7d664cf89d970c330528878abe) --- src/app/collection-page/collection-page-routing.module.ts | 4 +++- src/app/community-page/community-page-routing.module.ts | 4 +++- src/app/item-page/item-page-routing.module.ts | 7 ++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/app/collection-page/collection-page-routing.module.ts b/src/app/collection-page/collection-page-routing.module.ts index 9dc25b778e..6d07c467e6 100644 --- a/src/app/collection-page/collection-page-routing.module.ts +++ b/src/app/collection-page/collection-page-routing.module.ts @@ -36,7 +36,6 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; resolve: { dso: CollectionPageResolver, breadcrumb: CollectionBreadcrumbResolver, - menu: DSOEditMenuResolver }, runGuardsAndResolvers: 'always', children: [ @@ -66,6 +65,9 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; path: '', component: ThemedCollectionPageComponent, pathMatch: 'full', + resolve: { + menu: DSOEditMenuResolver, + }, } ], data: { diff --git a/src/app/community-page/community-page-routing.module.ts b/src/app/community-page/community-page-routing.module.ts index c37f8832f8..6412eb9f8e 100644 --- a/src/app/community-page/community-page-routing.module.ts +++ b/src/app/community-page/community-page-routing.module.ts @@ -29,7 +29,6 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; resolve: { dso: CommunityPageResolver, breadcrumb: CommunityBreadcrumbResolver, - menu: DSOEditMenuResolver }, runGuardsAndResolvers: 'always', children: [ @@ -49,6 +48,9 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; path: '', component: ThemedCommunityPageComponent, pathMatch: 'full', + resolve: { + menu: DSOEditMenuResolver, + }, } ], data: { diff --git a/src/app/item-page/item-page-routing.module.ts b/src/app/item-page/item-page-routing.module.ts index 0c855ab34d..5fb11f7056 100644 --- a/src/app/item-page/item-page-routing.module.ts +++ b/src/app/item-page/item-page-routing.module.ts @@ -28,7 +28,6 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; resolve: { dso: ItemPageResolver, breadcrumb: ItemBreadcrumbResolver, - menu: DSOEditMenuResolver }, runGuardsAndResolvers: 'always', children: [ @@ -36,10 +35,16 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; path: '', component: ThemedItemPageComponent, pathMatch: 'full', + resolve: { + menu: DSOEditMenuResolver, + }, }, { path: 'full', component: ThemedFullItemPageComponent, + resolve: { + menu: DSOEditMenuResolver, + }, }, { path: ITEM_EDIT_PATH, From b5a8b564730a68b382eaa4951c8fea89eb479c7a Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 29 Oct 2024 18:33:01 +0100 Subject: [PATCH 660/875] 117287: Prevent /api/eperson/epersons/undefined from being fired on the create ePerson page (cherry picked from commit 0cb5b76159ce41b8b15192010ff7c89ab9911d21) --- .../eperson-form/eperson-form.component.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts index 97a0a13727..9526725200 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -223,9 +223,11 @@ export class EPersonFormComponent implements OnInit, OnDestroy { * This method will initialise the page */ initialisePage() { - this.subs.push(this.epersonService.findById(this.route.snapshot.params.id).subscribe((ePersonRD: RemoteData) => { - this.epersonService.editEPerson(ePersonRD.payload); - })); + if (this.route.snapshot.params.id) { + this.subs.push(this.epersonService.findById(this.route.snapshot.params.id).subscribe((ePersonRD: RemoteData) => { + this.epersonService.editEPerson(ePersonRD.payload); + })); + } this.firstName = new DynamicInputModel({ id: 'firstName', label: this.translateService.instant(`${this.messagePrefix}.firstName`), From 830ada37f5ce502cea50a7772779d657cd4d0b3d Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 6 Nov 2024 14:07:58 +0100 Subject: [PATCH 661/875] 119915: Restored functionality to hide the research profile section on the profile page (cherry picked from commit ded0079f24e0f6670f357e545e9ff14219aa4913) --- src/app/profile-page/profile-page.component.html | 2 +- src/app/profile-page/profile-page.component.spec.ts | 6 +++--- src/app/profile-page/profile-page.component.ts | 7 ------- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/app/profile-page/profile-page.component.html b/src/app/profile-page/profile-page.component.html index 331c360054..e2769b38ec 100644 --- a/src/app/profile-page/profile-page.component.html +++ b/src/app/profile-page/profile-page.component.html @@ -1,7 +1,7 @@

    {{'profile.title' | translate}}

    - +
    {{'profile.card.researcher' | translate}}
    diff --git a/src/app/profile-page/profile-page.component.spec.ts b/src/app/profile-page/profile-page.component.spec.ts index 709ed56790..ec07cddd9d 100644 --- a/src/app/profile-page/profile-page.component.spec.ts +++ b/src/app/profile-page/profile-page.component.spec.ts @@ -320,7 +320,7 @@ describe('ProfilePageComponent', () => { }); it('should return true', () => { - const result = component.isResearcherProfileEnabled(); + const result = component.isResearcherProfileEnabled$; const expected = cold('a', { a: true }); @@ -336,7 +336,7 @@ describe('ProfilePageComponent', () => { }); it('should return false', () => { - const result = component.isResearcherProfileEnabled(); + const result = component.isResearcherProfileEnabled$; const expected = cold('a', { a: false }); @@ -352,7 +352,7 @@ describe('ProfilePageComponent', () => { }); it('should return false', () => { - const result = component.isResearcherProfileEnabled(); + const result = component.isResearcherProfileEnabled$; const expected = cold('a', { a: false }); diff --git a/src/app/profile-page/profile-page.component.ts b/src/app/profile-page/profile-page.component.ts index d49bdedb83..6e693461d2 100644 --- a/src/app/profile-page/profile-page.component.ts +++ b/src/app/profile-page/profile-page.component.ts @@ -192,13 +192,6 @@ export class ProfilePageComponent implements OnInit { this.updateProfile(); } - /** - * Returns true if the researcher profile feature is enabled, false otherwise. - */ - isResearcherProfileEnabled(): Observable { - return this.isResearcherProfileEnabled$.asObservable(); - } - /** * Returns an error message from a password validation request with a specific reason or * a default message without specific reason. From 5ee721f2c4b11c1d25c974d2e7c5880e7b9c45de Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 6 Dec 2024 18:08:57 +0100 Subject: [PATCH 662/875] 117287: Embed the communities/collections on the EPerson groups --- .../epeople-registry/eperson-form/eperson-form.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts index 296038ca2b..2e13671178 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -279,7 +279,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { this.groups = this.groupsDataService.findListByHref(eperson._links.groups.href, { currentPage: 1, elementsPerPage: this.config.pageSize - }); + }, undefined, undefined, followLink('object')); } this.formGroup.patchValue({ firstName: eperson != null ? eperson.firstMetadataValue('eperson.firstname') : '', From 297fc018922b213cc41e645e444562b562284719 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 02:29:01 +0000 Subject: [PATCH 663/875] Bump axios from 1.7.7 to 1.7.9 Bumps [axios](https://github.com/axios/axios) from 1.7.7 to 1.7.9. - [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.7.7...v1.7.9) --- updated-dependencies: - dependency-name: axios dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f8f725fafc..fe1adc036e 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@nicky-lenaers/ngx-scroll-to": "^14.0.0", "angular-idle-preload": "3.0.0", "angulartics2": "^12.2.1", - "axios": "^1.7.7", + "axios": "^1.7.9", "bootstrap": "^4.6.1", "cerialize": "0.1.18", "cli-progress": "^3.12.0", diff --git a/yarn.lock b/yarn.lock index 0106427952..8064831647 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3416,10 +3416,10 @@ axios@0.21.4: dependencies: follow-redirects "^1.14.0" -axios@^1.7.7: - version "1.7.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" - integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== +axios@^1.7.9: + version "1.7.9" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a" + integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" From 5a88cedc22e2f8a3264d03795ccf3cb718d52962 Mon Sep 17 00:00:00 2001 From: Andreas Awouters Date: Wed, 11 Dec 2024 11:17:01 +0100 Subject: [PATCH 664/875] 118223: Remove console.log --- .../edit-item-page/item-bitstreams/item-bitstreams.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts index 9bbf380487..bc771971d3 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts @@ -318,7 +318,6 @@ export class ItemBitstreamsService { switchMap(() => this.requestService.setStaleByHrefSubstring(bundle.self)), take(1), ).subscribe(() => { - console.log('got here!'); this.isPerformingMoveRequest.next(false); finish?.(); }); From c9d6c9556337c4875cfa5d83ede37f0e720d910a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 16 Dec 2024 14:06:50 -0600 Subject: [PATCH 665/875] Use fully qualified image names in Dockerfiles. Minor syntax fixes to ENV variables --- Dockerfile | 4 ++-- Dockerfile.dist | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8fac7495e1..e395e4b90e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # This image will be published as dspace/dspace-angular # See https://github.com/DSpace/dspace-angular/tree/main/docker for usage details -FROM node:18-alpine +FROM docker.io/node:18-alpine # Ensure Python and other build tools are available # These are needed to install some node modules, especially on linux/arm64 @@ -24,5 +24,5 @@ ENV NODE_OPTIONS="--max_old_space_size=4096" # Listen / accept connections from all IP addresses. # NOTE: At this time it is only possible to run Docker container in Production mode # if you have a public URL. See https://github.com/DSpace/dspace-angular/issues/1485 -ENV NODE_ENV development +ENV NODE_ENV=development CMD yarn serve --host 0.0.0.0 diff --git a/Dockerfile.dist b/Dockerfile.dist index 641c765cf2..c3ea539e04 100644 --- a/Dockerfile.dist +++ b/Dockerfile.dist @@ -4,7 +4,7 @@ # Test build: # docker build -f Dockerfile.dist -t dspace/dspace-angular:dspace-7_x-dist . -FROM node:18-alpine AS build +FROM docker.io/node:18-alpine AS build # Ensure Python and other build tools are available # These are needed to install some node modules, especially on linux/arm64 @@ -26,6 +26,6 @@ COPY --chown=node:node docker/dspace-ui.json /app/dspace-ui.json WORKDIR /app USER node -ENV NODE_ENV production +ENV NODE_ENV=production EXPOSE 4000 CMD pm2-runtime start dspace-ui.json --json From 996c877412a15483313bd7ae9f3c7f125a36f570 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 16 Dec 2024 14:12:01 -0600 Subject: [PATCH 666/875] Allow for other Docker registries to be used with all Docker compose scripts --- docker/cli.yml | 2 +- docker/db.entities.yml | 2 +- docker/docker-compose-ci.yml | 6 +++--- docker/docker-compose-dist.yml | 2 +- docker/docker-compose-rest.yml | 6 +++--- docker/docker-compose.yml | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docker/cli.yml b/docker/cli.yml index 890fbde956..11fe2ee662 100644 --- a/docker/cli.yml +++ b/docker/cli.yml @@ -21,7 +21,7 @@ networks: external: true services: dspace-cli: - image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}" + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}" container_name: dspace-cli environment: # Below syntax may look odd, but it is how to override dspace.cfg settings via env variables. diff --git a/docker/db.entities.yml b/docker/db.entities.yml index 5c0a15c8f6..26d848e022 100644 --- a/docker/db.entities.yml +++ b/docker/db.entities.yml @@ -14,7 +14,7 @@ # # Therefore, it should be kept in sync with that file services: dspacedb: - image: dspace/dspace-postgres-pgcrypto:loadsql + image: ${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}-loadsql environment: # This LOADSQL should be kept in sync with the URL in DSpace/DSpace # This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data diff --git a/docker/docker-compose-ci.yml b/docker/docker-compose-ci.yml index 9431a02a87..e09d88b472 100644 --- a/docker/docker-compose-ci.yml +++ b/docker/docker-compose-ci.yml @@ -33,7 +33,7 @@ services: # This allows us to generate statistics in e2e tests so that statistics pages can be tested thoroughly. solr__D__statistics__P__autoCommit: 'false' LOGGING_CONFIG: /dspace/config/log4j2-container.xml - image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}" + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}" depends_on: - dspacedb networks: @@ -60,7 +60,7 @@ services: # NOTE: This is customized to use our loadsql image, so that we are using a database with existing test data dspacedb: container_name: dspacedb - image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x-loadsql}" + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}-loadsql" environment: # This LOADSQL should be kept in sync with the LOADSQL in # https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml @@ -81,7 +81,7 @@ services: # DSpace Solr container dspacesolr: container_name: dspacesolr - image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}" + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}" networks: - dspacenet ports: diff --git a/docker/docker-compose-dist.yml b/docker/docker-compose-dist.yml index 1f4d2d7f5e..88e5be16a5 100644 --- a/docker/docker-compose-dist.yml +++ b/docker/docker-compose-dist.yml @@ -26,7 +26,7 @@ services: DSPACE_REST_HOST: demo.dspace.org DSPACE_REST_PORT: 443 DSPACE_REST_NAMESPACE: /server - image: dspace/dspace-angular:dspace-7_x-dist + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-dspace-7_x}-dist" build: context: .. dockerfile: Dockerfile.dist diff --git a/docker/docker-compose-rest.yml b/docker/docker-compose-rest.yml index 37bccee364..19d4d3c604 100644 --- a/docker/docker-compose-rest.yml +++ b/docker/docker-compose-rest.yml @@ -40,7 +40,7 @@ services: # from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above. proxies__P__trusted__P__ipranges: '172.23.0' LOGGING_CONFIG: /dspace/config/log4j2-container.xml - image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}" + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-test}" depends_on: - dspacedb networks: @@ -67,7 +67,7 @@ services: dspacedb: container_name: dspacedb # Uses a custom Postgres image with pgcrypto installed - image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}" + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-7_x}" environment: PGDATA: /pgdata POSTGRES_PASSWORD: dspace @@ -84,7 +84,7 @@ services: # DSpace Solr container dspacesolr: container_name: dspacesolr - image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}" + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-7_x}" networks: - dspacenet ports: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 8bce9548ab..90a1d0c21c 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -23,7 +23,7 @@ services: DSPACE_REST_HOST: localhost DSPACE_REST_PORT: 8080 DSPACE_REST_NAMESPACE: /server - image: dspace/dspace-angular:dspace-7_x + image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-dspace-7_x}" build: context: .. dockerfile: Dockerfile From 686b61915a39c53880af0c75c6e2fae72c90d094 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 16 Dec 2024 14:15:52 -0600 Subject: [PATCH 667/875] Update GitHub Actions for Docker & normal build to use GitHub Container Registry --- .github/workflows/build.yml | 3 +++ .github/workflows/docker.yml | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f6ffa5e004..d0a03cf413 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,6 +35,9 @@ jobs: NODE_OPTIONS: '--max-old-space-size=4096' # Project name to use when running "docker compose" prior to e2e tests COMPOSE_PROJECT_NAME: 'ci' + # Docker Registry to use for Docker compose scripts below. + # We use GitHub's Container Registry to avoid aggressive rate limits at DockerHub. + DOCKER_REGISTRY: ghcr.io strategy: # Create a matrix of Node versions to test against (in parallel) matrix: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index db42a36d3b..6b299ba6f1 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,6 +17,7 @@ on: permissions: contents: read # to fetch code (actions/checkout) + packages: write # to write images to GitHub Container Registry (GHCR) jobs: ############################################################# From cd500007b66b239322918beaedcea028491ea13f Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 16 Dec 2024 15:09:13 -0600 Subject: [PATCH 668/875] Login to GHCR in order to have access to private Docker images for e2e tests. --- .github/workflows/build.yml | 11 ++++++++++- .github/workflows/docker.yml | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d0a03cf413..bb641bea1e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,8 @@ name: Build on: [push, pull_request] permissions: - contents: read # to fetch code (actions/checkout) + contents: read # to fetch code (actions/checkout) + packages: read # to fetch private images from GitHub Container Registry (GHCR) jobs: tests: @@ -111,6 +112,14 @@ jobs: path: 'coverage/dspace-angular/lcov.info' retention-days: 14 + # Login to our Docker registry, so that we can access private Docker images using "docker compose" below. + - name: Login to ${{ env.DOCKER_REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKER_REGISTRY }} + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + # Using "docker compose" start backend using CI configuration # and load assetstore from a cached copy - name: Start DSpace REST Backend via Docker (for e2e tests) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6b299ba6f1..c9671bcac0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -16,7 +16,7 @@ on: pull_request: permissions: - contents: read # to fetch code (actions/checkout) + contents: read # to fetch code (actions/checkout) packages: write # to write images to GitHub Container Registry (GHCR) jobs: From 4bf4e18389abb26cecbef78760081b47cc4f95f4 Mon Sep 17 00:00:00 2001 From: Andrea-Guevara <101608067+Andrea-Guevara@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:18:24 -0300 Subject: [PATCH 669/875] More accessible file download link for users who use a screen reader (#3264) * More accessible file download link for users who use a screen reader * Refactoring implementation - More accessible file download link for users who use a screen reader * Fixing import error * Solving the spaces error * Solving the spaces error * Solving the spaces error in file pt-BR.json5 --------- Co-authored-by: andreaNeki --- .../file-download-link/file-download-link.component.html | 6 +++++- .../file-download-link/file-download-link.component.ts | 2 ++ src/assets/i18n/en.json5 | 2 ++ src/assets/i18n/es.json5 | 5 +++-- src/assets/i18n/pt-BR.json5 | 3 +++ 5 files changed, 15 insertions(+), 3 deletions(-) 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 7a5729cc2d..59f255a652 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 @@ -1,4 +1,8 @@ - + 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 a79a71b634..1488401ab1 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 @@ -1,6 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { Bitstream } from '../../core/shared/bitstream.model'; import { getBitstreamDownloadRoute, getBitstreamRequestACopyRoute } from '../../app-routing-paths'; +import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { hasValue, isNotEmpty } from '../empty.util'; @@ -48,6 +49,7 @@ export class FileDownloadLinkComponent implements OnInit { constructor( private authorizationService: AuthorizationDataService, + public dsoNameService: DSONameService, ) { } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index a3c98f7346..a56f1cd4f2 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -5386,6 +5386,8 @@ "browse.search-form.placeholder": "Search the repository", + "file-download-link.download": "Download ", + "register-page.registration.aria.label": "Enter your e-mail address", "forgot-email.form.aria.label": "Enter your e-mail address", diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index dfdf4ca628..51bd9c833c 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -7831,11 +7831,12 @@ //"browse.search-form.placeholder": "Search the repository", "browse.search-form.placeholder": "Buscar en el repositorio", + // "file-download-link.download": "Download ", + "file-download-link.download": "Descargar ", + // "register-page.registration.aria.label": "Enter your e-mail address", "register-page.registration.aria.label": "Introduzca su dirección de correo electrónico", // "forgot-email.form.aria.label": "Enter your e-mail address", "forgot-email.form.aria.label": "Introduzca su dirección de correo electrónico", - - } diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index eebf896889..13a6b652a6 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -7858,6 +7858,9 @@ //"browse.search-form.placeholder": "Search the repository", "browse.search-form.placeholder": "Buscar no repositório", + // "file-download-link.download": "Download ", + "file-download-link.download": "Baixar ", + // "register-page.registration.aria.label": "Enter your e-mail address", "register-page.registration.aria.label": "Digite seu e-mail", From 25ab69ff213f80e1cc48704317abeee92a6071dc Mon Sep 17 00:00:00 2001 From: igorbaptist4 Date: Wed, 11 Sep 2024 14:48:59 -0300 Subject: [PATCH 670/875] Configuring the URI link target --- .../metadata-values.component.html | 5 ++++- .../metadata-values.component.ts | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) 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 a598815d4c..a9576da26a 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 @@ -18,7 +18,10 @@ - diff --git a/src/app/item-page/field-components/metadata-values/metadata-values.component.ts b/src/app/item-page/field-components/metadata-values/metadata-values.component.ts index cbbae9006d..20df92099f 100644 --- a/src/app/item-page/field-components/metadata-values/metadata-values.component.ts +++ b/src/app/item-page/field-components/metadata-values/metadata-values.component.ts @@ -4,6 +4,7 @@ import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface'; import { BrowseDefinition } from '../../../core/shared/browse-definition.model'; import { hasValue } from '../../../shared/empty.util'; import { VALUE_LIST_BROWSE_DEFINITION } from '../../../core/shared/value-list-browse-definition.resource-type'; +import { environment } from '../../../../environments/environment'; /** * This component renders the configured 'values' into the ds-metadata-field-wrapper component. @@ -90,4 +91,25 @@ export class MetadataValuesComponent implements OnChanges { } return queryParams; } + + /** + * Checks if the given link value is an internal link. + * @param linkValue - The link value to check. + * @returns True if the link value starts with the base URL defined in the environment configuration, false otherwise. + */ + hasInternalLink(linkValue: string): boolean { + return linkValue.startsWith(environment.ui.baseUrl); + } + + /** + * This method performs a validation and determines the target of the url. + * @returns - Returns the target url. + */ + getLinkAttributes(urlValue: string): { target: string, rel: string } { + if (this.hasInternalLink(urlValue)) { + return { target: '_self', rel: '' }; + } else { + return { target: '_blank', rel: 'noopener noreferrer' }; + } + } } From 2a1ef02d7583eacb2e59d58c33486102ba6e2f17 Mon Sep 17 00:00:00 2001 From: igorbaptist4 Date: Wed, 11 Sep 2024 15:15:26 -0300 Subject: [PATCH 671/875] fix identation --- .../metadata-values/metadata-values.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/item-page/field-components/metadata-values/metadata-values.component.ts b/src/app/item-page/field-components/metadata-values/metadata-values.component.ts index 20df92099f..a902e48ed1 100644 --- a/src/app/item-page/field-components/metadata-values/metadata-values.component.ts +++ b/src/app/item-page/field-components/metadata-values/metadata-values.component.ts @@ -105,7 +105,7 @@ export class MetadataValuesComponent implements OnChanges { * This method performs a validation and determines the target of the url. * @returns - Returns the target url. */ - getLinkAttributes(urlValue: string): { target: string, rel: string } { + getLinkAttributes(urlValue: string): { target: string, rel: string } { if (this.hasInternalLink(urlValue)) { return { target: '_self', rel: '' }; } else { From c6ef2f1bd095e5e1bd417600cc817198cd8fd494 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 11 Dec 2024 13:07:48 -0300 Subject: [PATCH 672/875] Addition of unit tests for the getLinkAttributes() method --- .../metadata-values.component.spec.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/app/item-page/field-components/metadata-values/metadata-values.component.spec.ts b/src/app/item-page/field-components/metadata-values/metadata-values.component.spec.ts index 23f8098207..de46cd9e4c 100644 --- a/src/app/item-page/field-components/metadata-values/metadata-values.component.spec.ts +++ b/src/app/item-page/field-components/metadata-values/metadata-values.component.spec.ts @@ -73,4 +73,20 @@ describe('MetadataValuesComponent', () => { expect(comp.hasLink(mdValue)).toBe(true); }); + it('should return correct target and rel for internal links', () => { + spyOn(comp, 'hasInternalLink').and.returnValue(true); + const urlValue = '/internal-link'; + const result = comp.getLinkAttributes(urlValue); + expect(result.target).toBe('_self'); + expect(result.rel).toBe(''); + }); + + it('should return correct target and rel for external links', () => { + spyOn(comp, 'hasInternalLink').and.returnValue(false); + const urlValue = 'https://www.dspace.org'; + const result = comp.getLinkAttributes(urlValue); + expect(result.target).toBe('_blank'); + expect(result.rel).toBe('noopener noreferrer'); + }); + }); From b601405e564bf6c668a3cd6086c6c9d9a56eb2f8 Mon Sep 17 00:00:00 2001 From: andreaNeki Date: Fri, 27 Sep 2024 13:44:31 -0300 Subject: [PATCH 673/875] Adding the aria-label attribute to buttons --- .../vocabulary-treeview.component.html | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) 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 a0d320a854..a37b26e348 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html @@ -6,13 +6,16 @@ [attr.aria-label]="'vocabulary-treeview.search.form.search-placeholder' | translate" [placeholder]="'vocabulary-treeview.search.form.search-placeholder' | translate">
    - - -
    @@ -88,13 +91,15 @@ - - From 14680b013e5c7ced5d4243806a7d7a027111620d Mon Sep 17 00:00:00 2001 From: andreaNeki Date: Fri, 27 Sep 2024 14:01:44 -0300 Subject: [PATCH 674/875] Adding focus to the input after the reset button is clicked --- .../vocabulary-treeview.component.html | 2 +- .../vocabulary-treeview/vocabulary-treeview.component.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) 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 a37b26e348..a216384c9c 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html @@ -2,7 +2,7 @@
    -
    diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts index b1edda461e..2602cfd75e 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts @@ -1,5 +1,5 @@ import { FlatTreeControl } from '@angular/cdk/tree'; -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; import { Observable, Subscription } from 'rxjs'; import { Store } from '@ngrx/store'; @@ -29,6 +29,11 @@ import { AlertType } from '../../alert/alert-type'; }) export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges { + /** + * Implemented to manage focus on input + */ + @ViewChild('searchInput') searchInput: ElementRef; + /** * The {@link VocabularyOptions} object */ @@ -294,6 +299,7 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges this.storedNodeMap = new Map(); this.vocabularyTreeviewService.restoreNodes(); } + this.searchInput.nativeElement.focus(); } add() { From 75260b00d95d6bf3024a9387127ebab65e64b368 Mon Sep 17 00:00:00 2001 From: andreaNeki Date: Fri, 27 Sep 2024 15:08:53 -0300 Subject: [PATCH 675/875] =?UTF-8?q?Ensuring=20that=20the=20message=20?= =?UTF-8?q?=E2=80=9CThere=20were=20no=20items=20to=20show=E2=80=9D=20is=20?= =?UTF-8?q?announced=20to=20the=20screen=20reader=20when=20necessary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vocabulary-treeview.component.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 a216384c9c..ad894328a6 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.html @@ -24,9 +24,11 @@
    -

    - {{'vocabulary-treeview.search.no-result' | translate}} -

    +
    +

    + {{'vocabulary-treeview.search.no-result' | translate}} +

    +
    From a8ff6a41e051cb68c6083da2df588888d37e2903 Mon Sep 17 00:00:00 2001 From: andreaNeki Date: Fri, 27 Sep 2024 16:18:56 -0300 Subject: [PATCH 676/875] Trying to correct an error in the focus implementation --- .../vocabulary-treeview/vocabulary-treeview.component.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts index 2602cfd75e..5695644040 100644 --- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts +++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts @@ -32,7 +32,7 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges /** * Implemented to manage focus on input */ - @ViewChild('searchInput') searchInput: ElementRef; + @ViewChild('searchInput') searchInput!: ElementRef; /** * The {@link VocabularyOptions} object @@ -299,7 +299,9 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit, OnChanges this.storedNodeMap = new Map(); this.vocabularyTreeviewService.restoreNodes(); } - this.searchInput.nativeElement.focus(); + if (this.searchInput) { + this.searchInput.nativeElement.focus(); + } } add() { From abc3c41c635e5d8df49c75cddab40aa2951bcbb2 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Mon, 18 Nov 2024 14:07:34 +0100 Subject: [PATCH 677/875] [DURACOM-297] fix changing values in submission form after ordering (cherry picked from commit 1c9272107c427e3c1210c9df2c3f859da2463278) --- .../array-group/dynamic-form-array.component.html | 6 +++--- .../array-group/dynamic-form-array.component.ts | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html index dd19e6158d..a7a6b103e4 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html @@ -7,9 +7,9 @@
    -
    Date: Mon, 23 Dec 2024 02:28:39 +0000 Subject: [PATCH 678/875] Bump mirador from 3.3.0 to 3.4.2 Bumps [mirador](https://github.com/ProjectMirador/mirador) from 3.3.0 to 3.4.2. - [Release notes](https://github.com/ProjectMirador/mirador/releases) - [Commits](https://github.com/ProjectMirador/mirador/compare/v3.3.0...v3.4.2) --- updated-dependencies: - dependency-name: mirador dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 93c54cc7b7..613def9edf 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "lru-cache": "^7.14.1", "markdown-it": "^13.0.2", "markdown-it-mathjax3": "^4.3.2", - "mirador": "^3.3.0", + "mirador": "^3.4.2", "mirador-dl-plugin": "^0.13.0", "mirador-share-plugin": "^0.16.0", "morgan": "^1.10.0", diff --git a/yarn.lock b/yarn.lock index f1fcbe2cc2..fc1475cd45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1864,7 +1864,7 @@ resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== -"@material-ui/core@^4.11.0", "@material-ui/core@^4.12.4": +"@material-ui/core@^4.12.3", "@material-ui/core@^4.12.4": version "4.12.4" resolved "https://registry.npmjs.org/@material-ui/core/-/core-4.12.4.tgz" integrity sha512-tr7xekNlM9LjA6pagJmL8QCgZXaubWUwkJnoYcMKd4gw/t4XiyvnTkjdGrUVicyB2BsdaAv1tvow45bPM4sSwQ== @@ -8212,12 +8212,12 @@ mirador-share-plugin@^0.16.0: dependencies: notistack "^3.0.1" -mirador@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/mirador/-/mirador-3.3.0.tgz" - integrity sha512-BmGfRnWJ45B+vtiAwcFT7n9nKialfejE9UvuUK0NorO37ShArpsKr3yVSD4jQASwSR4DRRpPEG21jOk4WN7H3w== +mirador@^3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/mirador/-/mirador-3.4.2.tgz#51ec7ca1b9854792bbe2fa5e13b3692c92e97102" + integrity sha512-Gd7G4NkXq6/qD/De5soYspSo9VykAzrGFunKqUI3x9WShoZP23pYIEPoC/96tvfk3KMv+UbAUxDp99Xeo7vnVQ== dependencies: - "@material-ui/core" "^4.11.0" + "@material-ui/core" "^4.12.3" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "^4.0.0-alpha.53" "@researchgate/react-intersection-observer" "^1.0.0" @@ -8234,7 +8234,7 @@ mirador@^3.3.0: lodash "^4.17.11" manifesto.js "^4.2.0" normalize-url "^4.5.0" - openseadragon "^2.4.2" + openseadragon "^2.4.2 || ^3.0.0 || 4.0.x || ^4.1.1 || ^5.0.0" prop-types "^15.6.2" re-reselect "^4.0.0" react-aria-live "^2.0.5" @@ -8764,10 +8764,10 @@ open@8.4.2, open@^8.0.9: is-docker "^2.1.1" is-wsl "^2.2.0" -openseadragon@^2.4.2: - version "2.4.2" - resolved "https://registry.npmjs.org/openseadragon/-/openseadragon-2.4.2.tgz" - integrity sha512-398KbZwRtOYA6OmeWRY4Q0737NTacQ9Q6whmr9Lp1MNQO3p0eBz5LIASRne+4gwequcSM1vcHcjfy3dIndQziw== +"openseadragon@^2.4.2 || ^3.0.0 || 4.0.x || ^4.1.1 || ^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/openseadragon/-/openseadragon-5.0.1.tgz#ad3aaccc6c0f733c3153131e9af05bc2e17a1952" + integrity sha512-a/hjouW9i3UfWxRADVYN2MyRhXMGnE7x9VVL7/4jXCcDLFyO4UM5o4RStYtqa5BfaHw/wMNAaD2WbxQF8f1pJg== openurl@1.1.1: version "1.1.1" From 5c8eabddab7c4432942bdf1c52b5175481c1f233 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 02:28:53 +0000 Subject: [PATCH 679/875] Bump @fortawesome/fontawesome-free from 6.6.0 to 6.7.2 Bumps [@fortawesome/fontawesome-free](https://github.com/FortAwesome/Font-Awesome) from 6.6.0 to 6.7.2. - [Release notes](https://github.com/FortAwesome/Font-Awesome/releases) - [Changelog](https://github.com/FortAwesome/Font-Awesome/blob/6.x/CHANGELOG.md) - [Commits](https://github.com/FortAwesome/Font-Awesome/compare/6.6.0...6.7.2) --- updated-dependencies: - dependency-name: "@fortawesome/fontawesome-free" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 93c54cc7b7..dc984272f9 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "@angular/compiler-cli": "^15.2.10", "@angular/language-service": "^15.2.10", "@cypress/schematic": "^1.5.0", - "@fortawesome/fontawesome-free": "^6.6.0", + "@fortawesome/fontawesome-free": "^6.7.2", "@material-ui/core": "^4.12.4", "@material-ui/icons": "^4.11.3", "@ngrx/store-devtools": "^15.4.0", diff --git a/yarn.lock b/yarn.lock index f1fcbe2cc2..ce0490ec10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1749,10 +1749,10 @@ resolved "https://registry.yarnpkg.com/@fastify/deepmerge/-/deepmerge-1.3.0.tgz#8116858108f0c7d9fd460d05a7d637a13fe3239a" integrity sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A== -"@fortawesome/fontawesome-free@^6.6.0": - version "6.6.0" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.6.0.tgz#0e984f0f2344ee513c185d87d77defac4c0c8224" - integrity sha512-60G28ke/sXdtS9KZCpZSHHkCbdsOGEhIUGlwq6yhY74UpTiToIh8np7A8yphhM4BWsvNFtIvLpi4co+h9Mr9Ow== +"@fortawesome/fontawesome-free@^6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz#8249de9b7e22fcb3ceb5e66090c30a1d5492b81a" + integrity sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA== "@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": version "1.1.3" From 33fdee554ec4921cc5b11e00cebbc41b6fddcd2a Mon Sep 17 00:00:00 2001 From: jensvannerum <78496674+jensvannerum@users.noreply.github.com> Date: Wed, 1 Jan 2025 10:50:30 +0100 Subject: [PATCH 680/875] [Port dspace-7_x] Get rid of unnecessary and failing REST requests when navigating between different browse indexes (#3788) Co-authored-by: Koen Pauwels Co-authored-by: Jens Vannerum --- .../browse-by-date-page.component.ts | 18 +++++++++--------- .../browse-by-metadata-page.component.ts | 8 ++++++-- .../browse-by-title-page.component.ts | 8 +++----- src/app/menu.resolver.spec.ts | 11 +++++++---- src/app/menu.resolver.ts | 17 ++++++++++++++--- 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index d3c832d3e2..7e0b6f0f88 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -1,8 +1,7 @@ import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { BrowseByMetadataPageComponent, - browseParamsToOptions, - getBrowseSearchOptions + browseParamsToOptions } from '../browse-by-metadata-page/browse-by-metadata-page.component'; import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; @@ -11,7 +10,7 @@ import { BrowseService } from '../../core/browse/browse.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { PaginationService } from '../../core/pagination/pagination.service'; -import { map } from 'rxjs/operators'; +import { map, take } from 'rxjs/operators'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { isValidDate } from '../../shared/date.util'; @@ -52,15 +51,16 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { ngOnInit(): void { const sortConfig = new SortOptions('default', SortDirection.ASC); this.startsWithType = StartsWithType.date; - // include the thumbnail configuration in browse search options - this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig, this.fetchThumbnails)); this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig); this.subs.push( - observableCombineLatest([this.route.params, this.route.queryParams, this.route.data, - this.currentPagination$, this.currentSort$]).pipe( - map(([routeParams, queryParams, data, currentPage, currentSort]) => { - return [Object.assign({}, routeParams, queryParams, data), currentPage, currentSort]; + observableCombineLatest( + [ this.route.params.pipe(take(1)), + this.route.queryParams, + this.currentPagination$, + this.currentSort$]).pipe( + map(([routeParams, queryParams, currentPage, currentSort]) => { + return [Object.assign({}, routeParams, queryParams), currentPage, currentSort]; }) ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys; diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index c03cf03429..fe407a2fb0 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -15,7 +15,7 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { PaginationService } from '../../core/pagination/pagination.service'; -import { filter, map, mergeMap } from 'rxjs/operators'; +import { filter, map, mergeMap, take } from 'rxjs/operators'; import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { Bitstream } from '../../core/shared/bitstream.model'; import { Collection } from '../../core/shared/collection.model'; @@ -152,7 +152,11 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig); this.subs.push( - observableCombineLatest([this.route.params, this.route.queryParams, this.currentPagination$, this.currentSort$]).pipe( + observableCombineLatest( + [ this.route.params.pipe(take(1)), + this.route.queryParams, + this.currentPagination$, + this.currentSort$]).pipe( map(([routeParams, queryParams, currentPage, currentSort]) => { return [Object.assign({}, routeParams, queryParams),currentPage,currentSort]; }) diff --git a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index 58df79ebe8..11dc2a2a6a 100644 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -4,13 +4,13 @@ import { ActivatedRoute, Params, Router } from '@angular/router'; import { hasValue } from '../../shared/empty.util'; import { BrowseByMetadataPageComponent, - browseParamsToOptions, getBrowseSearchOptions + browseParamsToOptions } from '../browse-by-metadata-page/browse-by-metadata-page.component'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { BrowseService } from '../../core/browse/browse.service'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { PaginationService } from '../../core/pagination/pagination.service'; -import { map } from 'rxjs/operators'; +import { map, take } from 'rxjs/operators'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; @@ -38,12 +38,10 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent { ngOnInit(): void { const sortConfig = new SortOptions('dc.title', SortDirection.ASC); - // include the thumbnail configuration in browse search options - this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig, this.fetchThumbnails)); this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig); this.subs.push( - observableCombineLatest([this.route.params, this.route.queryParams, this.currentPagination$, this.currentSort$]).pipe( + observableCombineLatest([this.route.params.pipe(take(1)), this.route.queryParams, this.currentPagination$, this.currentSort$]).pipe( map(([routeParams, queryParams, currentPage, currentSort]) => { return [Object.assign({}, routeParams, queryParams),currentPage,currentSort]; }) diff --git a/src/app/menu.resolver.spec.ts b/src/app/menu.resolver.spec.ts index 838d5a53c5..12d5efa24e 100644 --- a/src/app/menu.resolver.spec.ts +++ b/src/app/menu.resolver.spec.ts @@ -16,9 +16,11 @@ import { MenuServiceStub } from './shared/testing/menu-service.stub'; import { MenuID } from './shared/menu/menu-id.model'; import { BrowseService } from './core/browse/browse.service'; import { cold } from 'jasmine-marbles'; -import createSpy = jasmine.createSpy; import { createSuccessfulRemoteDataObject$ } from './shared/remote-data.utils'; import { createPaginatedList } from './shared/testing/utils.test'; +import createSpy = jasmine.createSpy; +import { AuthService } from './core/auth/auth.service'; +import { AuthServiceStub } from './shared/testing/auth-service.stub'; const BOOLEAN = { t: true, f: false }; const MENU_STATE = { @@ -61,6 +63,7 @@ describe('MenuResolver', () => { { provide: BrowseService, useValue: browseService }, { provide: AuthorizationDataService, useValue: authorizationService }, { provide: ScriptDataService, useValue: scriptService }, + { provide: AuthService, useValue: AuthServiceStub }, { provide: NgbModal, useValue: { open: () => {/*comment*/ @@ -80,19 +83,19 @@ describe('MenuResolver', () => { describe('resolve', () => { it('should create all menus', (done) => { spyOn(resolver, 'createPublicMenu$').and.returnValue(observableOf(true)); - spyOn(resolver, 'createAdminMenu$').and.returnValue(observableOf(true)); + spyOn(resolver, 'createAdminMenuIfLoggedIn$').and.returnValue(observableOf(true)); resolver.resolve(null, null).subscribe(resolved => { expect(resolved).toBeTrue(); expect(resolver.createPublicMenu$).toHaveBeenCalled(); - expect(resolver.createAdminMenu$).toHaveBeenCalled(); + expect(resolver.createAdminMenuIfLoggedIn$).toHaveBeenCalled(); done(); }); }); it('should return an Observable that emits true as soon as all menus are created', () => { spyOn(resolver, 'createPublicMenu$').and.returnValue(cold('--(t|)', BOOLEAN)); - spyOn(resolver, 'createAdminMenu$').and.returnValue(cold('----(t|)', BOOLEAN)); + spyOn(resolver, 'createAdminMenuIfLoggedIn$').and.returnValue(cold('----(t|)', BOOLEAN)); expect(resolver.resolve(null, null)).toBeObservable(cold('----(t|)', BOOLEAN)); }); diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index cad6a6ec57..83c9351c7f 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; -import { combineLatest as observableCombineLatest, combineLatest, Observable } from 'rxjs'; +import { combineLatest as observableCombineLatest, combineLatest, Observable, of as observableOf } from 'rxjs'; import { MenuID } from './shared/menu/menu-id.model'; import { MenuState } from './shared/menu/menu-state.model'; import { MenuItemType } from './shared/menu/menu-item-type.model'; @@ -12,7 +12,7 @@ import { RemoteData } from './core/data/remote-data'; import { TextMenuItemModel } from './shared/menu/menu-item/models/text.model'; import { BrowseService } from './core/browse/browse.service'; import { MenuService } from './shared/menu/menu.service'; -import { filter, find, map, take } from 'rxjs/operators'; +import { filter, find, map, mergeMap, take } from 'rxjs/operators'; import { hasValue } from './shared/empty.util'; import { FeatureID } from './core/data/feature-authorization/feature-id'; import { @@ -47,6 +47,7 @@ import { import { ExportBatchSelectorComponent } from './shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component'; +import { AuthService } from './core/auth/auth.service'; /** * Creates all of the app's menus @@ -61,6 +62,7 @@ export class MenuResolver implements Resolve { protected authorizationService: AuthorizationDataService, protected modalService: NgbModal, protected scriptDataService: ScriptDataService, + protected authService: AuthService, ) { } @@ -70,7 +72,7 @@ export class MenuResolver implements Resolve { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { return combineLatest([ this.createPublicMenu$(), - this.createAdminMenu$(), + this.createAdminMenuIfLoggedIn$(), ]).pipe( map((menusDone: boolean[]) => menusDone.every(Boolean)), ); @@ -146,6 +148,15 @@ export class MenuResolver implements Resolve { return this.waitForMenu$(MenuID.PUBLIC); } + /** + * Initialize all menu sections and items for {@link MenuID.ADMIN}, only if the user is logged in. + */ + createAdminMenuIfLoggedIn$() { + return this.authService.isAuthenticated().pipe( + mergeMap((isAuthenticated) => isAuthenticated ? this.createAdminMenu$() : observableOf(true)), + ); + } + /** * Initialize all menu sections and items for {@link MenuID.ADMIN} */ From ecf3298345373aa51cb7c6523761b0f9d4f0a6f7 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 6 Dec 2024 17:34:37 +0100 Subject: [PATCH 681/875] fix value of selector in component annotation (cherry picked from commit 71de4b600b8ed268cbc88a1191f6c2f931e23705) --- .../org-unit-search-result-list-submission-element.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts index ef58007bae..76a5618292 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts @@ -22,7 +22,7 @@ import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.inter @listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModal) @listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants) @Component({ - selector: 'ds-person-search-result-list-submission-element', + selector: 'ds-org-unit-search-result-list-submission-element', styleUrls: ['./org-unit-search-result-list-submission-element.component.scss'], templateUrl: './org-unit-search-result-list-submission-element.component.html' }) From 894448e3dc6e161b947c6affb301d4047d9c6607 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 03:04:09 +0000 Subject: [PATCH 682/875] Bump postcss in the postcss group across 1 directory Bumps the postcss group with 1 update in the / directory: [postcss](https://github.com/postcss/postcss). Updates `postcss` from 8.4.47 to 8.4.49 - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.47...8.4.49) --- updated-dependencies: - dependency-name: postcss dependency-type: direct:development update-type: version-update:semver-patch dependency-group: postcss ... Signed-off-by: dependabot[bot] --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index f1fcbe2cc2..42cdc77d95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9051,7 +9051,7 @@ performance-now@^2.1.0: resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -picocolors@^1.0.0, picocolors@^1.1.0: +picocolors@^1.0.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -9425,12 +9425,12 @@ postcss@8.4.31: source-map-js "^1.0.2" postcss@^8.2.14, postcss@^8.3.11, postcss@^8.3.7, postcss@^8.4, postcss@^8.4.19: - version "8.4.47" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" - integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== + version "8.4.49" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" + integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== dependencies: nanoid "^3.3.7" - picocolors "^1.1.0" + picocolors "^1.1.1" source-map-js "^1.2.1" prelude-ls@^1.2.1: From d3d86f4f2d5da2c184eea4c522d0f6b163855a2e Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 7 Dec 2024 20:58:52 +0100 Subject: [PATCH 683/875] 119799: Prevent the lookup/lookup-name fields from resetting when hitting the enter key in another input field (cherry picked from commit c8694e1a87ab6ad70ef0f984a77f3e12dbd5605d) --- .../models/lookup/dynamic-lookup.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.html index 0ecddf9278..1920209368 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.html @@ -94,11 +94,11 @@ (scrolled)="onScroll()" [scrollWindow]="false"> - -
    From 60586f6c33b37c1b3c874839d98f4a31e759c94b Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 6 Dec 2024 18:05:07 +0100 Subject: [PATCH 690/875] fix nested span element (cherry picked from commit 97b5e0cc92420366355c00f5013550ce7715801d) --- ...-unit-search-result-list-submission-element.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html index e264958738..1f020d127f 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html @@ -11,11 +11,11 @@ - , + , - +
    From 7538f49a36c4b8971387de4ea884597ec5fcaeb8 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 6 Dec 2024 18:09:02 +0100 Subject: [PATCH 691/875] remove nested span element (cherry picked from commit acb7c9bd33bb678766ab219851999f217f7b81d5) --- .../journal-issue-search-result-list-element.component.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html index 2dd721b8f3..2de765d712 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html @@ -31,7 +31,6 @@ - - - ; From 04123f92aae4b654c98ebc7eed5c9b68d7342a24 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Fri, 6 Dec 2024 18:12:44 +0100 Subject: [PATCH 692/875] fix indentation (cherry picked from commit dbd67f056ebff2b5dd62264f8724c64cc11b5d4a) --- ...-search-result-list-element.component.html | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html index 2de765d712..3d1d7c3e09 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html @@ -22,21 +22,21 @@ class="lead item-list-title dont-break-out" [innerHTML]="dsoTitle"> - - - - ; - - - - - ; - - + + + + ; + + + - ; + + + - +
    From 76539dd8a18953f897148f484b8fdd45dc6f4dd9 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Tue, 7 Jan 2025 22:23:48 +0100 Subject: [PATCH 693/875] Reduce browse definition requests on simple item page (#3701) * 121561 Reduce the number of browse definition requests on Item pages by reusing the navbar request for all browse indexes * Fix test issues. --------- Co-authored-by: Koen Pauwels --- .../core/shared/browse-definition.model.ts | 8 +- .../hierarchical-browse-definition.model.ts | 5 +- .../non-hierarchical-browse-definition.ts | 3 - .../journal/journal.component.spec.ts | 5 +- ...item-page-abstract-field.component.spec.ts | 5 +- .../item-page-author-field.component.spec.ts | 5 +- .../item-page-date-field.component.spec.ts | 5 +- .../generic-item-page-field.component.spec.ts | 5 +- .../item-page-field.component.spec.ts | 5 +- .../item-page-field.component.ts | 38 +++++++- .../uri/item-page-uri-field.component.spec.ts | 5 +- .../publication/publication.component.spec.ts | 3 + .../item-types/shared/item.component.spec.ts | 3 + .../untyped-item.component.spec.ts | 3 + src/app/shared/testing/browse-service.stub.ts | 91 +++++++++++++++++++ 15 files changed, 169 insertions(+), 20 deletions(-) create mode 100644 src/app/shared/testing/browse-service.stub.ts diff --git a/src/app/core/shared/browse-definition.model.ts b/src/app/core/shared/browse-definition.model.ts index a5bed53c9f..bd7e4862ce 100644 --- a/src/app/core/shared/browse-definition.model.ts +++ b/src/app/core/shared/browse-definition.model.ts @@ -1,4 +1,7 @@ -import { autoserialize } from 'cerialize'; +import { + autoserialize, + autoserializeAs, +} from 'cerialize'; import { CacheableObject } from '../cache/cacheable-object.model'; /** @@ -9,6 +12,9 @@ export abstract class BrowseDefinition extends CacheableObject { @autoserialize id: string; + @autoserializeAs('metadata') + metadataKeys: string[]; + /** * Get the render type of the BrowseDefinition model */ diff --git a/src/app/core/shared/hierarchical-browse-definition.model.ts b/src/app/core/shared/hierarchical-browse-definition.model.ts index d561fff643..b64ecabdf2 100644 --- a/src/app/core/shared/hierarchical-browse-definition.model.ts +++ b/src/app/core/shared/hierarchical-browse-definition.model.ts @@ -1,4 +1,4 @@ -import { autoserialize, autoserializeAs, deserialize, inheritSerialization } from 'cerialize'; +import { autoserialize, deserialize, inheritSerialization } from 'cerialize'; import { typedObject } from '../cache/builders/build-decorators'; import { excludeFromEquals } from '../utilities/equals.decorators'; import { HIERARCHICAL_BROWSE_DEFINITION } from './hierarchical-browse-definition.resource-type'; @@ -26,9 +26,6 @@ export class HierarchicalBrowseDefinition extends BrowseDefinition { @autoserialize vocabulary: string; - @autoserializeAs('metadata') - metadataKeys: string[]; - get self(): string { return this._links.self.href; } diff --git a/src/app/core/shared/non-hierarchical-browse-definition.ts b/src/app/core/shared/non-hierarchical-browse-definition.ts index d5481fcc8d..cba6f3ecd6 100644 --- a/src/app/core/shared/non-hierarchical-browse-definition.ts +++ b/src/app/core/shared/non-hierarchical-browse-definition.ts @@ -16,9 +16,6 @@ export abstract class NonHierarchicalBrowseDefinition extends BrowseDefinition { @autoserializeAs('order') defaultSortOrder: string; - @autoserializeAs('metadata') - metadataKeys: string[]; - @autoserialize dataType: BrowseByDataType; } diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.spec.ts b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.spec.ts index 6e2ded334b..ace7a69f9a 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.spec.ts @@ -39,6 +39,8 @@ import { BrowseDefinitionDataServiceStub } from '../../../../shared/testing/browse-definition-data-service.stub'; import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service'; +import { BrowseService } from '../../../../core/browse/browse.service'; +import { BrowseServiceStub } from '../../../../shared/testing/browse-service.stub'; let comp: JournalComponent; let fixture: ComponentFixture; @@ -105,7 +107,8 @@ describe('JournalComponent', () => { { provide: WorkspaceitemDataService, useValue: {} }, { provide: SearchService, useValue: {} }, { provide: RouteService, useValue: mockRouteService }, - { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub } + { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }, + { provide: BrowseService, useValue: BrowseServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/item-page/simple/field-components/specific-field/abstract/item-page-abstract-field.component.spec.ts b/src/app/item-page/simple/field-components/specific-field/abstract/item-page-abstract-field.component.spec.ts index 4fb8889440..6bbee90945 100644 --- a/src/app/item-page/simple/field-components/specific-field/abstract/item-page-abstract-field.component.spec.ts +++ b/src/app/item-page/simple/field-components/specific-field/abstract/item-page-abstract-field.component.spec.ts @@ -6,6 +6,8 @@ import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loa import { SharedModule } from '../../../../../shared/shared.module'; import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { environment } from '../../../../../../environments/environment'; +import { BrowseService } from '../../../../../core/browse/browse.service'; +import { BrowseServiceStub } from '../../../../../shared/testing/browse-service.stub'; import { By } from '@angular/platform-browser'; import { BrowseDefinitionDataService } from '../../../../../core/browse/browse-definition-data.service'; import { BrowseDefinitionDataServiceStub } from '../../../../../shared/testing/browse-definition-data-service.stub'; @@ -27,7 +29,8 @@ describe('ItemPageAbstractFieldComponent', () => { ], providers: [ { provide: APP_CONFIG, useValue: environment }, - { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub } + { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }, + { provide: BrowseService, useValue: BrowseServiceStub }, ], declarations: [ItemPageAbstractFieldComponent], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/item-page/simple/field-components/specific-field/author/item-page-author-field.component.spec.ts b/src/app/item-page/simple/field-components/specific-field/author/item-page-author-field.component.spec.ts index 855a995142..d45261b85b 100644 --- a/src/app/item-page/simple/field-components/specific-field/author/item-page-author-field.component.spec.ts +++ b/src/app/item-page/simple/field-components/specific-field/author/item-page-author-field.component.spec.ts @@ -7,6 +7,8 @@ import { mockItemWithMetadataFieldsAndValue } from '../item-page-field.component import { ItemPageAuthorFieldComponent } from './item-page-author-field.component'; import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { environment } from '../../../../../../environments/environment'; +import { BrowseService } from '../../../../../core/browse/browse.service'; +import { BrowseServiceStub } from '../../../../../shared/testing/browse-service.stub'; import { BrowseDefinitionDataService } from '../../../../../core/browse/browse-definition-data.service'; import { BrowseDefinitionDataServiceStub } from '../../../../../shared/testing/browse-definition-data-service.stub'; @@ -27,7 +29,8 @@ describe('ItemPageAuthorFieldComponent', () => { })], providers: [ { provide: APP_CONFIG, useValue: environment }, - { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub } + { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }, + { provide: BrowseService, useValue: BrowseServiceStub }, ], declarations: [ItemPageAuthorFieldComponent, MetadataValuesComponent], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/item-page/simple/field-components/specific-field/date/item-page-date-field.component.spec.ts b/src/app/item-page/simple/field-components/specific-field/date/item-page-date-field.component.spec.ts index be124dab82..a88e9f74a7 100644 --- a/src/app/item-page/simple/field-components/specific-field/date/item-page-date-field.component.spec.ts +++ b/src/app/item-page/simple/field-components/specific-field/date/item-page-date-field.component.spec.ts @@ -7,6 +7,8 @@ import { mockItemWithMetadataFieldsAndValue } from '../item-page-field.component import { ItemPageDateFieldComponent } from './item-page-date-field.component'; import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { environment } from '../../../../../../environments/environment'; +import { BrowseService } from '../../../../../core/browse/browse.service'; +import { BrowseServiceStub } from '../../../../../shared/testing/browse-service.stub'; import { BrowseDefinitionDataService } from '../../../../../core/browse/browse-definition-data.service'; import { BrowseDefinitionDataServiceStub } from '../../../../../shared/testing/browse-definition-data-service.stub'; @@ -27,7 +29,8 @@ describe('ItemPageDateFieldComponent', () => { })], providers: [ { provide: APP_CONFIG, useValue: environment }, - { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub } + { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }, + { provide: BrowseService, useValue: BrowseServiceStub }, ], declarations: [ItemPageDateFieldComponent, MetadataValuesComponent], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/item-page/simple/field-components/specific-field/generic/generic-item-page-field.component.spec.ts b/src/app/item-page/simple/field-components/specific-field/generic/generic-item-page-field.component.spec.ts index fdf5ac1bb5..606f43ed9a 100644 --- a/src/app/item-page/simple/field-components/specific-field/generic/generic-item-page-field.component.spec.ts +++ b/src/app/item-page/simple/field-components/specific-field/generic/generic-item-page-field.component.spec.ts @@ -7,6 +7,8 @@ import { mockItemWithMetadataFieldsAndValue } from '../item-page-field.component import { GenericItemPageFieldComponent } from './generic-item-page-field.component'; import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { environment } from '../../../../../../environments/environment'; +import { BrowseService } from '../../../../../core/browse/browse.service'; +import { BrowseServiceStub } from '../../../../../shared/testing/browse-service.stub'; import { BrowseDefinitionDataService } from '../../../../../core/browse/browse-definition-data.service'; import { BrowseDefinitionDataServiceStub } from '../../../../../shared/testing/browse-definition-data-service.stub'; @@ -29,7 +31,8 @@ describe('GenericItemPageFieldComponent', () => { })], providers: [ { provide: APP_CONFIG, useValue: environment }, - { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub } + { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }, + { provide: BrowseService, useValue: BrowseServiceStub }, ], declarations: [GenericItemPageFieldComponent, MetadataValuesComponent], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/item-page/simple/field-components/specific-field/item-page-field.component.spec.ts b/src/app/item-page/simple/field-components/specific-field/item-page-field.component.spec.ts index 15b7a9df21..f0bddbc695 100644 --- a/src/app/item-page/simple/field-components/specific-field/item-page-field.component.spec.ts +++ b/src/app/item-page/simple/field-components/specific-field/item-page-field.component.spec.ts @@ -13,6 +13,8 @@ import { MarkdownPipe } from '../../../../shared/utils/markdown.pipe'; import { SharedModule } from '../../../../shared/shared.module'; import { APP_CONFIG } from '../../../../../config/app-config.interface'; import { By } from '@angular/platform-browser'; +import { BrowseService } from '../../../../core/browse/browse.service'; +import { BrowseServiceStub } from '../../../../shared/testing/browse-service.stub'; import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service'; import { BrowseDefinitionDataServiceStub } from '../../../../shared/testing/browse-definition-data-service.stub'; import { RouterTestingModule } from '@angular/router/testing'; @@ -51,7 +53,8 @@ describe('ItemPageFieldComponent', () => { ], providers: [ { provide: APP_CONFIG, useValue: appConfig }, - { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub } + { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }, + { provide: BrowseService, useValue: BrowseServiceStub }, ], declarations: [ItemPageFieldComponent, MetadataValuesComponent], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/item-page/simple/field-components/specific-field/item-page-field.component.ts b/src/app/item-page/simple/field-components/specific-field/item-page-field.component.ts index 3a18666a81..aa1e78f229 100644 --- a/src/app/item-page/simple/field-components/specific-field/item-page-field.component.ts +++ b/src/app/item-page/simple/field-components/specific-field/item-page-field.component.ts @@ -1,10 +1,20 @@ import { Component, Input } from '@angular/core'; import { Item } from '../../../../core/shared/item.model'; -import { map } from 'rxjs/operators'; +import intersectionWith from 'lodash/intersectionWith'; +import { + filter, + mergeAll, + take, +} from 'rxjs/operators'; import { Observable } from 'rxjs'; +import { BrowseService } from '../../../../core/browse/browse.service'; import { BrowseDefinition } from '../../../../core/shared/browse-definition.model'; import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service'; -import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { + getFirstCompletedRemoteData, + getPaginatedListPayload, + getRemoteDataPayload, +} from '../../../../core/shared/operators'; /** * This component can be used to represent metadata on a simple item page. @@ -17,7 +27,8 @@ import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; }) export class ItemPageFieldComponent { - constructor(protected browseDefinitionDataService: BrowseDefinitionDataService) { + constructor(protected browseDefinitionDataService: BrowseDefinitionDataService, + protected browseService: BrowseService) { } /** @@ -56,9 +67,26 @@ export class ItemPageFieldComponent { * link in dspace.cfg (webui.browse.link.) */ get browseDefinition(): Observable { - return this.browseDefinitionDataService.findByFields(this.fields).pipe( + return this.browseService.getBrowseDefinitions().pipe( getFirstCompletedRemoteData(), - map((def) => def.payload) + getRemoteDataPayload(), + getPaginatedListPayload(), + mergeAll(), + filter((def: BrowseDefinition) => + intersectionWith(def.metadataKeys, this.fields, ItemPageFieldComponent.fieldMatch).length > 0, + ), + take(1), ); } + + /** + * Returns true iff the spec and field match. + * @param spec Specification of a metadata field name: either a metadata field, or a prefix ending in ".*". + * @param field A metadata field name. + * @private + */ + private static fieldMatch(spec: string, field: string): boolean { + return field === spec + || (spec.endsWith('.*') && field.substring(0, spec.length - 1) === spec.substring(0, spec.length - 1)); + } } diff --git a/src/app/item-page/simple/field-components/specific-field/uri/item-page-uri-field.component.spec.ts b/src/app/item-page/simple/field-components/specific-field/uri/item-page-uri-field.component.spec.ts index cc55b76e3e..7b969737db 100644 --- a/src/app/item-page/simple/field-components/specific-field/uri/item-page-uri-field.component.spec.ts +++ b/src/app/item-page/simple/field-components/specific-field/uri/item-page-uri-field.component.spec.ts @@ -7,6 +7,8 @@ import { ItemPageUriFieldComponent } from './item-page-uri-field.component'; import { MetadataUriValuesComponent } from '../../../../field-components/metadata-uri-values/metadata-uri-values.component'; import { environment } from '../../../../../../environments/environment'; import { APP_CONFIG } from '../../../../../../config/app-config.interface'; +import { BrowseService } from '../../../../../core/browse/browse.service'; +import { BrowseServiceStub } from '../../../../../shared/testing/browse-service.stub'; import { BrowseDefinitionDataService } from '../../../../../core/browse/browse-definition-data.service'; import { BrowseDefinitionDataServiceStub } from '../../../../../shared/testing/browse-definition-data-service.stub'; @@ -28,7 +30,8 @@ describe('ItemPageUriFieldComponent', () => { })], providers: [ { provide: APP_CONFIG, useValue: environment }, - { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub } + { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }, + { provide: BrowseService, useValue: BrowseServiceStub }, ], declarations: [ItemPageUriFieldComponent, MetadataUriValuesComponent], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/item-page/simple/item-types/publication/publication.component.spec.ts b/src/app/item-page/simple/item-types/publication/publication.component.spec.ts index 211ec102bc..98db2da7f0 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.spec.ts +++ b/src/app/item-page/simple/item-types/publication/publication.component.spec.ts @@ -40,6 +40,8 @@ import { BrowseDefinitionDataService } from '../../../../core/browse/browse-defi import { BrowseDefinitionDataServiceStub } from '../../../../shared/testing/browse-definition-data-service.stub'; +import { BrowseService } from '../../../../core/browse/browse.service'; +import { BrowseServiceStub } from '../../../../shared/testing/browse-service.stub'; const noMetadata = new MetadataMap(); @@ -93,6 +95,7 @@ describe('PublicationComponent', () => { { provide: SearchService, useValue: {} }, { provide: RouteService, useValue: mockRouteService }, { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }, + { provide: BrowseService, useValue: BrowseServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/item-page/simple/item-types/shared/item.component.spec.ts b/src/app/item-page/simple/item-types/shared/item.component.spec.ts index 5bf08fc004..558cd6d379 100644 --- a/src/app/item-page/simple/item-types/shared/item.component.spec.ts +++ b/src/app/item-page/simple/item-types/shared/item.component.spec.ts @@ -44,6 +44,8 @@ import { BrowseDefinitionDataService } from '../../../../core/browse/browse-defi import { BrowseDefinitionDataServiceStub } from '../../../../shared/testing/browse-definition-data-service.stub'; +import { BrowseService } from '../../../../core/browse/browse.service'; +import { BrowseServiceStub } from '../../../../shared/testing/browse-service.stub'; import { buildPaginatedList } from '../../../../core/data/paginated-list.model'; import { PageInfo } from '../../../../core/shared/page-info.model'; @@ -133,6 +135,7 @@ export function getItemPageFieldsTest(mockItem: Item, component) { { provide: AuthorizationDataService, useValue: authorizationService }, { provide: ResearcherProfileDataService, useValue: {} }, { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }, + { provide: BrowseService, useValue: BrowseServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts index 4b7da40abe..3d72ec2360 100644 --- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts +++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.spec.ts @@ -41,6 +41,8 @@ import { BrowseDefinitionDataService } from '../../../../core/browse/browse-defi import { BrowseDefinitionDataServiceStub } from '../../../../shared/testing/browse-definition-data-service.stub'; +import { BrowseService } from '../../../../core/browse/browse.service'; +import { BrowseServiceStub } from '../../../../shared/testing/browse-service.stub'; const noMetadata = new MetadataMap(); @@ -96,6 +98,7 @@ describe('UntypedItemComponent', () => { { provide: ItemVersionsSharedService, useValue: {} }, { provide: RouteService, useValue: mockRouteService }, { provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }, + { provide: BrowseService, useValue: BrowseServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(UntypedItemComponent, { diff --git a/src/app/shared/testing/browse-service.stub.ts b/src/app/shared/testing/browse-service.stub.ts new file mode 100644 index 0000000000..85ca717cb0 --- /dev/null +++ b/src/app/shared/testing/browse-service.stub.ts @@ -0,0 +1,91 @@ +import { + EMPTY, + Observable, +} from 'rxjs'; + +import { + buildPaginatedList, + PaginatedList, +} from '../../core/data/paginated-list.model'; +import { RemoteData } from '../../core/data/remote-data'; +import { BrowseDefinition } from '../../core/shared/browse-definition.model'; +import { FlatBrowseDefinition } from '../../core/shared/flat-browse-definition.model'; +import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model'; +import { PageInfo } from '../../core/shared/page-info.model'; +import { ValueListBrowseDefinition } from '../../core/shared/value-list-browse-definition.model'; +import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; + +const mockData = [ + Object.assign(new FlatBrowseDefinition(), { + 'id': 'dateissued', + 'browseType': 'flatBrowse', + 'dataType': 'date', + 'sortOptions': EMPTY, + 'order': 'ASC', + 'type': 'browse', + 'metadataKeys': [ + 'dc.date.issued', + ], + '_links': EMPTY, + }), + + Object.assign(new ValueListBrowseDefinition(), { + 'id': 'author', + 'browseType': 'valueList', + 'dataType': 'text', + 'sortOptions': EMPTY, + 'order': 'ASC', + 'type': 'browse', + 'metadataKeys': [ + 'dc.contributor.*', + 'dc.creator', + ], + '_links': EMPTY, + }), + + Object.assign(new FlatBrowseDefinition(), { + 'id': 'title', + 'browseType': 'flatBrowse', + 'dataType': 'title', + 'sortOptions': EMPTY, + 'order': 'ASC', + 'type': 'browse', + 'metadataKeys': [ + 'dc.title', + ], + '_links': EMPTY, + }), + + Object.assign(new ValueListBrowseDefinition(), { + 'id': 'subject', + 'browseType': 'valueList', + 'dataType': 'text', + 'sortOptions': EMPTY, + 'order': 'ASC', + 'type': 'browse', + 'metadataKeys': [ + 'dc.subject.*', + ], + '_links': EMPTY, + }), + + Object.assign(new HierarchicalBrowseDefinition(), { + 'id': 'srsc', + 'browseType': 'hierarchicalBrowse', + 'facetType': 'subject', + 'vocabulary': 'srsc', + 'type': 'browse', + 'metadataKeys': [ + 'dc.subject', + ], + '_links': EMPTY, + }), +]; +export const BrowseServiceStub: any = { + /** + * Get all browse definitions. + */ + getBrowseDefinitions(): Observable>> { + return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), mockData)); + }, +}; From 05d5a0816d121c31c73edb606edb4f8c5aed9b83 Mon Sep 17 00:00:00 2001 From: VictorHugoDuranS Date: Fri, 10 Jan 2025 15:17:08 -0600 Subject: [PATCH 694/875] Change - Metadata field selector, add infinite scroll for data paginated (#3096) * Change - Metadata field selector add infinite scroll for data paginated * Update change on new BRANCH * Fix - LINT ERRORS * Fix - LINT ERRORS --- .../metadata-field-selector.component.html | 29 +++- .../metadata-field-selector.component.scss | 5 + .../metadata-field-selector.component.spec.ts | 9 +- .../metadata-field-selector.component.ts | 127 +++++++++++++----- 4 files changed, 128 insertions(+), 42 deletions(-) diff --git a/src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.html b/src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.html index 3f5af76051..a82f31b953 100644 --- a/src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.html +++ b/src/app/dso-shared/dso-edit-metadata/metadata-field-selector/metadata-field-selector.component.html @@ -6,13 +6,30 @@ [formControl]="input" (focusin)="query$.next(mdField)" (dsClickOutside)="query$.next(null)" - (click)="$event.stopPropagation();" /> + (click)="$event.stopPropagation();" + (keyup)="this.selectedValueLoading = false" + />
    {{ dsoType + '.edit.metadata.metadatafield.invalid' | translate }}
    - - -
    -

    {{'profile.groups.head' | translate}}

    -
      -
    • {{ dsoNameService.getName(group) }}
    • -
    -
    + + + + + +
    +

    {{ 'profile.groups.head' | translate }}

    +
      +
    • {{ dsoNameService.getName(group) }}
    • +
    +
    +
    +
    +
    + + + +
    diff --git a/src/app/profile-page/profile-page.component.ts b/src/app/profile-page/profile-page.component.ts index 6e693461d2..f8cb30a447 100644 --- a/src/app/profile-page/profile-page.component.ts +++ b/src/app/profile-page/profile-page.component.ts @@ -9,7 +9,12 @@ import { RemoteData } from '../core/data/remote-data'; import { PaginatedList } from '../core/data/paginated-list.model'; import { filter, switchMap, tap } from 'rxjs/operators'; import { EPersonDataService } from '../core/eperson/eperson-data.service'; -import { getAllSucceededRemoteData, getFirstCompletedRemoteData, getRemoteDataPayload } from '../core/shared/operators'; +import { + getAllCompletedRemoteData, + getAllSucceededRemoteData, + getFirstCompletedRemoteData, + getRemoteDataPayload +} from '../core/shared/operators'; import { hasValue, isNotEmpty } from '../shared/empty.util'; import { followLink } from '../shared/utils/follow-link-config.model'; import { AuthService } from '../core/auth/auth.service'; @@ -19,6 +24,8 @@ import { FeatureID } from '../core/data/feature-authorization/feature-id'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; import { ConfigurationProperty } from '../core/shared/configuration-property.model'; import { DSONameService } from '../core/breadcrumbs/dso-name.service'; +import { PaginationService } from '../core/pagination/pagination.service'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; @Component({ selector: 'ds-profile-page', @@ -79,6 +86,15 @@ export class ProfilePageComponent implements OnInit { private currentUser: EPerson; canChangePassword$: Observable; + /** + * Default configuration for group pagination + **/ + optionsGroupsPagination = Object.assign(new PaginationComponentOptions(),{ + id: 'page_groups', + currentPage: 1, + pageSize: 20, + }); + isResearcherProfileEnabled$: BehaviorSubject = new BehaviorSubject(false); constructor(private authService: AuthService, @@ -88,6 +104,7 @@ export class ProfilePageComponent implements OnInit { private authorizationService: AuthorizationDataService, private configurationService: ConfigurationDataService, public dsoNameService: DSONameService, + private paginationService: PaginationService, ) { } @@ -99,7 +116,18 @@ export class ProfilePageComponent implements OnInit { getRemoteDataPayload(), tap((user: EPerson) => this.currentUser = user) ); - this.groupsRD$ = this.user$.pipe(switchMap((user: EPerson) => user.groups)); + this.groupsRD$ = this.paginationService.getCurrentPagination(this.optionsGroupsPagination.id, this.optionsGroupsPagination).pipe( + switchMap((pageOptions: PaginationComponentOptions) => { + return this.epersonService.findById(this.currentUser.id, true, true, followLink('groups',{ + findListOptions: { + elementsPerPage: pageOptions.pageSize, + currentPage: pageOptions.currentPage, + } })); + }), + getAllCompletedRemoteData(), + getRemoteDataPayload(), + switchMap((user: EPerson) => user?.groups), + ); this.canChangePassword$ = this.user$.pipe(switchMap((user: EPerson) => this.authorizationService.isAuthorized(FeatureID.CanChangePassword, user._links.self.href))); this.specialGroupsRD$ = this.authService.getSpecialGroupsFromAuthStatus(); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index a56f1cd4f2..f53670c4e3 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1622,6 +1622,8 @@ "error.recent-submissions": "Error fetching recent submissions", + "error.profile-groups": "Error retrieving profile groups", + "error.search-results": "Error fetching search results", "error.invalid-search-query": "Search query is not valid. Please check Solr query syntax best practices for further information about this error.", From a57fd1a069a32375b4e5177969c076e93537657a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 02:39:03 +0000 Subject: [PATCH 696/875] Bump sass from 1.80.6 to 1.83.1 in the sass group across 1 directory Bumps the sass group with 1 update in the / directory: [sass](https://github.com/sass/dart-sass). Updates `sass` from 1.80.6 to 1.83.1 - [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.80.6...1.83.1) --- updated-dependencies: - dependency-name: sass dependency-type: direct:development update-type: version-update:semver-minor dependency-group: sass ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 15b2bb9779..8929f9ddb1 100644 --- a/package.json +++ b/package.json @@ -186,7 +186,7 @@ "react-copy-to-clipboard": "^5.1.0", "react-dom": "^16.14.0", "rimraf": "^3.0.2", - "sass": "~1.80.6", + "sass": "~1.83.1", "sass-loader": "^12.6.0", "sass-resources-loader": "^2.2.5", "ts-node": "^8.10.2", diff --git a/yarn.lock b/yarn.lock index 63a2fbbba5..89561983f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6620,6 +6620,11 @@ immutable@^4.0.0: resolved "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz" integrity sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg== +immutable@^5.0.2: + version "5.0.3" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1" + integrity sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" @@ -10235,13 +10240,13 @@ sass@1.58.1: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" -sass@^1.25.0, sass@~1.80.6: - version "1.80.6" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.80.6.tgz#5d0aa55763984effe41e40019c9571ab73e6851f" - integrity sha512-ccZgdHNiBF1NHBsWvacvT5rju3y1d/Eu+8Ex6c21nHp2lZGLBEtuwc415QfiI1PJa1TpCo3iXwwSRjRpn2Ckjg== +sass@^1.25.0, sass@~1.83.1: + version "1.83.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.83.1.tgz#dee1ab94b47a6f9993d3195d36f556bcbda64846" + integrity sha512-EVJbDaEs4Rr3F0glJzFSOvtg2/oy2V/YrGFPqPY24UqcLDWcI9ZY5sN+qyO3c/QCZwzgfirvhXvINiJCE/OLcA== dependencies: chokidar "^4.0.0" - immutable "^4.0.0" + immutable "^5.0.2" source-map-js ">=0.6.2 <2.0.0" optionalDependencies: "@parcel/watcher" "^2.4.1" From 3c16537fe46a0d9eeeec5eb7fe8438aeb3399442 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 02:39:41 +0000 Subject: [PATCH 697/875] Bump express from 4.21.1 to 4.21.2 Bumps [express](https://github.com/expressjs/express) from 4.21.1 to 4.21.2. - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.1...4.21.2) --- updated-dependencies: - dependency-name: express dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 15b2bb9779..483358b029 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "date-fns-tz": "^1.3.7", "deepmerge": "^4.3.1", "ejs": "^3.1.10", - "express": "^4.21.1", + "express": "^4.21.2", "express-rate-limit": "^5.1.3", "fast-json-patch": "^3.1.1", "filesize": "^6.1.0", diff --git a/yarn.lock b/yarn.lock index 63a2fbbba5..e9c821cb60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5617,10 +5617,10 @@ express-static-gzip@^2.2.0: parseurl "^1.3.3" serve-static "^1.16.2" -express@^4.17.3, express@^4.18.2, express@^4.21.1: - version "4.21.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.21.1.tgz#9dae5dda832f16b4eec941a4e44aa89ec481b281" - integrity sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ== +express@^4.17.3, express@^4.18.2, express@^4.21.2: + version "4.21.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" + integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== dependencies: accepts "~1.3.8" array-flatten "1.1.1" @@ -5641,7 +5641,7 @@ express@^4.17.3, express@^4.18.2, express@^4.21.1: methods "~1.1.2" on-finished "2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.10" + path-to-regexp "0.1.12" proxy-addr "~2.0.7" qs "6.13.0" range-parser "~1.2.1" @@ -9022,10 +9022,10 @@ path-scurry@^1.6.1: lru-cache "^9.0.0" minipass "^5.0.0" -path-to-regexp@0.1.10: - version "0.1.10" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" - integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== +path-to-regexp@0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" + integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== path-type@^4.0.0: version "4.0.0" @@ -10807,20 +10807,20 @@ statuses@2.0.1: resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -"statuses@>= 1.4.0 < 2", statuses@~1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz" - integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== +"statuses@>= 1.4.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== statuses@~1.3.1: version "1.3.1" resolved "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz" integrity sha512-wuTCPGlJONk/a1kqZ4fQM2+908lC7fa7nPYpTC1EhnvqLX/IICbeP1OZGDtA374trpSq68YubKUMo8oRhN46yg== -statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== +statuses@~1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz" + integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== stop-iteration-iterator@^1.0.0: version "1.0.0" From ecceaf9ee1bac2b22a9d50c3a4daf2a9b893cddc Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 15 Jan 2025 10:30:46 -0600 Subject: [PATCH 698/875] Ensure Admin sidebar link is visible before clicking links --- cypress/e2e/admin-add-new-modals.cy.ts | 3 +++ cypress/e2e/admin-edit-modals.cy.ts | 3 +++ cypress/e2e/admin-export-modals.cy.ts | 2 ++ 3 files changed, 8 insertions(+) diff --git a/cypress/e2e/admin-add-new-modals.cy.ts b/cypress/e2e/admin-add-new-modals.cy.ts index 565ae154f1..0179ca7a7c 100644 --- a/cypress/e2e/admin-add-new-modals.cy.ts +++ b/cypress/e2e/admin-add-new-modals.cy.ts @@ -12,6 +12,7 @@ describe('Admin Add New Modals', () => { cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu + cy.get('#admin-menu-section-new-title').should('be.visible'); cy.get('#admin-menu-section-new-title').click(); cy.get('a[data-test="menu.section.new_community"]').click(); @@ -25,6 +26,7 @@ describe('Admin Add New Modals', () => { cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu + cy.get('#admin-menu-section-new-title').should('be.visible'); cy.get('#admin-menu-section-new-title').click(); cy.get('a[data-test="menu.section.new_collection"]').click(); @@ -38,6 +40,7 @@ describe('Admin Add New Modals', () => { cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu + cy.get('#admin-menu-section-new-title').should('be.visible'); cy.get('#admin-menu-section-new-title').click(); cy.get('a[data-test="menu.section.new_item"]').click(); diff --git a/cypress/e2e/admin-edit-modals.cy.ts b/cypress/e2e/admin-edit-modals.cy.ts index e96d6ce898..79190bfce9 100644 --- a/cypress/e2e/admin-edit-modals.cy.ts +++ b/cypress/e2e/admin-edit-modals.cy.ts @@ -12,6 +12,7 @@ describe('Admin Edit Modals', () => { cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu + cy.get('#admin-menu-section-edit-title').should('be.visible'); cy.get('#admin-menu-section-edit-title').click(); cy.get('a[data-test="menu.section.edit_community"]').click(); @@ -25,6 +26,7 @@ describe('Admin Edit Modals', () => { cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu + cy.get('#admin-menu-section-edit-title').should('be.visible'); cy.get('#admin-menu-section-edit-title').click(); cy.get('a[data-test="menu.section.edit_collection"]').click(); @@ -38,6 +40,7 @@ describe('Admin Edit Modals', () => { cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu + cy.get('#admin-menu-section-edit-title').should('be.visible'); cy.get('#admin-menu-section-edit-title').click(); cy.get('a[data-test="menu.section.edit_item"]').click(); diff --git a/cypress/e2e/admin-export-modals.cy.ts b/cypress/e2e/admin-export-modals.cy.ts index c5b0b0908f..55d952cad8 100644 --- a/cypress/e2e/admin-export-modals.cy.ts +++ b/cypress/e2e/admin-export-modals.cy.ts @@ -12,6 +12,7 @@ describe('Admin Export Modals', () => { cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu + cy.get('#admin-menu-section-export-title').should('be.visible'); cy.get('#admin-menu-section-export-title').click(); cy.get('a[data-test="menu.section.export_metadata"]').click(); @@ -25,6 +26,7 @@ describe('Admin Export Modals', () => { cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu + cy.get('#admin-menu-section-export-title').should('be.visible'); cy.get('#admin-menu-section-export-title').click(); cy.get('a[data-test="menu.section.export_batch"]').click(); From 9e1ce916f655cccc82c51fdf71487cafa615697c Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 15 Jan 2025 10:31:10 -0600 Subject: [PATCH 699/875] Ensure Item Edit page tab is visible before & after clicking it. --- cypress/e2e/item-edit.cy.ts | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/cypress/e2e/item-edit.cy.ts b/cypress/e2e/item-edit.cy.ts index 604aab9d04..ad5d8ea093 100644 --- a/cypress/e2e/item-edit.cy.ts +++ b/cypress/e2e/item-edit.cy.ts @@ -13,9 +13,11 @@ beforeEach(() => { describe('Edit Item > Edit Metadata tab', () => { it('should pass accessibility tests', () => { + cy.get('a[data-test="metadata"]').should('be.visible'); cy.get('a[data-test="metadata"]').click(); - // Our selected tab should be active + // Our selected tab should be both visible & active + cy.get('a[data-test="metadata"]').should('be.visible'); cy.get('a[data-test="metadata"]').should('have.class', 'active'); // tag must be loaded @@ -34,9 +36,11 @@ describe('Edit Item > Edit Metadata tab', () => { describe('Edit Item > Status tab', () => { it('should pass accessibility tests', () => { + cy.get('a[data-test="status"]').should('be.visible'); cy.get('a[data-test="status"]').click(); - // Our selected tab should be active + // Our selected tab should be both visible & active + cy.get('a[data-test="status"]').should('be.visible'); cy.get('a[data-test="status"]').should('have.class', 'active'); // tag must be loaded @@ -50,9 +54,11 @@ describe('Edit Item > Status tab', () => { describe('Edit Item > Bitstreams tab', () => { it('should pass accessibility tests', () => { + cy.get('a[data-test="bitstreams"]').should('be.visible'); cy.get('a[data-test="bitstreams"]').click(); - // Our selected tab should be active + // Our selected tab should be both visible & active + cy.get('a[data-test="bitstreams"]').should('be.visible'); cy.get('a[data-test="bitstreams"]').should('have.class', 'active'); // tag must be loaded @@ -77,9 +83,11 @@ describe('Edit Item > Bitstreams tab', () => { describe('Edit Item > Curate tab', () => { it('should pass accessibility tests', () => { + cy.get('a[data-test="curate"]').should('be.visible'); cy.get('a[data-test="curate"]').click(); - // Our selected tab should be active + // Our selected tab should be both visible & active + cy.get('a[data-test="curate"]').should('be.visible'); cy.get('a[data-test="curate"]').should('have.class', 'active'); // tag must be loaded @@ -93,9 +101,11 @@ describe('Edit Item > Curate tab', () => { describe('Edit Item > Relationships tab', () => { it('should pass accessibility tests', () => { + cy.get('a[data-test="relationships"]').should('be.visible'); cy.get('a[data-test="relationships"]').click(); - // Our selected tab should be active + // Our selected tab should be both visible & active + cy.get('a[data-test="relationships"]').should('be.visible'); cy.get('a[data-test="relationships"]').should('have.class', 'active'); // tag must be loaded @@ -109,9 +119,11 @@ describe('Edit Item > Relationships tab', () => { describe('Edit Item > Version History tab', () => { it('should pass accessibility tests', () => { + cy.get('a[data-test="versionhistory"]').should('be.visible'); cy.get('a[data-test="versionhistory"]').click(); - // Our selected tab should be active + // Our selected tab should be both visible & active + cy.get('a[data-test="versionhistory"]').should('be.visible'); cy.get('a[data-test="versionhistory"]').should('have.class', 'active'); // tag must be loaded @@ -125,9 +137,11 @@ describe('Edit Item > Version History tab', () => { describe('Edit Item > Access Control tab', () => { it('should pass accessibility tests', () => { + cy.get('a[data-test="access-control"]').should('be.visible'); cy.get('a[data-test="access-control"]').click(); - // Our selected tab should be active + // Our selected tab should be both visible & active + cy.get('a[data-test="access-control"]').should('be.visible'); cy.get('a[data-test="access-control"]').should('have.class', 'active'); // tag must be loaded @@ -141,9 +155,11 @@ describe('Edit Item > Access Control tab', () => { describe('Edit Item > Collection Mapper tab', () => { it('should pass accessibility tests', () => { + cy.get('a[data-test="mapper"]').should('be.visible'); cy.get('a[data-test="mapper"]').click(); - // Our selected tab should be active + // Our selected tab should be both visible & active + cy.get('a[data-test="mapper"]').should('be.visible'); cy.get('a[data-test="mapper"]').should('have.class', 'active'); // tag must be loaded From fd166fe79a24ddf15d04961b0cb1c4150a3faeec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 19:51:07 +0000 Subject: [PATCH 700/875] Bump eslint-plugin-jsonc in the eslint group across 1 directory Bumps the eslint group with 1 update in the / directory: [eslint-plugin-jsonc](https://github.com/ota-meshi/eslint-plugin-jsonc). Updates `eslint-plugin-jsonc` from 2.16.0 to 2.18.2 - [Release notes](https://github.com/ota-meshi/eslint-plugin-jsonc/releases) - [Changelog](https://github.com/ota-meshi/eslint-plugin-jsonc/blob/master/CHANGELOG.md) - [Commits](https://github.com/ota-meshi/eslint-plugin-jsonc/compare/v2.16.0...v2.18.2) --- updated-dependencies: - dependency-name: eslint-plugin-jsonc dependency-type: direct:development update-type: version-update:semver-minor dependency-group: eslint ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 33 ++++++++++++++++++++++++--------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 3128aeb917..1b326f1221 100644 --- a/package.json +++ b/package.json @@ -162,7 +162,7 @@ "eslint-plugin-deprecation": "^1.5.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsdoc": "^45.0.0", - "eslint-plugin-jsonc": "^2.16.0", + "eslint-plugin-jsonc": "^2.18.2", "eslint-plugin-lodash": "^7.4.0", "eslint-plugin-unused-imports": "^2.0.0", "express-static-gzip": "^2.2.0", diff --git a/yarn.lock b/yarn.lock index 1ad8fc2f35..4b6faaa9ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5299,10 +5299,10 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-compat-utils@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz#7fc92b776d185a70c4070d03fd26fde3d59652e4" - integrity sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q== +eslint-compat-utils@^0.6.0: + version "0.6.4" + resolved "https://registry.yarnpkg.com/eslint-compat-utils/-/eslint-compat-utils-0.6.4.tgz#173d305132da755ac3612cccab03e1b2e14235ed" + integrity sha512-/u+GQt8NMfXO8w17QendT4gvO5acfxQsAKirAt0LVxDnr2N8YLCVbregaNc/Yhp7NM128DwCaRvr8PLDfeNkQw== dependencies: semver "^7.5.4" @@ -5315,6 +5315,13 @@ eslint-import-resolver-node@^0.3.9: is-core-module "^2.13.0" resolve "^1.22.4" +eslint-json-compat-utils@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/eslint-json-compat-utils/-/eslint-json-compat-utils-0.2.1.tgz#32931d42c723da383712f25177a2c57b9ef5f079" + integrity sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg== + dependencies: + esquery "^1.6.0" + eslint-module-utils@^2.12.0: version "2.12.0" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz#fe4cfb948d61f49203d7b08871982b65b9af0b0b" @@ -5370,13 +5377,14 @@ eslint-plugin-jsdoc@^45.0.0: semver "^7.5.1" spdx-expression-parse "^3.0.1" -eslint-plugin-jsonc@^2.16.0: - version "2.16.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.16.0.tgz#e90eca15aa2e172f5aca52a77fc8c819f52862d7" - integrity sha512-Af/ZL5mgfb8FFNleH6KlO4/VdmDuTqmM+SPnWcdoWywTetv7kq+vQe99UyQb9XO3b0OWLVuTH7H0d/PXYCMdSg== +eslint-plugin-jsonc@^2.18.2: + version "2.18.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.18.2.tgz#893520feb35d343e7438e2fb57fad179ef3ff6aa" + integrity sha512-SDhJiSsWt3nItl/UuIv+ti4g3m4gpGkmnUJS9UWR3TrpyNsIcnJoBRD7Kof6cM4Rk3L0wrmY5Tm3z7ZPjR2uGg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" - eslint-compat-utils "^0.5.0" + eslint-compat-utils "^0.6.0" + eslint-json-compat-utils "^0.2.1" espree "^9.6.1" graphemer "^1.4.0" jsonc-eslint-parser "^2.0.4" @@ -5520,6 +5528,13 @@ esquery@^1.4.2, esquery@^1.5.0: dependencies: estraverse "^5.1.0" +esquery@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" From 31c9b854c628863af65a9a8b7ef46d4e59c87af1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:54:06 +0000 Subject: [PATCH 701/875] Bump the testing group across 1 directory with 2 updates Bumps the testing group with 2 updates in the / directory: [cypress](https://github.com/cypress-io/cypress) and [ng-mocks](https://github.com/help-me-mom/ng-mocks). Updates `cypress` from 13.15.1 to 13.17.0 - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md) - [Commits](https://github.com/cypress-io/cypress/compare/v13.15.1...v13.17.0) Updates `ng-mocks` from 14.13.1 to 14.13.2 - [Release notes](https://github.com/help-me-mom/ng-mocks/releases) - [Changelog](https://github.com/help-me-mom/ng-mocks/blob/master/CHANGELOG.md) - [Commits](https://github.com/help-me-mom/ng-mocks/compare/v14.13.1...v14.13.2) --- updated-dependencies: - dependency-name: cypress dependency-type: direct:development update-type: version-update:semver-minor dependency-group: testing - dependency-name: ng-mocks dependency-type: direct:development update-type: version-update:semver-patch dependency-group: testing ... Signed-off-by: dependabot[bot] --- package.json | 4 +-- yarn.lock | 75 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index f7c2431009..aa09e1bb81 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "copy-webpack-plugin": "^6.4.1", "cross-env": "^7.0.3", "csstype": "^3.1.3", - "cypress": "^13.15.1", + "cypress": "^13.17.0", "cypress-axe": "^1.5.0", "deep-freeze": "0.0.1", "eslint": "^8.39.0", @@ -174,7 +174,7 @@ "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "karma-mocha-reporter": "2.2.5", - "ng-mocks": "^14.13.1", + "ng-mocks": "^14.13.2", "ngx-mask": "^13.1.7", "nodemon": "^2.0.22", "postcss": "^8.4", diff --git a/yarn.lock b/yarn.lock index 3548e550bd..d742d70c13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1534,10 +1534,10 @@ resolved "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz" integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw== -"@cypress/request@^3.0.4": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.5.tgz#d893a6e68ce2636c085fcd8d7283c3186499ba63" - integrity sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA== +"@cypress/request@^3.0.6": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.7.tgz#6a74a4da98d9e5ae9121d6e2d9c14780c9b5cf1a" + integrity sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -1552,9 +1552,9 @@ json-stringify-safe "~5.0.1" mime-types "~2.1.19" performance-now "^2.1.0" - qs "6.13.0" + qs "6.13.1" safe-buffer "^5.1.2" - tough-cookie "^4.1.3" + tough-cookie "^5.0.0" tunnel-agent "^0.6.0" uuid "^8.3.2" @@ -3940,10 +3940,10 @@ chrome-trace-event@^1.0.2: resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== -ci-info@^3.2.0: - version "3.8.0" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz" - integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== +ci-info@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.1.0.tgz#92319d2fa29d2620180ea5afed31f589bc98cf83" + integrity sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A== classnames@^2.2.6: version "2.3.2" @@ -4465,12 +4465,12 @@ cypress-axe@^1.5.0: resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-1.5.0.tgz#95082734583da77b51ce9b7784e14a442016c7a1" integrity sha512-Hy/owCjfj+25KMsecvDgo4fC/781ccL+e8p+UUYoadGVM2ogZF9XIKbiM6KI8Y3cEaSreymdD6ZzccbI2bY0lQ== -cypress@^13.15.1: - version "13.15.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.15.1.tgz#d85074e07cc576eb30068617d529719ef6093b69" - integrity sha512-DwUFiKXo4lef9kA0M4iEhixFqoqp2hw8igr0lTqafRb9qtU3X0XGxKbkSYsUFdkrAkphc7MPDxoNPhk5pj9PVg== +cypress@^13.17.0: + version "13.17.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.17.0.tgz#34c3d68080c4497eace0f353bd1629587a5f600d" + integrity sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA== dependencies: - "@cypress/request" "^3.0.4" + "@cypress/request" "^3.0.6" "@cypress/xvfb" "^1.2.4" "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" @@ -4481,6 +4481,7 @@ cypress@^13.15.1: cachedir "^2.3.0" chalk "^4.1.0" check-more-types "^2.24.0" + ci-info "^4.0.0" cli-cursor "^3.1.0" cli-table3 "~0.6.1" commander "^6.2.1" @@ -4495,7 +4496,6 @@ cypress@^13.15.1: figures "^3.2.0" fs-extra "^9.1.0" getos "^3.2.1" - is-ci "^3.0.1" is-installed-globally "~0.4.0" lazy-ass "^1.6.0" listr2 "^3.8.3" @@ -6832,13 +6832,6 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-ci@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" - integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== - dependencies: - ci-info "^3.2.0" - is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.15.1, is-core-module@^2.8.1: version "2.15.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" @@ -8382,10 +8375,10 @@ neo-async@^2.6.2: resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -ng-mocks@^14.13.1: - version "14.13.1" - resolved "https://registry.yarnpkg.com/ng-mocks/-/ng-mocks-14.13.1.tgz#9fef8cc902a0d9d3250550d83514f9c65d4cd366" - integrity sha512-eyfnjXeC108SqVD09i/cBwCpKkK0JjBoAg8jp7oQS2HS081K3WJTttFpgLGeLDYKmZsZ6nYpI+HHNQ3OksaJ7A== +ng-mocks@^14.13.2: + version "14.13.2" + resolved "https://registry.yarnpkg.com/ng-mocks/-/ng-mocks-14.13.2.tgz#ddd675d675eb16dfa85834e28dd42343853a6622" + integrity sha512-ItAB72Pc0uznL1j4TPsFp1wehhitVp7DARkc67aafeIk1FDgwnAZvzJwntMnIp/IWMSbzrEQ6kl3cc5euX1NRA== ng2-file-upload@1.4.0: version "1.4.0" @@ -9564,6 +9557,13 @@ qs@6.13.0: dependencies: side-channel "^1.0.6" +qs@6.13.1: + version "6.13.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.1.tgz#3ce5fc72bd3a8171b85c99b93c65dd20b7d1b16e" + integrity sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg== + dependencies: + side-channel "^1.0.6" + querystringify@^2.1.1: version "2.2.0" resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz" @@ -11081,6 +11081,18 @@ tiny-warning@^1.0.2: resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== +tldts-core@^6.1.72: + version "6.1.72" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.72.tgz#32b38e1843f4adab57d2414a9ec4af9a81826bc0" + integrity sha512-FW3H9aCaGTJ8l8RVCR3EX8GxsxDbQXuwetwwgXA2chYdsX+NY1ytCBl61narjjehWmCw92tc1AxlcY3668CU8g== + +tldts@^6.1.32: + version "6.1.72" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.72.tgz#9b85f47e451e2ff079fab5801b4fa156ecda69f4" + integrity sha512-QNtgIqSUb9o2CoUjX9T5TwaIvUUJFU1+12PJkgt42DFV2yf9J6549yTF2uGloQsJ/JOC8X+gIB81ind97hRiIQ== + dependencies: + tldts-core "^6.1.72" + tmp@0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz" @@ -11129,7 +11141,7 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@^4.0.0, tough-cookie@^4.1.2, tough-cookie@^4.1.3: +tough-cookie@^4.0.0, tough-cookie@^4.1.2: version "4.1.4" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== @@ -11139,6 +11151,13 @@ tough-cookie@^4.0.0, tough-cookie@^4.1.2, tough-cookie@^4.1.3: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.1.0.tgz#0667b0f2fbb5901fe6f226c3e0b710a9a4292f87" + integrity sha512-rvZUv+7MoBYTiDmFPBrhL7Ujx9Sk+q9wwm22x8c8T5IJaR+Wsyc7TNxbVxo84kZoRJZZMazowFLqpankBEQrGg== + dependencies: + tldts "^6.1.32" + tr46@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz" From 2abdf122b49dcbc2b895641a8a6da672b4227372 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 23:46:54 +0000 Subject: [PATCH 702/875] Bump sanitize-html from 2.13.1 to 2.14.0 Bumps [sanitize-html](https://github.com/apostrophecms/sanitize-html) from 2.13.1 to 2.14.0. - [Changelog](https://github.com/apostrophecms/sanitize-html/blob/main/CHANGELOG.md) - [Commits](https://github.com/apostrophecms/sanitize-html/compare/2.13.1...2.14.0) --- updated-dependencies: - dependency-name: sanitize-html dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f7c2431009..13385f027f 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "pem": "1.14.8", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.0", - "sanitize-html": "^2.13.1", + "sanitize-html": "^2.14.0", "sortablejs": "1.15.6", "uuid": "^8.3.2", "zone.js": "~0.13.3" diff --git a/yarn.lock b/yarn.lock index 3548e550bd..3854d68bbb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10208,10 +10208,10 @@ safe-stable-stringify@^2.4.3: resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sanitize-html@^2.13.1: - version "2.13.1" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.13.1.tgz#b4639b0a09574ab62b1b353cb99b1b87af742834" - integrity sha512-ZXtKq89oue4RP7abL9wp/9URJcqQNABB5GGJ2acW1sdO8JTVl92f4ygD7Yc9Ze09VAZhnt2zegeU0tbNsdcLYg== +sanitize-html@^2.14.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.14.0.tgz#bd2a7b97ee1d86a7f0e0babf3a4468f639c3a429" + integrity sha512-CafX+IUPxZshXqqRaG9ZClSlfPVjSxI0td7n07hk8QO2oO+9JDnlcL8iM8TWeOXOIBFgIOx6zioTzM53AOMn3g== dependencies: deepmerge "^4.2.2" escape-string-regexp "^4.0.0" From ce5ab70fd42868de51742ec5d88856199dd3cb56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 23:48:52 +0000 Subject: [PATCH 703/875] Bump isbot from 5.1.17 to 5.1.21 Bumps [isbot](https://github.com/omrilotan/isbot) from 5.1.17 to 5.1.21. - [Changelog](https://github.com/omrilotan/isbot/blob/main/CHANGELOG.md) - [Commits](https://github.com/omrilotan/isbot/compare/v5.1.17...v5.1.21) --- updated-dependencies: - dependency-name: isbot dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f7c2431009..fffa4a5f5b 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "filesize": "^6.1.0", "http-proxy-middleware": "^2.0.7", "http-terminator": "^3.2.0", - "isbot": "^5.1.17", + "isbot": "^5.1.21", "js-cookie": "2.2.1", "js-yaml": "^4.1.0", "json5": "^2.2.3", diff --git a/yarn.lock b/yarn.lock index 3548e550bd..6f752b2d39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7087,10 +7087,10 @@ isbinaryfile@^4.0.8: resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz" integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== -isbot@^5.1.17: - version "5.1.17" - resolved "https://registry.yarnpkg.com/isbot/-/isbot-5.1.17.tgz#ad7da5690a61bbb19056a069975c9a73182682a0" - integrity sha512-/wch8pRKZE+aoVhRX/hYPY1C7dMCeeMyhkQLNLNlYAbGQn9bkvMB8fOUXNnk5I0m4vDYbBJ9ciVtkr9zfBJ7qA== +isbot@^5.1.21: + version "5.1.21" + resolved "https://registry.yarnpkg.com/isbot/-/isbot-5.1.21.tgz#4e27526a71c8b9c243b1c7a6445ad4267fa83728" + integrity sha512-0q3naRVpENL0ReKHeNcwn/G7BDynp0DqZUckKyFtM9+hmpnPqgm8+8wbjiVZ0XNhq1wPQV28/Pb8Snh5adeUHA== isexe@^2.0.0: version "2.0.0" From 7472be009855376edccb5ac5242673f1bb44e75c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 22:57:43 +0000 Subject: [PATCH 704/875] Bump @types/lodash from 4.17.13 to 4.17.14 Bumps [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) from 4.17.13 to 4.17.14. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash) --- updated-dependencies: - dependency-name: "@types/lodash" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ce5449fa33..8ac4635e79 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "@types/grecaptcha": "^3.0.9", "@types/jasmine": "~3.6.0", "@types/js-cookie": "2.2.6", - "@types/lodash": "^4.17.13", + "@types/lodash": "^4.17.14", "@types/node": "^14.18.63", "@types/sanitize-html": "^2.13.0", "@typescript-eslint/eslint-plugin": "^5.62.0", diff --git a/yarn.lock b/yarn.lock index 96b6665499..8b9b52a4e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2539,10 +2539,10 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/lodash@^4.17.13": - version "4.17.13" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.13.tgz#786e2d67cfd95e32862143abe7463a7f90c300eb" - integrity sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg== +"@types/lodash@^4.17.14": + version "4.17.14" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.14.tgz#bafc053533f4cdc5fcc9635af46a963c1f3deaff" + integrity sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A== "@types/mime@*": version "3.0.1" From 9741ce3e697a75ec5955c8b2d2b00282fe815790 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 22:57:54 +0000 Subject: [PATCH 705/875] Bump mirador from 3.4.2 to 3.4.3 Bumps [mirador](https://github.com/ProjectMirador/mirador) from 3.4.2 to 3.4.3. - [Release notes](https://github.com/ProjectMirador/mirador/releases) - [Commits](https://github.com/ProjectMirador/mirador/compare/v3.4.2...v3.4.3) --- updated-dependencies: - dependency-name: mirador dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ce5449fa33..3d7e9c0ec7 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "lru-cache": "^7.14.1", "markdown-it": "^13.0.2", "markdown-it-mathjax3": "^4.3.2", - "mirador": "^3.4.2", + "mirador": "^3.4.3", "mirador-dl-plugin": "^0.13.0", "mirador-share-plugin": "^0.16.0", "morgan": "^1.10.0", diff --git a/yarn.lock b/yarn.lock index 96b6665499..48ca8c3da3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8226,10 +8226,10 @@ mirador-share-plugin@^0.16.0: dependencies: notistack "^3.0.1" -mirador@^3.4.2: - version "3.4.2" - resolved "https://registry.yarnpkg.com/mirador/-/mirador-3.4.2.tgz#51ec7ca1b9854792bbe2fa5e13b3692c92e97102" - integrity sha512-Gd7G4NkXq6/qD/De5soYspSo9VykAzrGFunKqUI3x9WShoZP23pYIEPoC/96tvfk3KMv+UbAUxDp99Xeo7vnVQ== +mirador@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/mirador/-/mirador-3.4.3.tgz#1b083bd037b851d1389758f3c86eb94832b00ec6" + integrity sha512-yHoug0MHy4e9apykbbBhK+4CmbZS94zMxmugw2E2VX6iB0b2PKKY0JfYr/QfXh9P29YnWAbymaXJVpgbHVpTVw== dependencies: "@material-ui/core" "^4.12.3" "@material-ui/icons" "^4.9.1" From 9bc65b99a65051bdc4d1d16115ea12c774d8c974 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 22:58:20 +0000 Subject: [PATCH 706/875] Bump core-js from 3.39.0 to 3.40.0 Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.39.0 to 3.40.0. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/commits/v3.40.0/packages/core-js) --- updated-dependencies: - dependency-name: core-js dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ce5449fa33..7e8f57e977 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "colors": "^1.4.0", "compression": "^1.7.5", "cookie-parser": "1.4.7", - "core-js": "^3.39.0", + "core-js": "^3.40.0", "date-fns": "^2.30.0", "date-fns-tz": "^1.3.7", "deepmerge": "^4.3.1", diff --git a/yarn.lock b/yarn.lock index 96b6665499..88a5116990 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4272,10 +4272,10 @@ core-js-compat@^3.25.1: dependencies: browserslist "^4.21.5" -core-js@^3.39.0: - version "3.39.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.39.0.tgz#57f7647f4d2d030c32a72ea23a0555b2eaa30f83" - integrity sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g== +core-js@^3.40.0: + version "3.40.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.40.0.tgz#2773f6b06877d8eda102fc42f828176437062476" + integrity sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ== core-util-is@1.0.2: version "1.0.2" From 39ddc5cbcd6bae6dd9a1bb2397be6f4a5ef334d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 22:58:28 +0000 Subject: [PATCH 707/875] Bump jsonschema from 1.4.1 to 1.5.0 Bumps [jsonschema](https://github.com/tdegrunt/jsonschema) from 1.4.1 to 1.5.0. - [Commits](https://github.com/tdegrunt/jsonschema/commits) --- updated-dependencies: - dependency-name: jsonschema dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ce5449fa33..2306326435 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "js-cookie": "2.2.1", "js-yaml": "^4.1.0", "json5": "^2.2.3", - "jsonschema": "1.4.1", + "jsonschema": "1.5.0", "jwt-decode": "^3.1.2", "klaro": "^0.7.21", "lodash": "^4.17.21", diff --git a/yarn.lock b/yarn.lock index 96b6665499..8de93af4f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7398,10 +7398,10 @@ jsonparse@^1.3.1: resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -jsonschema@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" - integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ== +jsonschema@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.5.0.tgz#f6aceb1ab9123563dd901d05f81f9d4883d3b7d8" + integrity sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw== jsprim@^2.0.2: version "2.0.2" From d0b65719544a0938859a10c795dd846a30eeee42 Mon Sep 17 00:00:00 2001 From: Andrea Barbasso <´andrea.barbasso@4science.com´> Date: Mon, 20 Jan 2025 15:31:31 +0100 Subject: [PATCH 708/875] [CST-14904] improve orcid logo image alt --- .../orcid-badge-and-tooltip.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.html b/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.html index fc34aee970..9f4821e780 100644 --- a/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.html +++ b/src/app/shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component.html @@ -2,7 +2,7 @@ [ngbTooltip]="orcidTooltipTemplate" class="orcid-icon" [ngClass]="{'not-authenticated': !authenticatedTimestamp}" - alt="orcid-logo" + alt="ORCID {{ orcidTooltip }}" src="assets/images/orcid.logo.icon.svg" data-test="orcidIcon"/> From 74d6dbb454cf40d5c418cc0c6cde05ea04de2103 Mon Sep 17 00:00:00 2001 From: Mohana Sarmiento Date: Mon, 20 Jan 2025 18:13:35 -0500 Subject: [PATCH 709/875] Fix double encoding of bitstream filenames in URL --- src/app/core/data/bitstream-data.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/data/bitstream-data.service.ts b/src/app/core/data/bitstream-data.service.ts index bf8e3585df..bb4ec28166 100644 --- a/src/app/core/data/bitstream-data.service.ts +++ b/src/app/core/data/bitstream-data.service.ts @@ -171,7 +171,7 @@ export class BitstreamDataService extends IdentifiableDataService imp searchParams.push(new RequestParam('sequenceId', sequenceId)); } if (hasValue(filename)) { - searchParams.push(new RequestParam('filename', encodeURIComponent(filename))); + searchParams.push(new RequestParam('filename', filename)); } const hrefObs = this.getSearchByHref( From 6035d9e925b66589a49fb2074b5e3d3f9b852586 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Tue, 21 Jan 2025 18:29:10 +0100 Subject: [PATCH 710/875] remove duplicated line (cherry picked from commit 5d04e44ff31593ec7c17635d0053d3b8b615fb07) --- src/styles/_bootstrap_variables_mapping.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/styles/_bootstrap_variables_mapping.scss b/src/styles/_bootstrap_variables_mapping.scss index 5a64be7e2a..944166399e 100644 --- a/src/styles/_bootstrap_variables_mapping.scss +++ b/src/styles/_bootstrap_variables_mapping.scss @@ -220,7 +220,6 @@ --bs-table-dark-hover-color: #{$table-dark-hover-color}; --bs-table-dark-hover-bg: #{$table-dark-hover-bg}; --bs-table-dark-border-color: #{$table-dark-border-color}; - --bs-table-dark-color: #{$table-dark-color}; --bs-table-striped-order: #{$table-striped-order}; --bs-table-caption-color: #{$table-caption-color}; --bs-table-bg-level: #{$table-bg-level}; From 2bd53d822abe8b811bcde9d613f68001a35ef3bf Mon Sep 17 00:00:00 2001 From: Mohana Sarmiento Date: Tue, 21 Jan 2025 16:26:01 -0500 Subject: [PATCH 711/875] Add unit test --- .../core/data/bitstream-data.service.spec.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/app/core/data/bitstream-data.service.spec.ts b/src/app/core/data/bitstream-data.service.spec.ts index 89178f8dd2..1f4b2efaf6 100644 --- a/src/app/core/data/bitstream-data.service.spec.ts +++ b/src/app/core/data/bitstream-data.service.spec.ts @@ -21,6 +21,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import objectContaining = jasmine.objectContaining; import { RemoteData } from './remote-data'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; +import { RequestParam } from '../cache/models/request-param.model'; describe('BitstreamDataService', () => { let service: BitstreamDataService; @@ -132,4 +133,30 @@ describe('BitstreamDataService', () => { expect(service.invalidateByHref).toHaveBeenCalledWith('fake-bitstream2-self'); }); }); + + describe('findByItemHandle', () => { + it('should encode the filename correctly in the search parameters', () => { + const handle = '123456789/1234'; + const sequenceId = '5'; + const filename = 'file with spaces.pdf'; + const searchParams = [ + new RequestParam('handle', handle), + new RequestParam('sequenceId', sequenceId), + new RequestParam('filename', filename) + ]; + const linksToFollow: FollowLinkConfig[] = []; + + spyOn(service as any, 'getSearchByHref').and.callThrough(); + + service.getSearchByHref('byItemHandle', { searchParams }, ...linksToFollow).subscribe((href) => { + expect(service.getSearchByHref).toHaveBeenCalledWith( + 'byItemHandle', + { searchParams }, + ...linksToFollow + ); + + expect(href).toBe(`${url}/bitstreams/search/byItemHandle?handle=123456789%2F1234&sequenceId=5&filename=file%20with%20spaces.pdf`); + }); + }); + }); }); From e3f13f8c54f6054765848c5f8f95e79fa3c7fd9d Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 21 Jan 2025 14:16:29 -0600 Subject: [PATCH 712/875] Ensure admin menu e2e tests hover over admin menu before clicking on it. Attempt to stabilize tests which open this menu. (cherry picked from commit e9cd0f4c64ad7293afde482b9f59ddc22b5f90cc) --- cypress/e2e/admin-add-new-modals.cy.ts | 3 +++ cypress/e2e/admin-edit-modals.cy.ts | 3 +++ cypress/e2e/admin-export-modals.cy.ts | 2 ++ 3 files changed, 8 insertions(+) diff --git a/cypress/e2e/admin-add-new-modals.cy.ts b/cypress/e2e/admin-add-new-modals.cy.ts index 0179ca7a7c..332d44da13 100644 --- a/cypress/e2e/admin-add-new-modals.cy.ts +++ b/cypress/e2e/admin-add-new-modals.cy.ts @@ -9,6 +9,7 @@ describe('Admin Add New Modals', () => { it('Add new Community modal should pass accessibility tests', () => { // Pin the sidebar open + cy.get('#sidebar-collapse-toggle').trigger('mouseover'); cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu @@ -23,6 +24,7 @@ describe('Admin Add New Modals', () => { it('Add new Collection modal should pass accessibility tests', () => { // Pin the sidebar open + cy.get('#sidebar-collapse-toggle').trigger('mouseover'); cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu @@ -37,6 +39,7 @@ describe('Admin Add New Modals', () => { it('Add new Item modal should pass accessibility tests', () => { // Pin the sidebar open + cy.get('#sidebar-collapse-toggle').trigger('mouseover'); cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu diff --git a/cypress/e2e/admin-edit-modals.cy.ts b/cypress/e2e/admin-edit-modals.cy.ts index 79190bfce9..8ba524d5be 100644 --- a/cypress/e2e/admin-edit-modals.cy.ts +++ b/cypress/e2e/admin-edit-modals.cy.ts @@ -9,6 +9,7 @@ describe('Admin Edit Modals', () => { it('Edit Community modal should pass accessibility tests', () => { // Pin the sidebar open + cy.get('#sidebar-collapse-toggle').trigger('mouseover'); cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu @@ -23,6 +24,7 @@ describe('Admin Edit Modals', () => { it('Edit Collection modal should pass accessibility tests', () => { // Pin the sidebar open + cy.get('#sidebar-collapse-toggle').trigger('mouseover'); cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu @@ -37,6 +39,7 @@ describe('Admin Edit Modals', () => { it('Edit Item modal should pass accessibility tests', () => { // Pin the sidebar open + cy.get('#sidebar-collapse-toggle').trigger('mouseover'); cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu diff --git a/cypress/e2e/admin-export-modals.cy.ts b/cypress/e2e/admin-export-modals.cy.ts index 55d952cad8..24a184fd35 100644 --- a/cypress/e2e/admin-export-modals.cy.ts +++ b/cypress/e2e/admin-export-modals.cy.ts @@ -9,6 +9,7 @@ describe('Admin Export Modals', () => { it('Export metadata modal should pass accessibility tests', () => { // Pin the sidebar open + cy.get('#sidebar-collapse-toggle').trigger('mouseover'); cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu @@ -23,6 +24,7 @@ describe('Admin Export Modals', () => { it('Export batch modal should pass accessibility tests', () => { // Pin the sidebar open + cy.get('#sidebar-collapse-toggle').trigger('mouseover'); cy.get('#sidebar-collapse-toggle').click(); // Click on entry of menu From b839252ed7fef45d9e7851012f4dc9c99e7f4c0c Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 22 Jan 2025 11:02:35 +0300 Subject: [PATCH 713/875] Restrict SSR to paths in the sitemap Because Angular SSR is not very efficient, after discussion with the Google Scholar team we realized a compromise would be to only use SSR for pages in the DSpace sitemap (and the home page). --- config/config.example.yml | 2 ++ server.ts | 2 +- src/config/universal-config.interface.ts | 5 +++++ src/environments/environment.production.ts | 1 + src/environments/environment.test.ts | 1 + src/environments/environment.ts | 1 + 6 files changed, 11 insertions(+), 1 deletion(-) diff --git a/config/config.example.yml b/config/config.example.yml index 42e13038d0..c1d7f967a4 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -23,6 +23,8 @@ ssr: # Determining which styles are critical is a relatively expensive operation; this option is # disabled (false) by default to boost server performance at the expense of loading smoothness. inlineCriticalCss: false + # Path prefixes to enable SSR for. By default these are limited to paths of primary DSpace objects. + paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ] # The REST API server settings # NOTE: these settings define which (publicly available) REST API to use. They are usually diff --git a/server.ts b/server.ts index 23d29723c6..82417f17dc 100644 --- a/server.ts +++ b/server.ts @@ -238,7 +238,7 @@ export function app() { * The callback function to serve server side angular */ function ngApp(req, res) { - if (environment.universal.preboot) { + if (environment.universal.preboot && req.method === 'GET' && (req.path === '/' || environment.universal.paths.some(pathPrefix => req.path.startsWith(pathPrefix)))) { // Render the page to user via SSR (server side rendering) serverSideRender(req, res); } else { diff --git a/src/config/universal-config.interface.ts b/src/config/universal-config.interface.ts index eb89264e37..e54168823f 100644 --- a/src/config/universal-config.interface.ts +++ b/src/config/universal-config.interface.ts @@ -13,4 +13,9 @@ export interface UniversalConfig extends Config { * loading smoothness. */ inlineCriticalCss?: boolean; + + /** + * Paths to enable SSR for. Defaults to the home page and paths in the sitemap. + */ + paths: Array; } diff --git a/src/environments/environment.production.ts b/src/environments/environment.production.ts index 931dd422e3..46a93519df 100644 --- a/src/environments/environment.production.ts +++ b/src/environments/environment.production.ts @@ -9,5 +9,6 @@ export const environment: Partial = { async: true, time: false, inlineCriticalCss: false, + paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ], } }; diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index cfd54781e6..e872285f61 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -12,6 +12,7 @@ export const environment: BuildConfig = { async: true, time: false, inlineCriticalCss: false, + paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ], }, // Angular Universal server settings. diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 7c09793790..25af371e47 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -14,6 +14,7 @@ export const environment: Partial = { async: true, time: false, inlineCriticalCss: false, + paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ], } }; From 6cb3e8bbfb3e7356e5a9f18b15c7e477be8cb23a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 23:47:21 +0000 Subject: [PATCH 714/875] Bump postcss from 8.4.49 to 8.5.1 in the postcss group Bumps the postcss group with 1 update: [postcss](https://github.com/postcss/postcss). Updates `postcss` from 8.4.49 to 8.5.1 - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.49...8.5.1) --- updated-dependencies: - dependency-name: postcss dependency-type: direct:development update-type: version-update:semver-minor dependency-group: postcss ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index ce5449fa33..980a98c209 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "ng-mocks": "^14.13.2", "ngx-mask": "^13.1.7", "nodemon": "^2.0.22", - "postcss": "^8.4", + "postcss": "^8.5", "postcss-import": "^14.0.0", "postcss-loader": "^4.0.3", "postcss-preset-env": "^7.4.2", diff --git a/yarn.lock b/yarn.lock index 96b6665499..39c6f0378d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8336,10 +8336,10 @@ mute-stream@0.0.8: resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nanoid@^3.3.6, nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== +nanoid@^3.3.6, nanoid@^3.3.8: + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== natural-compare-lite@^1.4.0: version "1.4.0" @@ -9438,12 +9438,12 @@ postcss@8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.2.14, postcss@^8.3.11, postcss@^8.3.7, postcss@^8.4, postcss@^8.4.19: - version "8.4.49" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" - integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== +postcss@^8.2.14, postcss@^8.3.11, postcss@^8.3.7, postcss@^8.4.19, postcss@^8.5: + version "8.5.1" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.1.tgz#e2272a1f8a807fafa413218245630b5db10a3214" + integrity sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ== dependencies: - nanoid "^3.3.7" + nanoid "^3.3.8" picocolors "^1.1.1" source-map-js "^1.2.1" From fc2d3f0a233b70b5bbf0857fa22c7fb7a3c4951e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 23:47:29 +0000 Subject: [PATCH 715/875] Bump sass from 1.83.1 to 1.83.4 in the sass group Bumps the sass group with 1 update: [sass](https://github.com/sass/dart-sass). Updates `sass` from 1.83.1 to 1.83.4 - [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.83.1...1.83.4) --- updated-dependencies: - dependency-name: sass dependency-type: direct:development update-type: version-update:semver-patch dependency-group: sass ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ce5449fa33..758d467bf6 100644 --- a/package.json +++ b/package.json @@ -186,7 +186,7 @@ "react-copy-to-clipboard": "^5.1.0", "react-dom": "^16.14.0", "rimraf": "^3.0.2", - "sass": "~1.83.1", + "sass": "~1.83.4", "sass-loader": "^12.6.0", "sass-resources-loader": "^2.2.5", "ts-node": "^8.10.2", diff --git a/yarn.lock b/yarn.lock index 96b6665499..7e9df932cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10255,10 +10255,10 @@ sass@1.58.1: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" -sass@^1.25.0, sass@~1.83.1: - version "1.83.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.83.1.tgz#dee1ab94b47a6f9993d3195d36f556bcbda64846" - integrity sha512-EVJbDaEs4Rr3F0glJzFSOvtg2/oy2V/YrGFPqPY24UqcLDWcI9ZY5sN+qyO3c/QCZwzgfirvhXvINiJCE/OLcA== +sass@^1.25.0, sass@~1.83.4: + version "1.83.4" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.83.4.tgz#5ccf60f43eb61eeec300b780b8dcb85f16eec6d1" + integrity sha512-B1bozCeNQiOgDcLd33e2Cs2U60wZwjUUXzh900ZyQF5qUasvMdDZYbQ566LJu7cqR+sAHlAfO6RMkaID5s6qpA== dependencies: chokidar "^4.0.0" immutable "^5.0.2" From c5fd4426cd5e688ff65b38d2e4c177831e8ecc5c Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Thu, 21 Nov 2024 16:28:41 +0100 Subject: [PATCH 716/875] [DURACOM-303] prevent possibly long-lasting search and browse calls in SSR --- .../browse-by-date-page.component.spec.ts | 39 +++++++++++++++++-- .../browse-by-date-page.component.ts | 11 +++++- .../browse-by-metadata-page.component.spec.ts | 36 +++++++++++++++-- .../browse-by-metadata-page.component.ts | 13 +++++-- .../browse-by-title-page.component.spec.ts | 32 ++++++++++++++- .../configuration-search-page.component.ts | 5 ++- .../shared/search/search.component.spec.ts | 32 ++++++++++++++- src/app/shared/search/search.component.ts | 24 +++++++++++- 8 files changed, 176 insertions(+), 16 deletions(-) diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts index b19250edae..ac572c7b86 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts @@ -1,5 +1,5 @@ import { BrowseByDatePageComponent } from './browse-by-date-page.component'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { TranslateModule } from '@ngx-translate/core'; @@ -9,7 +9,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { BrowseService } from '../../core/browse/browse.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { RouterMock } from '../../shared/mocks/router.mock'; -import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ChangeDetectorRef, NO_ERRORS_SCHEMA, PLATFORM_ID } from '@angular/core'; import { of as observableOf } from 'rxjs'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { Community } from '../../core/shared/community.model'; @@ -24,6 +24,7 @@ import { APP_CONFIG } from '../../../config/app-config.interface'; import { environment } from '../../../environments/environment'; import { SortDirection } from '../../core/cache/models/sort-options.model'; import { cold } from 'jasmine-marbles'; +import { Store } from "@ngrx/store"; describe('BrowseByDatePageComponent', () => { let comp: BrowseByDatePageComponent; @@ -95,7 +96,10 @@ describe('BrowseByDatePageComponent', () => { { provide: Router, useValue: new RouterMock() }, { provide: PaginationService, useValue: paginationService }, { provide: ChangeDetectorRef, useValue: mockCdRef }, - { provide: APP_CONFIG, useValue: environment } + { provide: APP_CONFIG, useValue: environment }, + { provide: Store, useValue: {} }, + { provide: APP_CONFIG, useValue: environment }, + { provide: PLATFORM_ID, useValue: 'browser' }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -131,4 +135,33 @@ describe('BrowseByDatePageComponent', () => { //expect(comp.startsWithOptions[0]).toEqual(new Date().getUTCFullYear()); expect(comp.startsWithOptions[0]).toEqual(1960); }); + + describe('when rendered in SSR', () => { + beforeEach(() => { + comp.platformId = 'server'; + spyOn((comp as any).browseService, 'getBrowseEntriesFor'); + }); + + it('should not call getBrowseEntriesFor on init', (done) => { + comp.ngOnInit(); + expect((comp as any).browseService.getBrowseEntriesFor).not.toHaveBeenCalled(); + comp.loading$.subscribe((res) => { + expect(res).toBeFalsy(); + done(); + }); + }); + }); + + describe('when rendered in CSR', () => { + beforeEach(() => { + comp.platformId = 'browser'; + spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry())); + }); + + it('should call getBrowseEntriesFor on init', fakeAsync(() => { + comp.ngOnInit(); + tick(100); + expect((comp as any).browseService.getBrowseEntriesFor).toHaveBeenCalled(); + })); + }); }); diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index 7e0b6f0f88..62bba4d86c 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, PLATFORM_ID } from '@angular/core'; import { BrowseByMetadataPageComponent, browseParamsToOptions @@ -11,6 +11,7 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { PaginationService } from '../../core/pagination/pagination.service'; import { map, take } from 'rxjs/operators'; +import { of as observableOf } from 'rxjs'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { isValidDate } from '../../shared/date.util'; @@ -18,6 +19,7 @@ import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { isPlatformServer } from "@angular/common"; @Component({ selector: 'ds-browse-by-date-page', @@ -44,11 +46,16 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { protected cdRef: ChangeDetectorRef, @Inject(APP_CONFIG) public appConfig: AppConfig, public dsoNameService: DSONameService, + @Inject(PLATFORM_ID) public platformId: any, ) { - super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService); + super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService, platformId); } ngOnInit(): void { + if (!this.renderOnServerSide && isPlatformServer(this.platformId)) { + this.loading$ = observableOf(false); + return; + } const sortConfig = new SortOptions('default', SortDirection.ASC); this.startsWithType = StartsWithType.date; this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.spec.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.spec.ts index 2bdecc2670..8eda34cd62 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.spec.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.spec.ts @@ -3,7 +3,7 @@ import { browseParamsToOptions, getBrowseSearchOptions } from './browse-by-metadata-page.component'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { BrowseService } from '../../core/browse/browse.service'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; @@ -13,7 +13,7 @@ import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe'; import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { Observable, of as observableOf } from 'rxjs'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { NO_ERRORS_SCHEMA, PLATFORM_ID } from '@angular/core'; import { RemoteData } from '../../core/data/remote-data'; import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model'; import { PageInfo } from '../../core/shared/page-info.model'; @@ -111,7 +111,8 @@ describe('BrowseByMetadataPageComponent', () => { { provide: DSpaceObjectDataService, useValue: mockDsoService }, { provide: PaginationService, useValue: paginationService }, { provide: Router, useValue: new RouterMock() }, - { provide: APP_CONFIG, useValue: environmentMock } + { provide: APP_CONFIG, useValue: environmentMock }, + { provide: PLATFORM_ID, useValue: 'browser' }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -224,6 +225,35 @@ describe('BrowseByMetadataPageComponent', () => { expect(result.fetchThumbnail).toBeTrue(); }); }); + + describe('when rendered in SSR', () => { + beforeEach(() => { + comp.platformId = 'server'; + spyOn((comp as any).browseService, 'getBrowseEntriesFor'); + }); + + it('should not call getBrowseEntriesFor on init', (done) => { + comp.ngOnInit(); + expect((comp as any).browseService.getBrowseEntriesFor).not.toHaveBeenCalled(); + comp.loading$.subscribe((res) => { + expect(res).toBeFalsy(); + done(); + }); + }); + }); + + describe('when rendered in CSR', () => { + beforeEach(() => { + comp.platformId = 'browser'; + spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry())); + }); + + it('should call getBrowseEntriesFor on init', fakeAsync(() => { + comp.ngOnInit(); + tick(100); + expect((comp as any).browseService.getBrowseEntriesFor).toHaveBeenCalled(); + })); + }); }); export function toRemoteData(objects: any[]): Observable>> { diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index fe407a2fb0..4629982e65 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -1,5 +1,5 @@ import { combineLatest as observableCombineLatest, Observable, Subscription, of as observableOf } from 'rxjs'; -import { Component, Inject, OnInit, OnDestroy } from '@angular/core'; +import { Component, Inject, OnInit, OnDestroy, Input, PLATFORM_ID } from '@angular/core'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; @@ -37,7 +37,10 @@ export const BBM_PAGINATION_ID = 'bbm'; * 'dc.contributor.*' */ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { - + /** + * Defines whether to fetch search results during SSR execution + */ + @Input() renderOnServerSide = false; /** * The list of browse-entries to display */ @@ -134,6 +137,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { protected router: Router, @Inject(APP_CONFIG) public appConfig: AppConfig, public dsoNameService: DSONameService, + @Inject(PLATFORM_ID) public platformId: any, ) { this.fetchThumbnails = this.appConfig.browseBy.showThumbnails; @@ -146,7 +150,10 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { ngOnInit(): void { - + if (!this.renderOnServerSide && isPlatformServer(this.platformId)) { + this.loading$ = observableOf(false); + return; + } const sortConfig = new SortOptions('default', SortDirection.ASC); this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig)); this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); diff --git a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts index e32c0ac430..9c9586032d 100644 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts +++ b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts @@ -1,4 +1,4 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; import { Item } from '../../core/shared/item.model'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; @@ -22,6 +22,7 @@ import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; import { APP_CONFIG } from '../../../config/app-config.interface'; import { environment } from '../../../environments/environment'; +import { BrowseEntry } from "../../core/shared/browse-entry.model"; describe('BrowseByTitlePageComponent', () => { @@ -97,4 +98,33 @@ describe('BrowseByTitlePageComponent', () => { expect(result.payload.page).toEqual(mockItems); }); }); + + describe('when rendered in SSR', () => { + beforeEach(() => { + comp.platformId = 'server'; + spyOn((comp as any).browseService, 'getBrowseEntriesFor'); + }); + + it('should not call getBrowseEntriesFor on init', (done) => { + comp.ngOnInit(); + expect((comp as any).browseService.getBrowseEntriesFor).not.toHaveBeenCalled(); + comp.loading$.subscribe((res) => { + expect(res).toBeFalsy(); + done(); + }); + }); + }); + + describe('when rendered in CSR', () => { + beforeEach(() => { + comp.platformId = 'browser'; + spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry())); + }); + + it('should call getBrowseEntriesFor on init', fakeAsync(() => { + comp.ngOnInit(); + tick(100); + expect((comp as any).browseService.getBrowseEntriesFor).toHaveBeenCalled(); + })); + }); }); diff --git a/src/app/search-page/configuration-search-page.component.ts b/src/app/search-page/configuration-search-page.component.ts index 9196dda025..13e4709ca0 100644 --- a/src/app/search-page/configuration-search-page.component.ts +++ b/src/app/search-page/configuration-search-page.component.ts @@ -1,7 +1,7 @@ import { HostWindowService } from '../shared/host-window.service'; import { SidebarService } from '../shared/sidebar/sidebar.service'; import { SearchComponent } from '../shared/search/search.component'; -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, PLATFORM_ID } from '@angular/core'; import { pushInOut } from '../shared/animations/push'; import { SEARCH_CONFIG_SERVICE } from '../my-dspace-page/my-dspace-page.component'; import { SearchConfigurationService } from '../core/shared/search/search-configuration.service'; @@ -35,7 +35,8 @@ export class ConfigurationSearchPageComponent extends SearchComponent { protected routeService: RouteService, protected router: Router, @Inject(APP_CONFIG) protected appConfig: AppConfig, + @Inject(PLATFORM_ID) public platformId: any, ) { - super(service, sidebarService, windowService, searchConfigService, routeService, router, appConfig); + super(service, sidebarService, windowService, searchConfigService, routeService, router, appConfig, platformId); } } diff --git a/src/app/shared/search/search.component.spec.ts b/src/app/shared/search/search.component.spec.ts index 8ffd832009..05d4fc6b85 100644 --- a/src/app/shared/search/search.component.spec.ts +++ b/src/app/shared/search/search.component.spec.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA, PLATFORM_ID } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; @@ -216,6 +216,7 @@ export function configureSearchComponentTestingModule(compType, additionalDeclar useValue: searchConfigurationServiceStub }, { provide: APP_CONFIG, useValue: environment }, + { provide: PLATFORM_ID, useValue: 'browser' }, ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(compType, { @@ -374,5 +375,34 @@ describe('SearchComponent', () => { expect(result).toBeNull(); }); }); + + describe('when rendered in SSR', () => { + beforeEach(() => { + comp.platformId = 'server'; + }); + + it('should not call search method on init', (done) => { + comp.ngOnInit(); + //Check that the first method from which the search depend upon is not being called + expect(searchConfigurationServiceStub.getCurrentConfiguration).not.toHaveBeenCalled(); + comp.initialized$.subscribe((res) => { + expect(res).toBeTruthy(); + done(); + }); + }); + }); + + describe('when rendered in CSR', () => { + beforeEach(() => { + comp.platformId = 'browser'; + }); + + it('should call search method on init', fakeAsync(() => { + comp.ngOnInit(); + tick(100); + //Check that the last method from which the search depend upon is being called + expect(searchServiceStub.search).toHaveBeenCalled(); + })); + }); }); }); diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index 5a848c9786..6040a47d6e 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -1,4 +1,14 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Inject, Input, OnInit, Output, OnDestroy } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Inject, + Input, + OnInit, + Output, + OnDestroy, + PLATFORM_ID +} from '@angular/core'; import { NavigationStart, Router } from '@angular/router'; import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; @@ -38,6 +48,7 @@ import { ITEM_MODULE_PATH } from '../../item-page/item-page-routing-paths'; import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths'; import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths'; import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; +import { isPlatformServer } from "@angular/common"; @Component({ selector: 'ds-search', @@ -176,6 +187,11 @@ export class SearchComponent implements OnDestroy, OnInit { */ @Input() scope: string; + /** + * Defines whether to fetch search results during SSR execution + */ + @Input() renderOnServerSide = false; + /** * The current configuration used during the search */ @@ -285,6 +301,7 @@ export class SearchComponent implements OnDestroy, OnInit { protected routeService: RouteService, protected router: Router, @Inject(APP_CONFIG) protected appConfig: AppConfig, + @Inject(PLATFORM_ID) public platformId: any, ) { this.isXsOrSm$ = this.windowService.isXsOrSm(); } @@ -297,6 +314,11 @@ export class SearchComponent implements OnDestroy, OnInit { * If something changes, update the list of scopes for the dropdown */ ngOnInit(): void { + if (!this.renderOnServerSide && isPlatformServer(this.platformId)) { + this.initialized$.next(true); + return; + } + if (this.useUniquePageId) { // Create an unique pagination id related to the instance of the SearchComponent this.paginationId = uniqueId(this.paginationId); From 5868a3198f9d12d817ac845ea1d04c59aa756359 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 24 Jan 2025 16:49:25 +0100 Subject: [PATCH 717/875] [DURACOM-303] fix missing imports --- .../browse-by-date-page.component.spec.ts | 1 + .../browse-by-metadata-page.component.ts | 1 + .../browse-by-title-page.component.ts | 11 +++++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts index ac572c7b86..d94e7df04f 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts @@ -25,6 +25,7 @@ import { environment } from '../../../environments/environment'; import { SortDirection } from '../../core/cache/models/sort-options.model'; import { cold } from 'jasmine-marbles'; import { Store } from "@ngrx/store"; +import { BrowseEntry } from "../../core/shared/browse-entry.model"; describe('BrowseByDatePageComponent', () => { let comp: BrowseByDatePageComponent; diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 4629982e65..e18841f9ca 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -22,6 +22,7 @@ import { Collection } from '../../core/shared/collection.model'; import { Community } from '../../core/shared/community.model'; import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { isPlatformServer } from "@angular/common"; export const BBM_PAGINATION_ID = 'bbm'; diff --git a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index 11dc2a2a6a..0d8a2e32ed 100644 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -1,5 +1,5 @@ import { combineLatest as observableCombineLatest } from 'rxjs'; -import { Component, Inject } from '@angular/core'; +import { Component, Inject, PLATFORM_ID } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { hasValue } from '../../shared/empty.util'; import { @@ -11,9 +11,11 @@ import { BrowseService } from '../../core/browse/browse.service'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { PaginationService } from '../../core/pagination/pagination.service'; import { map, take } from 'rxjs/operators'; +import { of as observableOf } from 'rxjs'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { isPlatformServer } from "@angular/common"; @Component({ selector: 'ds-browse-by-title-page', @@ -32,11 +34,16 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent { protected router: Router, @Inject(APP_CONFIG) public appConfig: AppConfig, public dsoNameService: DSONameService, + @Inject(PLATFORM_ID) public platformId: any, ) { - super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService); + super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService, platformId); } ngOnInit(): void { + if (!this.renderOnServerSide && isPlatformServer(this.platformId)) { + this.loading$ = observableOf(false); + return; + } const sortConfig = new SortOptions('dc.title', SortDirection.ASC); this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig); From 39c5de6e18ff7e4baa78861b760990c775433562 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Tue, 26 Nov 2024 13:11:26 +0100 Subject: [PATCH 718/875] [DURACOM-303] implement skeleton component for search results --- package.json | 1 + .../search-results-skeleton.component.html | 12 +++++++ .../search-results-skeleton.component.scss | 0 .../search-results-skeleton.component.spec.ts | 27 ++++++++++++++ .../search-results-skeleton.component.ts | 36 +++++++++++++++++++ .../search-results.component.html | 7 +++- .../search-results.component.spec.ts | 2 ++ src/themes/custom/lazy-theme.module.ts | 4 +++ 8 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html create mode 100644 src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss create mode 100644 src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts create mode 100644 src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts diff --git a/package.json b/package.json index c2aa6d74dd..86a506c57a 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "ngx-infinite-scroll": "^15.0.0", "ngx-pagination": "6.0.3", "ngx-sortablejs": "^11.1.0", + "ngx-skeleton-loader": "^9.0.0", "ngx-ui-switch": "^14.1.0", "nouislider": "^15.8.1", "pem": "1.14.8", diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html new file mode 100644 index 0000000000..33f05985e1 --- /dev/null +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html @@ -0,0 +1,12 @@ +@for (result of loadingResults; track result) { +
    + @if(showThumbnails) { + + } + +
    + + +
    +
    +} diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts new file mode 100644 index 0000000000..0ec74eb946 --- /dev/null +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts @@ -0,0 +1,27 @@ +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; + +import { SearchResultsSkeletonComponent } from './search-results-skeleton.component'; + +describe('SearchResultsSkeletonComponent', () => { + let component: SearchResultsSkeletonComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SearchResultsSkeletonComponent, NgxSkeletonLoaderModule], + }) + .compileComponents(); + + fixture = TestBed.createComponent(SearchResultsSkeletonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts new file mode 100644 index 0000000000..d35c5d1733 --- /dev/null +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts @@ -0,0 +1,36 @@ +import { + Component, + Input, + OnInit, +} from '@angular/core'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; + +import { hasValue } from '../../../empty.util'; + +@Component({ + selector: 'ds-search-results-skeleton', + standalone: true, + imports: [ + NgxSkeletonLoaderModule, + ], + templateUrl: './search-results-skeleton.component.html', + styleUrl: './search-results-skeleton.component.scss', +}) +export class SearchResultsSkeletonComponent implements OnInit { + @Input() + showThumbnails: boolean; + + @Input() + numberOfResults = 0; + + public loadingResults: number[]; + + ngOnInit() { + this.loadingResults = Array.from({ length: this.numberOfResults }, (_, i) => i + 1); + + if (!hasValue(this.showThumbnails)) { + // this is needed as the default value of show thumbnails is true but set in lower levels of the DOM. + this.showThumbnails = true; + } + } +} 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 1f1e58ea10..1ad0129d2d 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -19,7 +19,12 @@ (selectObject)="selectObject.emit($event)">
    - + +
    diff --git a/src/app/shared/search/search-results/search-results.component.spec.ts b/src/app/shared/search/search-results/search-results.component.spec.ts index 4cc4f84f65..cf4bcf61a5 100644 --- a/src/app/shared/search/search-results/search-results.component.spec.ts +++ b/src/app/shared/search/search-results/search-results.component.spec.ts @@ -7,6 +7,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { SearchResultsComponent } from './search-results.component'; import { QueryParamsDirectiveStub } from '../../testing/query-params-directive.stub'; import { createFailedRemoteDataObject } from '../../remote-data.utils'; +import { SearchResultsSkeletonComponent } from "./search-results-skeleton/search-results-skeleton.component"; describe('SearchResultsComponent', () => { let comp: SearchResultsComponent; @@ -19,6 +20,7 @@ describe('SearchResultsComponent', () => { imports: [TranslateModule.forRoot(), NoopAnimationsModule], declarations: [ SearchResultsComponent, + SearchResultsSkeletonComponent, QueryParamsDirectiveStub], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index 73400e7880..1fe476c1e1 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -159,6 +159,9 @@ import { RequestCopyModule } from 'src/app/request-copy/request-copy.module'; import {UserMenuComponent} from './app/shared/auth-nav-menu/user-menu/user-menu.component'; import { BrowseByComponent } from './app/shared/browse-by/browse-by.component'; import { RegisterEmailFormComponent } from './app/register-email-form/register-email-form.component'; +import { + SearchResultsSkeletonComponent +} from "../../app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component"; const DECLARATIONS = [ FileSectionComponent, @@ -245,6 +248,7 @@ const DECLARATIONS = [ UserMenuComponent, BrowseByComponent, RegisterEmailFormComponent, + SearchResultsSkeletonComponent, ]; @NgModule({ From 6cd092671f1dfd382410517fccd55650db0edf6a Mon Sep 17 00:00:00 2001 From: DSpace Bot <68393067+dspace-bot@users.noreply.github.com> Date: Fri, 24 Jan 2025 09:58:41 -0600 Subject: [PATCH 719/875] [Port dspace-7_x] Improving the color contrast of home news content (#3890) * Improving the color contrast of home news content (cherry picked from commit 2290b871f72acc85dd05955a7e2877f8cea16238) * Improved color contrast (cherry picked from commit 62b6677e04b6ba6578603d1ceb6ae40e967340de) * Adjusting font color changes (cherry picked from commit be335415fd8e62ed108c6652544588c3be46ac88) --------- Co-authored-by: root --- .../dspace/app/home-page/home-news/home-news.component.scss | 6 ++++++ src/themes/dspace/styles/_theme_css_variable_overrides.scss | 2 +- .../dspace/styles/_theme_sass_variable_overrides.scss | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/themes/dspace/app/home-page/home-news/home-news.component.scss b/src/themes/dspace/app/home-page/home-news/home-news.component.scss index 3c3aa8b445..d00b0ec959 100644 --- a/src/themes/dspace/app/home-page/home-news/home-news.component.scss +++ b/src/themes/dspace/app/home-page/home-news/home-news.component.scss @@ -4,6 +4,7 @@ div.background-image-container { color: white; position: relative; + font-weight: 600; .background-image > img { background-color: var(--bs-info); @@ -68,6 +69,11 @@ color: var(--ds-home-news-link-hover-color); } } + + .lead { + font-size: 1.25rem; + font-weight: 400; + } } diff --git a/src/themes/dspace/styles/_theme_css_variable_overrides.scss b/src/themes/dspace/styles/_theme_css_variable_overrides.scss index d016ff4032..8b95cd0033 100644 --- a/src/themes/dspace/styles/_theme_css_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_css_variable_overrides.scss @@ -11,7 +11,7 @@ --ds-header-height: 90px; } - --ds-banner-text-background: rgba(0, 0, 0, 0.45); + --ds-banner-text-background: rgba(0, 0, 0, 0.58); --ds-banner-background-gradient-width: 300px; --ds-header-navbar-border-bottom-height: 5px; diff --git a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss index 17155b15a1..3750b1f826 100644 --- a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss @@ -83,7 +83,7 @@ $navbar-dark-color: #fff; /*** CUSTOM DSPACE VARIABLES ***/ -$ds-home-news-link-color: #92c642; +$ds-home-news-link-color: #D2FC93; $ds-header-navbar-border-bottom-color: #92c642; $ds-breadcrumb-link-color: #154E66 !default; From 126f3c71f4a2ad5a9f6b52b098ed1317e3c0d935 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Wed, 27 Nov 2024 16:01:41 +0100 Subject: [PATCH 720/875] [DURACOM-303] add skeleton loader for search results and filters --- .../search-filters.component.html | 6 ++++- .../search-filters.component.scss | 10 ++++++- .../search-filters.component.ts | 8 +++++- .../search-results-skeleton.component.html | 18 ++++++++++--- .../search-results-skeleton.component.scss | 27 +++++++++++++++++++ .../search-results-skeleton.component.ts | 3 +++ .../search-results.component.html | 2 +- src/app/shared/search/search.component.ts | 3 +++ src/config/search-page-config.interface.ts | 16 +++++++++++ src/styles/_custom_variables.scss | 11 ++++++++ .../styles/_theme_css_variable_overrides.scss | 1 + 11 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 src/config/search-page-config.interface.ts diff --git a/src/app/shared/search/search-filters/search-filters.component.html b/src/app/shared/search/search-filters/search-filters.component.html index c006d80c44..97f8c75234 100644 --- a/src/app/shared/search/search-filters/search-filters.component.html +++ b/src/app/shared/search/search-filters/search-filters.component.html @@ -1,7 +1,11 @@

    {{"search.filters.head" | translate}}

    -
    +
    + + + + {{"search.filters.reset" | translate}} diff --git a/src/app/shared/search/search-filters/search-filters.component.scss b/src/app/shared/search/search-filters/search-filters.component.scss index b5b2816e89..a103119daf 100644 --- a/src/app/shared/search/search-filters/search-filters.component.scss +++ b/src/app/shared/search/search-filters/search-filters.component.scss @@ -1,2 +1,10 @@ @import '../../../../styles/variables'; -@import '../../../../styles/mixins'; \ No newline at end of file +@import '../../../../styles/mixins'; + +:host ::ng-deep { + ngx-skeleton-loader .skeleton-loader { + height: var(--ds-filters-skeleton-height); + margin-bottom: var(--ds-filters-skeleton-spacing); + padding: var(--ds-filters-skeleton-spacing); + } +} diff --git a/src/app/shared/search/search-filters/search-filters.component.ts b/src/app/shared/search/search-filters/search-filters.component.ts index 766939226d..c960dfda56 100644 --- a/src/app/shared/search/search-filters/search-filters.component.ts +++ b/src/app/shared/search/search-filters/search-filters.component.ts @@ -12,6 +12,7 @@ import { SearchFilterService } from '../../../core/shared/search/search-filter.s import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; import { currentPath } from '../../utils/route.utils'; import { hasValue } from '../../empty.util'; +import { APP_CONFIG, AppConfig } from "../../../../config/app-config.interface"; @Component({ selector: 'ds-search-filters', @@ -61,6 +62,7 @@ export class SearchFiltersComponent implements OnInit, OnDestroy { searchLink: string; subs = []; + defaultFilterCount: number; /** * Initialize instance variables @@ -68,12 +70,16 @@ export class SearchFiltersComponent implements OnInit, OnDestroy { * @param {SearchFilterService} filterService * @param {Router} router * @param {SearchConfigurationService} searchConfigService + * @param appConfig */ constructor( private searchService: SearchService, private filterService: SearchFilterService, private router: Router, - @Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService) { + @Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService, + @Inject(APP_CONFIG) protected appConfig: AppConfig, + ) { + this.defaultFilterCount = this.appConfig.search.defaultFilterCount ?? 5; } ngOnInit(): void { diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html index 33f05985e1..5972a54dc4 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html @@ -1,12 +1,24 @@ +
    +
    + +
    +
    + @for (result of loadingResults; track result) {
    @if(showThumbnails) { - +
    + +
    }
    - - +
    + +
    +
    + +
    } diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss index e69de29bb2..c982d14fb0 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss @@ -0,0 +1,27 @@ +:host ::ng-deep { + .info-skeleton, .badge-skeleton, .text-skeleton{ + ngx-skeleton-loader .skeleton-loader { + height: var(--ds-search-skeleton-text-height); + } + } + + .badge-skeleton, .info-skeleton { + ngx-skeleton-loader .skeleton-loader { + width: var(--ds-search-skeleton-badge-width); + } + } + + .info-skeleton { + ngx-skeleton-loader .skeleton-loader { + width: var(--ds-search-skeleton-info-width); + } + } + + .thumbnail-skeleton { + ngx-skeleton-loader .skeleton-loader { + width: var(--ds-search-skeleton-thumbnail-width); + height: var(--ds-search-skeleton-thumbnail-height); + padding: var(--ds-search-skeleton-thumbnail-padding); + } + } +} diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts index d35c5d1733..fef1529460 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts @@ -23,6 +23,9 @@ export class SearchResultsSkeletonComponent implements OnInit { @Input() numberOfResults = 0; + @Input() + textLineCount = 2; + public loadingResults: number[]; ngOnInit() { 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 1ad0129d2d..a190b64752 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -20,7 +20,7 @@
    diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index 6040a47d6e..362257be0e 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -315,6 +315,9 @@ export class SearchComponent implements OnDestroy, OnInit { */ ngOnInit(): void { if (!this.renderOnServerSide && isPlatformServer(this.platformId)) { + this.subs.push(this.getSearchOptions().pipe(distinctUntilChanged()).subscribe((options) => { + this.searchOptions$.next(options); + })); this.initialized$.next(true); return; } diff --git a/src/config/search-page-config.interface.ts b/src/config/search-page-config.interface.ts new file mode 100644 index 0000000000..7d582866e7 --- /dev/null +++ b/src/config/search-page-config.interface.ts @@ -0,0 +1,16 @@ +import { AdvancedSearchConfig } from './advance-search-config.interface'; +import { Config } from './config.interface'; + +export interface SearchConfig extends Config { + + /** + * List of standard filter to select in adding advanced Search + * Used by {@link UploadBitstreamComponent}. + */ + advancedFilters: AdvancedSearchConfig; + /** + * Number used to render n skeletons while the filters are loading + */ + defaultFilterCount?: number; + +} diff --git a/src/styles/_custom_variables.scss b/src/styles/_custom_variables.scss index aa67acac1c..96db246079 100644 --- a/src/styles/_custom_variables.scss +++ b/src/styles/_custom_variables.scss @@ -138,4 +138,15 @@ --green1: #1FB300; // This variable represents the success color for the Klaro cookie banner --button-text-color-cookie: #333; // This variable represents the text color for buttons in the Klaro cookie banner --very-dark-cyan: #215E50; // This variable represents the background color of the save cookies button + + --ds-search-skeleton-text-height: 20px; + --ds-search-skeleton-thumbnail-height: 100px; + --ds-search-skeleton-thumbnail-width: 120px; + --ds-search-skeleton-thumbnail-padding: 1em; + --ds-search-skeleton-text-line-count: 2; + --ds-search-skeleton-badge-width: 75px; + --ds-search-skeleton-info-width: 200px; + + --ds-filters-skeleton-height: 40px; + --ds-filters-skeleton-spacing: 12px; } diff --git a/src/themes/dspace/styles/_theme_css_variable_overrides.scss b/src/themes/dspace/styles/_theme_css_variable_overrides.scss index d016ff4032..5978eadb28 100644 --- a/src/themes/dspace/styles/_theme_css_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_css_variable_overrides.scss @@ -18,6 +18,7 @@ /* set the next two properties as `--ds-header-navbar-border-bottom-*` in order to keep the bottom border of the header when navbar is expanded */ + --ds-expandable-navbar-border-top-color: #{$white}; --ds-expandable-navbar-border-top-height: 0; --ds-expandable-navbar-padding-top: 0; From 61eb14783e3995fe2beede1801b1027a62032f0b Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 24 Jan 2025 17:03:47 +0100 Subject: [PATCH 721/875] [DURACOM-303] resolve conflicts --- src/config/app-config.interface.ts | 2 ++ src/config/default-app-config.ts | 5 +++++ src/config/search-page-config.interface.ts | 7 ------- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index a2bf0cb876..8b8dcf74a7 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -23,6 +23,7 @@ import { MarkdownConfig } from './markdown-config.interface'; import { FilterVocabularyConfig } from './filter-vocabulary-config'; import { DiscoverySortConfig } from './discovery-sort.config'; import { LiveRegionConfig } from '../app/shared/live-region/live-region.config'; +import { SearchConfig } from "./search-page-config.interface"; interface AppConfig extends Config { ui: UIServerConfig; @@ -50,6 +51,7 @@ interface AppConfig extends Config { vocabularies: FilterVocabularyConfig[]; comcolSelectionSort: DiscoverySortConfig; liveRegion: LiveRegionConfig; + search: SearchConfig } /** diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index a3a490538c..0915f90069 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -23,6 +23,7 @@ import { MarkdownConfig } from './markdown-config.interface'; import { FilterVocabularyConfig } from './filter-vocabulary-config'; import { DiscoverySortConfig } from './discovery-sort.config'; import { LiveRegionConfig } from '../app/shared/live-region/live-region.config'; +import { SearchConfig } from "./search-page-config.interface"; export class DefaultAppConfig implements AppConfig { production = false; @@ -442,4 +443,8 @@ export class DefaultAppConfig implements AppConfig { messageTimeOutDurationMs: 30000, isVisible: false, }; + + search: SearchConfig = { + defaultFilterCount: 2 + } } diff --git a/src/config/search-page-config.interface.ts b/src/config/search-page-config.interface.ts index 7d582866e7..cc63607f7f 100644 --- a/src/config/search-page-config.interface.ts +++ b/src/config/search-page-config.interface.ts @@ -1,13 +1,6 @@ -import { AdvancedSearchConfig } from './advance-search-config.interface'; import { Config } from './config.interface'; export interface SearchConfig extends Config { - - /** - * List of standard filter to select in adding advanced Search - * Used by {@link UploadBitstreamComponent}. - */ - advancedFilters: AdvancedSearchConfig; /** * Number used to render n skeletons while the filters are loading */ From d6f0f02c1aaf437126289ac6ed0180ea266ac8ab Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Thu, 28 Nov 2024 13:54:48 +0100 Subject: [PATCH 722/875] [DURACOM-303] minor restyle of skeleton for mobile --- .../search-results-skeleton.component.html | 6 +++--- .../search-results-skeleton.component.scss | 5 +++++ .../search/search-results/search-results.component.html | 2 +- src/config/default-app-config.ts | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html index 5972a54dc4..4bc70d1263 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html @@ -1,11 +1,11 @@ -
    -
    +
    +
    @for (result of loadingResults; track result) { -
    +
    @if(showThumbnails) {
    diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss index c982d14fb0..02711a33b6 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss @@ -25,3 +25,8 @@ } } } + +.result-row { + margin-right: 0; + margin-left: 0; +} 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 a190b64752..1ad0129d2d 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -20,7 +20,7 @@
    diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 0915f90069..7ed4168217 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -445,6 +445,6 @@ export class DefaultAppConfig implements AppConfig { }; search: SearchConfig = { - defaultFilterCount: 2 + defaultFilterCount: 5 } } From 312a2a7f58decb141822c81fc5e4c86b10331f2f Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Thu, 28 Nov 2024 15:01:00 +0100 Subject: [PATCH 723/875] [DURACOM-303] fix lint and tests --- .../search/search-filters/search-filters.component.spec.ts | 4 +++- .../search/search-results/search-results.component.spec.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/shared/search/search-filters/search-filters.component.spec.ts b/src/app/shared/search/search-filters/search-filters.component.spec.ts index 522459b603..c8ad89910c 100644 --- a/src/app/shared/search/search-filters/search-filters.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filters.component.spec.ts @@ -9,6 +9,8 @@ import { SearchFiltersComponent } from './search-filters.component'; import { SearchService } from '../../../core/shared/search/search.service'; import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub'; +import { APP_CONFIG } from "../../../../config/app-config.interface"; +import { environment } from "../../../../environments/environment"; describe('SearchFiltersComponent', () => { let comp: SearchFiltersComponent; @@ -37,7 +39,7 @@ describe('SearchFiltersComponent', () => { { provide: SearchService, useValue: searchServiceStub }, { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }, { provide: SearchFilterService, useValue: searchFiltersStub }, - + { provide: APP_CONFIG, useValue: environment }, ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(SearchFiltersComponent, { diff --git a/src/app/shared/search/search-results/search-results.component.spec.ts b/src/app/shared/search/search-results/search-results.component.spec.ts index cf4bcf61a5..dc961c97cb 100644 --- a/src/app/shared/search/search-results/search-results.component.spec.ts +++ b/src/app/shared/search/search-results/search-results.component.spec.ts @@ -65,7 +65,7 @@ describe('SearchResultsComponent', () => { it('should display link with new search where query is quoted if search return a error 400', () => { (comp as any).searchResults = createFailedRemoteDataObject('Error', 400); - (comp as any).searchConfig = { query: 'foobar' }; + (comp as any).searchConfig = { query: 'foobar', pagination: { pageSize: 10 } }; fixture.detectChanges(); const linkDes = fixture.debugElement.queryAll(By.directive(QueryParamsDirectiveStub)); From b482b58d530b946b4e3376acb406e1ee04c68059 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 20 Dec 2024 15:36:06 +0100 Subject: [PATCH 724/875] [DURACOM-303] adapt tests --- .../browse-by-date-page.component.spec.ts | 12 ++++++------ .../browse-by-title-page.component.spec.ts | 17 ++++++++++------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts index d94e7df04f..2fe673f821 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts @@ -140,12 +140,12 @@ describe('BrowseByDatePageComponent', () => { describe('when rendered in SSR', () => { beforeEach(() => { comp.platformId = 'server'; - spyOn((comp as any).browseService, 'getBrowseEntriesFor'); + spyOn((comp as any).browseService, 'getBrowseItemsFor'); }); - it('should not call getBrowseEntriesFor on init', (done) => { + it('should not call getBrowseItemsFor on init', (done) => { comp.ngOnInit(); - expect((comp as any).browseService.getBrowseEntriesFor).not.toHaveBeenCalled(); + expect((comp as any).browseService.getBrowseItemsFor).not.toHaveBeenCalled(); comp.loading$.subscribe((res) => { expect(res).toBeFalsy(); done(); @@ -156,13 +156,13 @@ describe('BrowseByDatePageComponent', () => { describe('when rendered in CSR', () => { beforeEach(() => { comp.platformId = 'browser'; - spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry())); + spyOn((comp as any).browseService, 'getBrowseItemsFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry())); }); - it('should call getBrowseEntriesFor on init', fakeAsync(() => { + it('should call getBrowseItemsFor on init', fakeAsync(() => { comp.ngOnInit(); tick(100); - expect((comp as any).browseService.getBrowseEntriesFor).toHaveBeenCalled(); + expect((comp as any).browseService.getBrowseItemsFor).toHaveBeenCalled(); })); }); }); diff --git a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts index 9c9586032d..653ad596c5 100644 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts +++ b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts @@ -64,7 +64,8 @@ describe('BrowseByTitlePageComponent', () => { const activatedRouteStub = Object.assign(new ActivatedRouteStub(), { params: observableOf({}), - data: observableOf({ metadata: 'title' }) + queryParams: observableOf({}), + data: observableOf({ metadata: 'title' }), }); const paginationService = new PaginationServiceStub(); @@ -102,12 +103,13 @@ describe('BrowseByTitlePageComponent', () => { describe('when rendered in SSR', () => { beforeEach(() => { comp.platformId = 'server'; - spyOn((comp as any).browseService, 'getBrowseEntriesFor'); + spyOn((comp as any).browseService, 'getBrowseItemsFor'); + fixture.detectChanges(); }); - it('should not call getBrowseEntriesFor on init', (done) => { + it('should not call getBrowseItemsFor on init', (done) => { comp.ngOnInit(); - expect((comp as any).browseService.getBrowseEntriesFor).not.toHaveBeenCalled(); + expect((comp as any).browseService.getBrowseItemsFor).not.toHaveBeenCalled(); comp.loading$.subscribe((res) => { expect(res).toBeFalsy(); done(); @@ -118,13 +120,14 @@ describe('BrowseByTitlePageComponent', () => { describe('when rendered in CSR', () => { beforeEach(() => { comp.platformId = 'browser'; - spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry())); + fixture.detectChanges(); + spyOn((comp as any).browseService, 'getBrowseItemsFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry())); }); - it('should call getBrowseEntriesFor on init', fakeAsync(() => { + it('should call getBrowseItemsFor on init', fakeAsync(() => { comp.ngOnInit(); tick(100); - expect((comp as any).browseService.getBrowseEntriesFor).toHaveBeenCalled(); + expect((comp as any).browseService.getBrowseItemsFor).toHaveBeenCalled(); })); }); }); From 6756a78ba03944a8ee28a582a89480fd2a3fc3e4 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 10 Jan 2025 12:01:50 +0100 Subject: [PATCH 725/875] [DURACOM-303] restyle skeleton, add filter badge skeleton --- .../search-filters/search-filters.component.scss | 2 ++ .../search-results-skeleton.component.scss | 7 +++++++ .../search-results/search-results.component.html | 11 +++++++++++ .../search-results/search-results.component.scss | 10 ++++++++++ .../search/search-results/search-results.component.ts | 9 +++++++++ src/styles/_custom_variables.scss | 6 ++++-- 6 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 src/app/shared/search/search-results/search-results.component.scss diff --git a/src/app/shared/search/search-filters/search-filters.component.scss b/src/app/shared/search/search-filters/search-filters.component.scss index a103119daf..6170b9281c 100644 --- a/src/app/shared/search/search-filters/search-filters.component.scss +++ b/src/app/shared/search/search-filters/search-filters.component.scss @@ -6,5 +6,7 @@ height: var(--ds-filters-skeleton-height); margin-bottom: var(--ds-filters-skeleton-spacing); padding: var(--ds-filters-skeleton-spacing); + background-color: var(--bs-light); + box-shadow: none; } } diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss index 02711a33b6..1a111f9dd9 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss @@ -22,8 +22,15 @@ width: var(--ds-search-skeleton-thumbnail-width); height: var(--ds-search-skeleton-thumbnail-height); padding: var(--ds-search-skeleton-thumbnail-padding); + margin-right: var(--ds-search-skeleton-thumbnail-margin); + border-radius: 0; } } + + ngx-skeleton-loader .skeleton-loader { + background-color: var(--bs-light); + box-shadow: none; + } } .result-row { 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 1ad0129d2d..d812561ea7 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -1,3 +1,13 @@ +@if ((filters$ | async).length > 0 && isLoading()) { +
    +
    +
    + +
    +
    +
    +} +

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

    @@ -19,6 +29,7 @@ (selectObject)="selectObject.emit($event)">
    + ; + /** * The link type of the listed search results */ @@ -104,6 +109,10 @@ export class SearchResultsComponent { @Output() selectObject: EventEmitter = new EventEmitter(); + constructor(private searchConfigService: SearchConfigurationService) { + this.filters$ = this.searchConfigService.getCurrentFilters(); + } + /** * Check if search results are loading */ diff --git a/src/styles/_custom_variables.scss b/src/styles/_custom_variables.scss index 96db246079..d64f1c6acd 100644 --- a/src/styles/_custom_variables.scss +++ b/src/styles/_custom_variables.scss @@ -140,11 +140,13 @@ --very-dark-cyan: #215E50; // This variable represents the background color of the save cookies button --ds-search-skeleton-text-height: 20px; - --ds-search-skeleton-thumbnail-height: 100px; - --ds-search-skeleton-thumbnail-width: 120px; + --ds-search-skeleton-thumbnail-height: 125px; + --ds-search-skeleton-thumbnail-width: 90px; --ds-search-skeleton-thumbnail-padding: 1em; + --ds-search-skeleton-thumbnail-margin: 1em; --ds-search-skeleton-text-line-count: 2; --ds-search-skeleton-badge-width: 75px; + --ds-search-skeleton-filter-badge-width: 200px; --ds-search-skeleton-info-width: 200px; --ds-filters-skeleton-height: 40px; From 8ee05f43521462b85f041c1c903f5eb7f52d94bd Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 24 Jan 2025 17:11:13 +0100 Subject: [PATCH 726/875] [DURACOM-303] fix missing url link --- src/app/shared/search/search-results/search-results.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/shared/search/search-results/search-results.component.ts b/src/app/shared/search/search-results/search-results.component.ts index e4ebf2819b..296d73f9d8 100644 --- a/src/app/shared/search/search-results/search-results.component.ts +++ b/src/app/shared/search/search-results/search-results.component.ts @@ -22,6 +22,7 @@ export interface SelectionConfig { @Component({ selector: 'ds-search-results', + styleUrls: ['./search-results.component.scss'], templateUrl: './search-results.component.html', animations: [ fadeIn, From 99656f1357438695385820f2c95d4ca5a58993b9 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 10 Jan 2025 12:54:40 +0100 Subject: [PATCH 727/875] [DURACOM-303] add loop for filters count --- .../search/search-results/search-results.component.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 d812561ea7..643cf5eb4b 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -1,8 +1,12 @@ @if ((filters$ | async).length > 0 && isLoading()) {
    -
    - +
    + @for (filter of (filters$| async); track filter.key) { +
    + +
    + }
    From 9467838066cb48906dcda334919f468fea59efbe Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 10 Jan 2025 18:08:20 +0100 Subject: [PATCH 728/875] [DURACOM-303] add grid layout, make SSR enabling configurable, minor restyle of skeletons --- config/config.example.yml | 12 +++++- .../browse-by-metadata-page.component.ts | 4 +- .../search-filter/search-filter.component.ts | 7 +++- .../search-filters.component.html | 9 +++-- .../search-filters.component.ts | 11 +++++ .../search-results-skeleton.component.html | 40 ++++++++++++------- .../search-results-skeleton.component.scss | 20 ++++++++++ .../search-results-skeleton.component.ts | 17 ++++++++ .../search-results.component.html | 10 ++--- .../search-results.component.scss | 9 ++++- .../search-results.component.ts | 6 ++- src/app/shared/search/search.component.ts | 2 + src/environments/environment.production.ts | 4 +- src/environments/environment.test.ts | 2 + src/environments/environment.ts | 4 +- src/styles/_custom_variables.scss | 2 + 16 files changed, 127 insertions(+), 32 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index c1d7f967a4..0a039a40ad 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -25,6 +25,14 @@ ssr: inlineCriticalCss: false # Path prefixes to enable SSR for. By default these are limited to paths of primary DSpace objects. paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ] + # Whether to enable rendering of Search component on SSR. + # If set to true the component will be included in the HTML returned from the server side rendering. + # If set to false the component will not be included in the HTML returned from the server side rendering. + enableSearchComponent: false, + # Whether to enable rendering of Browse component on SSR. + # If set to true the component will be included in the HTML returned from the server side rendering. + # If set to false the component will not be included in the HTML returned from the server side rendering. + enableBrowseComponent: false, # The REST API server settings # NOTE: these settings define which (publicly available) REST API to use. They are usually @@ -84,7 +92,7 @@ cache: anonymousCache: # Maximum number of pages to cache. Default is zero (0) which means anonymous user cache is disabled. # As all pages are cached in server memory, increasing this value will increase memory needs. - # Individual cached pages are usually small (<100KB), so a value of max=1000 would only require ~100MB of memory. + # Individual cached pages are usually small (<100KB), so a value of max=1000 would only require ~100MB of memory. max: 0 # Amount of time after which cached pages are considered stale (in ms). After becoming stale, the cached # copy is automatically refreshed on the next request. @@ -394,7 +402,7 @@ vocabularies: vocabulary: 'srsc' enabled: true -# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query. +# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query. comcolSelectionSort: sortField: 'dc.title' sortDirection: 'ASC' diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index e18841f9ca..7f14f45b2c 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -23,6 +23,7 @@ import { Community } from '../../core/shared/community.model'; import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { isPlatformServer } from "@angular/common"; +import { environment } from "../../../environments/environment"; export const BBM_PAGINATION_ID = 'bbm'; @@ -147,7 +148,8 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { currentPage: 1, pageSize: this.appConfig.browseBy.pageSize, }); - } + this.renderOnServerSide = environment.ssr.enableBrowseComponent; + } ngOnInit(): void { diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts index 67e8906bb5..6f88d3924a 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, Input, OnInit } from '@angular/core'; +import { Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core'; import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { filter, map, startWith, switchMap, take } from 'rxjs/operators'; @@ -43,6 +43,8 @@ export class SearchFilterComponent implements OnInit { */ @Input() scope: string; + @Output() isVisibilityComputed = new EventEmitter(); + /** * True when the filter is 100% collapsed in the UI */ @@ -99,6 +101,9 @@ export class SearchFilterComponent implements OnInit { this.filterService.expand(this.filter.name); } }); + this.active$.pipe(take(1)).subscribe(() => { + this.isVisibilityComputed.emit(true); + }) } /** diff --git a/src/app/shared/search/search-filters/search-filters.component.html b/src/app/shared/search/search-filters/search-filters.component.html index 97f8c75234..45875ad7ae 100644 --- a/src/app/shared/search/search-filters/search-filters.component.html +++ b/src/app/shared/search/search-filters/search-filters.component.html @@ -1,11 +1,12 @@

    {{"search.filters.head" | translate}}

    -
    +
    - +
    - + + - + {{"search.filters.reset" | translate}} diff --git a/src/app/shared/search/search-filters/search-filters.component.ts b/src/app/shared/search/search-filters/search-filters.component.ts index c960dfda56..28f351cb6c 100644 --- a/src/app/shared/search/search-filters/search-filters.component.ts +++ b/src/app/shared/search/search-filters/search-filters.component.ts @@ -61,6 +61,11 @@ export class SearchFiltersComponent implements OnInit, OnDestroy { */ searchLink: string; + /** + * Filters for which visibility has been computed + */ + filtersWithComputedVisibility = 0; + subs = []; defaultFilterCount: number; @@ -114,4 +119,10 @@ export class SearchFiltersComponent implements OnInit, OnDestroy { } }); } + + countFiltersWithComputedVisibility(computed: boolean) { + if (computed) { + this.filtersWithComputedVisibility += 1; + } + } } diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html index 4bc70d1263..84a67f4358 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html @@ -1,24 +1,36 @@
    -
    +
    -@for (result of loadingResults; track result) { -
    - @if(showThumbnails) { -
    - +@if((viewMode$ | async) === ViewMode.ListElement) { + @for (result of loadingResults; track result) { +
    + @if(showThumbnails) { +
    + +
    + } +
    +
    + +
    +
    + +
    +
    +
    + } +} @else if ((viewMode$ | async) === ViewMode.GridElement) { +
    + @for (result of loadingResults; track result) { +
    +
    + +
    } -
    -
    - -
    -
    - -
    -
    } diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss index 1a111f9dd9..e05e4be6d8 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss @@ -1,4 +1,6 @@ :host ::ng-deep { + --ds-wrapper-grid-spacing: calc(var(--bs-spacer) / 2); + .info-skeleton, .badge-skeleton, .text-skeleton{ ngx-skeleton-loader .skeleton-loader { height: var(--ds-search-skeleton-text-height); @@ -27,13 +29,31 @@ } } + .card-skeleton { + ngx-skeleton-loader .skeleton-loader { + height: var(--ds-search-skeleton-card-height); + } + } + ngx-skeleton-loader .skeleton-loader { background-color: var(--bs-light); box-shadow: none; } + + .card-columns { + margin-left: calc(-1 * var(--ds-wrapper-grid-spacing)); + margin-right: calc(-1 * var(--ds-wrapper-grid-spacing)); + column-gap: 0; + + .card-column { + padding-left: var(--ds-wrapper-grid-spacing); + padding-right: var(--ds-wrapper-grid-spacing); + } + } } .result-row { margin-right: 0; margin-left: 0; } + diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts index fef1529460..d2896460e1 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts @@ -1,10 +1,17 @@ +import { + AsyncPipe, + NgForOf, +} from '@angular/common'; import { Component, Input, OnInit, } from '@angular/core'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import { Observable } from 'rxjs'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { ViewMode } from '../../../../core/shared/view-mode.model'; import { hasValue } from '../../../empty.util'; @Component({ @@ -12,6 +19,8 @@ import { hasValue } from '../../../empty.util'; standalone: true, imports: [ NgxSkeletonLoaderModule, + AsyncPipe, + NgForOf, ], templateUrl: './search-results-skeleton.component.html', styleUrl: './search-results-skeleton.component.scss', @@ -26,8 +35,16 @@ export class SearchResultsSkeletonComponent implements OnInit { @Input() textLineCount = 2; + public viewMode$: Observable; + public loadingResults: number[]; + protected readonly ViewMode = ViewMode; + + constructor(private searchService: SearchService) { + this.viewMode$ = searchService.getViewMode(); + } + ngOnInit() { this.loadingResults = Array.from({ length: this.numberOfResults }, (_, i) => i + 1); 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 643cf5eb4b..71839d009b 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -1,12 +1,10 @@ -@if ((filters$ | async).length > 0 && isLoading()) { +@if ((activeFilters$ | async).length > 0 && (appliedFilters$ | async).length === 0) {
    -
    - @for (filter of (filters$| async); track filter.key) { -
    - +
    +
    +
    - }
    diff --git a/src/app/shared/search/search-results/search-results.component.scss b/src/app/shared/search/search-results/search-results.component.scss index 4bf3f8325e..6e369c729b 100644 --- a/src/app/shared/search/search-results/search-results.component.scss +++ b/src/app/shared/search/search-results/search-results.component.scss @@ -4,7 +4,14 @@ background-color: var(--bs-light); box-shadow: none; width: var(--ds-search-skeleton-filter-badge-width); - height: var(--ds-search-skeleton-text-height); + height: var(--ds-search-skeleton-badge-height); + margin-bottom: 0; + margin-right: calc(var(--bs-spacer) / 4); } } + + .filters-badge-skeleton-container { + display: flex; + max-height: var(--ds-search-skeleton-badge-height); + } } diff --git a/src/app/shared/search/search-results/search-results.component.ts b/src/app/shared/search/search-results/search-results.component.ts index 296d73f9d8..1faf8dd778 100644 --- a/src/app/shared/search/search-results/search-results.component.ts +++ b/src/app/shared/search/search-results/search-results.component.ts @@ -14,6 +14,7 @@ import { PaginatedSearchOptions } from '../models/paginated-search-options.model import { SearchFilter } from "../models/search-filter.model"; import { Observable } from "rxjs"; import { SearchConfigurationService } from "../../../core/shared/search/search-configuration.service"; +import { SearchService } from "../../../core/shared/search/search.service"; export interface SelectionConfig { repeatable: boolean; @@ -110,7 +111,10 @@ export class SearchResultsComponent { @Output() selectObject: EventEmitter = new EventEmitter(); - constructor(private searchConfigService: SearchConfigurationService) { + constructor( + protected searchConfigService: SearchConfigurationService, + protected searchService: SearchService, + ) { this.filters$ = this.searchConfigService.getCurrentFilters(); } diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index 362257be0e..1e8811aa86 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -49,6 +49,7 @@ import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-ro import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths'; import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; import { isPlatformServer } from "@angular/common"; +import { environment } from 'src/environments/environment'; @Component({ selector: 'ds-search', @@ -304,6 +305,7 @@ export class SearchComponent implements OnDestroy, OnInit { @Inject(PLATFORM_ID) public platformId: any, ) { this.isXsOrSm$ = this.windowService.isXsOrSm(); + this.renderOnServerSide = environment.universal.enableSearchComponent; } /** diff --git a/src/environments/environment.production.ts b/src/environments/environment.production.ts index 46a93519df..c3cb74651b 100644 --- a/src/environments/environment.production.ts +++ b/src/environments/environment.production.ts @@ -10,5 +10,7 @@ export const environment: Partial = { time: false, inlineCriticalCss: false, paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ], - } + enableSearchComponent: false, + enableBrowseComponent: false, + }, }; diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index e872285f61..ea49f5eb10 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -13,6 +13,8 @@ export const environment: BuildConfig = { time: false, inlineCriticalCss: false, paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ], + enableSearchComponent: false, + enableBrowseComponent: false, }, // Angular Universal server settings. diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 25af371e47..419238f264 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -15,7 +15,9 @@ export const environment: Partial = { time: false, inlineCriticalCss: false, paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ], - } + enableSearchComponent: false, + enableBrowseComponent: false, + }, }; /* diff --git a/src/styles/_custom_variables.scss b/src/styles/_custom_variables.scss index d64f1c6acd..d47f5d2dfa 100644 --- a/src/styles/_custom_variables.scss +++ b/src/styles/_custom_variables.scss @@ -140,6 +140,7 @@ --very-dark-cyan: #215E50; // This variable represents the background color of the save cookies button --ds-search-skeleton-text-height: 20px; + --ds-search-skeleton-badge-height: 18px; --ds-search-skeleton-thumbnail-height: 125px; --ds-search-skeleton-thumbnail-width: 90px; --ds-search-skeleton-thumbnail-padding: 1em; @@ -148,6 +149,7 @@ --ds-search-skeleton-badge-width: 75px; --ds-search-skeleton-filter-badge-width: 200px; --ds-search-skeleton-info-width: 200px; + --ds-search-skeleton-card-height: 435px; --ds-filters-skeleton-height: 40px; --ds-filters-skeleton-spacing: 12px; From 53335658e0874b84841473307d9cd546bd369962 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 24 Jan 2025 17:29:37 +0100 Subject: [PATCH 729/875] [DURACOM-303] adapt interface for ssr --- .../browse-by-metadata-page.component.ts | 2 +- src/config/universal-config.interface.ts | 9 +++++++++ src/environments/environment.test.ts | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 7f14f45b2c..06a29aad1a 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -148,7 +148,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { currentPage: 1, pageSize: this.appConfig.browseBy.pageSize, }); - this.renderOnServerSide = environment.ssr.enableBrowseComponent; + this.renderOnServerSide = environment.universal.enableBrowseComponent; } diff --git a/src/config/universal-config.interface.ts b/src/config/universal-config.interface.ts index e54168823f..e3f7f399a9 100644 --- a/src/config/universal-config.interface.ts +++ b/src/config/universal-config.interface.ts @@ -18,4 +18,13 @@ export interface UniversalConfig extends Config { * Paths to enable SSR for. Defaults to the home page and paths in the sitemap. */ paths: Array; + /** + * Whether to enable rendering of search component on SSR + */ + enableSearchComponent: boolean; + + /** + * Whether to enable rendering of browse component on SSR + */ + enableBrowseComponent: boolean; } diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index ea49f5eb10..4f832e1ac4 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -323,4 +323,8 @@ export const environment: BuildConfig = { messageTimeOutDurationMs: 30000, isVisible: false, }, + + search: { + defaultFilterCount: 5 + } }; From 2c28f7526366450022d7533e1952463fdc31ce52 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 10 Jan 2025 18:20:58 +0100 Subject: [PATCH 730/875] [DURACOM-303] refactor param, add example of configuration --- config/config.example.yml | 9 +++++++++ .../search/search-filters/search-filters.component.ts | 2 +- src/config/search-page-config.interface.ts | 5 ++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index 0a039a40ad..6674bc5334 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -420,3 +420,12 @@ liveRegion: messageTimeOutDurationMs: 30000 # The visibility of the live region. Setting this to true is only useful for debugging purposes. isVisible: false + + +# Search settings +search: + # Number used to render n UI elements called loading skeletons that act as placeholders. + # These elements indicate that some content will be loaded in their stead. + # Since we don't know how many filters will be loaded before we receive a response from the server we use this parameter for the skeletons count. + # e.g. If we set 5 then 5 loading skeletons will be visualized before the actual filters are retrieved. + defaultFiltersCount: 5 diff --git a/src/app/shared/search/search-filters/search-filters.component.ts b/src/app/shared/search/search-filters/search-filters.component.ts index 28f351cb6c..4667694edd 100644 --- a/src/app/shared/search/search-filters/search-filters.component.ts +++ b/src/app/shared/search/search-filters/search-filters.component.ts @@ -84,7 +84,7 @@ export class SearchFiltersComponent implements OnInit, OnDestroy { @Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService, @Inject(APP_CONFIG) protected appConfig: AppConfig, ) { - this.defaultFilterCount = this.appConfig.search.defaultFilterCount ?? 5; + this.defaultFilterCount = this.appConfig.search.defaultFiltersCount ?? 5; } ngOnInit(): void { diff --git a/src/config/search-page-config.interface.ts b/src/config/search-page-config.interface.ts index cc63607f7f..0f4a145868 100644 --- a/src/config/search-page-config.interface.ts +++ b/src/config/search-page-config.interface.ts @@ -2,7 +2,10 @@ import { Config } from './config.interface'; export interface SearchConfig extends Config { /** - * Number used to render n skeletons while the filters are loading + * Number used to render n UI elements called loading skeletons that act as placeholders. + * These elements indicate that some content will be loaded in their stead. + * Since we don't know how many filters will be loaded before we receive a response from the server we use this parameter for the skeletons count. + * For instance f we set 5 then 5 loading skeletons will be visualized before the actual filters are retrieved. */ defaultFilterCount?: number; From 311f648c3f8707fdaa8124a9ddb355e7ff227876 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Mon, 13 Jan 2025 10:51:53 +0100 Subject: [PATCH 731/875] [DURACOM-303] rename variable, minor code refactor --- .../shared/search/search-filters/search-filters.component.ts | 2 +- .../search-results-skeleton.component.spec.ts | 5 +++++ .../search-results-skeleton.component.ts | 2 +- src/app/shared/testing/search-configuration-service.stub.ts | 4 ++++ src/config/default-app-config.ts | 2 +- src/config/search-page-config.interface.ts | 4 ++-- 6 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/app/shared/search/search-filters/search-filters.component.ts b/src/app/shared/search/search-filters/search-filters.component.ts index 4667694edd..1f08a5ac5d 100644 --- a/src/app/shared/search/search-filters/search-filters.component.ts +++ b/src/app/shared/search/search-filters/search-filters.component.ts @@ -84,7 +84,7 @@ export class SearchFiltersComponent implements OnInit, OnDestroy { @Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService, @Inject(APP_CONFIG) protected appConfig: AppConfig, ) { - this.defaultFilterCount = this.appConfig.search.defaultFiltersCount ?? 5; + this.defaultFilterCount = this.appConfig.search.filterPlaceholdersCount ?? 5; } ngOnInit(): void { diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts index 0ec74eb946..68c8db5a8e 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts @@ -4,6 +4,8 @@ import { } from '@angular/core/testing'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { SearchServiceStub } from '../../../testing/search-service.stub'; import { SearchResultsSkeletonComponent } from './search-results-skeleton.component'; describe('SearchResultsSkeletonComponent', () => { @@ -13,6 +15,9 @@ describe('SearchResultsSkeletonComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [SearchResultsSkeletonComponent, NgxSkeletonLoaderModule], + providers: [ + { provide: SearchService, useValue: new SearchServiceStub() }, + ], }) .compileComponents(); diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts index d2896460e1..28a603cfa3 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts @@ -42,7 +42,7 @@ export class SearchResultsSkeletonComponent implements OnInit { protected readonly ViewMode = ViewMode; constructor(private searchService: SearchService) { - this.viewMode$ = searchService.getViewMode(); + this.viewMode$ = this.searchService.getViewMode(); } ngOnInit() { diff --git a/src/app/shared/testing/search-configuration-service.stub.ts b/src/app/shared/testing/search-configuration-service.stub.ts index 78b358f0d4..ef72e40041 100644 --- a/src/app/shared/testing/search-configuration-service.stub.ts +++ b/src/app/shared/testing/search-configuration-service.stub.ts @@ -13,6 +13,10 @@ export class SearchConfigurationServiceStub { return observableOf([]); } + getCurrentFilters() { + return observableOf([]); + } + getCurrentScope(a) { return observableOf('test-id'); } diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 7ed4168217..2384942e3b 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -445,6 +445,6 @@ export class DefaultAppConfig implements AppConfig { }; search: SearchConfig = { - defaultFilterCount: 5 + filterPlaceholdersCount: 5 } } diff --git a/src/config/search-page-config.interface.ts b/src/config/search-page-config.interface.ts index 0f4a145868..410876cde2 100644 --- a/src/config/search-page-config.interface.ts +++ b/src/config/search-page-config.interface.ts @@ -5,8 +5,8 @@ export interface SearchConfig extends Config { * Number used to render n UI elements called loading skeletons that act as placeholders. * These elements indicate that some content will be loaded in their stead. * Since we don't know how many filters will be loaded before we receive a response from the server we use this parameter for the skeletons count. - * For instance f we set 5 then 5 loading skeletons will be visualized before the actual filters are retrieved. + * For instance if we set 5 then 5 loading skeletons will be visualized before the actual filters are retrieved. */ - defaultFilterCount?: number; + filterPlaceholdersCount?: number; } From 32364e4f68e8153ecb1143bc9d2827a9789d6f16 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Mon, 13 Jan 2025 17:31:12 +0100 Subject: [PATCH 732/875] [DURACOM-303] add override possibility with input --- .../browse-by-metadata-page.component.ts | 3 +-- src/app/shared/search/search.component.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 06a29aad1a..5575b153db 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -148,12 +148,11 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { currentPage: 1, pageSize: this.appConfig.browseBy.pageSize, }); - this.renderOnServerSide = environment.universal.enableBrowseComponent; } ngOnInit(): void { - if (!this.renderOnServerSide && isPlatformServer(this.platformId)) { + if (!this.renderOnServerSide && !environment.universal.enableBrowseComponent && isPlatformServer(this.platformId)) { this.loading$ = observableOf(false); return; } diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index 1e8811aa86..e69a183408 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -305,7 +305,6 @@ export class SearchComponent implements OnDestroy, OnInit { @Inject(PLATFORM_ID) public platformId: any, ) { this.isXsOrSm$ = this.windowService.isXsOrSm(); - this.renderOnServerSide = environment.universal.enableSearchComponent; } /** @@ -316,7 +315,7 @@ export class SearchComponent implements OnDestroy, OnInit { * If something changes, update the list of scopes for the dropdown */ ngOnInit(): void { - if (!this.renderOnServerSide && isPlatformServer(this.platformId)) { + if (!this.renderOnServerSide && !environment.universal.enableSearchComponent && isPlatformServer(this.platformId)) { this.subs.push(this.getSearchOptions().pipe(distinctUntilChanged()).subscribe((options) => { this.searchOptions$.next(options); })); From 8a4e8f60451ef83c8f5f9ee95f1c874424408a73 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Mon, 20 Jan 2025 13:16:36 +0100 Subject: [PATCH 733/875] [DURACOM-303] fix SSR check on template and on components missing the environment config. Add descriptive comment for skeleton component. Fix JS error on SSR. --- .../browse-by-date-page/browse-by-date-page.component.ts | 3 ++- .../browse-by-metadata-page.component.spec.ts | 6 +++--- .../browse-by-metadata-page.component.ts | 7 ++++++- .../search-results-skeleton.component.ts | 3 +++ 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index 62bba4d86c..86a2f3dfa3 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -20,6 +20,7 @@ import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { isPlatformServer } from "@angular/common"; +import { environment } from "../../../environments/environment"; @Component({ selector: 'ds-browse-by-date-page', @@ -52,7 +53,7 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { } ngOnInit(): void { - if (!this.renderOnServerSide && isPlatformServer(this.platformId)) { + if (!this.renderOnServerSide && !environment.universal.enableBrowseComponent && isPlatformServer(this.platformId)) { this.loading$ = observableOf(false); return; } diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.spec.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.spec.ts index 8eda34cd62..4ea68eaa0c 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.spec.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.spec.ts @@ -228,8 +228,8 @@ describe('BrowseByMetadataPageComponent', () => { describe('when rendered in SSR', () => { beforeEach(() => { - comp.platformId = 'server'; - spyOn((comp as any).browseService, 'getBrowseEntriesFor'); + comp.ssrRenderingDisabled = true; + spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(null)); }); it('should not call getBrowseEntriesFor on init', (done) => { @@ -244,7 +244,7 @@ describe('BrowseByMetadataPageComponent', () => { describe('when rendered in CSR', () => { beforeEach(() => { - comp.platformId = 'browser'; + comp.ssrRenderingDisabled = false; spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry())); }); diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 5575b153db..a7357aaaf3 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -131,6 +131,10 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { * Observable determining if the loading animation needs to be shown */ loading$ = observableOf(true); + /** + * Whether this component should be rendered or not in SSR + */ + ssrRenderingDisabled = false; public constructor(protected route: ActivatedRoute, protected browseService: BrowseService, @@ -148,11 +152,12 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { currentPage: 1, pageSize: this.appConfig.browseBy.pageSize, }); + this.ssrRenderingDisabled = !this.renderOnServerSide && !environment.universal.enableBrowseComponent && isPlatformServer(this.platformId); } ngOnInit(): void { - if (!this.renderOnServerSide && !environment.universal.enableBrowseComponent && isPlatformServer(this.platformId)) { + if (this.ssrRenderingDisabled) { this.loading$ = observableOf(false); return; } diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts index 28a603cfa3..16df026cfd 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts @@ -25,6 +25,9 @@ import { hasValue } from '../../../empty.util'; templateUrl: './search-results-skeleton.component.html', styleUrl: './search-results-skeleton.component.scss', }) +/** + * Component to show placeholders for search results while loading, to give a loading feedback to the user without layout shifting. + */ export class SearchResultsSkeletonComponent implements OnInit { @Input() showThumbnails: boolean; From 171a971572cc9c634677376c6063bd0f54487dfe Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 24 Jan 2025 17:41:38 +0100 Subject: [PATCH 734/875] [DURACOM-303] resolve conflicts --- .../browse-by-metadata-page.component.html | 2 +- .../browse-by-title-page/browse-by-title-page.component.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html index a6abd5a7a4..9628de44b2 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html @@ -1,4 +1,4 @@ -
    +
    diff --git a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index 0d8a2e32ed..74a109574c 100644 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -16,6 +16,7 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { isPlatformServer } from "@angular/common"; +import { environment } from "../../../environments/environment"; @Component({ selector: 'ds-browse-by-title-page', @@ -40,7 +41,7 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent { } ngOnInit(): void { - if (!this.renderOnServerSide && isPlatformServer(this.platformId)) { + if (!this.renderOnServerSide && !environment.universal.enableBrowseComponent && isPlatformServer(this.platformId)) { this.loading$ = observableOf(false); return; } From 483b97d9c187f415f5d3ad7579c4e4b773dbd782 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Tue, 21 Jan 2025 11:01:54 +0100 Subject: [PATCH 735/875] [DURACOM-303] refactor thumbnail's skeleton style --- .../search-results-skeleton.component.html | 12 +++++++----- .../search-results-skeleton.component.scss | 11 ++++------- .../search-results-skeleton.component.ts | 19 +++++++++++++++---- src/styles/_custom_variables.scss | 3 --- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html index 84a67f4358..a42bda0674 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html @@ -5,14 +5,16 @@
    @if((viewMode$ | async) === ViewMode.ListElement) { - @for (result of loadingResults; track result) { -
    + @for (result of loadingResults; track result; let first = $first) { +
    @if(showThumbnails) { -
    - +
    +
    + +
    } -
    +
    diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss index e05e4be6d8..9345f1ab43 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.scss @@ -20,12 +20,13 @@ } .thumbnail-skeleton { + max-width: var(--ds-thumbnail-max-width); + height: 100%; + ngx-skeleton-loader .skeleton-loader { - width: var(--ds-search-skeleton-thumbnail-width); - height: var(--ds-search-skeleton-thumbnail-height); - padding: var(--ds-search-skeleton-thumbnail-padding); margin-right: var(--ds-search-skeleton-thumbnail-margin); border-radius: 0; + height: 100%; } } @@ -52,8 +53,4 @@ } } -.result-row { - margin-right: 0; - margin-left: 0; -} diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts index 16df026cfd..3fba70b119 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts @@ -29,17 +29,28 @@ import { hasValue } from '../../../empty.util'; * Component to show placeholders for search results while loading, to give a loading feedback to the user without layout shifting. */ export class SearchResultsSkeletonComponent implements OnInit { + /** + * Whether the search result contains thumbnail + */ @Input() showThumbnails: boolean; - + /** + * The number of search result loaded in the current page + */ @Input() numberOfResults = 0; - + /** + * How many placeholder are displayed for the search result text + */ @Input() textLineCount = 2; - + /** + * The view mode of the search page + */ public viewMode$: Observable; - + /** + * Array built from numberOfResults to count and iterate based on index + */ public loadingResults: number[]; protected readonly ViewMode = ViewMode; diff --git a/src/styles/_custom_variables.scss b/src/styles/_custom_variables.scss index d47f5d2dfa..3c3244549e 100644 --- a/src/styles/_custom_variables.scss +++ b/src/styles/_custom_variables.scss @@ -141,9 +141,6 @@ --ds-search-skeleton-text-height: 20px; --ds-search-skeleton-badge-height: 18px; - --ds-search-skeleton-thumbnail-height: 125px; - --ds-search-skeleton-thumbnail-width: 90px; - --ds-search-skeleton-thumbnail-padding: 1em; --ds-search-skeleton-thumbnail-margin: 1em; --ds-search-skeleton-text-line-count: 2; --ds-search-skeleton-badge-width: 75px; From 990f00b129c2b7f23e8ac41904d4778b4e07e702 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 24 Jan 2025 18:26:28 +0100 Subject: [PATCH 736/875] [DURACOM-303] adapt solution to older version of angular --- package.json | 2 +- .../browse-by-date-page.component.spec.ts | 4 +-- .../browse-by-date-page.component.ts | 4 +-- .../browse-by-metadata-page.component.ts | 4 +-- .../browse-by-title-page.component.spec.ts | 2 +- .../browse-by-title-page.component.ts | 4 +-- .../search-filter/search-filter.component.ts | 12 ++++---- .../search-filters.component.html | 3 +- .../search-filters.component.spec.ts | 4 +-- .../search-filters.component.ts | 15 ++++++---- .../search-results-skeleton.component.html | 30 +++++++++---------- .../search-results-skeleton.component.ts | 13 +------- .../search-results.component.html | 12 -------- .../search-results.component.spec.ts | 2 +- .../search-results.component.ts | 8 ++--- src/app/shared/search/search.component.ts | 2 +- src/app/shared/search/search.module.ts | 9 +++++- src/config/app-config.interface.ts | 2 +- src/config/default-app-config.ts | 4 +-- src/environments/environment.test.ts | 2 +- src/themes/custom/lazy-theme.module.ts | 7 ++--- yarn.lock | 15 ++++++++++ 22 files changed, 82 insertions(+), 78 deletions(-) diff --git a/package.json b/package.json index 86a506c57a..76ae2d5827 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "ngx-infinite-scroll": "^15.0.0", "ngx-pagination": "6.0.3", "ngx-sortablejs": "^11.1.0", - "ngx-skeleton-loader": "^9.0.0", + "ngx-skeleton-loader": "^7.0.0", "ngx-ui-switch": "^14.1.0", "nouislider": "^15.8.1", "pem": "1.14.8", diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts index 2fe673f821..f64091e41f 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.spec.ts @@ -24,8 +24,8 @@ import { APP_CONFIG } from '../../../config/app-config.interface'; import { environment } from '../../../environments/environment'; import { SortDirection } from '../../core/cache/models/sort-options.model'; import { cold } from 'jasmine-marbles'; -import { Store } from "@ngrx/store"; -import { BrowseEntry } from "../../core/shared/browse-entry.model"; +import { Store } from '@ngrx/store'; +import { BrowseEntry } from '../../core/shared/browse-entry.model'; describe('BrowseByDatePageComponent', () => { let comp: BrowseByDatePageComponent; diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index 86a2f3dfa3..afb58e6ad2 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -19,8 +19,8 @@ import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; -import { isPlatformServer } from "@angular/common"; -import { environment } from "../../../environments/environment"; +import { isPlatformServer } from '@angular/common'; +import { environment } from '../../../environments/environment'; @Component({ selector: 'ds-browse-by-date-page', diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index a7357aaaf3..52cf3b9d7b 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -22,8 +22,8 @@ import { Collection } from '../../core/shared/collection.model'; import { Community } from '../../core/shared/community.model'; import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; -import { isPlatformServer } from "@angular/common"; -import { environment } from "../../../environments/environment"; +import { isPlatformServer } from '@angular/common'; +import { environment } from '../../../environments/environment'; export const BBM_PAGINATION_ID = 'bbm'; diff --git a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts index 653ad596c5..1b8eb352a3 100644 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts +++ b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.spec.ts @@ -22,7 +22,7 @@ import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; import { APP_CONFIG } from '../../../config/app-config.interface'; import { environment } from '../../../environments/environment'; -import { BrowseEntry } from "../../core/shared/browse-entry.model"; +import { BrowseEntry } from '../../core/shared/browse-entry.model'; describe('BrowseByTitlePageComponent', () => { diff --git a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index 74a109574c..518fdf8c15 100644 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -15,8 +15,8 @@ import { of as observableOf } from 'rxjs'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; -import { isPlatformServer } from "@angular/common"; -import { environment } from "../../../environments/environment"; +import { isPlatformServer } from '@angular/common'; +import { environment } from '../../../environments/environment'; @Component({ selector: 'ds-browse-by-title-page', diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts index 6f88d3924a..a440652fe5 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts @@ -93,7 +93,9 @@ export class SearchFilterComponent implements OnInit { */ ngOnInit() { this.selectedValues$ = this.getSelectedValues(); - this.active$ = this.isActive(); + this.active$ = this.isActive().pipe( + startWith(true) + ); this.collapsed$ = this.isCollapsed(); this.initializeFilter(); this.selectedValues$.pipe(take(1)).subscribe((selectedValues) => { @@ -101,9 +103,9 @@ export class SearchFilterComponent implements OnInit { this.filterService.expand(this.filter.name); } }); - this.active$.pipe(take(1)).subscribe(() => { + this.isActive().pipe(take(1)).subscribe(() => { this.isVisibilityComputed.emit(true); - }) + }); } /** @@ -192,7 +194,7 @@ export class SearchFilterComponent implements OnInit { } )); } - }), - startWith(true)); + }) + ); } } diff --git a/src/app/shared/search/search-filters/search-filters.component.html b/src/app/shared/search/search-filters/search-filters.component.html index 45875ad7ae..e1e54c683d 100644 --- a/src/app/shared/search/search-filters/search-filters.component.html +++ b/src/app/shared/search/search-filters/search-filters.component.html @@ -1,6 +1,7 @@

    {{"search.filters.head" | translate}}

    -
    +
    diff --git a/src/app/shared/search/search-filters/search-filters.component.spec.ts b/src/app/shared/search/search-filters/search-filters.component.spec.ts index c8ad89910c..864d7b583e 100644 --- a/src/app/shared/search/search-filters/search-filters.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filters.component.spec.ts @@ -9,8 +9,8 @@ import { SearchFiltersComponent } from './search-filters.component'; import { SearchService } from '../../../core/shared/search/search.service'; import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub'; -import { APP_CONFIG } from "../../../../config/app-config.interface"; -import { environment } from "../../../../environments/environment"; +import { APP_CONFIG } from '../../../../config/app-config.interface'; +import { environment } from '../../../../environments/environment'; describe('SearchFiltersComponent', () => { let comp: SearchFiltersComponent; diff --git a/src/app/shared/search/search-filters/search-filters.component.ts b/src/app/shared/search/search-filters/search-filters.component.ts index 1f08a5ac5d..b491f21177 100644 --- a/src/app/shared/search/search-filters/search-filters.component.ts +++ b/src/app/shared/search/search-filters/search-filters.component.ts @@ -2,7 +2,7 @@ import { Component, Inject, Input, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { BehaviorSubject, Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { map, tap } from 'rxjs/operators'; import { SearchService } from '../../../core/shared/search/search.service'; import { RemoteData } from '../../../core/data/remote-data'; @@ -12,7 +12,7 @@ import { SearchFilterService } from '../../../core/shared/search/search-filter.s import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; import { currentPath } from '../../utils/route.utils'; import { hasValue } from '../../empty.util'; -import { APP_CONFIG, AppConfig } from "../../../../config/app-config.interface"; +import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface'; @Component({ selector: 'ds-search-filters', @@ -88,10 +88,13 @@ export class SearchFiltersComponent implements OnInit, OnDestroy { } ngOnInit(): void { - this.clearParams = this.searchConfigService.getCurrentFrontendFilters().pipe(map((filters) => { - Object.keys(filters).forEach((f) => filters[f] = null); - return filters; - })); + this.clearParams = this.searchConfigService.getCurrentFrontendFilters().pipe( + tap(() => this.filtersWithComputedVisibility = 0), + map((filters) => { + Object.keys(filters).forEach((f) => filters[f] = null); + return filters; + }) + ); this.searchLink = this.getSearchLink(); } diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html index a42bda0674..bd98040257 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html @@ -4,16 +4,14 @@
    -@if((viewMode$ | async) === ViewMode.ListElement) { - @for (result of loadingResults; track result; let first = $first) { -
    - @if(showThumbnails) { -
    -
    - -
    + + +
    +
    +
    +
    - } +
    @@ -23,16 +21,18 @@
    - } -} @else if ((viewMode$ | async) === ViewMode.GridElement) { +
    +
    + +
    - @for (result of loadingResults; track result) { +
    - } - +
    -} +
    + diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts index 3fba70b119..179b26f1fd 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.ts @@ -1,13 +1,8 @@ -import { - AsyncPipe, - NgForOf, -} from '@angular/common'; import { Component, Input, OnInit, } from '@angular/core'; -import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Observable } from 'rxjs'; import { SearchService } from '../../../../core/shared/search/search.service'; @@ -16,14 +11,8 @@ import { hasValue } from '../../../empty.util'; @Component({ selector: 'ds-search-results-skeleton', - standalone: true, - imports: [ - NgxSkeletonLoaderModule, - AsyncPipe, - NgForOf, - ], templateUrl: './search-results-skeleton.component.html', - styleUrl: './search-results-skeleton.component.scss', + styleUrls: ['./search-results-skeleton.component.scss'], }) /** * Component to show placeholders for search results while loading, to give a loading feedback to the user without layout shifting. 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 71839d009b..a2bc91195c 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -1,15 +1,3 @@ -@if ((activeFilters$ | async).length > 0 && (appliedFilters$ | async).length === 0) { -
    -
    -
    -
    - -
    -
    -
    -
    -} -

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

    diff --git a/src/app/shared/search/search-results/search-results.component.spec.ts b/src/app/shared/search/search-results/search-results.component.spec.ts index dc961c97cb..c2b181038a 100644 --- a/src/app/shared/search/search-results/search-results.component.spec.ts +++ b/src/app/shared/search/search-results/search-results.component.spec.ts @@ -7,7 +7,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { SearchResultsComponent } from './search-results.component'; import { QueryParamsDirectiveStub } from '../../testing/query-params-directive.stub'; import { createFailedRemoteDataObject } from '../../remote-data.utils'; -import { SearchResultsSkeletonComponent } from "./search-results-skeleton/search-results-skeleton.component"; +import { SearchResultsSkeletonComponent } from './search-results-skeleton/search-results-skeleton.component'; describe('SearchResultsComponent', () => { let comp: SearchResultsComponent; diff --git a/src/app/shared/search/search-results/search-results.component.ts b/src/app/shared/search/search-results/search-results.component.ts index 1faf8dd778..9050ce9cd0 100644 --- a/src/app/shared/search/search-results/search-results.component.ts +++ b/src/app/shared/search/search-results/search-results.component.ts @@ -11,10 +11,10 @@ import { CollectionElementLinkType } from '../../object-collection/collection-el import { ViewMode } from '../../../core/shared/view-mode.model'; import { Context } from '../../../core/shared/context.model'; import { PaginatedSearchOptions } from '../models/paginated-search-options.model'; -import { SearchFilter } from "../models/search-filter.model"; -import { Observable } from "rxjs"; -import { SearchConfigurationService } from "../../../core/shared/search/search-configuration.service"; -import { SearchService } from "../../../core/shared/search/search.service"; +import { SearchFilter } from '../models/search-filter.model'; +import { Observable } from 'rxjs'; +import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; +import { SearchService } from '../../../core/shared/search/search.service'; export interface SelectionConfig { repeatable: boolean; diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index e69a183408..527d2d2719 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -48,7 +48,7 @@ import { ITEM_MODULE_PATH } from '../../item-page/item-page-routing-paths'; import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths'; import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths'; import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; -import { isPlatformServer } from "@angular/common"; +import { isPlatformServer } from '@angular/common'; import { environment } from 'src/environments/environment'; @Component({ diff --git a/src/app/shared/search/search.module.ts b/src/app/shared/search/search.module.ts index 69500999a8..e2075a5f56 100644 --- a/src/app/shared/search/search.module.ts +++ b/src/app/shared/search/search.module.ts @@ -34,6 +34,10 @@ import { ThemedSearchSettingsComponent } from './search-settings/themed-search-s import { NouisliderModule } from 'ng2-nouislider'; import { ThemedSearchFiltersComponent } from './search-filters/themed-search-filters.component'; import { ThemedSearchSidebarComponent } from './search-sidebar/themed-search-sidebar.component'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import { + SearchResultsSkeletonComponent +} from './search-results/search-results-skeleton/search-results-skeleton.component'; const COMPONENTS = [ SearchComponent, @@ -62,6 +66,7 @@ const COMPONENTS = [ ThemedSearchSettingsComponent, ThemedSearchFiltersComponent, ThemedSearchSidebarComponent, + SearchResultsSkeletonComponent ]; const ENTRY_COMPONENTS = [ @@ -74,6 +79,7 @@ const ENTRY_COMPONENTS = [ SearchFacetSelectedOptionComponent, SearchFacetRangeOptionComponent, SearchAuthorityFilterComponent, + SearchResultsSkeletonComponent ]; /** @@ -93,11 +99,12 @@ export const MODELS = [ imports: [ CommonModule, TranslateModule.forChild({ - missingTranslationHandler: { provide: MissingTranslationHandler, useClass: MissingTranslationHelper }, + missingTranslationHandler: {provide: MissingTranslationHandler, useClass: MissingTranslationHelper}, useDefaultLang: true }), SharedModule.withEntryComponents(), NouisliderModule, + NgxSkeletonLoaderModule, ], exports: [ ...COMPONENTS diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index 8b8dcf74a7..af78d4ab88 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -23,7 +23,7 @@ import { MarkdownConfig } from './markdown-config.interface'; import { FilterVocabularyConfig } from './filter-vocabulary-config'; import { DiscoverySortConfig } from './discovery-sort.config'; import { LiveRegionConfig } from '../app/shared/live-region/live-region.config'; -import { SearchConfig } from "./search-page-config.interface"; +import { SearchConfig } from './search-page-config.interface'; interface AppConfig extends Config { ui: UIServerConfig; diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 2384942e3b..de4f3bd56e 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -23,7 +23,7 @@ import { MarkdownConfig } from './markdown-config.interface'; import { FilterVocabularyConfig } from './filter-vocabulary-config'; import { DiscoverySortConfig } from './discovery-sort.config'; import { LiveRegionConfig } from '../app/shared/live-region/live-region.config'; -import { SearchConfig } from "./search-page-config.interface"; +import { SearchConfig } from './search-page-config.interface'; export class DefaultAppConfig implements AppConfig { production = false; @@ -446,5 +446,5 @@ export class DefaultAppConfig implements AppConfig { search: SearchConfig = { filterPlaceholdersCount: 5 - } + }; } diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 4f832e1ac4..e6ffe85df6 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -325,6 +325,6 @@ export const environment: BuildConfig = { }, search: { - defaultFilterCount: 5 + filterPlaceholdersCount: 5 } }; diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index 1fe476c1e1..546d2dccbf 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -159,9 +159,8 @@ import { RequestCopyModule } from 'src/app/request-copy/request-copy.module'; import {UserMenuComponent} from './app/shared/auth-nav-menu/user-menu/user-menu.component'; import { BrowseByComponent } from './app/shared/browse-by/browse-by.component'; import { RegisterEmailFormComponent } from './app/register-email-form/register-email-form.component'; -import { - SearchResultsSkeletonComponent -} from "../../app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component"; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; + const DECLARATIONS = [ FileSectionComponent, @@ -248,7 +247,6 @@ const DECLARATIONS = [ UserMenuComponent, BrowseByComponent, RegisterEmailFormComponent, - SearchResultsSkeletonComponent, ]; @NgModule({ @@ -309,6 +307,7 @@ const DECLARATIONS = [ NgxGalleryModule, FormModule, RequestCopyModule, + NgxSkeletonLoaderModule ], declarations: DECLARATIONS, exports: [ diff --git a/yarn.lock b/yarn.lock index 7760902b72..39661b8d43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8415,6 +8415,14 @@ ngx-pagination@6.0.3: dependencies: tslib "^2.3.0" +ngx-skeleton-loader@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/ngx-skeleton-loader/-/ngx-skeleton-loader-7.0.0.tgz#3b1325025a7208a20f3a0fdba6e578532a09cfcd" + integrity sha512-myc6GNcNhyksZrimIFkCxeihi0kQ8JhQVZiGbtiIv4gYrnnRk5nXbs3kYitK8E8OstHG+jlsmRofqGBxuIsYTA== + dependencies: + perf-marks "^1.13.4" + tslib "^2.0.0" + ngx-sortablejs@^11.1.0: version "11.1.0" resolved "https://registry.npmjs.org/ngx-sortablejs/-/ngx-sortablejs-11.1.0.tgz" @@ -9060,6 +9068,13 @@ pend@~1.2.0: resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== +perf-marks@^1.13.4: + version "1.14.2" + resolved "https://registry.yarnpkg.com/perf-marks/-/perf-marks-1.14.2.tgz#7511c24239b9c2071717993a33ec3057f387b8c7" + integrity sha512-N0/bQcuTlETpgox/DsXS1voGjqaoamMoiyhncgeW3rSHy/qw8URVgmPRYfFDQns/+C6yFUHDbeSBGL7ixT6Y4A== + dependencies: + tslib "^2.1.0" + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" From ecd2faea4aa3c4322e98da295b3f826b6c1b3d10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 17:43:00 +0000 Subject: [PATCH 737/875] Bump cross-spawn from 7.0.3 to 7.0.6 Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6. - [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md) - [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6) --- updated-dependencies: - dependency-name: cross-spawn dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7760902b72..3a723f2ab9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4331,9 +4331,9 @@ cross-env@^7.0.3: cross-spawn "^7.0.1" cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" From 147564b114afd7c88246da0dbafe4073707f0f06 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Thu, 10 Oct 2024 18:13:34 +0200 Subject: [PATCH 738/875] [CST-17153] improve descriptive labels for show/hide more buttons on related items lists (cherry picked from commit 7cd82e542bec81bf768124c20ea18162881cb56d) --- .../simple/related-items/related-items.component.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/item-page/simple/related-items/related-items.component.html b/src/app/item-page/simple/related-items/related-items.component.html index bee1f345fd..b15c93178c 100644 --- a/src/app/item-page/simple/related-items/related-items.component.html +++ b/src/app/item-page/simple/related-items/related-items.component.html @@ -7,12 +7,12 @@
    - +
    - +
    From 5d6bf0bb334ddda17741837058a27de60f1a9e26 Mon Sep 17 00:00:00 2001 From: autavares-dev Date: Mon, 22 Jul 2024 21:20:05 -0300 Subject: [PATCH 739/875] Fix truncatable-part keyboard accessibility (cherry picked from commit 6cb4faa8d34cbb18eb0516506c2c6da6b5e15df9) --- .../truncatable-part.component.html | 18 +++++++++++------- .../truncatable-part.component.spec.ts | 13 +++++++++++++ .../truncatable-part.component.ts | 7 +++++++ 3 files changed, 31 insertions(+), 7 deletions(-) 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 32a2a21349..55ebe2d957 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html @@ -2,12 +2,16 @@
    - -
    diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts index 08d3e18117..a40703bbf3 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts @@ -73,6 +73,11 @@ describe('TruncatablePartComponent', () => { const a = fixture.debugElement.query(By.css('.collapseButton')); expect(a).toBeNull(); }); + + it('expandButton aria-expanded should be false', () => { + const btn = fixture.debugElement.query(By.css('.expandButton')); + expect(btn.nativeElement.getAttribute('aria-expanded')).toEqual('false'); + }); }); describe('When the item is expanded', () => { @@ -101,6 +106,14 @@ describe('TruncatablePartComponent', () => { const a = fixture.debugElement.query(By.css('.collapseButton')); expect(a).not.toBeNull(); }); + + it('collapseButton aria-expanded should be true', () => { + (comp as any).setLines(); + (comp as any).expandable = true; + fixture.detectChanges(); + const btn = fixture.debugElement.query(By.css('.collapseButton')); + expect(btn.nativeElement.getAttribute('aria-expanded')).toEqual('true'); + }); }); }); diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts index 790bd5985d..c25e44c968 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts @@ -136,6 +136,13 @@ export class TruncatablePartComponent implements AfterViewChecked, OnInit, OnDes } } + /** + * Indicates if the content is expanded, button state is 'Collapse' + */ + public get isExpanded() { + return this.expand && this.expandable; + } + /** * Unsubscribe from the subscription */ From 868b0211186e9c1d4507d5afafc4ff42d7b5d92b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 02:35:56 +0000 Subject: [PATCH 740/875] Bump @babel/runtime from 7.26.0 to 7.26.7 Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.26.0 to 7.26.7. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.26.7/packages/babel-runtime) --- updated-dependencies: - dependency-name: "@babel/runtime" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e94785d93e..cd5c2e6461 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "@angular/platform-browser-dynamic": "^15.2.10", "@angular/platform-server": "^15.2.10", "@angular/router": "^15.2.10", - "@babel/runtime": "7.26.0", + "@babel/runtime": "7.26.7", "@kolkov/ngx-gallery": "^2.0.1", "@ng-bootstrap/ng-bootstrap": "^11.0.0", "@ng-dynamic-forms/core": "^15.0.0", diff --git a/yarn.lock b/yarn.lock index 58d8f59fbc..23db53d7cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1352,10 +1352,10 @@ dependencies: regenerator-runtime "^0.13.11" -"@babel/runtime@7.26.0", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.14.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" - integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== +"@babel/runtime@7.26.7", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.14.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.7.tgz#f4e7fe527cd710f8dc0618610b61b4b060c3c341" + integrity sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ== dependencies: regenerator-runtime "^0.14.0" From 8a4f24fc31bd1fad18f63cb71a0f377a51f63d87 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Mon, 27 Jan 2025 09:34:42 +0100 Subject: [PATCH 741/875] [DURACOM-303] adapt tests dependencies and template variable --- .../search-results-skeleton.component.html | 2 +- .../search-results-skeleton.component.spec.ts | 3 ++- .../search-results/search-results.component.spec.ts | 11 ++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html index bd98040257..c318eea2e1 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.html @@ -5,7 +5,7 @@
    - +
    diff --git a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts index 68c8db5a8e..86277181fe 100644 --- a/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts +++ b/src/app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component.spec.ts @@ -14,7 +14,8 @@ describe('SearchResultsSkeletonComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [SearchResultsSkeletonComponent, NgxSkeletonLoaderModule], + imports: [NgxSkeletonLoaderModule], + declarations: [SearchResultsSkeletonComponent], providers: [ { provide: SearchService, useValue: new SearchServiceStub() }, ], diff --git a/src/app/shared/search/search-results/search-results.component.spec.ts b/src/app/shared/search/search-results/search-results.component.spec.ts index c2b181038a..1ef596a4ed 100644 --- a/src/app/shared/search/search-results/search-results.component.spec.ts +++ b/src/app/shared/search/search-results/search-results.component.spec.ts @@ -8,6 +8,10 @@ import { SearchResultsComponent } from './search-results.component'; import { QueryParamsDirectiveStub } from '../../testing/query-params-directive.stub'; import { createFailedRemoteDataObject } from '../../remote-data.utils'; import { SearchResultsSkeletonComponent } from './search-results-skeleton/search-results-skeleton.component'; +import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; +import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub'; +import { SearchService } from '../../../core/shared/search/search.service'; +import { SearchServiceStub } from '../../testing/search-service.stub'; describe('SearchResultsComponent', () => { let comp: SearchResultsComponent; @@ -21,7 +25,12 @@ describe('SearchResultsComponent', () => { declarations: [ SearchResultsComponent, SearchResultsSkeletonComponent, - QueryParamsDirectiveStub], + QueryParamsDirectiveStub + ], + providers: [ + { provide: SearchConfigurationService, useValue: new SearchConfigurationServiceStub() }, + { provide: SearchService, useValue: new SearchServiceStub() }, + ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); })); From cd2f8abd2d14a75433a5c0ce3c6362206c4b7eb2 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Fri, 24 Jan 2025 11:23:48 +0100 Subject: [PATCH 742/875] 117544: fix issues latest 7.x merge --- .../filtered-items.component.html | 0 ...llection-source-controls.component.spec.ts | 1 - .../dso-edit-metadata-value.component.html | 47 ------------------- 3 files changed, 48 deletions(-) delete mode 100644 src/app/admin/admin-reports/filtered-items/filtered-items.component.html diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items.component.html b/src/app/admin/admin-reports/filtered-items/filtered-items.component.html deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts index 12de34d3a9..ed53f8ab2f 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.spec.ts @@ -191,7 +191,6 @@ describe('CollectionSourceControlsComponent', () => { const buttons = fixture.debugElement.queryAll(By.css('button')); buttons.forEach(button => { - console.log(button.nativeElement); expect(button.nativeElement.getAttribute('aria-disabled')).toBe('true'); expect(button.nativeElement.classList.contains('disabled')).toBeTrue(); }); diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html index e07585b39b..f96759a75e 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html @@ -6,53 +6,6 @@ - - - - -
    - - - {{ dsoType + '.edit.metadata.authority.label' | translate }} {{ mdValue.newValue.authority }} - -
    -
    -
    - - - - -
    -
    {{ mdRepresentationName$ | async }} From 8c92364d43e464a00585304550b7dc9e8a2d5e11 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 9 Jan 2025 18:46:10 +0100 Subject: [PATCH 743/875] 122357: Ensure the request href$ observable aren't triggered multiple times (cherry picked from commit 3ecdfe422df3b31b20d257f9197d0b2e7d250920) --- src/app/core/data/base/base-data.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/core/data/base/base-data.service.ts b/src/app/core/data/base/base-data.service.ts index 18131dea63..234e7272a6 100644 --- a/src/app/core/data/base/base-data.service.ts +++ b/src/app/core/data/base/base-data.service.ts @@ -6,7 +6,7 @@ * http://www.dspace.org/license/ */ -import { AsyncSubject, from as observableFrom, Observable, of as observableOf } from 'rxjs'; +import { AsyncSubject, from as observableFrom, Observable, of as observableOf, shareReplay } from 'rxjs'; import { map, mergeMap, skipWhile, switchMap, take, tap, toArray } from 'rxjs/operators'; import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util'; import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; @@ -264,6 +264,7 @@ export class BaseDataService implements HALDataServic isNotEmptyOperator(), take(1), map((href: string) => this.buildHrefFromFindOptions(href, {}, [], ...linksToFollow)), + shareReplay(1), ); const startTime: number = new Date().getTime(); @@ -299,6 +300,7 @@ export class BaseDataService implements HALDataServic isNotEmptyOperator(), take(1), map((href: string) => this.buildHrefFromFindOptions(href, options, [], ...linksToFollow)), + shareReplay(1), ); const startTime: number = new Date().getTime(); From bb3d2bb4837a29664defa8830ac138edc63118d6 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 9 Jan 2025 15:18:24 +0100 Subject: [PATCH 744/875] 122357: Reduced the amount of times the browse observables are fired again (cherry picked from commit 0570542b1eb2874617deac27f38c4ecec7bbc1c3) --- .../browse-by-date-page.component.ts | 23 +++++++++------- .../browse-by-metadata-page.component.ts | 26 ++++++++++--------- .../browse-by-title-page.component.ts | 21 ++++++++++----- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index afb58e6ad2..cca56647d8 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -10,7 +10,7 @@ import { BrowseService } from '../../core/browse/browse.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { PaginationService } from '../../core/pagination/pagination.service'; -import { map, take } from 'rxjs/operators'; +import { map, distinctUntilChanged } from 'rxjs/operators'; import { of as observableOf } from 'rxjs'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; @@ -61,16 +61,19 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { this.startsWithType = StartsWithType.date; this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig); + const routeParams$: Observable = observableCombineLatest([ + this.route.params, + this.route.queryParams, + ]).pipe( + map(([params, queryParams]: [Params, Params]) => Object.assign({}, params, queryParams)), + distinctUntilChanged((prev: Params, curr: Params) => prev.id === curr.id && prev.startsWith === curr.startsWith), + ); this.subs.push( - observableCombineLatest( - [ this.route.params.pipe(take(1)), - this.route.queryParams, - this.currentPagination$, - this.currentSort$]).pipe( - map(([routeParams, queryParams, currentPage, currentSort]) => { - return [Object.assign({}, routeParams, queryParams), currentPage, currentSort]; - }) - ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { + observableCombineLatest([ + routeParams$, + this.currentPagination$, + this.currentSort$, + ]).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys; this.browseId = params.id || this.defaultBrowseId; this.startsWith = +params.startsWith || params.startsWith; diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 52cf3b9d7b..5f921667bf 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -15,7 +15,7 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { PaginationService } from '../../core/pagination/pagination.service'; -import { filter, map, mergeMap, take } from 'rxjs/operators'; +import { filter, map, mergeMap, distinctUntilChanged } from 'rxjs/operators'; import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { Bitstream } from '../../core/shared/bitstream.model'; import { Collection } from '../../core/shared/collection.model'; @@ -162,20 +162,22 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { return; } const sortConfig = new SortOptions('default', SortDirection.ASC); - this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig)); this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig); + const routeParams$: Observable = observableCombineLatest([ + this.route.params, + this.route.queryParams, + ]).pipe( + map(([params, queryParams]: [Params, Params]) => Object.assign({}, params, queryParams)), + distinctUntilChanged((prev: Params, curr: Params) => prev.id === curr.id && prev.authority === curr.authority && prev.value === curr.value && prev.startsWith === curr.startsWith), + ); this.subs.push( - observableCombineLatest( - [ this.route.params.pipe(take(1)), - this.route.queryParams, - this.currentPagination$, - this.currentSort$]).pipe( - map(([routeParams, queryParams, currentPage, currentSort]) => { - return [Object.assign({}, routeParams, queryParams),currentPage,currentSort]; - }) - ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { - this.browseId = params.id || this.defaultBrowseId; + observableCombineLatest([ + routeParams$, + this.currentPagination$, + this.currentSort$, + ]).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { + this.browseId = params.id || this.defaultBrowseId; this.authority = params.authority; if (typeof params.value === 'string'){ diff --git a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index 518fdf8c15..10968d265f 100644 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -1,4 +1,4 @@ -import { combineLatest as observableCombineLatest } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { Component, Inject, PLATFORM_ID } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { hasValue } from '../../shared/empty.util'; @@ -10,7 +10,7 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv import { BrowseService } from '../../core/browse/browse.service'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { PaginationService } from '../../core/pagination/pagination.service'; -import { map, take } from 'rxjs/operators'; +import { map, distinctUntilChanged } from 'rxjs/operators'; import { of as observableOf } from 'rxjs'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; @@ -48,12 +48,19 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent { const sortConfig = new SortOptions('dc.title', SortDirection.ASC); this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig); this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig); + const routeParams$: Observable = observableCombineLatest([ + this.route.params, + this.route.queryParams, + ]).pipe( + map(([params, queryParams]: [Params, Params]) => Object.assign({}, params, queryParams)), + distinctUntilChanged((prev: Params, curr: Params) => prev.id === curr.id && prev.startsWith === curr.startsWith), + ); this.subs.push( - observableCombineLatest([this.route.params.pipe(take(1)), this.route.queryParams, this.currentPagination$, this.currentSort$]).pipe( - map(([routeParams, queryParams, currentPage, currentSort]) => { - return [Object.assign({}, routeParams, queryParams),currentPage,currentSort]; - }) - ).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { + observableCombineLatest([ + routeParams$, + this.currentPagination$, + this.currentSort$, + ]).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => { this.startsWith = +params.startsWith || params.startsWith; this.browseId = params.id || this.defaultBrowseId; this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails), undefined, undefined); From 5a5c1f18e3a1e20b892899106139ef97d5a55f30 Mon Sep 17 00:00:00 2001 From: VictorDuranEscire Date: Tue, 28 Jan 2025 17:07:00 -0600 Subject: [PATCH 745/875] Changing metadata in a user profile without specifying a password brings up a success and an error panel (#3818) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Agregar validador para mostrar mensaje de error solo si se escribe la contraseña actual * Agregar validador para mostrar mensaje de error solo si se escribe la contraseña actual * Set constant variable for valid current password in profile page --- src/app/profile-page/profile-page.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/profile-page/profile-page.component.ts b/src/app/profile-page/profile-page.component.ts index f8cb30a447..7fb1a14124 100644 --- a/src/app/profile-page/profile-page.component.ts +++ b/src/app/profile-page/profile-page.component.ts @@ -173,7 +173,8 @@ export class ProfilePageComponent implements OnInit { */ updateSecurity() { const passEntered = isNotEmpty(this.password); - if (this.invalidSecurity) { + const validCurrentPassword = isNotEmpty(this.currentPassword); + if (validCurrentPassword && !passEntered) { this.notificationsService.error(this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.general')); } if (!this.invalidSecurity && passEntered) { From e296295df59099c3778f735334c98038821774d7 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 29 Jan 2025 15:57:55 -0600 Subject: [PATCH 746/875] Fix YAML syntax and revert section name to "universal" as that's the name in 7.x --- config/config.example.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index 6674bc5334..c79c87f4d7 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -17,8 +17,8 @@ ui: # Trust X-FORWARDED-* headers from proxies (default = true) useProxies: true -# Angular Server Side Rendering (SSR) settings -ssr: +# Angular Universal / Server Side Rendering (SSR) settings +universal: # Whether to tell Angular to inline "critical" styles into the server-side rendered HTML. # Determining which styles are critical is a relatively expensive operation; this option is # disabled (false) by default to boost server performance at the expense of loading smoothness. @@ -28,11 +28,11 @@ ssr: # Whether to enable rendering of Search component on SSR. # If set to true the component will be included in the HTML returned from the server side rendering. # If set to false the component will not be included in the HTML returned from the server side rendering. - enableSearchComponent: false, + enableSearchComponent: false # Whether to enable rendering of Browse component on SSR. # If set to true the component will be included in the HTML returned from the server side rendering. # If set to false the component will not be included in the HTML returned from the server side rendering. - enableBrowseComponent: false, + enableBrowseComponent: false # The REST API server settings # NOTE: these settings define which (publicly available) REST API to use. They are usually From 470ad807410fbc733dc84c57346e9955757ab8ed Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 5 Jul 2024 10:34:49 +0200 Subject: [PATCH 747/875] 116404: Fixed expandable navbar section loosing focus on expand through keyboard (cherry picked from commit 2547b1218f36f00e1a62522a47d9c9f59dbc91e9) --- .../expandable-admin-sidebar-section.component.ts | 4 ++-- .../expandable-navbar-section.component.html | 12 +++++------- .../expandable-navbar-section.component.ts | 10 ++++------ .../menu/menu-section/menu-section.component.ts | 8 +++++--- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts index e2995598f8..d25d7c6b40 100644 --- a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts +++ b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts @@ -67,8 +67,8 @@ export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionC this.sidebarActiveBg$ = this.variableService.getVariable('--ds-admin-sidebar-active-bg'); this.isSidebarCollapsed$ = this.menuService.isMenuCollapsed(this.menuID); this.isSidebarPreviewCollapsed$ = this.menuService.isMenuPreviewCollapsed(this.menuID); - this.isExpanded$ = combineLatestObservable([this.active, this.isSidebarCollapsed$, this.isSidebarPreviewCollapsed$]).pipe( - map(([active, sidebarCollapsed, sidebarPreviewCollapsed]) => (active && (!sidebarCollapsed || !sidebarPreviewCollapsed))) + this.isExpanded$ = combineLatestObservable([this.active$, this.isSidebarCollapsed$, this.isSidebarPreviewCollapsed$]).pipe( + map(([active, sidebarCollapsed, sidebarPreviewCollapsed]) => (active && (!sidebarCollapsed || !sidebarPreviewCollapsed))), ); } diff --git a/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.html b/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.html index 7f9b4a546f..5119cec689 100644 --- a/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.html +++ b/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.html @@ -1,9 +1,8 @@
    + (mouseenter)="onMouseEnter($event)" + (mouseleave)="onMouseLeave($event)" + data-test="navbar-section-wrapper"> - -
    diff --git a/src/app/shared/menu/menu-section/menu-section.component.ts b/src/app/shared/menu/menu-section/menu-section.component.ts index 2611935dfa..e0464a769f 100644 --- a/src/app/shared/menu/menu-section/menu-section.component.ts +++ b/src/app/shared/menu/menu-section/menu-section.component.ts @@ -85,10 +85,14 @@ export class MenuSectionComponent implements OnInit, OnDestroy { /** * Deactivate this section + * * @param {Event} event The user event that triggered this method + * @param skipEvent Weather the event should still be triggered after deactivating the section or not */ - deactivateSection(event: Event) { - event.preventDefault(); + deactivateSection(event: Event, skipEvent = true): void { + if (skipEvent) { + event.preventDefault(); + } this.menuService.deactivateSection(this.menuID, this.section.id); } From 53186988f58073054c9b269144aab50d98da9755 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 5 Jul 2024 13:30:01 +0200 Subject: [PATCH 749/875] 116404: Added navigation with arrow keys in navbar & collapsed the expandable menu when hovering outside of it (cherry picked from commit 05232cdf2b068223c8147ed61afe364fc92986d1) --- .../expandable-navbar-section.component.html | 59 ++++++------ .../expandable-navbar-section.component.ts | 92 +++++++++++++++++-- src/app/navbar/navbar.module.ts | 7 ++ .../shared/utils/hover-outside.directive.ts | 50 ++++++++++ 4 files changed, 169 insertions(+), 39 deletions(-) create mode 100644 src/app/shared/utils/hover-outside.directive.ts diff --git a/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.html b/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.html index f37fc222d5..f8facaa82a 100644 --- a/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.html +++ b/src/app/navbar/expandable-navbar-section/expandable-navbar-section.component.html @@ -1,36 +1,37 @@ -
    - +
    + - - -