diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index be15b0a507..e50105b879 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] -* 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). @@ -19,8 +19,10 @@ List of changes in this PR: _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!_ - [ ] 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 [TSLint](https://palantir.github.io/tslint/) validation using `yarn run lint` -- [ ] My PR doesn't introduce circular dependencies +- [ ] 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). -- [ ] If my PR includes new, third-party 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 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/.github/workflows/build.yml b/.github/workflows/build.yml index c58e09edf2..f3b7aff689 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,17 +15,19 @@ jobs: env: # The ci step will test the dspace-angular code against DSpace REST. # Direct that step to utilize a DSpace REST service that has been started in docker. - DSPACE_REST_HOST: localhost + DSPACE_REST_HOST: 127.0.0.1 DSPACE_REST_PORT: 8080 DSPACE_REST_NAMESPACE: '/server' DSPACE_REST_SSL: false + # Spin up UI on 127.0.0.1 to avoid host resolution issues in e2e tests with Node 18+ + DSPACE_UI_HOST: 127.0.0.1 # When Chrome version is specified, we pin to a specific version of Chrome # Comment this out to use the latest release #CHROME_VERSION: "90.0.4430.212-1" strategy: # Create a matrix of Node versions to test against (in parallel) matrix: - node-version: [14.x, 16.x] + node-version: [16.x, 18.x] # Do NOT exit immediately if one matrix job fails fail-fast: false # These are the actual CI steps to perform per job @@ -112,7 +114,7 @@ jobs: start: yarn run serve:ssr # Wait for backend & frontend to be available # NOTE: We use the 'sites' REST endpoint to also ensure the database is ready - wait-on: http://localhost:8080/server/api/core/sites, http://localhost:4000 + wait-on: http://127.0.0.1:8080/server/api/core/sites, http://127.0.0.1:4000 # Wait for 2 mins max for everything to respond wait-on-timeout: 120 @@ -147,7 +149,7 @@ jobs: run: | nohup yarn run serve:ssr & printf 'Waiting for app to start' - until curl --output /dev/null --silent --head --fail http://localhost:4000/home; do + until curl --output /dev/null --silent --head --fail http://127.0.0.1:4000/home; do printf '.' sleep 2 done @@ -158,7 +160,7 @@ jobs: # This step also prints entire HTML of homepage for easier debugging if grep fails. - name: Verify SSR (server-side rendering) run: | - result=$(wget -O- -q http://localhost:4000/home) + result=$(wget -O- -q http://127.0.0.1:4000/home) echo "$result" echo "$result" | grep -oE "]*>" | grep DSpace diff --git a/.github/workflows/codescan.yml b/.github/workflows/codescan.yml new file mode 100644 index 0000000000..35a2e2d24a --- /dev/null +++ b/.github/workflows/codescan.yml @@ -0,0 +1,49 @@ +# DSpace CodeQL code scanning configuration for GitHub +# https://docs.github.com/en/code-security/code-scanning +# +# NOTE: Code scanning must be run separate from our default build.yml +# 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. +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + # Don't run if PR is only updating static documentation + paths-ignore: + - '**/*.md' + - '**/*.txt' + schedule: + - cron: "37 0 * * 1" + +jobs: + analyze: + name: Analyze Code + runs-on: ubuntu-latest + # Limit permissions of this GitHub action. Can only write to security-events + permissions: + actions: read + contents: read + security-events: write + + steps: + # https://github.com/actions/checkout + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + # https://github.com/github/codeql-action + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: javascript + + # Autobuild attempts to build any compiled languages + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # Perform GitHub Code Scanning. + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..4e732302f4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,46 @@ +# How to Contribute + +DSpace is a community built and supported project. We do not have a centralized development or support team, but have a dedicated group of volunteers who help us improve the software, documentation, resources, etc. + +* [Contribute new code via a Pull Request](#contribute-new-code-via-a-pull-request) +* [Contribute documentation](#contribute-documentation) +* [Help others on mailing lists or Slack](#help-others-on-mailing-lists-or-slack) +* [Join a working or interest group](#join-a-working-or-interest-group) + +## 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). + +Code Contribution Checklist +- [ ] PRs _should_ be smaller in size (ideally less than 1,000 lines of code, not including comments & tests) +- [ ] PRs **must** pass [ESLint](https://eslint.org/) validation using `yarn lint` +- [ ] 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). +- [ ] 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). + +Additional details on the code contribution process can be found in our [Code Contribution Guidelines](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines) + +## Contribute documentation + +DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC7x + +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. + +## 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). + +## Join a working or interest group + +Most of the work in building/improving DSpace comes via [Working Groups](https://wiki.lyrasis.org/display/DSPACE/DSpace+Working+Groups) or [Interest Groups](https://wiki.lyrasis.org/display/DSPACE/DSpace+Interest+Groups). + +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 diff --git a/Dockerfile b/Dockerfile index a7c1640d0b..61d960e7d3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,15 @@ # This image will be published as dspace/dspace-angular # See https://github.com/DSpace/dspace-angular/tree/main/docker for usage details -FROM node:14-alpine +FROM node:18-alpine WORKDIR /app ADD . /app/ EXPOSE 4000 +# Ensure Python and other build tools are available +# These are needed to install some node modules, especially on linux/arm64 +RUN apk add --update python3 make g++ && rm -rf /var/cache/apk/* + # We run yarn install with an increased network timeout (5min) to avoid "ESOCKETTIMEDOUT" errors from hub.docker.com # See, for example https://github.com/yarnpkg/yarn/issues/5540 RUN yarn install --network-timeout 300000 diff --git a/README.md b/README.md index 0ede4d4d19..c90dc1d08f 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ https://wiki.lyrasis.org/display/DSDOC7x/Installing+DSpace Quick start ----------- -**Ensure you're running [Node](https://nodejs.org) `v14.x` or `v16.x`, [npm](https://www.npmjs.com/) >= `v5.x` and [yarn](https://yarnpkg.com) == `v1.x`** +**Ensure you're running [Node](https://nodejs.org) `v16.x` or `v18.x`, [npm](https://www.npmjs.com/) >= `v5.x` and [yarn](https://yarnpkg.com) == `v1.x`** ```bash # clone the repo @@ -90,7 +90,7 @@ Requirements ------------ - [Node.js](https://nodejs.org) and [yarn](https://yarnpkg.com) -- Ensure you're running node `v14.x` or `v16.x` and yarn == `v1.x` +- Ensure you're running node `v16.x` or `v18.x` and yarn == `v1.x` If you have [`nvm`](https://github.com/creationix/nvm#install-script) or [`nvm-windows`](https://github.com/coreybutler/nvm-windows) installed, which is highly recommended, you can run `nvm install --lts && nvm use` to install and start using the latest Node LTS. @@ -379,10 +379,10 @@ To get the most out of TypeScript, you'll need a TypeScript-aware editor. We've - [Sublime Text](http://www.sublimetext.com/3) - [Typescript-Sublime-Plugin](https://github.com/Microsoft/Typescript-Sublime-plugin#installation) -Collaborating +Contributing ------------- -See [the guide on the wiki](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+-+Angular+UI+Development#DSpace7-AngularUIDevelopment-Howtocontribute) +See [Contributing documentation](CONTRIBUTING.md) File Structure -------------- diff --git a/angular.json b/angular.json index 2ece0c5e7d..b32670ad77 100644 --- a/angular.json +++ b/angular.json @@ -25,12 +25,10 @@ } }, "allowedCommonJsDependencies": [ - "angular2-text-mask", "cerialize", "core-js", "lodash", "jwt-decode", - "url-parse", "uuid", "webfontloader", "zone.js" diff --git a/config/config.example.yml b/config/config.example.yml index 27400f0041..9abf167b90 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -55,6 +55,8 @@ auth: # Form settings form: + # Sets the spellcheck textarea attribute value + spellCheck: true # NOTE: Map server-side validators to comparative Angular form validators validatorMap: required: required @@ -143,6 +145,9 @@ languages: - code: nl label: Nederlands active: true + - code: pl + label: Polski + active: true - code: pt-PT label: Português active: true @@ -170,6 +175,10 @@ languages: - code: el label: Ελληνικά active: true + - code: uk + label: Yкраї́нська + active: true + # Browse-By Pages browseBy: @@ -207,6 +216,11 @@ item: undoTimeout: 10000 # 10 seconds # Show the item access status label in items lists showAccessStatuses: false + bitstream: + # Number of entries in the bitstream list in the item view page. + # Rounded to the nearest size in the list of selectable sizes on the + # settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'. + pageSize: 5 # Collection Page Config collection: @@ -295,4 +309,4 @@ info: # display in supported metadata fields. By default, only dc.description.abstract is supported. markdown: enabled: false - mathjax: false \ No newline at end of file + mathjax: false diff --git a/cypress.json b/cypress.json index 80358eb6dd..3adf7839c2 100644 --- a/cypress.json +++ b/cypress.json @@ -5,7 +5,7 @@ "screenshotsFolder": "cypress/screenshots", "pluginsFile": "cypress/plugins/index.ts", "fixturesFolder": "cypress/fixtures", - "baseUrl": "http://localhost:4000", + "baseUrl": "http://127.0.0.1:4000", "retries": { "runMode": 2, "openMode": 0 @@ -22,4 +22,4 @@ "DSPACE_TEST_SUBMIT_USER": "dspacedemo+submit@gmail.com", "DSPACE_TEST_SUBMIT_USER_PASSWORD": "dspace" } -} \ No newline at end of file +} diff --git a/docker/docker-compose-ci.yml b/docker/docker-compose-ci.yml index dbe9500499..ef84c14f43 100644 --- a/docker/docker-compose-ci.yml +++ b/docker/docker-compose-ci.yml @@ -24,8 +24,8 @@ services: # __D__ => "-" (e.g. google__D__metadata => google-metadata) # dspace.dir, dspace.server.url and dspace.ui.url dspace__P__dir: /dspace - dspace__P__server__P__url: http://localhost:8080/server - dspace__P__ui__P__url: http://localhost:4000 + dspace__P__server__P__url: http://127.0.0.1:8080/server + dspace__P__ui__P__url: http://127.0.0.1:4000 # db.url: Ensure we are using the 'dspacedb' image for our database db__P__url: 'jdbc:postgresql://dspacedb:5432/dspace' # solr.server: Ensure we are using the 'dspacesolr' image for Solr diff --git a/package.json b/package.json index 705dc8e345..387e094a67 100644 --- a/package.json +++ b/package.json @@ -54,18 +54,18 @@ "ts-node": "10.2.1" }, "dependencies": { - "@angular/animations": "~13.2.6", + "@angular/animations": "~13.3.12", "@angular/cdk": "^13.2.6", - "@angular/common": "~13.2.6", - "@angular/compiler": "~13.2.6", - "@angular/core": "~13.2.6", - "@angular/forms": "~13.2.6", - "@angular/localize": "13.2.6", - "@angular/platform-browser": "~13.2.6", - "@angular/platform-browser-dynamic": "~13.2.6", - "@angular/platform-server": "~13.2.6", - "@angular/router": "~13.2.6", - "@babel/runtime": "^7.17.2", + "@angular/common": "~13.3.12", + "@angular/compiler": "~13.3.12", + "@angular/core": "~13.3.12", + "@angular/forms": "~13.3.12", + "@angular/localize": "13.3.12", + "@angular/platform-browser": "~13.3.12", + "@angular/platform-browser-dynamic": "~13.3.12", + "@angular/platform-server": "~13.3.12", + "@angular/router": "~13.3.12", + "@babel/runtime": "7.17.2", "@kolkov/ngx-gallery": "^2.0.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -77,15 +77,15 @@ "@ngrx/store": "^13.0.2", "@nguniversal/express-engine": "^13.0.2", "@ngx-translate/core": "^13.0.0", - "@nicky-lenaers/ngx-scroll-to": "^9.0.0", + "@nicky-lenaers/ngx-scroll-to": "^13.0.0", "@types/grecaptcha": "^3.0.4", "angular-idle-preload": "3.0.0", "angulartics2": "^12.0.0", "axios": "^0.27.2", - "bootstrap": "4.3.1", - "caniuse-lite": "^1.0.30001165", + "bootstrap": "^4.6.1", "cerialize": "0.1.18", "cli-progress": "^3.8.0", + "colors": "^1.4.0", "compression": "^1.7.4", "cookie-parser": "1.4.5", "core-js": "^3.7.0", @@ -95,14 +95,11 @@ "express": "^4.17.1", "express-rate-limit": "^5.1.3", "fast-json-patch": "^3.0.0-1", - "file-saver": "^2.0.5", "filesize": "^6.1.0", - "font-awesome": "4.7.0", "http-proxy-middleware": "^1.0.5", - "https": "1.0.0", "js-cookie": "2.2.1", "js-yaml": "^4.1.0", - "json5": "^2.1.3", + "json5": "^2.2.2", "jsonschema": "1.4.0", "jwt-decode": "^3.1.2", "klaro": "^0.7.18", @@ -119,43 +116,38 @@ "ngx-infinite-scroll": "^10.0.1", "ngx-pagination": "5.0.0", "ngx-sortablejs": "^11.1.0", - "ngx-ui-switch": "^11.0.1", + "ngx-ui-switch": "^13.0.2", "nouislider": "^14.6.3", "pem": "1.14.4", - "postcss-cli": "^9.1.0", "prop-types": "^15.7.2", "react-copy-to-clipboard": "^5.0.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.5.5", "sanitize-html": "^2.7.2", "sortablejs": "1.13.0", - "tslib": "^2.0.0", - "url-parse": "^1.5.6", "uuid": "^8.3.2", "webfontloader": "1.6.28", "zone.js": "~0.11.5" }, "devDependencies": { "@angular-builders/custom-webpack": "~13.1.0", - "@angular-devkit/build-angular": "~13.2.6", + "@angular-devkit/build-angular": "~13.3.10", "@angular-eslint/builder": "13.1.0", "@angular-eslint/eslint-plugin": "13.1.0", "@angular-eslint/eslint-plugin-template": "13.1.0", "@angular-eslint/schematics": "13.1.0", "@angular-eslint/template-parser": "13.1.0", - "@angular/cli": "~13.2.6", - "@angular/compiler-cli": "~13.2.6", - "@angular/language-service": "~13.2.6", + "@angular/cli": "~13.3.10", + "@angular/compiler-cli": "~13.3.12", + "@angular/language-service": "~13.3.12", "@cypress/schematic": "^1.5.0", - "@fortawesome/fontawesome-free": "^5.5.0", + "@fortawesome/fontawesome-free": "^6.2.1", "@ngrx/store-devtools": "^13.0.2", "@ngtools/webpack": "^13.2.6", - "@nguniversal/builders": "^13.0.2", + "@nguniversal/builders": "^13.1.1", "@types/deep-freeze": "0.1.2", "@types/express": "^4.17.9", - "@types/file-saver": "^2.0.1", "@types/jasmine": "~3.6.0", - "@types/jasminewd2": "~2.0.8", "@types/js-cookie": "2.2.6", "@types/lodash": "^4.14.165", "@types/node": "^14.14.9", @@ -166,26 +158,18 @@ "compression-webpack-plugin": "^9.2.0", "copy-webpack-plugin": "^6.4.1", "cross-env": "^7.0.3", - "css-loader": "^6.2.0", - "css-minimizer-webpack-plugin": "^3.4.1", - "cssnano": "^5.0.6", "cypress": "9.7.0", "cypress-axe": "^0.14.0", - "debug-loader": "^0.0.1", "deep-freeze": "0.0.1", - "dotenv": "^8.2.0", "eslint": "^8.2.0", "eslint-plugin-deprecation": "^1.3.2", "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jsdoc": "^38.0.6", + "eslint-plugin-jsdoc": "^39.6.4", "eslint-plugin-lodash": "^7.4.0", "eslint-plugin-unused-imports": "^2.0.0", "express-static-gzip": "^2.1.5", - "fork-ts-checker-webpack-plugin": "^6.0.3", - "html-loader": "^1.3.2", "jasmine-core": "^3.8.0", "jasmine-marbles": "0.9.2", - "jasmine-spec-reporter": "~5.0.0", "karma": "^6.3.14", "karma-chrome-launcher": "~3.1.0", "karma-coverage-istanbul-reporter": "~3.0.2", @@ -193,26 +177,20 @@ "karma-jasmine-html-reporter": "^1.5.0", "karma-mocha-reporter": "2.2.5", "ngx-mask": "^13.1.7", - "nodemon": "^2.0.15", + "nodemon": "^2.0.20", "postcss": "^8.1", "postcss-apply": "0.12.0", "postcss-import": "^14.0.0", "postcss-loader": "^4.0.3", "postcss-preset-env": "^7.4.2", "postcss-responsive-type": "1.0.0", - "protractor": "^7.0.0", - "protractor-istanbul-plugin": "2.0.0", - "raw-loader": "0.5.1", "react": "^16.14.0", "react-dom": "^16.14.0", "rimraf": "^3.0.2", "rxjs-spy": "^8.0.2", - "sass": "~1.32.6", + "sass": "~1.33.0", "sass-loader": "^12.6.0", "sass-resources-loader": "^2.1.1", - "string-replace-loader": "^3.1.0", - "terser-webpack-plugin": "^2.3.1", - "ts-loader": "^5.2.0", "ts-node": "^8.10.2", "typescript": "~4.5.5", "webpack": "^5.69.1", diff --git a/src/app/access-control/access-control.module.ts b/src/app/access-control/access-control.module.ts index 891238bbed..afb92a9111 100644 --- a/src/app/access-control/access-control.module.ts +++ b/src/app/access-control/access-control.module.ts @@ -10,6 +10,16 @@ import { MembersListComponent } from './group-registry/group-form/members-list/m import { SubgroupsListComponent } from './group-registry/group-form/subgroup-list/subgroups-list.component'; import { GroupsRegistryComponent } from './group-registry/groups-registry.component'; import { FormModule } from '../shared/form/form.module'; +import { DYNAMIC_ERROR_MESSAGES_MATCHER, DynamicErrorMessagesMatcher } from '@ng-dynamic-forms/core'; +import { AbstractControl } from '@angular/forms'; + +/** + * Condition for displaying error messages on email form field + */ +export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher = + (control: AbstractControl, model: any, hasFocus: boolean) => { + return (control.touched && !hasFocus) || (control.errors?.emailTaken && hasFocus); + }; @NgModule({ imports: [ @@ -26,6 +36,12 @@ import { FormModule } from '../shared/form/form.module'; GroupFormComponent, SubgroupsListComponent, MembersListComponent + ], + providers: [ + { + provide: DYNAMIC_ERROR_MESSAGES_MATCHER, + useValue: ValidateEmailErrorStateMatcher + }, ] }) /** 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 b0178f1294..584b28ba1e 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 @@ -46,6 +46,7 @@ import { followLink } from '../../../shared/utils/follow-link-config.model'; import { NoContent } from '../../../core/shared/NoContent.model'; import { Operation } from 'fast-json-patch'; import { ValidateGroupExists } from './validators/group-exists.validator'; +import { environment } from '../../../../environments/environment'; @Component({ selector: 'ds-group-form', @@ -194,6 +195,7 @@ export class GroupFormComponent implements OnInit, OnDestroy { label: groupDescription, name: 'groupDescription', required: false, + spellCheck: environment.form.spellCheck, }); this.formModel = [ this.groupName, diff --git a/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.ts b/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.ts index 161cfa7ecf..142f6fb83d 100644 --- a/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.ts +++ b/src/app/admin/admin-registries/bitstream-formats/format-form/format-form.component.ts @@ -15,6 +15,7 @@ import { Router } from '@angular/router'; import { hasValue, isEmpty } from '../../../../shared/empty.util'; import { TranslateService } from '@ngx-translate/core'; import { getBitstreamFormatsModuleRoute } from '../../admin-registries-routing-paths'; +import { environment } from '../../../../../environments/environment'; /** * The component responsible for rendering the form to create/edit a bitstream format @@ -90,6 +91,7 @@ export class FormatFormComponent implements OnInit { name: 'description', label: 'admin.registries.bitstream-formats.edit.description.label', hint: 'admin.registries.bitstream-formats.edit.description.hint', + spellCheck: environment.form.spellCheck, }), new DynamicSelectModel({ diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index 0ddbefd253..dff2e506c3 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -10,6 +10,7 @@ import { AdminSearchModule } from './admin-search-page/admin-search.module'; import { AdminSidebarSectionComponent } from './admin-sidebar/admin-sidebar-section/admin-sidebar-section.component'; import { ExpandableAdminSidebarSectionComponent } from './admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component'; import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component'; +import { UploadModule } from '../shared/upload/upload.module'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -25,7 +26,8 @@ const ENTRY_COMPONENTS = [ AccessControlModule, AdminSearchModule.withEntryComponents(), AdminWorkflowModuleModule.withEntryComponents(), - SharedModule + SharedModule, + UploadModule, ], declarations: [ AdminCurationTasksComponent, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 392969d041..750d63beda 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,14 +1,12 @@ import { APP_BASE_HREF, CommonModule, DOCUMENT } from '@angular/common'; import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; -import { AbstractControl } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { EffectsModule } from '@ngrx/effects'; import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store'; import { MetaReducer, StoreModule, USER_PROVIDED_META_REDUCERS } from '@ngrx/store'; -import { DYNAMIC_ERROR_MESSAGES_MATCHER, DYNAMIC_MATCHER_PROVIDERS, DynamicErrorMessagesMatcher } from '@ng-dynamic-forms/core'; import { TranslateModule } from '@ngx-translate/core'; import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to'; import { AppRoutingModule } from './app-routing.module'; @@ -28,7 +26,6 @@ import { XsrfInterceptor } from './core/xsrf/xsrf.interceptor'; import { LogInterceptor } from './core/log/log.interceptor'; import { EagerThemesModule } from '../themes/eager-themes.module'; import { APP_CONFIG, AppConfig } from '../config/app-config.interface'; -import { NgxMaskModule } from 'ngx-mask'; import { StoreDevModules } from '../config/store/devtools'; import { RootModule } from './root.module'; @@ -46,14 +43,6 @@ export function getMetaReducers(appConfig: AppConfig): MetaReducer[] { return appConfig.debug ? [...appMetaReducers, ...debugMetaReducers] : appMetaReducers; } -/** - * Condition for displaying error messages on email form field - */ -export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher = - (control: AbstractControl, model: any, hasFocus: boolean) => { - return (control.touched && !hasFocus) || (control.errors?.emailTaken && hasFocus); - }; - const IMPORTS = [ CommonModule, SharedModule, @@ -64,7 +53,6 @@ const IMPORTS = [ ScrollToModule.forRoot(), NgbModule, TranslateModule.forRoot(), - NgxMaskModule.forRoot(), EffectsModule.forRoot(appEffects), StoreModule.forRoot(appReducers, storeModuleConfig), StoreRouterConnectingModule.forRoot(), @@ -113,11 +101,6 @@ const PROVIDERS = [ useClass: LogInterceptor, multi: true }, - { - provide: DYNAMIC_ERROR_MESSAGES_MATCHER, - useValue: ValidateEmailErrorStateMatcher - }, - ...DYNAMIC_MATCHER_PROVIDERS, ]; const DECLARATIONS = [ diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index f84db92445..4b49cb4825 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -42,10 +42,6 @@ import { filterReducer, SearchFiltersState } from './shared/search/search-filters/search-filter/search-filter.reducer'; -import { - sidebarFilterReducer, - SidebarFiltersState -} from './shared/sidebar/filter/sidebar-filter.reducer'; import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer'; import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer'; import { ThemeState, themeReducer } from './shared/theme-support/theme.reducer'; @@ -59,7 +55,6 @@ export interface AppState { metadataRegistry: MetadataRegistryState; notifications: NotificationsState; sidebar: SidebarState; - sidebarFilter: SidebarFiltersState; searchFilter: SearchFiltersState; truncatable: TruncatablesState; cssVariables: CSSVariablesState; @@ -81,7 +76,6 @@ export const appReducers: ActionReducerMap = { metadataRegistry: metadataRegistryReducer, notifications: notificationsReducer, sidebar: sidebarReducer, - sidebarFilter: sidebarFilterReducer, searchFilter: filterReducer, truncatable: truncatableReducer, cssVariables: cssVariablesReducer, diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.html b/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.html similarity index 100% rename from src/app/shared/bitstream-download-page/bitstream-download-page.component.html rename to src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.html diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts b/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.spec.ts similarity index 98% rename from src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts rename to src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.spec.ts index 4100653e0f..e84b254eae 100644 --- a/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts +++ b/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.spec.ts @@ -6,7 +6,7 @@ import { Bitstream } from '../../core/shared/bitstream.model'; import { BitstreamDownloadPageComponent } from './bitstream-download-page.component'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { HardRedirectService } from '../../core/services/hard-redirect.service'; -import { createSuccessfulRemoteDataObject } from '../remote-data.utils'; +import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; import { ActivatedRoute, Router } from '@angular/router'; import { getForbiddenRoute } from '../../app-routing-paths'; import { TranslateModule } from '@ngx-translate/core'; diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts b/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts similarity index 98% rename from src/app/shared/bitstream-download-page/bitstream-download-page.component.ts rename to src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts index 9dd8c7c723..51ec762ec3 100644 --- a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts +++ b/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { filter, map, switchMap, take } from 'rxjs/operators'; import { ActivatedRoute, Router } from '@angular/router'; -import { hasValue, isNotEmpty } from '../empty.util'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { getRemoteDataPayload} from '../../core/shared/operators'; import { Bitstream } from '../../core/shared/bitstream.model'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; diff --git a/src/app/bitstream-page/bitstream-page-routing.module.ts b/src/app/bitstream-page/bitstream-page-routing.module.ts index 0bdda29ddf..c2abe511a4 100644 --- a/src/app/bitstream-page/bitstream-page-routing.module.ts +++ b/src/app/bitstream-page/bitstream-page-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router'; import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { BitstreamPageResolver } from './bitstream-page.resolver'; -import { BitstreamDownloadPageComponent } from '../shared/bitstream-download-page/bitstream-download-page.component'; +import { BitstreamDownloadPageComponent } from './bitstream-download-page/bitstream-download-page.component'; import { ResourcePolicyTargetResolver } from '../shared/resource-policies/resolvers/resource-policy-target.resolver'; import { ResourcePolicyCreateComponent } from '../shared/resource-policies/create/resource-policy-create.component'; import { ResourcePolicyResolver } from '../shared/resource-policies/resolvers/resource-policy.resolver'; diff --git a/src/app/bitstream-page/bitstream-page.module.ts b/src/app/bitstream-page/bitstream-page.module.ts index d168a06db2..992f714bf9 100644 --- a/src/app/bitstream-page/bitstream-page.module.ts +++ b/src/app/bitstream-page/bitstream-page.module.ts @@ -6,6 +6,7 @@ import { BitstreamPageRoutingModule } from './bitstream-page-routing.module'; import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bitstream-authorizations.component'; import { FormModule } from '../shared/form/form.module'; import { ResourcePoliciesModule } from '../shared/resource-policies/resource-policies.module'; +import { BitstreamDownloadPageComponent } from './bitstream-download-page/bitstream-download-page.component'; /** * This module handles all components that are necessary for Bitstream related pages @@ -20,7 +21,8 @@ import { ResourcePoliciesModule } from '../shared/resource-policies/resource-pol ], declarations: [ BitstreamAuthorizationsComponent, - EditBitstreamPageComponent + EditBitstreamPageComponent, + BitstreamDownloadPageComponent, ] }) export class BitstreamPageModule { diff --git a/src/app/breadcrumbs/breadcrumbs.component.html b/src/app/breadcrumbs/breadcrumbs.component.html index 51524fde48..bff792eeff 100644 --- a/src/app/breadcrumbs/breadcrumbs.component.html +++ b/src/app/breadcrumbs/breadcrumbs.component.html @@ -10,7 +10,7 @@ - + diff --git a/src/app/breadcrumbs/breadcrumbs.component.scss b/src/app/breadcrumbs/breadcrumbs.component.scss index 412dca87db..a4d83b82ea 100644 --- a/src/app/breadcrumbs/breadcrumbs.component.scss +++ b/src/app/breadcrumbs/breadcrumbs.component.scss @@ -23,11 +23,14 @@ li.breadcrumb-item { } } -li.breadcrumb-item > a { - color: var(--ds-breadcrumb-link-color) !important; +li.breadcrumb-item { + a { + color: var(--ds-breadcrumb-link-color); + } } + li.breadcrumb-item.active { - color: var(--ds-breadcrumb-link-active-color) !important; + color: var(--ds-breadcrumb-link-active-color); } .breadcrumb-item+ .breadcrumb-item::before { diff --git a/src/app/collection-page/collection-form/collection-form.models.ts b/src/app/collection-page/collection-form/collection-form.models.ts index 37e9d8a9a0..1a009d95a1 100644 --- a/src/app/collection-page/collection-form/collection-form.models.ts +++ b/src/app/collection-page/collection-form/collection-form.models.ts @@ -1,5 +1,6 @@ import { DynamicFormControlModel, DynamicInputModel, DynamicTextAreaModel } from '@ng-dynamic-forms/core'; import { DynamicSelectModelConfig } from '@ng-dynamic-forms/core/lib/model/select/dynamic-select.model'; +import { environment } from '../../../environments/environment'; export const collectionFormEntityTypeSelectionConfig: DynamicSelectModelConfig = { id: 'entityType', @@ -26,21 +27,26 @@ export const collectionFormModels: DynamicFormControlModel[] = [ new DynamicTextAreaModel({ id: 'description', name: 'dc.description', + spellCheck: environment.form.spellCheck, }), new DynamicTextAreaModel({ id: 'abstract', name: 'dc.description.abstract', + spellCheck: environment.form.spellCheck, }), new DynamicTextAreaModel({ id: 'rights', name: 'dc.rights', + spellCheck: environment.form.spellCheck, }), new DynamicTextAreaModel({ id: 'tableofcontents', name: 'dc.description.tableofcontents', + spellCheck: environment.form.spellCheck, }), new DynamicTextAreaModel({ id: 'license', name: 'dc.rights.license', + spellCheck: environment.form.spellCheck, }) ]; diff --git a/src/app/collection-page/collection-page-routing.module.ts b/src/app/collection-page/collection-page-routing.module.ts index a616c92fa4..9dc25b778e 100644 --- a/src/app/collection-page/collection-page-routing.module.ts +++ b/src/app/collection-page/collection-page-routing.module.ts @@ -74,6 +74,7 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; id: 'statistics_collection_:id', active: true, visible: true, + index: 2, model: { type: MenuItemType.LINK, text: 'menu.section.statistics', diff --git a/src/app/collection-page/collection-page.module.ts b/src/app/collection-page/collection-page.module.ts index c35ebf9021..ff49b983ff 100644 --- a/src/app/collection-page/collection-page.module.ts +++ b/src/app/collection-page/collection-page.module.ts @@ -25,7 +25,7 @@ import { ComcolModule } from '../shared/comcol/comcol.module'; StatisticsModule.forRoot(), EditItemPageModule, CollectionFormModule, - ComcolModule + ComcolModule, ], declarations: [ CollectionPageComponent, @@ -38,7 +38,7 @@ import { ComcolModule } from '../shared/comcol/comcol.module'; ], providers: [ SearchService, - ] + ], }) export class CollectionPageModule { diff --git a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts index 45612be41a..18f7feb699 100644 --- a/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts +++ b/src/app/collection-page/edit-collection-page/edit-collection-page.module.ts @@ -25,7 +25,7 @@ import { ComcolModule } from '../../shared/comcol/comcol.module'; CollectionFormModule, ResourcePoliciesModule, FormModule, - ComcolModule + ComcolModule, ], declarations: [ EditCollectionPageComponent, diff --git a/src/app/community-list-page/community-list-page.module.ts b/src/app/community-list-page/community-list-page.module.ts index 18c28068be..15946b2e89 100644 --- a/src/app/community-list-page/community-list-page.module.ts +++ b/src/app/community-list-page/community-list-page.module.ts @@ -6,6 +6,7 @@ import { CommunityListPageRoutingModule } from './community-list-page.routing.mo import { CommunityListComponent } from './community-list/community-list.component'; import { ThemedCommunityListPageComponent } from './themed-community-list-page.component'; import { ThemedCommunityListComponent } from './community-list/themed-community-list.component'; +import { CdkTreeModule } from '@angular/cdk/tree'; const DECLARATIONS = [ @@ -21,13 +22,15 @@ const DECLARATIONS = [ imports: [ CommonModule, SharedModule, - CommunityListPageRoutingModule + CommunityListPageRoutingModule, + CdkTreeModule, ], declarations: [ ...DECLARATIONS ], exports: [ ...DECLARATIONS, + CdkTreeModule, ], }) export class CommunityListPageModule { diff --git a/src/app/community-page/community-form/community-form.component.ts b/src/app/community-page/community-form/community-form.component.ts index a3730fd418..c6dd1147c3 100644 --- a/src/app/community-page/community-form/community-form.component.ts +++ b/src/app/community-page/community-form/community-form.component.ts @@ -13,6 +13,7 @@ import { CommunityDataService } from '../../core/data/community-data.service'; import { AuthService } from '../../core/auth/auth.service'; import { RequestService } from '../../core/data/request.service'; import { ObjectCacheService } from '../../core/cache/object-cache.service'; +import { environment } from '../../../environments/environment'; /** * Form used for creating and editing communities @@ -52,18 +53,22 @@ export class CommunityFormComponent extends ComColFormComponent { new DynamicTextAreaModel({ id: 'description', name: 'dc.description', + spellCheck: environment.form.spellCheck, }), new DynamicTextAreaModel({ id: 'abstract', name: 'dc.description.abstract', + spellCheck: environment.form.spellCheck, }), new DynamicTextAreaModel({ id: 'rights', name: 'dc.rights', + spellCheck: environment.form.spellCheck, }), new DynamicTextAreaModel({ id: 'tableofcontents', name: 'dc.description.tableofcontents', + spellCheck: environment.form.spellCheck, }), ]; diff --git a/src/app/community-page/community-page-routing.module.ts b/src/app/community-page/community-page-routing.module.ts index 4a5e028583..c37f8832f8 100644 --- a/src/app/community-page/community-page-routing.module.ts +++ b/src/app/community-page/community-page-routing.module.ts @@ -57,6 +57,7 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; id: 'statistics_community_:id', active: true, visible: true, + index: 2, model: { type: MenuItemType.LINK, text: 'menu.section.statistics', diff --git a/src/app/community-page/community-page.module.ts b/src/app/community-page/community-page.module.ts index 7cf2c8db8a..1dd9e82499 100644 --- a/src/app/community-page/community-page.module.ts +++ b/src/app/community-page/community-page.module.ts @@ -36,7 +36,7 @@ const DECLARATIONS = [CommunityPageComponent, CommunityPageRoutingModule, StatisticsModule.forRoot(), CommunityFormModule, - ComcolModule + ComcolModule, ], declarations: [ ...DECLARATIONS diff --git a/src/app/community-page/edit-community-page/edit-community-page.module.ts b/src/app/community-page/edit-community-page/edit-community-page.module.ts index 2b0fc73f2a..0479ea6bc6 100644 --- a/src/app/community-page/edit-community-page/edit-community-page.module.ts +++ b/src/app/community-page/edit-community-page/edit-community-page.module.ts @@ -21,7 +21,7 @@ import { ComcolModule } from '../../shared/comcol/comcol.module'; EditCommunityPageRoutingModule, CommunityFormModule, ComcolModule, - ResourcePoliciesModule + ResourcePoliciesModule, ], declarations: [ EditCommunityPageComponent, diff --git a/src/app/core/breadcrumbs/dso-name.service.ts b/src/app/core/breadcrumbs/dso-name.service.ts index d56f4a00eb..64f37baa65 100644 --- a/src/app/core/breadcrumbs/dso-name.service.ts +++ b/src/app/core/breadcrumbs/dso-name.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { hasValue, isEmpty } from '../../shared/empty.util'; import { DSpaceObject } from '../shared/dspace-object.model'; import { TranslateService } from '@ngx-translate/core'; +import { Metadata } from '../shared/metadata.utils'; /** * Returns a name for a {@link DSpaceObject} based @@ -67,4 +68,45 @@ export class DSONameService { return name; } + /** + * Gets the Hit highlight + * + * @param object + * @param dso + * + * @returns {string} html embedded hit highlight. + */ + getHitHighlights(object: any, dso: DSpaceObject): string { + const types = dso.getRenderTypes(); + const entityType = types + .filter((type) => typeof type === 'string') + .find((type: string) => (['Person', 'OrgUnit']).includes(type)) as string; + if (entityType === 'Person') { + const familyName = this.firstMetadataValue(object, dso, 'person.familyName'); + const givenName = this.firstMetadataValue(object, dso, 'person.givenName'); + if (isEmpty(familyName) && isEmpty(givenName)) { + return this.firstMetadataValue(object, dso, 'dc.title') || dso.name; + } else if (isEmpty(familyName) || isEmpty(givenName)) { + return familyName || givenName; + } + return `${familyName}, ${givenName}`; + } else if (entityType === 'OrgUnit') { + return this.firstMetadataValue(object, dso, 'organization.legalName'); + } + return this.firstMetadataValue(object, dso, 'dc.title') || dso.name || this.translateService.instant('dso.name.untitled'); + } + + /** + * Gets the first matching metadata string value from hitHighlights or dso metadata, preferring hitHighlights. + * + * @param object + * @param dso + * @param {string|string[]} keyOrKeys The metadata key(s) in scope. Wildcards are supported; see [[Metadata]]. + * + * @returns {string} the first matching string value, or `undefined`. + */ + firstMetadataValue(object: any, dso: DSpaceObject, keyOrKeys: string | string[]): string { + return Metadata.firstValue([object.hitHighlights, dso.metadata], keyOrKeys); + } + } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 90cefd54c7..ede23ba43b 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -2,15 +2,12 @@ import { CommonModule } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core'; -import { DynamicFormLayoutService, DynamicFormService, DynamicFormValidationService } from '@ng-dynamic-forms/core'; import { EffectsModule } from '@ngrx/effects'; import { Action, StoreConfig, StoreModule } from '@ngrx/store'; import { MyDSpaceGuard } from '../my-dspace-page/my-dspace.guard'; import { isNotEmpty } from '../shared/empty.util'; -import { FormBuilderService } from '../shared/form/builder/form-builder.service'; -import { FormService } from '../shared/form/form.service'; import { HostWindowService } from '../shared/host-window.service'; import { MenuService } from '../shared/menu/menu.service'; import { EndpointMockingRestService } from '../shared/mocks/dspace-rest/endpoint-mocking-rest.service'; @@ -24,8 +21,6 @@ import { SelectableListService } from '../shared/object-list/selectable-list/sel import { ObjectSelectService } from '../shared/object-select/object-select.service'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { SidebarService } from '../shared/sidebar/sidebar.service'; -import { UploaderService } from '../shared/uploader/uploader.service'; -import { SectionFormOperationsService } from '../submission/sections/form/section-form-operations.service'; import { AuthenticatedGuard } from './auth/authenticated.guard'; import { AuthStatus } from './auth/models/auth-status.model'; import { BrowseService } from './browse/browse.service'; @@ -137,9 +132,6 @@ import { import { Registration } from './shared/registration.model'; import { MetadataSchemaDataService } from './data/metadata-schema-data.service'; import { MetadataFieldDataService } from './data/metadata-field-data.service'; -import { - DsDynamicTypeBindRelationService -} from '../shared/form/builder/ds-dynamic-form-ui/ds-dynamic-type-bind-relation.service'; import { TokenResponseParsingService } from './auth/token-response-parsing.service'; import { SubmissionCcLicenseDataService } from './submission/submission-cc-license-data.service'; import { SubmissionCcLicence } from './submission/models/submission-cc-license.model'; @@ -149,7 +141,6 @@ import { VocabularyEntry } from './submission/vocabularies/models/vocabulary-ent import { Vocabulary } from './submission/vocabularies/models/vocabulary.model'; import { VocabularyEntryDetail } from './submission/vocabularies/models/vocabulary-entry-detail.model'; import { VocabularyService } from './submission/vocabularies/vocabulary.service'; -import { VocabularyTreeviewService } from '../shared/vocabulary-treeview/vocabulary-treeview.service'; import { ConfigurationDataService } from './data/configuration-data.service'; import { ConfigurationProperty } from './shared/configuration-property.model'; import { ReloadGuard } from './reload/reload.guard'; @@ -210,12 +201,6 @@ const PROVIDERS = [ DSOResponseParsingService, { provide: MOCK_RESPONSE_MAP, useValue: mockResponseMap }, { provide: DspaceRestService, useFactory: restServiceFactory, deps: [MOCK_RESPONSE_MAP, HttpClient] }, - DynamicFormLayoutService, - DynamicFormService, - DynamicFormValidationService, - FormBuilderService, - SectionFormOperationsService, - FormService, EPersonDataService, LinkHeadService, HALEndpointService, @@ -244,12 +229,10 @@ const PROVIDERS = [ SubmissionResponseParsingService, SubmissionJsonPatchOperationsService, JsonPatchOperationsBuilder, - UploaderService, UUIDService, NotificationsService, WorkspaceitemDataService, WorkflowItemDataService, - UploaderService, DSpaceObjectDataService, ConfigurationDataService, DSOChangeAnalyzer, @@ -266,7 +249,6 @@ const PROVIDERS = [ ClaimedTaskDataService, PoolTaskDataService, BitstreamDataService, - DsDynamicTypeBindRelationService, EntityTypeDataService, ContentSourceResponseParsingService, ItemTemplateDataService, @@ -302,7 +284,6 @@ const PROVIDERS = [ VocabularyService, VocabularyDataService, VocabularyEntryDetailsDataService, - VocabularyTreeviewService, SequenceService, GroupDataService, FeedbackDataService, diff --git a/src/app/shared/uploader/uploader.service.ts b/src/app/core/drag.service.ts similarity index 53% rename from src/app/shared/uploader/uploader.service.ts rename to src/app/core/drag.service.ts index 548de34f9c..d5f329d362 100644 --- a/src/app/shared/uploader/uploader.service.ts +++ b/src/app/core/drag.service.ts @@ -1,7 +1,17 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + import { Injectable } from '@angular/core'; -@Injectable() -export class UploaderService { +@Injectable({ + providedIn: 'root' +}) +export class DragService { private _overrideDragOverPage = false; public overrideDragOverPage() { diff --git a/src/app/core/locale/locale.service.ts b/src/app/core/locale/locale.service.ts index 68d2839d42..16a35b8ae5 100644 --- a/src/app/core/locale/locale.service.ts +++ b/src/app/core/locale/locale.service.ts @@ -40,7 +40,7 @@ export class LocaleService { protected translate: TranslateService, protected authService: AuthService, protected routeService: RouteService, - @Inject(DOCUMENT) private document: any + @Inject(DOCUMENT) protected document: any ) { } diff --git a/src/app/core/locale/server-locale.service.ts b/src/app/core/locale/server-locale.service.ts index f438643e49..556619b946 100644 --- a/src/app/core/locale/server-locale.service.ts +++ b/src/app/core/locale/server-locale.service.ts @@ -1,12 +1,31 @@ import { LANG_ORIGIN, LocaleService } from './locale.service'; -import { Injectable } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { combineLatest, Observable, of as observableOf } from 'rxjs'; import { map, mergeMap, take } from 'rxjs/operators'; -import { isEmpty, isNotEmpty } from '../../shared/empty.util'; +import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; +import { NativeWindowRef, NativeWindowService } from '../services/window.service'; +import { REQUEST } from '@nguniversal/express-engine/tokens'; +import { CookieService } from '../services/cookie.service'; +import { TranslateService } from '@ngx-translate/core'; +import { AuthService } from '../auth/auth.service'; +import { RouteService } from '../services/route.service'; +import { DOCUMENT } from '@angular/common'; @Injectable() export class ServerLocaleService extends LocaleService { + constructor( + @Inject(NativeWindowService) protected _window: NativeWindowRef, + @Inject(REQUEST) protected req: Request, + protected cookie: CookieService, + protected translate: TranslateService, + protected authService: AuthService, + protected routeService: RouteService, + @Inject(DOCUMENT) protected document: any + ) { + super(_window, cookie, translate, authService, routeService, document); + } + /** * Get the languages list of the user in Accept-Language format * @@ -50,6 +69,10 @@ export class ServerLocaleService extends LocaleService { if (isNotEmpty(epersonLang)) { languages.push(...epersonLang); } + if (hasValue(this.req.headers['accept-language'])) { + languages.push(...this.req.headers['accept-language'].split(',') + ); + } return languages; }) ); diff --git a/src/app/core/shared/media-viewer-item.model.ts b/src/app/core/shared/media-viewer-item.model.ts index cd3a31bd0b..1cf4948408 100644 --- a/src/app/core/shared/media-viewer-item.model.ts +++ b/src/app/core/shared/media-viewer-item.model.ts @@ -14,6 +14,11 @@ export class MediaViewerItem { */ format: string; + /** + * Incoming Bitsream format mime type + */ + mimetype: string; + /** * Incoming Bitsream thumbnail */ 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 7a38a02cf4..954f7bc591 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 @@ -61,7 +61,7 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes this.useNameVariants = this.context === Context.EntitySearchModalWithNameVariants; if (this.useNameVariants) { - const defaultValue = this.dsoTitle; + const defaultValue = this.dso ? this.dsoNameService.getName(this.dso) : undefined; const alternatives = this.allMetadataValues(this.alternativeField); this.allSuggestions = [defaultValue, ...alternatives]; diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts index 7d761c42dd..305407f8d2 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts @@ -55,7 +55,7 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu ngOnInit() { super.ngOnInit(); - const defaultValue = this.dsoTitle; + const defaultValue = this.dso ? this.dsoNameService.getName(this.dso) : undefined; const alternatives = this.allMetadataValues(this.alternativeField); this.allSuggestions = [defaultValue, ...alternatives]; diff --git a/src/app/shared/item/item-alerts/item-alerts.component.html b/src/app/item-page/alerts/item-alerts.component.html similarity index 100% rename from src/app/shared/item/item-alerts/item-alerts.component.html rename to src/app/item-page/alerts/item-alerts.component.html diff --git a/src/app/shared/item/item-alerts/item-alerts.component.scss b/src/app/item-page/alerts/item-alerts.component.scss similarity index 100% rename from src/app/shared/item/item-alerts/item-alerts.component.scss rename to src/app/item-page/alerts/item-alerts.component.scss diff --git a/src/app/shared/item/item-alerts/item-alerts.component.spec.ts b/src/app/item-page/alerts/item-alerts.component.spec.ts similarity index 97% rename from src/app/shared/item/item-alerts/item-alerts.component.spec.ts rename to src/app/item-page/alerts/item-alerts.component.spec.ts index fed81199fd..a933eb6a58 100644 --- a/src/app/shared/item/item-alerts/item-alerts.component.spec.ts +++ b/src/app/item-page/alerts/item-alerts.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ItemAlertsComponent } from './item-alerts.component'; import { TranslateModule } from '@ngx-translate/core'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { Item } from '../../../core/shared/item.model'; +import { Item } from '../../core/shared/item.model'; import { By } from '@angular/platform-browser'; describe('ItemAlertsComponent', () => { diff --git a/src/app/shared/item/item-alerts/item-alerts.component.ts b/src/app/item-page/alerts/item-alerts.component.ts similarity index 80% rename from src/app/shared/item/item-alerts/item-alerts.component.ts rename to src/app/item-page/alerts/item-alerts.component.ts index 4c2d60d24c..d7a84db015 100644 --- a/src/app/shared/item/item-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 '../../alert/aletr-type'; +import { Item } from '../../core/shared/item.model'; +import { AlertType } from '../../shared/alert/aletr-type'; @Component({ selector: 'ds-item-alerts', diff --git a/src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.html b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html similarity index 100% rename from src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.html rename to src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html diff --git a/src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.spec.ts b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.spec.ts similarity index 90% rename from src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.spec.ts rename to src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.spec.ts index cc44ef8587..cbfbdf361f 100644 --- a/src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.spec.ts +++ b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.spec.ts @@ -1,30 +1,30 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { AuthService } from '../../core/auth/auth.service'; +import { AuthService } from '../../../core/auth/auth.service'; import { of as observableOf } from 'rxjs'; -import { Bitstream } from '../../core/shared/bitstream.model'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; +import { Bitstream } from '../../../core/shared/bitstream.model'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ -} from '../remote-data.utils'; +} from '../../../shared/remote-data.utils'; import { ActivatedRoute, Router } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { BitstreamRequestACopyPageComponent } from './bitstream-request-a-copy-page.component'; import { By } from '@angular/platform-browser'; -import { RouterStub } from '../testing/router.stub'; +import { RouterStub } from '../../../shared/testing/router.stub'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { NotificationsServiceStub } from '../testing/notifications-service.stub'; -import { ItemRequestDataService } from '../../core/data/item-request-data.service'; -import { NotificationsService } from '../notifications/notifications.service'; -import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; -import { DSONameServiceMock } from '../mocks/dso-name.service.mock'; -import { Item } from '../../core/shared/item.model'; -import { EPerson } from '../../core/eperson/models/eperson.model'; -import { ItemRequest } from '../../core/shared/item-request.model'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; +import { ItemRequestDataService } from '../../../core/data/item-request-data.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../shared/mocks/dso-name.service.mock'; +import { Item } from '../../../core/shared/item.model'; +import { EPerson } from '../../../core/eperson/models/eperson.model'; +import { ItemRequest } from '../../../core/shared/item-request.model'; import { Location } from '@angular/common'; -import { BitstreamDataService } from '../../core/data/bitstream-data.service'; +import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; describe('BitstreamRequestACopyPageComponent', () => { diff --git a/src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.ts b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts similarity index 85% rename from src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.ts rename to src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts index 511079a701..59819a4a66 100644 --- a/src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.ts +++ b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts @@ -1,25 +1,25 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { filter, map, switchMap, take } from 'rxjs/operators'; import { ActivatedRoute, Router } from '@angular/router'; -import { hasValue, isNotEmpty } from '../empty.util'; -import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; -import { Bitstream } from '../../core/shared/bitstream.model'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { AuthService } from '../../core/auth/auth.service'; +import { hasValue, isNotEmpty } from '../../../shared/empty.util'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; +import { Bitstream } from '../../../core/shared/bitstream.model'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; +import { AuthService } from '../../../core/auth/auth.service'; import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; -import { getBitstreamDownloadRoute, getForbiddenRoute } from '../../app-routing-paths'; +import { getBitstreamDownloadRoute, getForbiddenRoute } from '../../../app-routing-paths'; import { TranslateService } from '@ngx-translate/core'; -import { EPerson } from '../../core/eperson/models/eperson.model'; +import { EPerson } from '../../../core/eperson/models/eperson.model'; import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; -import { ItemRequestDataService } from '../../core/data/item-request-data.service'; -import { ItemRequest } from '../../core/shared/item-request.model'; -import { Item } from '../../core/shared/item.model'; -import { NotificationsService } from '../notifications/notifications.service'; -import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { ItemRequestDataService } from '../../../core/data/item-request-data.service'; +import { ItemRequest } from '../../../core/shared/item-request.model'; +import { Item } from '../../../core/shared/item.model'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; import { Location } from '@angular/common'; -import { BitstreamDataService } from '../../core/data/bitstream-data.service'; -import { getItemPageRoute } from '../../item-page/item-page-routing-paths'; +import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; +import { getItemPageRoute } from '../../item-page-routing-paths'; @Component({ selector: 'ds-bitstream-request-a-copy-page', diff --git a/src/app/item-page/bitstreams/upload/upload-bitstream.component.ts b/src/app/item-page/bitstreams/upload/upload-bitstream.component.ts index 1e5295a347..74019de7cc 100644 --- a/src/app/item-page/bitstreams/upload/upload-bitstream.component.ts +++ b/src/app/item-page/bitstreams/upload/upload-bitstream.component.ts @@ -4,7 +4,7 @@ import { RemoteData } from '../../../core/data/remote-data'; import { Item } from '../../../core/shared/item.model'; import { map, take, switchMap } from 'rxjs/operators'; import { ActivatedRoute, Router } from '@angular/router'; -import { UploaderOptions } from '../../../shared/uploader/uploader-options.model'; +import { UploaderOptions } from '../../../shared/upload/uploader/uploader-options.model'; import { hasValue, isEmpty, isNotEmpty } from '../../../shared/empty.util'; import { ItemDataService } from '../../../core/data/item-data.service'; import { AuthService } from '../../../core/auth/auth.service'; @@ -14,7 +14,7 @@ import { PaginatedList } from '../../../core/data/paginated-list.model'; import { Bundle } from '../../../core/shared/bundle.model'; import { BundleDataService } from '../../../core/data/bundle-data.service'; import { getFirstSucceededRemoteDataPayload, getFirstCompletedRemoteData } from '../../../core/shared/operators'; -import { UploaderComponent } from '../../../shared/uploader/uploader.component'; +import { UploaderComponent } from '../../../shared/upload/uploader/uploader.component'; import { RequestService } from '../../../core/data/request.service'; import { getBitstreamModuleRoute } from '../../../app-routing-paths'; import { getEntityEditRoute } from '../../item-page-routing-paths'; 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 3ed741bc1a..fafbae0bd4 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 @@ -36,6 +36,7 @@ import { ItemVersionHistoryComponent } from './item-version-history/item-version import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component'; import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe'; import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module'; +import { ItemVersionsModule } from '../versions/item-versions.module'; /** @@ -50,7 +51,8 @@ import { ResourcePoliciesModule } from '../../shared/resource-policies/resource- SearchPageModule, DragDropModule, ResourcePoliciesModule, - NgbModule + NgbModule, + ItemVersionsModule, ], declarations: [ EditItemPageComponent, diff --git a/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts b/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts index 1059ed12da..f8a8a83f29 100644 --- a/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts +++ b/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts @@ -7,7 +7,7 @@ import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.m import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { VarDirective } from '../../../../shared/utils/var.directive'; import { FileSizePipe } from '../../../../shared/utils/file-size-pipe'; -import { MetadataFieldWrapperComponent } from '../../../field-components/metadata-field-wrapper/metadata-field-wrapper.component'; +import { MetadataFieldWrapperComponent } from '../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component'; import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { Bitstream } from '../../../../core/shared/bitstream.model'; @@ -18,6 +18,8 @@ import { NotificationsService } from '../../../../shared/notifications/notificat import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; import { PaginationService } from '../../../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; +import { APP_CONFIG } from 'src/config/app-config.interface'; +import { environment } from 'src/environments/environment'; describe('FullFileSectionComponent', () => { let comp: FullFileSectionComponent; @@ -69,7 +71,8 @@ describe('FullFileSectionComponent', () => { providers: [ { provide: BitstreamDataService, useValue: bitstreamDataService }, { provide: NotificationsService, useValue: new NotificationsServiceStub() }, - { provide: PaginationService, useValue: paginationService } + { provide: PaginationService, useValue: paginationService }, + { provide: APP_CONFIG, useValue: environment }, ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/item-page/full/field-components/file-section/full-file-section.component.ts b/src/app/item-page/full/field-components/file-section/full-file-section.component.ts index e21c1a32eb..3be0d58c81 100644 --- a/src/app/item-page/full/field-components/file-section/full-file-section.component.ts +++ b/src/app/item-page/full/field-components/file-section/full-file-section.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Inject, Input, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; @@ -14,6 +14,7 @@ import { NotificationsService } from '../../../../shared/notifications/notificat import { TranslateService } from '@ngx-translate/core'; import { hasValue, isEmpty } from '../../../../shared/empty.util'; import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface'; /** * This component renders the file section of the item @@ -34,26 +35,26 @@ export class FullFileSectionComponent extends FileSectionComponent implements On originals$: Observable>>; licenses$: Observable>>; - pageSize = 5; originalOptions = Object.assign(new PaginationComponentOptions(), { id: 'obo', currentPage: 1, - pageSize: this.pageSize + pageSize: this.appConfig.item.bitstream.pageSize }); licenseOptions = Object.assign(new PaginationComponentOptions(), { id: 'lbo', currentPage: 1, - pageSize: this.pageSize + pageSize: this.appConfig.item.bitstream.pageSize }); constructor( bitstreamDataService: BitstreamDataService, protected notificationsService: NotificationsService, protected translateService: TranslateService, - protected paginationService: PaginationService + protected paginationService: PaginationService, + @Inject(APP_CONFIG) protected appConfig: AppConfig ) { - super(bitstreamDataService, notificationsService, translateService); + super(bitstreamDataService, notificationsService, translateService, appConfig); } ngOnInit(): void { diff --git a/src/app/item-page/item-page-routing.module.ts b/src/app/item-page/item-page-routing.module.ts index 03c5342eda..0c855ab34d 100644 --- a/src/app/item-page/item-page-routing.module.ts +++ b/src/app/item-page/item-page-routing.module.ts @@ -14,9 +14,7 @@ import { ThemedItemPageComponent } from './simple/themed-item-page.component'; import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component'; import { MenuItemType } from '../shared/menu/menu-item-type.model'; import { VersionPageComponent } from './version-page/version-page/version-page.component'; -import { - BitstreamRequestACopyPageComponent -} from '../shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component'; +import { BitstreamRequestACopyPageComponent } from './bitstreams/request-a-copy/bitstream-request-a-copy-page.component'; import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths'; import { OrcidPageComponent } from './orcid-page/orcid-page.component'; import { OrcidPageGuard } from './orcid-page/orcid-page.guard'; @@ -69,6 +67,7 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; id: 'statistics_item_:id', active: true, visible: true, + index: 2, model: { type: MenuItemType.LINK, text: 'menu.section.statistics', diff --git a/src/app/item-page/item-page.module.ts b/src/app/item-page/item-page.module.ts index b34e99d596..5842e9e17e 100644 --- a/src/app/item-page/item-page.module.ts +++ b/src/app/item-page/item-page.module.ts @@ -45,6 +45,12 @@ import { OrcidPageComponent } from './orcid-page/orcid-page.component'; import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; import { OrcidSyncSettingsComponent } from './orcid-page/orcid-sync-settings/orcid-sync-settings.component'; import { OrcidQueueComponent } from './orcid-page/orcid-queue/orcid-queue.component'; +import { UploadModule } from '../shared/upload/upload.module'; +import { ItemAlertsComponent } from './alerts/item-alerts.component'; +import { ItemVersionsModule } from './versions/item-versions.module'; +import { BitstreamRequestACopyPageComponent } from './bitstreams/request-a-copy/bitstream-request-a-copy-page.component'; +import { FileSectionComponent } from './simple/field-components/file-section/file-section.component'; +import { ItemSharedModule } from './item-shared.module'; const ENTRY_COMPONENTS = [ @@ -54,6 +60,7 @@ const ENTRY_COMPONENTS = [ ]; const DECLARATIONS = [ + FileSectionComponent, ThemedFileSectionComponent, ItemPageComponent, ThemedItemPageComponent, @@ -80,7 +87,10 @@ const DECLARATIONS = [ OrcidPageComponent, OrcidAuthComponent, OrcidSyncSettingsComponent, - OrcidQueueComponent + OrcidQueueComponent, + ItemAlertsComponent, + VersionedItemComponent, + BitstreamRequestACopyPageComponent, ]; @NgModule({ @@ -89,17 +99,21 @@ const DECLARATIONS = [ SharedModule.withEntryComponents(), ItemPageRoutingModule, EditItemPageModule, + ItemVersionsModule, + ItemSharedModule, StatisticsModule.forRoot(), JournalEntitiesModule.withEntryComponents(), ResearchEntitiesModule.withEntryComponents(), NgxGalleryModule, - NgbAccordionModule + NgbAccordionModule, + UploadModule, ], declarations: [ ...DECLARATIONS, + ], exports: [ - ...DECLARATIONS + ...DECLARATIONS, ] }) export class ItemPageModule { diff --git a/src/app/item-page/item-shared.module.ts b/src/app/item-page/item-shared.module.ts index b191b6c4b3..c558b11692 100644 --- a/src/app/item-page/item-shared.module.ts +++ b/src/app/item-page/item-shared.module.ts @@ -7,10 +7,32 @@ import { TranslateModule } from '@ngx-translate/core'; import { DYNAMIC_FORM_CONTROL_MAP_FN } from '@ng-dynamic-forms/core'; import { dsDynamicFormControlMapFn } from '../shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component'; import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component'; +import { ItemVersionsDeleteModalComponent } from './versions/item-versions-delete-modal/item-versions-delete-modal.component'; +import { ItemVersionsSummaryModalComponent } from './versions/item-versions-summary-modal/item-versions-summary-modal.component'; +import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component'; +import { DsoPageVersionButtonComponent } from '../shared/dso-page/dso-page-version-button/dso-page-version-button.component'; +import { PersonPageClaimButtonComponent } from '../shared/dso-page/person-page-claim-button/person-page-claim-button.component'; +import { GenericItemPageFieldComponent } from './simple/field-components/specific-field/generic/generic-item-page-field.component'; +import { MetadataRepresentationListComponent } from './simple/metadata-representation-list/metadata-representation-list.component'; +import { RelatedItemsComponent } from './simple/related-items/related-items-component'; +import { DsoPageOrcidButtonComponent } from '../shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component'; + +const ENTRY_COMPONENTS = [ + ItemVersionsDeleteModalComponent, + ItemVersionsSummaryModalComponent, +]; const COMPONENTS = [ + ...ENTRY_COMPONENTS, RelatedEntitiesSearchComponent, - TabbedRelatedEntitiesSearchComponent + TabbedRelatedEntitiesSearchComponent, + MetadataValuesComponent, + DsoPageVersionButtonComponent, + PersonPageClaimButtonComponent, + GenericItemPageFieldComponent, + MetadataRepresentationListComponent, + RelatedItemsComponent, + DsoPageOrcidButtonComponent ]; @NgModule({ @@ -30,7 +52,8 @@ const COMPONENTS = [ { provide: DYNAMIC_FORM_CONTROL_MAP_FN, useValue: dsDynamicFormControlMapFn - } + }, + ...ENTRY_COMPONENTS, ] }) export class ItemSharedModule { } diff --git a/src/app/item-page/media-viewer/media-viewer-video/caption-info.ts b/src/app/item-page/media-viewer/media-viewer-video/caption-info.ts new file mode 100644 index 0000000000..43996d096d --- /dev/null +++ b/src/app/item-page/media-viewer/media-viewer-video/caption-info.ts @@ -0,0 +1,11 @@ +/* + The class is designed to host information related to Video Captioning support + and used in HTML 5 video track + src: source vtt file + srclang: two letter language code + langLabel: language label + */ +export class CaptionInfo { + constructor(public src: string, public srclang: string, public langLabel: string ) { + } +} diff --git a/src/app/item-page/media-viewer/media-viewer-video/language-helper.ts b/src/app/item-page/media-viewer/media-viewer-video/language-helper.ts new file mode 100644 index 0000000000..b27ab9983f --- /dev/null +++ b/src/app/item-page/media-viewer/media-viewer-video/language-helper.ts @@ -0,0 +1,190 @@ +export const languageHelper = { + ab: 'Abkhazian', + aa: 'Afar', + af: 'Afrikaans', + ak: 'Akan', + sq: 'Albanian', + am: 'Amharic', + ar: 'Arabic', + an: 'Aragonese', + hy: 'Armenian', + as: 'Assamese', + av: 'Avaric', + ae: 'Avestan', + ay: 'Aymara', + az: 'Azerbaijani', + bm: 'Bambara', + ba: 'Bashkir', + eu: 'Basque', + be: 'Belarusian', + bn: 'Bengali (Bangla)', + bh: 'Bihari', + bi: 'Bislama', + bs: 'Bosnian', + br: 'Breton', + bg: 'Bulgarian', + my: 'Burmese', + ca: 'Catalan', + ch: 'Chamorro', + ce: 'Chechen', + ny: 'Chichewa, Chewa, Nyanja', + zh: 'Chinese', + cv: 'Chuvash', + kw: 'Cornish', + co: 'Corsican', + cr: 'Cree', + hr: 'Croatian', + cs: 'Czech', + da: 'Danish', + dv: 'Divehi, Dhivehi, Maldivian', + nl: 'Dutch', + dz: 'Dzongkha', + en: 'English', + eo: 'Esperanto', + et: 'Estonian', + ee: 'Ewe', + fo: 'Faroese', + fj: 'Fijian', + fi: 'Finnish', + fr: 'French', + ff: 'Fula, Fulah, Pulaar, Pular', + gl: 'Galician', + gd: 'Gaelic (Scottish)', + gv: 'Gaelic (Manx)', + ka: 'Georgian', + de: 'German', + el: 'Greek', + gn: 'Guarani', + gu: 'Gujarati', + ht: 'Haitian Creole', + ha: 'Hausa', + he: 'Hebrew', + hz: 'Herero', + hi: 'Hindi', + ho: 'Hiri Motu', + hu: 'Hungarian', + is: 'Icelandic', + io: 'Ido', + ig: 'Igbo', + in: 'Indonesian', + ia: 'Interlingua', + ie: 'Interlingue', + iu: 'Inuktitut', + ik: 'Inupiak', + ga: 'Irish', + it: 'Italian', + ja: 'Japanese', + jv: 'Javanese', + kl: 'Kalaallisut, Greenlandic', + kn: 'Kannada', + kr: 'Kanuri', + ks: 'Kashmiri', + kk: 'Kazakh', + km: 'Khmer', + ki: 'Kikuyu', + rw: 'Kinyarwanda (Rwanda)', + rn: 'Kirundi', + ky: 'Kyrgyz', + kv: 'Komi', + kg: 'Kongo', + ko: 'Korean', + ku: 'Kurdish', + kj: 'Kwanyama', + lo: 'Lao', + la: 'Latin', + lv: 'Latvian (Lettish)', + li: 'Limburgish ( Limburger)', + ln: 'Lingala', + lt: 'Lithuanian', + lu: 'Luga-Katanga', + lg: 'Luganda, Ganda', + lb: 'Luxembourgish', + mk: 'Macedonian', + mg: 'Malagasy', + ms: 'Malay', + ml: 'Malayalam', + mt: 'Maltese', + mi: 'Maori', + mr: 'Marathi', + mh: 'Marshallese', + mo: 'Moldavian', + mn: 'Mongolian', + na: 'Nauru', + nv: 'Navajo', + ng: 'Ndonga', + nd: 'Northern Ndebele', + ne: 'Nepali', + no: 'Norwegian', + nb: 'Norwegian bokmål', + nn: 'Norwegian nynorsk', + oc: 'Occitan', + oj: 'Ojibwe', + cu: 'Old Church Slavonic, Old Bulgarian', + or: 'Oriya', + om: 'Oromo (Afaan Oromo)', + os: 'Ossetian', + pi: 'Pāli', + ps: 'Pashto, Pushto', + fa: 'Persian (Farsi)', + pl: 'Polish', + pt: 'Portuguese', + pa: 'Punjabi (Eastern)', + qu: 'Quechua', + rm: 'Romansh', + ro: 'Romanian', + ru: 'Russian', + se: 'Sami', + sm: 'Samoan', + sg: 'Sango', + sa: 'Sanskrit', + sr: 'Serbian', + sh: 'Serbo-Croatian', + st: 'Sesotho', + tn: 'Setswana', + sn: 'Shona', + ii: 'Sichuan Yi, Nuosu', + sd: 'Sindhi', + si: 'Sinhalese', + ss: 'Siswati (Swati)', + sk: 'Slovak', + sl: 'Slovenian', + so: 'Somali', + nr: 'Southern Ndebele', + es: 'Spanish', + su: 'Sundanese', + sw: 'Swahili (Kiswahili)', + sv: 'Swedish', + tl: 'Tagalog', + ty: 'Tahitian', + tg: 'Tajik', + ta: 'Tamil', + tt: 'Tatar', + te: 'Telugu', + th: 'Thai', + bo: 'Tibetan', + ti: 'Tigrinya', + to: 'Tonga', + ts: 'Tsonga', + tr: 'Turkish', + tk: 'Turkmen', + tw: 'Twi', + ug: 'Uyghur', + uk: 'Ukrainian', + ur: 'Urdu', + uz: 'Uzbek', + ve: 'Venda', + vi: 'Vietnamese', + vo: 'Volapük', + wa: 'Wallon', + cy: 'Welsh', + wo: 'Wolof', + fy: 'Western Frisian', + xh: 'Xhosa', + yi: 'Yiddish', + yo: 'Yoruba', + za: 'Zhuang, Chuang', + zu: 'Zulu' +}; + + + diff --git a/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html b/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html index a4493e36fc..0cc854b272 100644 --- a/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html +++ b/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html @@ -1,4 +1,5 @@ +> + + + + + + +
diff --git a/src/app/my-dspace-page/collection-selector/collection-selector.component.spec.ts b/src/app/my-dspace-page/collection-selector/collection-selector.component.spec.ts index ce54d326fc..af043b447b 100644 --- a/src/app/my-dspace-page/collection-selector/collection-selector.component.spec.ts +++ b/src/app/my-dspace-page/collection-selector/collection-selector.component.spec.ts @@ -128,10 +128,13 @@ describe('CollectionSelectorComponent', () => { beforeEach(() => { scheduler = getTestScheduler(); - fixture = TestBed.createComponent(CollectionSelectorComponent); + fixture = TestBed.overrideComponent(CollectionSelectorComponent, { + set: { + template: '' + } + }).createComponent(CollectionSelectorComponent); component = fixture.componentInstance; fixture.detectChanges(); - }); it('should create', () => { diff --git a/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.spec.ts b/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.spec.ts index fb43c253eb..ed61fab1d6 100644 --- a/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.spec.ts +++ b/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.spec.ts @@ -16,10 +16,10 @@ import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; import { getMockScrollToService } from '../../shared/mocks/scroll-to-service.mock'; -import { UploaderService } from '../../shared/uploader/uploader.service'; +import { DragService } from '../../core/drag.service'; import { HostWindowService } from '../../shared/host-window.service'; import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub'; -import { UploaderComponent } from '../../shared/uploader/uploader.component'; +import { UploaderComponent } from '../../shared/upload/uploader/uploader.component'; import { HttpXsrfTokenExtractor } from '@angular/common/http'; import { CookieService } from '../../core/services/cookie.service'; import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock'; @@ -59,7 +59,7 @@ describe('MyDSpaceNewSubmissionComponent test', () => { NgbModal, ChangeDetectorRef, MyDSpaceNewSubmissionComponent, - UploaderService, + DragService, { provide: HttpXsrfTokenExtractor, useValue: new HttpXsrfTokenExtractorMock('mock-token') }, { provide: CookieService, useValue: new CookieServiceMock() }, { provide: HostWindowService, useValue: new HostWindowServiceStub(800) }, diff --git a/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts b/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts index b2ba6fe2af..0694fc63bf 100644 --- a/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts +++ b/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts @@ -8,13 +8,13 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { AuthService } from '../../core/auth/auth.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; -import { UploaderOptions } from '../../shared/uploader/uploader-options.model'; +import { UploaderOptions } from '../../shared/upload/uploader/uploader-options.model'; import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; import { hasValue } from '../../shared/empty.util'; import { SearchResult } from '../../shared/search/models/search-result.model'; import { CollectionSelectorComponent } from '../collection-selector/collection-selector.component'; -import { UploaderComponent } from '../../shared/uploader/uploader.component'; -import { UploaderError } from '../../shared/uploader/uploader-error.model'; +import { UploaderComponent } from '../../shared/upload/uploader/uploader.component'; +import { UploaderError } from '../../shared/upload/uploader/uploader-error.model'; import { Router } from '@angular/router'; /** diff --git a/src/app/my-dspace-page/my-dspace-page.module.ts b/src/app/my-dspace-page/my-dspace-page.module.ts index 2ccddd87f7..6ad50af96a 100644 --- a/src/app/my-dspace-page/my-dspace-page.module.ts +++ b/src/app/my-dspace-page/my-dspace-page.module.ts @@ -14,6 +14,7 @@ import { MyDSpaceNewSubmissionDropdownComponent } from './my-dspace-new-submissi import { MyDSpaceNewExternalDropdownComponent } from './my-dspace-new-submission/my-dspace-new-external-dropdown/my-dspace-new-external-dropdown.component'; import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component'; import { SearchModule } from '../shared/search/search.module'; +import { UploadModule } from '../shared/upload/upload.module'; const DECLARATIONS = [ MyDSpacePageComponent, @@ -30,7 +31,8 @@ const DECLARATIONS = [ SharedModule, SearchModule, MyDspacePageRoutingModule, - MyDspaceSearchModule.withEntryComponents() + MyDspaceSearchModule.withEntryComponents(), + UploadModule, ], declarations: DECLARATIONS, providers: [ diff --git a/src/app/my-dspace-page/my-dspace-search.module.ts b/src/app/my-dspace-page/my-dspace-search.module.ts index a0fa76fafa..1ce39991b3 100644 --- a/src/app/my-dspace-page/my-dspace-search.module.ts +++ b/src/app/my-dspace-page/my-dspace-search.module.ts @@ -17,9 +17,16 @@ import { PoolSearchResultDetailElementComponent } from '../shared/object-detail/ import { ClaimedApprovedSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component'; import { ClaimedDeclinedSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component'; import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module'; +import { ItemSubmitterComponent } from '../shared/object-collection/shared/mydspace-item-submitter/item-submitter.component'; +import { ItemDetailPreviewComponent } from '../shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component'; +import { ItemDetailPreviewFieldComponent } from '../shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview-field/item-detail-preview-field.component'; +import { ItemListPreviewComponent } from '../shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component'; +import { ThemedItemListPreviewComponent } from '../shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component'; +import { MyDSpaceItemStatusComponent } from '../shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component'; +import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module'; +import { MyDSpaceActionsModule } from '../shared/mydspace-actions/mydspace-actions.module'; const ENTRY_COMPONENTS = [ - // put only entry components that use custom decorator WorkspaceItemSearchResultListElementComponent, WorkflowItemSearchResultListElementComponent, ClaimedSearchResultListElementComponent, @@ -31,7 +38,17 @@ const ENTRY_COMPONENTS = [ WorkflowItemSearchResultDetailElementComponent, ClaimedTaskSearchResultDetailElementComponent, PoolSearchResultDetailElementComponent, - ItemSearchResultListElementSubmissionComponent + ItemSearchResultListElementSubmissionComponent, +]; + +const DECLARATIONS = [ + ...ENTRY_COMPONENTS, + ItemSubmitterComponent, + ItemDetailPreviewComponent, + ItemDetailPreviewFieldComponent, + ItemListPreviewComponent, + ThemedItemListPreviewComponent, + MyDSpaceItemStatusComponent, ]; @NgModule({ @@ -39,10 +56,12 @@ const ENTRY_COMPONENTS = [ CommonModule, SharedModule, MyDspacePageRoutingModule, - ResearchEntitiesModule.withEntryComponents() + MyDSpaceActionsModule, + ResearchEntitiesModule.withEntryComponents(), + JournalEntitiesModule.withEntryComponents(), ], declarations: [ - ...ENTRY_COMPONENTS + ...DECLARATIONS, ] }) diff --git a/src/app/navbar/navbar.component.scss b/src/app/navbar/navbar.component.scss index fed88c5c68..441ee82c96 100644 --- a/src/app/navbar/navbar.component.scss +++ b/src/app/navbar/navbar.component.scss @@ -1,5 +1,5 @@ nav.navbar { - border-bottom: 1px var(--bs-gray-400) solid; + border-bottom: 1px var(--ds-header-navbar-border-bottom-color) solid; align-items: baseline; } diff --git a/src/app/navbar/navbar.module.ts b/src/app/navbar/navbar.module.ts index af2bf036bd..de3244099d 100644 --- a/src/app/navbar/navbar.module.ts +++ b/src/app/navbar/navbar.module.ts @@ -21,6 +21,7 @@ const effects = [ const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator NavbarSectionComponent, + ExpandableNavbarSectionComponent, ThemedExpandableNavbarSectionComponent, ]; @@ -34,11 +35,9 @@ const ENTRY_COMPONENTS = [ CoreModule.forRoot() ], declarations: [ + ...ENTRY_COMPONENTS, NavbarComponent, ThemedNavbarComponent, - NavbarSectionComponent, - ExpandableNavbarSectionComponent, - ThemedExpandableNavbarSectionComponent, ], providers: [], exports: [ 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 bac922c73b..cf3b4b13d2 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 @@ -95,6 +95,10 @@ describe('RegisterEmailComponent', () => { comp.form.patchValue({email: 'valid@email.org'}); expect(comp.form.invalid).toBeFalse(); }); + it('should be valid when uppercase letters are used', () => { + comp.form.patchValue({email: 'VALID@email.org'}); + expect(comp.form.invalid).toBeFalse(); + }); }); describe('register', () => { it('should send a registration to the service and on success display a message and return to home', () => { 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 ced87b9e75..561bd53e67 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -79,7 +79,9 @@ export class RegisterEmailFormComponent implements OnInit { this.form = this.formBuilder.group({ email: new FormControl('', { validators: [Validators.required, - Validators.pattern('^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$') + // Regex pattern borrowed from HTML5 specs for a valid email address: + // https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address + Validators.pattern('^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$') ], }) }); diff --git a/src/app/search-page/search-page.module.ts b/src/app/search-page/search-page.module.ts index 758eca15c0..19fd9bd309 100644 --- a/src/app/search-page/search-page.module.ts +++ b/src/app/search-page/search-page.module.ts @@ -7,7 +7,6 @@ import { ConfigurationSearchPageGuard } from './configuration-search-page.guard' import { SearchTrackerComponent } from './search-tracker.component'; import { StatisticsModule } from '../statistics/statistics.module'; import { SearchPageComponent } from './search-page.component'; -import { SidebarFilterService } from '../shared/sidebar/filter/sidebar-filter.service'; import { SearchFilterService } from '../core/shared/search/search-filter.service'; import { SearchConfigurationService } from '../core/shared/search/search-configuration.service'; import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module'; @@ -34,7 +33,6 @@ const components = [ declarations: components, providers: [ SidebarService, - SidebarFilterService, SearchFilterService, ConfigurationSearchPageGuard, SearchConfigurationService diff --git a/src/app/shared/collection-dropdown/themed-collection-dropdown.component.ts b/src/app/shared/collection-dropdown/themed-collection-dropdown.component.ts new file mode 100644 index 0000000000..27c883099d --- /dev/null +++ b/src/app/shared/collection-dropdown/themed-collection-dropdown.component.ts @@ -0,0 +1,33 @@ +import { CollectionDropdownComponent, CollectionListEntry } from './collection-dropdown.component'; +import { ThemedComponent } from '../theme-support/themed.component'; +import { Component, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'ds-themed-collection-dropdown', + styleUrls: [], + templateUrl: '../../shared/theme-support/themed.component.html', +}) +export class ThemedCollectionDropdownComponent extends ThemedComponent { + + @Input() entityType: string; + + @Output() searchComplete = new EventEmitter(); + + @Output() theOnlySelectable = new EventEmitter(); + + @Output() selectionChange = new EventEmitter(); + + protected inAndOutputNames: (keyof CollectionDropdownComponent & keyof this)[] = ['entityType', 'searchComplete', 'theOnlySelectable', 'selectionChange']; + + protected getComponentName(): string { + return 'CollectionDropdownComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../themes/${themeName}/app/shared/collection-dropdown/collection-dropdown.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./collection-dropdown.component`); + } +} diff --git a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts index 29be240753..23dfca8616 100644 --- a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts +++ b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts @@ -17,8 +17,8 @@ import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.mod import { ResourceType } from '../../../../core/shared/resource-type'; import { hasValue, isNotEmpty } from '../../../empty.util'; import { NotificationsService } from '../../../notifications/notifications.service'; -import { UploaderOptions } from '../../../uploader/uploader-options.model'; -import { UploaderComponent } from '../../../uploader/uploader.component'; +import { UploaderOptions } from '../../../upload/uploader/uploader-options.model'; +import { UploaderComponent } from '../../../upload/uploader/uploader.component'; import { Operation } from 'fast-json-patch'; import { NoContent } from '../../../../core/shared/NoContent.model'; import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; diff --git a/src/app/shared/comcol/comcol.module.ts b/src/app/shared/comcol/comcol.module.ts index 094387929a..efbcedf2c6 100644 --- a/src/app/shared/comcol/comcol.module.ts +++ b/src/app/shared/comcol/comcol.module.ts @@ -15,6 +15,7 @@ import { ThemedComcolPageBrowseByComponent } from './comcol-page-browse-by/theme import { ComcolRoleComponent } from './comcol-forms/edit-comcol-page/comcol-role/comcol-role.component'; import { SharedModule } from '../shared.module'; import { FormModule } from '../form/form.module'; +import { UploadModule } from '../upload/upload.module'; const COMPONENTS = [ ComcolPageContentComponent, @@ -28,9 +29,7 @@ const COMPONENTS = [ ComcolPageBrowseByComponent, ThemedComcolPageBrowseByComponent, ComcolRoleComponent, - ThemedComcolPageHandleComponent - ]; @NgModule({ @@ -40,10 +39,12 @@ const COMPONENTS = [ imports: [ CommonModule, FormModule, - SharedModule + SharedModule, + UploadModule, ], exports: [ - ...COMPONENTS + ...COMPONENTS, + UploadModule, ] }) export class ComcolModule { } diff --git a/src/app/shared/cookies/browser-klaro.service.ts b/src/app/shared/cookies/browser-klaro.service.ts index 56e371242b..2b09c0bf15 100644 --- a/src/app/shared/cookies/browser-klaro.service.ts +++ b/src/app/shared/cookies/browser-klaro.service.ts @@ -1,5 +1,4 @@ -import { Injectable } from '@angular/core'; -import { setup, show } from 'klaro/dist/klaro-no-translations'; +import { Inject, Injectable, InjectionToken } from '@angular/core'; import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { AuthService } from '../../core/auth/auth.service'; import { TranslateService } from '@ngx-translate/core'; @@ -43,6 +42,17 @@ const cookiePurposeMessagePrefix = 'cookies.consent.purpose.'; */ const updateDebounce = 300; +/** + * By using this injection token instead of importing directly we can keep Klaro out of the main bundle + */ +const LAZY_KLARO = new InjectionToken>( + 'Lazily loaded Klaro', + { + providedIn: 'root', + factory: async () => (await import('klaro/dist/klaro-no-translations')), + } +); + /** * Browser implementation for the KlaroService, representing a service for handling Klaro consent preferences and UI */ @@ -65,7 +75,9 @@ export class BrowserKlaroService extends KlaroService { private authService: AuthService, private ePersonService: EPersonDataService, private configService: ConfigurationDataService, - private cookieService: CookieService) { + private cookieService: CookieService, + @Inject(LAZY_KLARO) private lazyKlaro: Promise, + ) { super(); } @@ -103,7 +115,6 @@ export class BrowserKlaroService extends KlaroService { if (hideRegistrationVerification) { servicesToHideArray.push(CAPTCHA_NAME); } - console.log(servicesToHideArray); return servicesToHideArray; }) ); @@ -135,8 +146,7 @@ export class BrowserKlaroService extends KlaroService { this.translateConfiguration(); this.klaroConfig.services = this.filterConfigServices(servicesToHide); - - setup(this.klaroConfig); + this.lazyKlaro.then(({ setup }) => setup(this.klaroConfig)); }); } @@ -220,7 +230,7 @@ export class BrowserKlaroService extends KlaroService { * Show the cookie consent form */ showSettings() { - show(this.klaroConfig); + this.lazyKlaro.then(({show}) => show(this.klaroConfig)); } /** diff --git a/src/app/shared/dso-page/dso-page.module.ts b/src/app/shared/dso-page/dso-page.module.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.ts b/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.ts index ab48d058ca..cc1f9822d6 100644 --- a/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.ts +++ b/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.ts @@ -53,8 +53,9 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent * Perform a search for authorized collections with the current query and page * @param query Query to search objects for * @param page Page to retrieve + * @param useCache Whether or not to use the cache */ - search(query: string, page: number): Observable>>> { + search(query: string, page: number, useCache: boolean = true): Observable>>> { let searchListService$: Observable>> = null; const findOptions: FindListOptions = { currentPage: page, @@ -69,7 +70,7 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent findOptions); } else { searchListService$ = this.collectionDataService - .getAuthorizedCollection(query, findOptions, true, false, followLink('parentCommunity')); + .getAuthorizedCollection(query, findOptions, useCache, false, followLink('parentCommunity')); } return searchListService$.pipe( getFirstCompletedRemoteData(), diff --git a/src/app/shared/dso-selector/dso-selector/dso-selector.component.html b/src/app/shared/dso-selector/dso-selector/dso-selector.component.html index 8abb8ad558..c4f5dbc4cd 100644 --- a/src/app/shared/dso-selector/dso-selector/dso-selector.component.html +++ b/src/app/shared/dso-selector/dso-selector/dso-selector.component.html @@ -21,12 +21,12 @@