diff --git a/.eslintrc.json b/.eslintrc.json index 4cc8c6dbc5..5fb4c12171 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -165,6 +165,7 @@ "@angular-eslint/no-output-native": "warn", "@angular-eslint/no-output-on-prefix": "warn", "@angular-eslint/no-conflicting-lifecycle": "warn", + "@angular-eslint/use-lifecycle-interface": "error", "@typescript-eslint/no-inferrable-types":[ "error", diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 8e4ed0811d..0cce2848a7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,16 +7,16 @@ assignees: '' --- -**Describe the bug** +## Describe the bug A clear and concise description of what the bug is. Include the version(s) of DSpace where you've seen this problem & what *web browser* you were using. Link to examples if they are public. -**To Reproduce** +## To Reproduce Steps to reproduce the behavior: 1. Do this 2. Then this... -**Expected behavior** +## Expected behavior A clear and concise description of what you expected to happen. -**Related work** +## Related work Link to any related tickets or PRs here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 34cc2c9e4f..9eaa4d9f3f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,14 +7,14 @@ assignees: '' --- -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] +## Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem or use case is. For example, I'm always frustrated when [...] -**Describe the solution you'd like** +## Describe the solution you'd like A clear and concise description of what you want to happen. -**Describe alternatives or workarounds you've considered** +## Describe alternatives or workarounds you've considered A clear and concise description of any alternative solutions or features you've considered. -**Additional context** -Add any other context or screenshots about the feature request here. +## Additional information +Add any other information, related tickets or screenshots about the feature request here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e50105b879..873c5467fd 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,7 @@ ## References _Add references/links to any related issues or PRs. These may include:_ -* Fixes #`issue-number` (if this fixes an issue ticket) -* Requires DSpace/DSpace#`pr-number` (if a REST API PR is required to test this) +* Fixes #issue-number (if this fixes an issue ticket) +* Requires DSpace/DSpace#pr-number (if a REST API PR is required to test this) ## Description Short summary of changes (1-2 sentences). @@ -16,13 +16,18 @@ List of changes in this PR: **Include guidance for how to test or review your PR.** This may include: steps to reproduce a bug, screenshots or description of a new feature, or reasons behind specific changes. ## Checklist -_This checklist provides a reminder of what we are going to look for when reviewing your PR. You need not complete this checklist prior to creating your PR (draft PRs are always welcome). If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!_ +_This checklist provides a reminder of what we are going to look for when reviewing your PR. You do not need to complete this checklist prior creating your PR (draft PRs are always welcome). +However, reviewers may request that you complete any actions in this list if you have not done so. If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!_ -- [ ] My PR is small in size (e.g. less than 1,000 lines of code, not including comments & specs/tests), or I have provided reasons as to why that's not possible. -- [ ] My PR passes [ESLint](https://eslint.org/) validation using `yarn lint` -- [ ] My PR doesn't introduce circular dependencies (verified via `yarn check-circ-deps`) -- [ ] My PR includes [TypeDoc](https://typedoc.org/) comments for _all new (or modified) public methods and classes_. It also includes TypeDoc for large or complex private methods. -- [ ] My PR passes all specs/tests and includes new/updated specs or tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide). +- [ ] My PR is **created against the `main` branch** of code (unless it is a backport or is fixing an issue specific to an older branch). +- [ ] My PR is **small in size** (e.g. less than 1,000 lines of code, not including comments & specs/tests), or I have provided reasons as to why that's not possible. +- [ ] My PR **passes [ESLint](https://eslint.org/)** validation using `yarn lint` +- [ ] My PR **doesn't introduce circular dependencies** (verified via `yarn check-circ-deps`) +- [ ] My PR **includes [TypeDoc](https://typedoc.org/) comments** for _all new (or modified) public methods and classes_. It also includes TypeDoc for large or complex private methods. +- [ ] My PR **passes all specs/tests and includes new/updated specs or tests** based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide). +- [ ] My PR **aligns with [Accessibility guidelines](https://wiki.lyrasis.org/display/DSDOC8x/Accessibility)** if it makes changes to the user interface. +- [ ] My PR **uses i18n (internationalization) keys** instead of hardcoded English text, to allow for translations. +- [ ] My PR **includes details on how to test it**. I've provided clear instructions to reviewers on how to successfully test this fix or feature. - [ ] If my PR includes new libraries/dependencies (in `package.json`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. - [ ] If my PR includes new features or configurations, I've provided basic technical documentation in the PR itself. - [ ] If my PR fixes an issue ticket, I've [linked them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4e732302f4..6525750e4e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ DSpace is a community built and supported project. We do not have a centralized ## Contribute new code via a Pull Request We accept [GitHub Pull Requests (PRs)](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) at any time from anyone. -Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC7x/Release+Notes). +Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC8x/Release+Notes). Code Contribution Checklist - [ ] PRs _should_ be smaller in size (ideally less than 1,000 lines of code, not including comments & tests) @@ -18,6 +18,9 @@ Code Contribution Checklist - [ ] PRs **must** not introduce circular dependencies (verified via `yarn check-circ-deps`) - [ ] PRs **must** include [TypeDoc](https://typedoc.org/) comments for _all new (or modified) public methods and classes_. Large or complex private methods should also have TypeDoc. - [ ] PRs **must** pass all automated pecs/tests and includes new/updated specs or tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide). +- [ ] User interface changes **must** align with [Accessibility guidelines](https://wiki.lyrasis.org/display/DSDOC8x/Accessibility) +- [ ] PRs **must** use i18n (internationalization) keys instead of hardcoded English text, to allow for translations. +- [ ] Details on how to test the PR **must** be provided. Reviewers must be aware of any steps they need to take to successfully test your fix or feature. - [ ] If a PR includes new libraries/dependencies (in `package.json`), then their software licenses **must** align with the [DSpace BSD License](https://github.com/DSpace/dspace-angular/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation. - [ ] Basic technical documentation _should_ be provided for any new features or configuration, either in the PR itself or in the DSpace Wiki documentation. - [ ] If a PR fixes an issue ticket, please [link them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue). @@ -26,7 +29,7 @@ Additional details on the code contribution process can be found in our [Code Co ## Contribute documentation -DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC7x +DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC If you find areas of the DSpace Documentation which you wish to improve, please request a Wiki account by emailing wikihelp@lyrasis.org. Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) or email) for access to edit our Documentation. @@ -34,7 +37,7 @@ Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyra ## Help others on mailing lists or Slack DSpace has our own [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) community and [Mailing Lists](https://wiki.lyrasis.org/display/DSPACE/Mailing+Lists) where discussions take place and questions are answered. -Anyone is welcome to join and help others. We just ask you to follow our [Code of Conduct](https://www.lyrasis.org/about/Pages/Code-of-Conduct.aspx) (adopted via LYRASIS). +Anyone is welcome to join and help others. We just ask you to follow our [Code of Conduct](https://www.lyrasis.org/about/Pages/Code-of-Conduct.aspx) (adopted via Lyrasis). ## Join a working or interest group @@ -42,5 +45,5 @@ Most of the work in building/improving DSpace comes via [Working Groups](https:/ All working/interest groups are open to anyone to join and participate. A few key groups to be aware of include: -* [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) - This is the main (mostly volunteer) development team. We meet weekly to review our current development [project board](https://github.com/orgs/DSpace/projects), assigning tickets and/or PRs. -* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team) - This is an interest group for repository managers/administrators. We meet monthly to discuss DSpace, share tips & provide feedback back to developers. \ No newline at end of file +* [DSpace Developer Team](https://wiki.lyrasis.org/display/DSPACE/Developer+Meetings) - This is the primary, volunteer development team. We meet weekly to review our current development [project board](https://github.com/orgs/DSpace/projects), assigning tickets and/or PRs. This is also were discussions of the next release or major issues occur. Anyone is welcome to attend. +* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team) - This is an interest group for repository managers/administrators. We meet monthly to discuss DSpace, share tips & provide feedback back to developers. Anyone is welcome to attend. diff --git a/Dockerfile.dist b/Dockerfile.dist index e4b467ae26..4c47b0cb40 100644 --- a/Dockerfile.dist +++ b/Dockerfile.dist @@ -4,7 +4,7 @@ # Test build: # docker build -f Dockerfile.dist -t dspace/dspace-angular:latest-dist . -FROM node:18-alpine as build +FROM 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 diff --git a/docker/README.md b/docker/README.md index 64d8ddc2c5..3dc5fd5055 100644 --- a/docker/README.md +++ b/docker/README.md @@ -59,19 +59,19 @@ A default/demo version of this image is built *automatically*. ## To refresh / pull DSpace images from Dockerhub ``` -docker-compose -f docker/docker-compose.yml pull +docker compose -f docker/docker-compose.yml pull ``` ## To build DSpace images using code in your branch ``` -docker-compose -f docker/docker-compose.yml build +docker compose -f docker/docker-compose.yml build ``` ## To start DSpace (REST and Angular) from your branch This command provides a quick way to start both the frontend & backend from this single codebase ``` -docker-compose -p d8 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d +docker compose -p d8 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d ``` Keep in mind, you may also start the backend by cloning the 'DSpace/DSpace' GitHub repository separately. See the next section. @@ -86,14 +86,14 @@ _The system will be started in 2 steps. Each step shares the same docker network From 'DSpace/DSpace' clone (build first as needed): ``` -docker-compose -p d8 up -d +docker compose -p d8 up -d ``` NOTE: More detailed instructions on starting the backend via Docker can be found in the [Docker Compose instructions for the Backend](https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/README.md). From 'DSpace/dspace-angular' clone (build first as needed) ``` -docker-compose -p d8 -f docker/docker-compose.yml up -d +docker compose -p d8 -f docker/docker-compose.yml up -d ``` At this point, you should be able to access the UI from http://localhost:4000, @@ -105,21 +105,21 @@ This allows you to run the Angular UI in *production* mode, pointing it at the d (https://demo.dspace.org/server/ or https://sandbox.dspace.org/server/). ``` -docker-compose -f docker/docker-compose-dist.yml pull -docker-compose -f docker/docker-compose-dist.yml build -docker-compose -p d8 -f docker/docker-compose-dist.yml up -d +docker compose -f docker/docker-compose-dist.yml pull +docker compose -f docker/docker-compose-dist.yml build +docker compose -p d8 -f docker/docker-compose-dist.yml up -d ``` ## Ingest test data from AIPDIR Create an administrator ``` -docker-compose -p d8 -f docker/cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en +docker compose -p d8 -f docker/cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en ``` Load content from AIP files ``` -docker-compose -p d8 -f docker/cli.yml -f ./docker/cli.ingest.yml run --rm dspace-cli +docker compose -p d8 -f docker/cli.yml -f ./docker/cli.ingest.yml run --rm dspace-cli ``` ## Alternative Ingest - Use Entities dataset @@ -127,12 +127,12 @@ _Delete your docker volumes or use a unique project (-p) name_ Start DSpace with Database Content from a database dump ``` -docker-compose -p d8 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml -f docker/db.entities.yml up -d +docker compose -p d8 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml -f docker/db.entities.yml up -d ``` Load assetstore content and trigger a re-index of the repository ``` -docker-compose -p d8 -f docker/cli.yml -f docker/cli.assetstore.yml run --rm dspace-cli +docker compose -p d8 -f docker/cli.yml -f docker/cli.assetstore.yml run --rm dspace-cli ``` ## End to end testing of the REST API (runs in GitHub Actions CI). @@ -140,5 +140,5 @@ _In this instance, only the REST api runs in Docker using the Entities dataset. This command is only really useful for testing our Continuous Integration process. ``` -docker-compose -p d8ci -f docker/docker-compose-ci.yml up -d +docker compose -p d8ci -f docker/docker-compose-ci.yml up -d ``` diff --git a/docker/db.entities.yml b/docker/db.entities.yml index ae1ac77346..b3cf5bd86f 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::${DSPACE_VER:-latest}-loadsql + image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}-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 @@ -34,4 +34,4 @@ services: - | while (! /dev/null 2>&1; do sleep 1; done; /dspace/bin/dspace database migrate ignored - java -jar /dspace/webapps/server-boot.jar --dspace.dir=/dspace \ No newline at end of file + java -jar /dspace/webapps/server-boot.jar --dspace.dir=/dspace diff --git a/docker/docker-compose-rest.yml b/docker/docker-compose-rest.yml index c04948e4b2..09dfcf2a5f 100644 --- a/docker/docker-compose-rest.yml +++ b/docker/docker-compose-rest.yml @@ -101,7 +101,7 @@ services: # * First, run precreate-core to create the core (if it doesn't yet exist). If exists already, this is a no-op # * Second, copy configsets to this core: # Updates to Solr configs require the container to be rebuilt/restarted: - # `docker-compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build dspacesolr` + # `docker compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build dspacesolr` entrypoint: - /bin/bash - '-c' diff --git a/package.json b/package.json index ada0525149..a8d505eff9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dspace-angular", - "version": "8.1.0-next", + "version": "9.0.0-next", "scripts": { "ng": "ng", "config:watch": "nodemon", 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 a148559bd2..be989d49e2 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 @@ -4,7 +4,10 @@ import { NgForOf, NgIf, } from '@angular/common'; -import { Component } from '@angular/core'; +import { + Component, + OnDestroy, +} from '@angular/core'; import { Router, RouterLink, @@ -60,7 +63,7 @@ import { MetadataSchemaFormComponent } from './metadata-schema-form/metadata-sch * 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 { /** * A list of all the current metadata schemas within the repository diff --git a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts index b4bebfc924..e1f54bd8d3 100644 --- a/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts +++ b/src/app/admin/admin-reports/filtered-collections/filtered-collections.component.ts @@ -4,6 +4,7 @@ import { } from '@angular/common'; import { Component, + OnInit, ViewChild, } from '@angular/core'; import { @@ -40,7 +41,7 @@ import { FilteredCollections } from './filtered-collections.model'; ], standalone: true, }) -export class FilteredCollectionsComponent { +export class FilteredCollectionsComponent implements OnInit { queryForm: FormGroup; results: FilteredCollections = new FilteredCollections(); diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts b/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts index 9fd4643573..4cb93aa9be 100644 --- a/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts +++ b/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts @@ -5,6 +5,7 @@ import { } from '@angular/common'; import { Component, + OnInit, ViewChild, } from '@angular/core'; import { @@ -66,7 +67,7 @@ import { QueryPredicate } from './query-predicate.model'; ], standalone: true, }) -export class FilteredItemsComponent { +export class FilteredItemsComponent implements OnInit { collections: OptionVO[]; presetQueries: PresetQuery[]; @@ -90,7 +91,7 @@ export class FilteredItemsComponent { private formBuilder: FormBuilder, private restService: DspaceRestService) {} - ngOnInit() { + ngOnInit(): void { this.loadCollections(); this.loadPresetQueries(); this.loadMetadataFields(); diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts index 172226dd07..38352e7816 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component.ts @@ -1,4 +1,7 @@ -import { Component } from '@angular/core'; +import { + Component, + OnInit, +} from '@angular/core'; import { RouterLink } from '@angular/router'; import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths'; @@ -21,10 +24,10 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g /** * The component for displaying a list element for a collection search result on the admin search page */ -export class CollectionAdminSearchResultGridElementComponent extends SearchResultGridElementComponent { +export class CollectionAdminSearchResultGridElementComponent extends SearchResultGridElementComponent implements OnInit { editPath: string; - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); this.editPath = getCollectionEditRoute(this.dso.uuid); } diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts index 50be35229d..509c1e9266 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component.ts @@ -1,4 +1,7 @@ -import { Component } from '@angular/core'; +import { + Component, + OnInit, +} from '@angular/core'; import { RouterLink } from '@angular/router'; import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths'; @@ -21,10 +24,10 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g /** * The component for displaying a list element for a community search result on the admin search page */ -export class CommunityAdminSearchResultGridElementComponent extends SearchResultGridElementComponent { +export class CommunityAdminSearchResultGridElementComponent extends SearchResultGridElementComponent implements OnInit { editPath: string; - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); this.editPath = getCommunityEditRoute(this.dso.uuid); } diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts index 37afbf14fe..0924379ea5 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component.ts @@ -1,4 +1,7 @@ -import { Component } from '@angular/core'; +import { + Component, + OnInit, +} from '@angular/core'; import { RouterLink } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; @@ -22,10 +25,10 @@ import { SearchResultListElementComponent } from '../../../../../shared/object-l /** * The component for displaying a list element for a collection search result on the admin search page */ -export class CollectionAdminSearchResultListElementComponent extends SearchResultListElementComponent { +export class CollectionAdminSearchResultListElementComponent extends SearchResultListElementComponent implements OnInit { editPath: string; - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); this.editPath = getCollectionEditRoute(this.dso.uuid); } diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts index 5861f15c1f..c4146dbd60 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component.ts @@ -1,4 +1,7 @@ -import { Component } from '@angular/core'; +import { + Component, + OnInit, +} from '@angular/core'; import { RouterLink } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; @@ -22,10 +25,10 @@ import { SearchResultListElementComponent } from '../../../../../shared/object-l /** * The component for displaying a list element for a community search result on the admin search page */ -export class CommunityAdminSearchResultListElementComponent extends SearchResultListElementComponent { +export class CommunityAdminSearchResultListElementComponent extends SearchResultListElementComponent implements OnInit { editPath: string; - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); this.editPath = getCommunityEditRoute(this.dso.uuid); } diff --git a/src/app/collection-page/edit-collection-page/collection-curate/collection-curate.component.ts b/src/app/collection-page/edit-collection-page/collection-curate/collection-curate.component.ts index 14ba01421b..370506e473 100644 --- a/src/app/collection-page/edit-collection-page/collection-curate/collection-curate.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-curate/collection-curate.component.ts @@ -1,5 +1,8 @@ import { AsyncPipe } from '@angular/common'; -import { Component } from '@angular/core'; +import { + Component, + OnInit, +} from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { Observable } from 'rxjs'; @@ -28,7 +31,7 @@ import { hasValue } from '../../../shared/empty.util'; ], standalone: true, }) -export class CollectionCurateComponent { +export class CollectionCurateComponent implements OnInit { dsoRD$: Observable>; collectionName$: Observable; diff --git a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts index 3b51a8e9d9..fdd65a43c7 100644 --- a/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts @@ -95,7 +95,7 @@ export class CollectionSourceControlsComponent implements OnInit, OnDestroy { ) { } - ngOnInit() { + ngOnInit(): void { // ensure the contentSource gets updated after being set to stale this.contentSource$ = this.collectionService.findByHref(this.collection._links.self.href, false).pipe( getAllSucceededRemoteDataPayload(), diff --git a/src/app/core/forward-client-ip/forward-client-ip.interceptor.ts b/src/app/core/forward-client-ip/forward-client-ip.interceptor.ts index 037f690057..7fe4263d46 100644 --- a/src/app/core/forward-client-ip/forward-client-ip.interceptor.ts +++ b/src/app/core/forward-client-ip/forward-client-ip.interceptor.ts @@ -27,6 +27,14 @@ export class ForwardClientIpInterceptor implements HttpInterceptor { */ intercept(httpRequest: HttpRequest, next: HttpHandler): Observable> { const clientIp = this.req.get('x-forwarded-for') || this.req.connection.remoteAddress; - return next.handle(httpRequest.clone({ setHeaders: { 'X-Forwarded-For': clientIp } })); + const headers = { 'X-Forwarded-For': clientIp }; + + // if the request has a user-agent retain it + const userAgent = this.req.get('user-agent'); + if (userAgent) { + headers['User-Agent'] = userAgent; + } + + return next.handle(httpRequest.clone({ setHeaders: headers })); } } diff --git a/src/app/core/shared/client-math.service.ts b/src/app/core/shared/client-math.service.ts index 2fde521ba9..0630ec3246 100644 --- a/src/app/core/shared/client-math.service.ts +++ b/src/app/core/shared/client-math.service.ts @@ -31,7 +31,7 @@ export class ClientMathService extends MathService { protected mathJaxOptions = { tex: { - inlineMath: [['$', '$'], ['\\(', '\\)']], + inlineMath: [['$', '$'], ['$$', '$$'], ['\\(', '\\)']], }, svg: { fontCache: 'global', @@ -108,7 +108,7 @@ export class ClientMathService extends MathService { */ render(element: HTMLElement) { if (environment.markdown.mathjax) { - this._window.nativeWindow.MathJax.typesetPromise([element]); + return (window as any).MathJax.typesetPromise([element]) as Promise; } } } diff --git a/src/app/core/shared/math.service.spec.ts b/src/app/core/shared/math.service.spec.ts index cc0b2814f2..75d8bf1024 100644 --- a/src/app/core/shared/math.service.spec.ts +++ b/src/app/core/shared/math.service.spec.ts @@ -22,8 +22,8 @@ export class MockMathService extends MathService { return of(true); } - render(element: HTMLElement): void { - return; + render(element: HTMLElement): Promise { + return Promise.resolve(); } } diff --git a/src/app/core/shared/math.service.ts b/src/app/core/shared/math.service.ts index c06ce06220..909e039564 100644 --- a/src/app/core/shared/math.service.ts +++ b/src/app/core/shared/math.service.ts @@ -15,5 +15,5 @@ export abstract class MathService { protected abstract registerMathJaxAsync(config: MathJaxConfig): Promise; abstract ready(): Observable; - abstract render(element: HTMLElement): void; + abstract render(element: HTMLElement): Promise; } diff --git a/src/app/core/shared/server-math.service.ts b/src/app/core/shared/server-math.service.ts index 75fa775fee..62070dbafc 100644 --- a/src/app/core/shared/server-math.service.ts +++ b/src/app/core/shared/server-math.service.ts @@ -47,6 +47,6 @@ export class ServerMathService extends MathService { } render(element: HTMLElement) { - return; + return Promise.resolve(); } } diff --git a/src/app/core/submission/submission-duplicate-data.service.ts b/src/app/core/submission/submission-duplicate-data.service.ts index 755fec05c2..b327342469 100644 --- a/src/app/core/submission/submission-duplicate-data.service.ts +++ b/src/app/core/submission/submission-duplicate-data.service.ts @@ -1,5 +1,8 @@ /* eslint-disable max-classes-per-file */ -import { Injectable } from '@angular/core'; +import { + Injectable, + OnDestroy, +} from '@angular/core'; import { Observable } from 'rxjs'; import { Duplicate } from '../../shared/object-list/duplicate-data/duplicate.model'; @@ -33,7 +36,8 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; * */ @Injectable({ providedIn: 'root' }) -export class SubmissionDuplicateDataService extends BaseDataService implements SearchData { +export class SubmissionDuplicateDataService extends BaseDataService + implements SearchData, OnDestroy { /** * The ResponseParsingService constructor name diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html index d6c72abdb9..f8b193f4a0 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html @@ -1,21 +1,25 @@ 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 1ddac22dd3..ac524739d4 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 @@ -91,6 +91,11 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple */ range: [number | undefined, number | undefined]; + /** + * The range currently selected by the slider + */ + sliderRange: [number | undefined, number | undefined]; + /** * Whether the sider is being controlled by the keyboard. * Supresses any changes until the key is released. @@ -145,6 +150,15 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple }; } + /** + * Updates the sliderRange property with the current slider range. + * This method is called whenever the slider value changes, but it does not immediately apply the changes. + * @param range - The current range selected by the slider + */ + onSliderChange(range: [number | undefined, number | undefined]): void { + this.sliderRange = range; + } + /** * Submits new custom range values to the range filter from the widget */ @@ -182,5 +196,4 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple shouldShowSlider(): boolean { return isPlatformBrowser(this.platformId); } - } diff --git a/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.html b/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.html index babfa91f0b..38c2af97fc 100644 --- a/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.html +++ b/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.html @@ -19,9 +19,9 @@ {{ ((fileObject === null || fileObject === undefined) ? dropMessageLabel : dropMessageLabelReplacement) | translate}} {{'uploader.or' | translate}}

diff --git a/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.ts b/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.ts index 8c04bd264b..e937b1ea0f 100644 --- a/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.ts +++ b/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.component.ts @@ -54,7 +54,7 @@ export class FileDropzoneNoUploaderComponent implements OnInit { /** * The function to call when file is added */ - @Output() onFileAdded: EventEmitter = new EventEmitter(); + @Output() onFileAdded: EventEmitter = new EventEmitter(); /** * The uploader configuration options @@ -83,15 +83,17 @@ export class FileDropzoneNoUploaderComponent implements OnInit { } @HostListener('window:drop', ['$event']) - onDrop(event: any) { + onDrop(event: DragEvent) { event.preventDefault(); + event.stopPropagation(); } @HostListener('window:dragover', ['$event']) - onDragOver(event: any) { + onDragOver(event: DragEvent) { // Show drop area on the page event.preventDefault(); - if ((event.target as any).tagName !== 'HTML') { + event.stopPropagation(); + if ((event.target as HTMLElement).tagName !== 'HTML') { this.isOverDocumentDropZone = observableOf(true); } } @@ -105,11 +107,18 @@ export class FileDropzoneNoUploaderComponent implements OnInit { } } + public handleFileInput(event: Event) { + const input = event.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + this.setFile(input.files); + } + } + /** * Set file * @param files */ - setFile(files) { + public setFile(files: FileList) { this.fileObject = files.length > 0 ? files[0] : undefined; this.onFileAdded.emit(this.fileObject); } diff --git a/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.scss b/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.scss index 5d06cd5718..91eb2ae868 100644 --- a/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.scss +++ b/src/app/shared/upload/file-dropzone-no-uploader/file-dropzone-no-uploader.scss @@ -6,10 +6,16 @@ top: 0; left: 0; z-index: -1; + display: block; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease, visibility 0.3s ease; } .ds-document-drop-zone-active { z-index: var(--ds-drop-zone-area-z-index) !important; + opacity: 1; + visibility: visible; } .ds-document-drop-zone-inner { diff --git a/src/app/shared/upload/uploader/uploader.component.ts b/src/app/shared/upload/uploader/uploader.component.ts index f4b2c35314..fbab09be3e 100644 --- a/src/app/shared/upload/uploader/uploader.component.ts +++ b/src/app/shared/upload/uploader/uploader.component.ts @@ -1,12 +1,14 @@ import { CommonModule } from '@angular/common'; import { HttpXsrfTokenExtractor } from '@angular/common/http'; import { + AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostListener, Input, + OnInit, Output, ViewEncapsulation, } from '@angular/core'; @@ -42,7 +44,7 @@ import { UploaderProperties } from './uploader-properties.model'; standalone: true, imports: [TranslateModule, FileUploadModule, CommonModule], }) -export class UploaderComponent { +export class UploaderComponent implements OnInit, AfterViewInit { /** * The message to show when drag files on the drop zone @@ -122,7 +124,7 @@ export class UploaderComponent { /** * Method provided by Angular. Invoked after the constructor. */ - ngOnInit() { + ngOnInit(): void { this.uploaderId = 'ds-drag-and-drop-uploader' + uniqueId(); this.checkConfig(this.uploadFilesOptions); this.uploader = new FileUploader({ @@ -147,7 +149,7 @@ export class UploaderComponent { } } - ngAfterViewInit() { + ngAfterViewInit(): void { this.uploader.onAfterAddingAll = ((items) => { this.onFileSelected.emit(items); }); diff --git a/src/app/shared/utils/markdown.directive.ts b/src/app/shared/utils/markdown.directive.ts index a38b079ffd..0540e25cc5 100644 --- a/src/app/shared/utils/markdown.directive.ts +++ b/src/app/shared/utils/markdown.directive.ts @@ -55,30 +55,40 @@ export class MarkdownDirective implements OnInit, OnDestroy { async render(value: string, forcePreview = false): Promise { if (isEmpty(value) || (!environment.markdown.enabled && !forcePreview)) { - return value; + this.el.innerHTML = value; + return; + } else { + if (environment.markdown.mathjax) { + this.renderMathjaxThenMarkdown(value); + } else { + this.renderMarkdown(value); + } } + } + + private renderMathjaxThenMarkdown(value: string) { + const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, value); + this.el.innerHTML = sanitized; + this.mathService.ready().pipe( + filter((ready) => ready), + take(1), + takeUntil(this.alive$), + ).subscribe(() => { + this.mathService.render(this.el)?.then(_ => { + this.renderMarkdown(this.el.innerHTML, true); + }); + }); + } + + private async renderMarkdown(value: string, alreadySanitized = false) { const MarkdownIt = await this.markdownIt; const md = new MarkdownIt({ html: true, linkify: true, }); - const html = this.sanitizer.sanitize(SecurityContext.HTML, md.render(value)); + const html = alreadySanitized ? md.render(value) : this.sanitizer.sanitize(SecurityContext.HTML, md.render(value)); this.el.innerHTML = html; - - if (environment.markdown.mathjax) { - this.renderMathjax(); - } - } - - private renderMathjax() { - this.mathService.ready().pipe( - filter((ready) => ready), - take(1), - takeUntil(this.alive$), - ).subscribe(() => { - this.mathService.render(this.el); - }); } ngOnDestroy() { diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index dcbcfd9cb3..2aec1c9e30 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -5,6 +5,7 @@ import { EventEmitter, Input, OnChanges, + OnDestroy, OnInit, Output, SimpleChanges, @@ -58,7 +59,7 @@ import { SubmissionService } from '../../submission.service'; ThemedCollectionDropdownComponent, ], }) -export class SubmissionFormCollectionComponent implements OnChanges, OnInit { +export class SubmissionFormCollectionComponent implements OnDestroy, OnChanges, OnInit { /** * The current collection id this submission belonging to diff --git a/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts b/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts index e694a868ca..3632ec6760 100644 --- a/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts +++ b/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts @@ -3,6 +3,7 @@ import { Component, Input, OnChanges, + OnDestroy, } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { @@ -43,7 +44,7 @@ import parseSectionErrors from '../../utils/parseSectionErrors'; ], standalone: true, }) -export class SubmissionUploadFilesComponent implements OnChanges { +export class SubmissionUploadFilesComponent implements OnChanges, OnDestroy { /** * The collection id this submission belonging to diff --git a/src/app/submission/sections/duplicates/section-duplicates.component.ts b/src/app/submission/sections/duplicates/section-duplicates.component.ts index d90a0ca85a..885511ca52 100644 --- a/src/app/submission/sections/duplicates/section-duplicates.component.ts +++ b/src/app/submission/sections/duplicates/section-duplicates.component.ts @@ -7,6 +7,7 @@ import { ChangeDetectionStrategy, Component, Inject, + OnInit, } from '@angular/core'; import { TranslateModule, @@ -48,7 +49,7 @@ import { SectionsService } from '../sections.service'; standalone: true, }) -export class SubmissionSectionDuplicatesComponent extends SectionModelComponent { +export class SubmissionSectionDuplicatesComponent extends SectionModelComponent implements OnInit { protected readonly Metadata = Metadata; /** * The Alert categories. diff --git a/src/app/submission/sections/identifiers/section-identifiers.component.ts b/src/app/submission/sections/identifiers/section-identifiers.component.ts index 1483ea47b7..e297c795df 100644 --- a/src/app/submission/sections/identifiers/section-identifiers.component.ts +++ b/src/app/submission/sections/identifiers/section-identifiers.component.ts @@ -7,6 +7,7 @@ import { ChangeDetectionStrategy, Component, Inject, + OnInit, } from '@angular/core'; import { TranslateModule, @@ -47,7 +48,7 @@ import { SectionsService } from '../sections.service'; standalone: true, }) -export class SubmissionSectionIdentifiersComponent extends SectionModelComponent { +export class SubmissionSectionIdentifiersComponent extends SectionModelComponent implements OnInit { /** * The Alert categories. * @type {AlertType} @@ -76,7 +77,6 @@ export class SubmissionSectionIdentifiersComponent extends SectionModelComponent /** * Initialize instance variables. * - * @param {PaginationService} paginationService * @param {TranslateService} translate * @param {SectionsService} sectionService * @param {SubmissionService} submissionService @@ -93,7 +93,7 @@ export class SubmissionSectionIdentifiersComponent extends SectionModelComponent super(injectedCollectionId, injectedSectionData, injectedSubmissionId); } - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); } diff --git a/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routes.ts b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routes.ts index 8f07c24edc..35cf2521c7 100644 --- a/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routes.ts +++ b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routes.ts @@ -50,16 +50,6 @@ export const ROUTES: Route[] = [ }, data: { title: 'workspace-item.delete', breadcrumbKey: 'workspace-item.delete' }, }, - { - canActivate: [authenticatedGuard], - path: 'delete', - component: ThemedWorkspaceItemsDeletePageComponent, - resolve: { - dso: itemFromWorkspaceResolver, - breadcrumb: i18nBreadcrumbResolver, - }, - data: { title: 'workspace-item.delete', breadcrumbKey: 'workspace-item.delete' }, - }, ], }, ]; diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 7f2485ab29..285bbfa43d 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -5881,7 +5881,7 @@ "workflow-item.delete.notification.success.title": "Gelöscht", // "workflow-item.delete.notification.success.content": "This workflow item was successfully deleted", - "workflow-item.delete.notification.success.content": "Dieses Workflow-Item wurde gelöscht.", + "workflow-item.delete.notification.success.content": "Dieses Workflow-Item wurde erfolgreich gelöscht.", // "workflow-item.delete.notification.error.title": "Something went wrong", "workflow-item.delete.notification.error.title": "Das hat leider nicht funktioniert.", @@ -5926,4 +5926,36 @@ // "workflow-item.send-back.button.confirm": "Send back" "workflow-item.send-back.button.confirm": "Zurücksenden", + // "workflow-item.view.breadcrumbs": "Workflow View", + "workflow-item.view.breadcrumbs": "Workflow-Ansicht", + + // "workspace-item.view.breadcrumbs": "Workspace View", + "workspace-item.view.breadcrumbs": "Workspace-Ansicht", + + // "workspace-item.view.title": "Workspace View", + "workspace-item.view.title": "Workspace-Ansicht", + + // "workspace-item.delete.breadcrumbs": "Workspace Delete", + "workspace-item.delete.breadcrumbs": "Workspace-Item löschen", + + // "workspace-item.delete.header": "Delete workspace item", + "workspace-item.delete.header": "Workspace-Item löschen", + + // "workspace-item.delete.button.confirm": "Delete", + "workspace-item.delete.button.confirm": "Löschen", + + // "workspace-item.delete.button.cancel": "Cancel", + "workspace-item.delete.button.cancel": "Abbrechen", + + // "workspace-item.delete.notification.success.title": "Deleted", + "workspace-item.delete.notification.success.title": "Gelöscht", + + // "workspace-item.delete.title": "This workspace item was successfully deleted", + "workspace-item.delete.title": "Dieses Workspace-Item wurde erfolgreich gelöscht.", + + // "workspace-item.delete.notification.error.title": "Something went wrong", + "workspace-item.delete.notification.error.title": "Das hat leider nicht funktioniert.", + + // "workspace-item.delete.notification.error.content": "The workspace item could not be deleted", + "workspace-item.delete.notification.error.content": "Das Workspace-Item konnte nicht gelöscht werden.", } diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 8dc8c95a39..399363f017 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -6386,10 +6386,10 @@ "submission.workflow.tasks.pool.show-detail": "Afficher le détail", // "submission.workspace.generic.view": "View", - "submission.workspace.generic.view": "Voir", + "submission.workspace.generic.view": "Afficher", // "submission.workspace.generic.view-help": "Select this option to view the item's metadata.", - "submission.workspace.generic.view-help": "Sélectionner cette option pour voir les métadonnées de l'item.", + "submission.workspace.generic.view-help": "Sélectionner cette option pour afficher les métadonnées de l'item.", // "thumbnail.default.alt": "Thumbnail Image", "thumbnail.default.alt": "Vignette d'image", diff --git a/src/themes/dspace/app/home-page/home-news/home-news.component.html b/src/themes/dspace/app/home-page/home-news/home-news.component.html index ca686a9933..ca053fc662 100644 --- a/src/themes/dspace/app/home-page/home-news/home-news.component.html +++ b/src/themes/dspace/app/home-page/home-news/home-news.component.html @@ -3,7 +3,7 @@
-

DSpace 8

+

DSpace 9

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