Compare commits

...

417 Commits

Author SHA1 Message Date
Tim Donohue
2bf9e11a7c Update version tag for release 2025-07-14 16:36:18 -05:00
Tim Donohue
f0cba80262 Sync i18n files to prepare for 9.1 release. 2025-07-11 13:29:47 -05:00
Tim Donohue
ad51dc884d Merge pull request #4541 from DSpace/backport-4487-to-dspace-9_x
[Port dspace-9_x] Russian translation
2025-07-10 16:47:28 -05:00
Tim Donohue
28a914a335 Enable Russian by default. Reorder alphabetical by language code
(cherry picked from commit 032381008e)
2025-07-10 21:09:03 +00:00
Sergio Fernández Celorio
3cd4959d8f Fixed lint errors
(cherry picked from commit f85042e0d6)
2025-07-10 21:09:03 +00:00
Sergio Fernández Celorio
5888277ca7 Russian translation
(cherry picked from commit b6ee8dff9f)
2025-07-10 21:09:03 +00:00
Tim Donohue
e258fb7af7 Merge pull request #4540 from alexandrevryghem/bootstrap-issues_contribute-9_x
[Port dspace-9_x] Migrated remaining bootstrap 4 classes & other minor css fixes
2025-07-10 12:04:57 -05:00
Alexandre Vryghem
cbb01c514d Merge branch 'bootstrap-issues_contribute-9.0' into bootstrap-issues_contribute-9_x 2025-07-10 00:49:51 +02:00
Alexandre Vryghem
1bc47d06b7 Merge branch 'w2p-132350_minor-css-fixes_contribute-7.6' into bootstrap-issues_contribute-9_x 2025-07-10 00:48:15 +02:00
Tim Donohue
d0f61c1e50 Merge pull request #4498 from DSpace/dependabot/npm_and_yarn/dspace-9_x/bootstrap-5.3.7
Bump bootstrap from 5.3.6 to 5.3.7
2025-07-09 08:56:47 -05:00
dependabot[bot]
44c7e4b524 Bump bootstrap from 5.3.6 to 5.3.7
Bumps [bootstrap](https://github.com/twbs/bootstrap) from 5.3.6 to 5.3.7.
- [Release notes](https://github.com/twbs/bootstrap/releases)
- [Commits](https://github.com/twbs/bootstrap/compare/v5.3.6...v5.3.7)

---
updated-dependencies:
- dependency-name: bootstrap
  dependency-version: 5.3.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-08 21:20:23 +00:00
Tim Donohue
598f5884e4 Merge pull request #4473 from DSpace/dependabot/npm_and_yarn/dspace-9_x/angular-43581e9bed
Bump the angular group with 3 updates
2025-07-08 16:18:28 -05:00
Alexandre Vryghem
e3b5405698 132007: Auto-fixed the ng generate @angular/core:control-flow issues 2025-07-08 12:51:31 +02:00
Alexandre Vryghem
b4d694e0c0 132007: Bootstrap fixes 2025-07-08 11:06:30 +02:00
dependabot[bot]
36c19ef976 Bump the angular group with 3 updates
Bumps the angular group with 3 updates: [@angular/ssr](https://github.com/angular/angular-cli), [@angular-devkit/build-angular](https://github.com/angular/angular-cli) and [@angular/cli](https://github.com/angular/angular-cli).


Updates `@angular/ssr` from 18.2.19 to 18.2.20
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/18.2.19...18.2.20)

Updates `@angular-devkit/build-angular` from 18.2.19 to 18.2.20
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/18.2.19...18.2.20)

Updates `@angular/cli` from 18.2.19 to 18.2.20
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/18.2.19...18.2.20)

---
updated-dependencies:
- dependency-name: "@angular/ssr"
  dependency-version: 18.2.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular-devkit/build-angular"
  dependency-version: 18.2.20
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/cli"
  dependency-version: 18.2.20
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-07 06:26:31 +00:00
Tim Donohue
8685e1398b Merge pull request #4526 from DSpace/backport-4521-to-dspace-9_x
[Port dspace-9_x] Update pt-PT.json5
2025-07-02 15:39:14 -05:00
Ricardo Saraiva
4956d26f69 Update pt-PT.json5
Minor corretions and updates.

(cherry picked from commit d17f0a2a3d)
2025-07-02 20:38:49 +00:00
Tim Donohue
5c419bd975 Merge pull request #4522 from atmire/vocabulary-preloadlevel-fix-9_x
[Port dspace-9_x] Vocabulary preloadlevel fix
2025-07-02 11:26:06 -05:00
Andreas Awouters
c253cff188 Merge branch 'w2p-122839_vocabulary-preloadlevel-fix' into vocabulary-preloadlevel-fix-9_x
# Conflicts:
#	src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.spec.ts
#	src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts
2025-07-02 10:18:33 +02:00
Zoltán Kanász-Nagy
9be1562083 Improve Hungarian translations in DSpace 9.0 (#4506)
* QREPO-0 improving HU translations

* QREPO-0 improving HU translations

* QREPO-0 HU translations: grant-request-copy messages

* QREPO-0 improving HU translations

* QREPO-0 HU translation improvements

* QREPO-0 HU translation improvements

* QREPO-0 HU translation improvements

* QREPO-0 HU translation improvements

* QREPO-0 HU translations improvements

* QREPO-0 HU translation improvements

* QREPO-0 HU translation improvements

* QREPO-0 removing TODO comments

* QREPO-0 inserting some missing new lines

* QREPO-0 HU translation improvements
2025-06-30 14:37:11 -05:00
Alexandre Vryghem
45934e31f2 132350: Fixed some SASS variables not being overridable in the themes
Also fixed the authority icons not being totally centered in the onebox submission fields
2025-06-25 11:51:58 +02:00
Alan Orth
ff6d0ddc9f Merge pull request #4476 from DSpace/dependabot/npm_and_yarn/dspace-9_x/sass-63defccb8e 2025-06-17 08:23:30 +03:00
Alan Orth
e017f99125 Merge pull request #4477 from DSpace/dependabot/npm_and_yarn/dspace-9_x/axios-1.10.0
Bump axios from 1.9.0 to 1.10.0
2025-06-17 07:00:56 +03:00
dependabot[bot]
945f1bcec3 Bump axios from 1.9.0 to 1.10.0
Bumps [axios](https://github.com/axios/axios) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 07:16:12 +00:00
dependabot[bot]
cecb8f6c69 Bump sass from 1.89.1 to 1.89.2 in the sass group
Bumps the sass group with 1 update: [sass](https://github.com/sass/dart-sass).


Updates `sass` from 1.89.1 to 1.89.2
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.89.1...1.89.2)

---
updated-dependencies:
- dependency-name: sass
  dependency-version: 1.89.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: sass
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 07:11:18 +00:00
Alan Orth
b26b14b764 Merge pull request #4455 from DSpace/backport-4389-to-dspace-9_x
[Port dspace-9_x] Confirmation dialog before deleting Group
2025-06-14 08:02:44 +03:00
Jukka Lipka
2ed43035eb fix: add styles to confirmation modal
Words are forced to break if no more space available

(cherry picked from commit 20510591e4)
2025-06-13 18:39:45 +00:00
Jukka Lipka
d3b098b407 fix: cleanup / unnecessary changes reverted
(cherry picked from commit 55d2f0ce38)
2025-06-13 18:39:45 +00:00
Jukka Lipka
f506be3dea fix: added translations
(cherry picked from commit 137bbc35bd)
2025-06-13 18:39:45 +00:00
Jukka Lipka
29e8c29ade fix: confirm dialog for group deletion
Introduced a confirmation modal before deleting groups in both the Group Registry and Comcol Role components. This ensures users explicitly confirm deletion, reducing accidental data loss. Updated relevant tests and added new translations for modal text.

(cherry picked from commit 57bf254bec)
2025-06-13 18:39:45 +00:00
Tim Donohue
3acdb3124e Merge pull request #4452 from DSpace/backport-4434-to-dspace-9_x
[Port dspace-9_x] Resolve field instance models using index when filtering errors
2025-06-12 12:48:04 -05:00
kshepherd
d0464c4886 Merge pull request #4447 from ybnd/defer-geospatial-map-components_9.x
Lazy-load geospatial map code (9.x)
2025-06-12 19:21:18 +02:00
Kim Shepherd
036dba942d Resolve field instance models using index when filtering errors
(cherry picked from commit d218e22944)
2025-06-12 16:40:49 +00:00
Yury Bondarenko
6c3bbf617c Remove unneeded platform checks 2025-06-12 17:55:05 +02:00
Yury Bondarenko
c147354ee1 Fix failing unit test
According to the Angular docs it should be possible to "trigger" the different states of the @defer block
https://angular.dev/guide/templates/defer#testing-defer-blocks
...but I wasn't able to get this example to work either.

This was the only case where we directly test an inner component's reflected inputs, so IMO it makes sense to "let it go".
2025-06-11 16:27:12 +02:00
Yury Bondarenko
343718d07b Defer geospatial map components 2025-06-11 14:28:37 +02:00
Alan Orth
f8c6a80fbe Merge pull request #4444 from DSpace/dependabot/npm_and_yarn/dspace-9_x/babel/runtime-7.27.6
Bump @babel/runtime from 7.27.4 to 7.27.6
2025-06-11 12:20:38 +03:00
dependabot[bot]
a009f3549a Bump @babel/runtime from 7.27.4 to 7.27.6
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.27.4 to 7.27.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.6/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 7.27.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 06:02:37 +00:00
Alan Orth
85128a4cfd Merge pull request #4432 from DSpace/dependabot/npm_and_yarn/dspace-9_x/sass-d83863101d
Bump sass from 1.89.0 to 1.89.1 in the sass group
2025-06-05 08:53:10 +03:00
dependabot[bot]
c9e6a65eec Bump sass from 1.89.0 to 1.89.1 in the sass group
Bumps the sass group with 1 update: [sass](https://github.com/sass/dart-sass).


Updates `sass` from 1.89.0 to 1.89.1
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.89.0...1.89.1)

---
updated-dependencies:
- dependency-name: sass
  dependency-version: 1.89.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: sass
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-04 13:49:33 +00:00
Alan Orth
790e7d580b Merge pull request #4429 from DSpace/backport-4076-to-dspace-9_x
[Port dspace-9_x] fix typo in German translation of bitstream.edit.form.description.hint
2025-06-04 14:06:39 +03:00
Sascha Szott
eb8f35f9f1 fix typo in German translation of bitstream.edit.form.description.hint
(cherry picked from commit c72af8edb5)
2025-06-04 09:52:37 +00:00
Alan Orth
f798b30c06 Merge pull request #4408 from DSpace/dependabot/npm_and_yarn/dspace-9_x/postcss-3f4fa9ea8d
Bump postcss from 8.5.3 to 8.5.4 in the postcss group
2025-06-03 09:08:24 +03:00
Alan Orth
8fb625ed73 Merge pull request #4413 from DSpace/dependabot/npm_and_yarn/dspace-9_x/babel/runtime-7.27.4
Bump @babel/runtime from 7.27.3 to 7.27.4
2025-06-03 09:05:26 +03:00
Tim Donohue
ba3c696c47 Merge pull request #4419 from DSpace/backport-4362-to-dspace-9_x
[Port dspace-9_x] add Gujarati(gu) and marathi(mr) json5 file
2025-06-02 13:00:36 -05:00
gaurav patel
d7d598e972 Rename Gu.json5 to gu.json5
(cherry picked from commit 5ac8dc13ac)
2025-06-02 17:13:50 +00:00
gauravd2t
0a6550986b change file name
(cherry picked from commit ce34bd271d)
2025-06-02 17:13:50 +00:00
gauravd2t
36878b119a add Gujarati and marathi json5 file
(cherry picked from commit dade46ca79)
2025-06-02 17:13:50 +00:00
dependabot[bot]
e7b85eee5b Bump @babel/runtime from 7.27.3 to 7.27.4
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.27.3 to 7.27.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.4/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 7.27.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 06:47:51 +00:00
dependabot[bot]
c81ddf1520 Bump postcss from 8.5.3 to 8.5.4 in the postcss group
Bumps the postcss group with 1 update: [postcss](https://github.com/postcss/postcss).


Updates `postcss` from 8.5.3 to 8.5.4
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.3...8.5.4)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: postcss
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 06:42:02 +00:00
Tim Donohue
f626e0baf9 Merge pull request #4394 from DSpace/dependabot/npm_and_yarn/dspace-9_x/testing-ff22ddd0c9
Bump ng-mocks from 14.13.4 to 14.13.5 in the testing group
2025-05-29 08:49:17 -05:00
Alan Orth
fc2c32e1c6 Merge pull request #4397 from DSpace/dependabot/npm_and_yarn/dspace-9_x/babel/runtime-7.27.3
Bump @babel/runtime from 7.27.1 to 7.27.3
2025-05-29 08:40:53 +03:00
Tim Donohue
e359fb9169 Merge pull request #4396 from DSpace/dependabot/npm_and_yarn/dspace-9_x/webpack-51a062295f
Bump webpack from 5.99.8 to 5.99.9 in the webpack group
2025-05-28 15:49:51 -05:00
dependabot[bot]
c947f49820 Bump @babel/runtime from 7.27.1 to 7.27.3
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.27.1 to 7.27.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.3/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 7.27.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-28 18:30:17 +00:00
dependabot[bot]
6ac6ac8998 Bump webpack from 5.99.8 to 5.99.9 in the webpack group
Bumps the webpack group with 1 update: [webpack](https://github.com/webpack/webpack).


Updates `webpack` from 5.99.8 to 5.99.9
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.99.8...v5.99.9)

---
updated-dependencies:
- dependency-name: webpack
  dependency-version: 5.99.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: webpack
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-28 18:29:56 +00:00
dependabot[bot]
b726688b47 Bump ng-mocks from 14.13.4 to 14.13.5 in the testing group
Bumps the testing group with 1 update: [ng-mocks](https://github.com/help-me-mom/ng-mocks).


Updates `ng-mocks` from 14.13.4 to 14.13.5
- [Release notes](https://github.com/help-me-mom/ng-mocks/releases)
- [Changelog](https://github.com/help-me-mom/ng-mocks/blob/master/CHANGELOG.md)
- [Commits](https://github.com/help-me-mom/ng-mocks/compare/v14.13.4...v14.13.5)

---
updated-dependencies:
- dependency-name: ng-mocks
  dependency-version: 14.13.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: testing
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-28 18:29:21 +00:00
Tim Donohue
d87b83f9e9 Update version tag for development of next release 2025-05-23 11:06:00 -05:00
Tim Donohue
bcc437d029 Update version tag for release 2025-05-23 11:00:57 -05:00
Tim Donohue
269404cb4f Merge pull request #4367 from tdonohue/sync_i18n
Sync all i18n files with en.json5 for 9.0 release
2025-05-22 14:56:02 -05:00
Tim Donohue
120817f5f4 Sync all i18n files with en.json5 2025-05-22 14:14:17 -05:00
Tim Donohue
c8a136f0d5 Merge pull request #4364 from tdonohue/fix_typo
Remove stray comma from `config.example.yml` which makes example config invalid
2025-05-22 12:04:52 -05:00
Tim Donohue
bcd2081ee3 Remove stray comma 2025-05-22 10:36:44 -05:00
Tim Donohue
e623b68d91 Merge pull request #4359 from GauravD2t/translation_hindi_gu_ma
Re-translated Hindi(hi) language labels
2025-05-21 16:01:55 -05:00
Tim Donohue
814b411192 Merge pull request #4360 from the-library-code/fix_submission_section_enabling
Fix and simplify submission section 'enabled' logic
2025-05-21 15:12:54 -05:00
Kim Shepherd
f77dfa37da Fix and simplify submission section 'enabled' logic
Fixes cases where a section needs to be disabled on save/init
because it is empty and non-mandatory
2025-05-21 19:29:08 +02:00
gauravd2t
04964787fa Trailing spaces not allowed 2025-05-21 13:25:25 +05:30
gauravd2t
20748d2f64 remove Trailing spaces and Expected indentation of 2 2025-05-21 12:50:33 +05:30
gauravd2t
b5a29fa400 change hindi translation label 2025-05-21 10:48:13 +05:30
Tim Donohue
2fffda3a28 Merge pull request #4351 from DSpace/dependabot/npm_and_yarn/eslint-1ccb09f07e
Bump eslint-plugin-jsonc from 2.20.0 to 2.20.1 in the eslint group
2025-05-19 11:07:27 -05:00
Tim Donohue
537fb4bfda Merge pull request #4352 from DSpace/dependabot/npm_and_yarn/sass-5219375de9
Bump sass from 1.88.0 to 1.89.0 in the sass group
2025-05-19 11:05:16 -05:00
Tim Donohue
4ab895f821 Merge pull request #4353 from DSpace/dependabot/npm_and_yarn/isbot-5.1.28
Bump isbot from 5.1.27 to 5.1.28
2025-05-19 10:23:00 -05:00
dependabot[bot]
880b9e4130 Bump isbot from 5.1.27 to 5.1.28
Bumps [isbot](https://github.com/omrilotan/isbot) from 5.1.27 to 5.1.28.
- [Changelog](https://github.com/omrilotan/isbot/blob/main/CHANGELOG.md)
- [Commits](https://github.com/omrilotan/isbot/compare/v5.1.27...v5.1.28)

---
updated-dependencies:
- dependency-name: isbot
  dependency-version: 5.1.28
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 02:50:08 +00:00
dependabot[bot]
d965968fc9 Bump sass from 1.88.0 to 1.89.0 in the sass group
Bumps the sass group with 1 update: [sass](https://github.com/sass/dart-sass).


Updates `sass` from 1.88.0 to 1.89.0
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.88.0...1.89.0)

---
updated-dependencies:
- dependency-name: sass
  dependency-version: 1.89.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: sass
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 02:44:15 +00:00
dependabot[bot]
3f02846fed Bump eslint-plugin-jsonc from 2.20.0 to 2.20.1 in the eslint group
Bumps the eslint group with 1 update: [eslint-plugin-jsonc](https://github.com/ota-meshi/eslint-plugin-jsonc).


Updates `eslint-plugin-jsonc` from 2.20.0 to 2.20.1
- [Release notes](https://github.com/ota-meshi/eslint-plugin-jsonc/releases)
- [Changelog](https://github.com/ota-meshi/eslint-plugin-jsonc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ota-meshi/eslint-plugin-jsonc/compare/v2.20.0...v2.20.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsonc
  dependency-version: 2.20.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: eslint
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 02:43:17 +00:00
Tim Donohue
a36e085920 Merge pull request #3390 from alexandrevryghem/w2p-117616_ported-custom-atmire-eslint-rules
Enforce DSpace-specific coding conventions
2025-05-16 16:03:49 -05:00
Alexandre Vryghem
443809f3b2 117616: Disabled unstable linting rules 2025-05-16 21:52:22 +02:00
Alexandre Vryghem
e1f3cc09cb 117616: Auto-fixed new linting rules 2025-05-16 18:21:05 +02:00
Alexandre Vryghem
f14cd51678 117616: Allow CallExpressions like forwardRef in the standalone imports array as well 2025-05-16 18:13:51 +02:00
Alexandre Vryghem
f0a00aca95 117616: Added support to alias-import to disallow aliasing certain imports 2025-05-16 18:13:51 +02:00
Alexandre Vryghem
aed0460cfe 117616: Created custom sort-standalone-imports rule 2025-05-16 18:13:51 +02:00
Alexandre Vryghem
6017537107 117616: Fixed unique-decorators rule not working in IntelliJ because the decorator calls were registered every time the file was updated 2025-05-16 18:13:50 +02:00
Alexandre Vryghem
c6657b5f9b 117616: Added default options in .eslintrc.json
Also enforced the trailing commas rule in .editorconfig for IntelliJ
2025-05-16 18:13:50 +02:00
Alexandre Vryghem
2d8fada3c4 117616: Created documentation for the custom lint options 2025-05-16 18:13:50 +02:00
Alexandre Vryghem
eb0572640b 117616: Ported themed-decorators rule 2025-05-16 18:13:50 +02:00
Alexandre Vryghem
598471913e 117616: Fixed unique-decorators rule to work across multiple files
Also moved the logic to filter out unwanted decorators to the ESLint's selector and fixed a typo
2025-05-16 18:13:50 +02:00
Yury Bondarenko
8ff5a23c40 117616: Fix rule creator call & add structure test case 2025-05-16 18:13:49 +02:00
Alexandre Vryghem
01c8c60624 117616: Ported unique-decorators rule 2025-05-16 18:13:49 +02:00
Alexandre Vryghem
4200357100 117616: Ported themed-wrapper-no-input-defaults rule 2025-05-16 18:13:49 +02:00
Alexandre Vryghem
39fec7ce64 117616: Replace all the usages of the import 2025-05-16 18:13:49 +02:00
Alexandre Vryghem
f10447b8d3 117616: Fixed alias-imports edge case where duplicate imports with different aliases were both kept 2025-05-16 18:13:49 +02:00
Alexandre Vryghem
abb03799e0 117616: Ported alias-imports rule 2025-05-16 18:13:48 +02:00
Tim Donohue
d4aedbbb51 Merge pull request #3613 from atmire/accessibility-settings-main
Accessibility settings page
2025-05-16 11:00:21 -05:00
Andreas Awouters
d1beb28de0 119602: Open tooltip to left of info icon 2025-05-16 17:10:40 +02:00
Andreas Awouters
e3152c94e0 119602: Align accessibility link with other footer links 2025-05-16 17:01:19 +02:00
Andreas Awouters
e9b8b25da5 Merge remote-tracking branch 'dspace/main' into accessibility-settings-main
# Conflicts:
#	cypress/support/e2e.ts
#	src/app/core/auth/auth.service.ts
#	src/app/footer/footer.component.html
2025-05-16 08:21:14 +02:00
Tim Donohue
5cdc72ddbf Merge pull request #4341 from 4Science/task/main/CST-19327
Use ellipsis for truncatable parts
2025-05-15 14:55:31 -05:00
Tim Donohue
be3dbd604b Merge pull request #4340 from alexandrevryghem/w2p-130424_impossible-to-add-new-values-for-fields-without-qualifiers_contribute-main
Fix unqualified metadata field validation in Edit Metadata tab by sorting fields in validation request
2025-05-15 14:20:13 -05:00
Tim Donohue
f277832dc1 Merge pull request #4329 from 4Science/task/main/DURACOM-368
Enforce standalone components/directives/pipes via linting
2025-05-15 13:47:54 -05:00
Tim Donohue
ba3c43ac9a Merge pull request #3889 from 4Science/task/main/DURACOM-309_from-community-main
Add new orejime cookie for correlation-id header
2025-05-15 10:38:34 -05:00
Andrea Barbasso
520831bd33 [DURACOM-309] add new enableCookieConsentPopup property to config.example.yml 2025-05-15 16:22:26 +02:00
Andrea Barbasso
6703a07207 [CST-19327] use ellipsis for truncatable parts 2025-05-15 14:17:59 +02:00
Andrea Barbasso
056351ddfe [DURACOM-309] fix error occurring when preferences is undefined 2025-05-15 13:27:58 +02:00
Andrea Barbasso
87e4a33621 Merge branch 'main' into task/main/DURACOM-309_from-community-main
# Conflicts:
#	src/app/footer/footer.component.ts
2025-05-15 12:17:27 +02:00
Andrea Barbasso
9be8a87a57 [DURACOM-309] fix pipeline, remove cookie settings link from footer if popup is disabled 2025-05-15 11:21:07 +02:00
Alexandre Vryghem
15f96f3c82 Merge branch 'w2p-130424_impossible-to-add-new-values-for-fields-without-qualifiers_contribute-7.6' 2025-05-15 01:25:53 +02:00
Tim Donohue
98da22047a Merge pull request #4335 from alexandrevryghem/w2p-130479_fix-accessibility-issues-for-the-language-toggle-and-recent-submissions_contribute-main
Improve accessibility for screen readers
2025-05-14 16:41:03 -05:00
Andrea Barbasso
887bf0d266 [DURACOM-309] fix circular dependency 2025-05-14 17:43:12 +02:00
Andrea Barbasso
f1a1aebe33 [DURACOM-309] improve e2e pipeline, fix cookie issues 2025-05-14 17:12:13 +02:00
Tim Donohue
ebd69c04e8 Merge pull request #4317 from DSpace/dependabot/npm_and_yarn/sass-d0f2b75659
Bump sass from 1.87.0 to 1.88.0 in the sass group
2025-05-14 09:59:48 -05:00
Alexandre Vryghem
aba3a9439d 129964: Fixed the header role structure being invalid in the custom theme
- Replaced the menubar role from the parent of all the header buttons like lang switch, auth menu & help toggle with toolbar
- Replaced the remaining `<a>` buttons in the header with `<button>` to make them expandable with space
- Fixed accessibility issues flagged by axe DevTools in the user menu dropdown
2025-05-14 16:55:07 +02:00
dependabot[bot]
63c2ac0a96 Bump sass from 1.87.0 to 1.88.0 in the sass group
Bumps the sass group with 1 update: [sass](https://github.com/sass/dart-sass).


Updates `sass` from 1.87.0 to 1.88.0
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.87.0...1.88.0)

---
updated-dependencies:
- dependency-name: sass
  dependency-version: 1.88.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: sass
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-13 22:06:15 +00:00
Tim Donohue
96d2b66068 Merge pull request #4318 from DSpace/dependabot/npm_and_yarn/webpack-ab0e10ec03
Bump webpack from 5.99.7 to 5.99.8 in the webpack group
2025-05-13 17:04:45 -05:00
Tim Donohue
d32681bbe9 Merge pull request #4072 from rsaraivac/patch-4
Update pt-PT.json5
2025-05-13 16:53:58 -05:00
Tim Donohue
dfc1ecef25 Merge pull request #3373 from DanGastardelli/AdjustTextNotThumbnail
Adjust the font size of the 'No thumbnails available' text.
2025-05-13 16:41:04 -05:00
Tim Donohue
cef7cae573 Merge pull request #4060 from alexandrevryghem/w2p-127655_fix-submission-form-getting-stuck-in-loop_contribute-main
Fix infinite loading submission forms
2025-05-13 15:02:10 -05:00
Alexandre Vryghem
be85947fb6 Merge branch 'w2p-130479_fix-accessibility-issues-for-the-language-toggle-and-recent-submissions' 2025-05-13 20:28:00 +02:00
Alexandre Vryghem
830be1f15a 129964: Hide duplicate links for keyboard navigators 2025-05-13 18:59:04 +02:00
Alexandre Vryghem
8ff943b084 129964: Made DSO badges more screen-reader friendly
- Added commas between the labels to prevent screen readers from reading it as a sentence
- Added hidden text describing what the badges represent
2025-05-13 18:52:14 +02:00
Alexandre Vryghem
545b2ff8a7 130479: Hide no results status when query is empty 2025-05-13 18:30:38 +02:00
wout
163cc75437 130479: using ul > li in recent-item + "Load more ..." to "<a>" element 2025-05-13 18:30:38 +02:00
wout
e9940f4005 130479: add aria-selected to selected language + globe button uses a button element 2025-05-13 18:30:37 +02:00
Ricardo Saraiva
8c8ff6189b Update pt-PT.json5
Lint correction.
2025-05-13 16:41:00 +01:00
Ricardo Saraiva
788f173066 Merge branch 'main' into patch-4 2025-05-13 16:29:45 +01:00
Tim Donohue
daaa8109d6 Merge pull request #4326 from tdonohue/test_for_error_codes
Add basic automated tests to ensure common HTTP Return Codes do not break
2025-05-13 09:35:43 -05:00
Andrea Barbasso
7ec20edbc0 [DURACOM-368] enforce standalone components/directives/pipes via linting 2025-05-13 15:22:07 +02:00
Tim Donohue
80c38f0c3d Add basic tests to our build to verify correct HTTP return codes for 301, 403, 404 and 500. 2025-05-12 16:46:48 -05:00
Tim Donohue
6a138eccc6 Merge pull request #4227 from 4Science/task/main/DURACOM-344
Remove `ssr.paths` configuration and replace with `ssr.excludePathPatterns` which excludes specific paths from SSR
2025-05-12 14:11:34 -05:00
Tim Donohue
1fe9065fca Merge pull request #4324 from 4Science/task/main/DURACOM-367
Fix matomo SSR error on bitstream download page
2025-05-12 12:17:59 -05:00
FrancescoMolinaro
c442d35505 [DURACOM-344] refactor solution to avoid double slashes 2025-05-12 17:34:07 +02:00
Tim Donohue
7da8f92b15 Merge pull request #4319 from DSpace/dependabot/npm_and_yarn/bootstrap-5.3.6
Bump bootstrap from 5.3.5 to 5.3.6
2025-05-12 10:14:05 -05:00
dependabot[bot]
de66aa294f Bump bootstrap from 5.3.5 to 5.3.6
Bumps [bootstrap](https://github.com/twbs/bootstrap) from 5.3.5 to 5.3.6.
- [Release notes](https://github.com/twbs/bootstrap/releases)
- [Commits](https://github.com/twbs/bootstrap/compare/v5.3.5...v5.3.6)

---
updated-dependencies:
- dependency-name: bootstrap
  dependency-version: 5.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-12 02:16:17 +00:00
dependabot[bot]
5f53a7c966 Bump webpack from 5.99.7 to 5.99.8 in the webpack group
Bumps the webpack group with 1 update: [webpack](https://github.com/webpack/webpack).


Updates `webpack` from 5.99.7 to 5.99.8
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.99.7...v5.99.8)

---
updated-dependencies:
- dependency-name: webpack
  dependency-version: 5.99.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: webpack
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-12 02:10:22 +00:00
Tim Donohue
6115e673cd Merge pull request #4316 from tdonohue/missing_i18n_labels
Add missing i18n labels, mostly for OpenAlex import of related Entities
2025-05-09 16:36:58 -05:00
Tim Donohue
5b18304e1c Merge pull request #3307 from alexandrevryghem/w2p-118157_entity-label-fix_contribute-main
Made the labels in edit relationships tab unique
2025-05-09 16:31:12 -05:00
Tim Donohue
a51cc5485c Merge pull request #4315 from pilasou/pilasou-translations-fr
Updates on the fr.json5
2025-05-09 16:20:38 -05:00
Tim Donohue
512d2e215f Add missing i18n labels, mostly for OpenAlex import of related Entities 2025-05-09 16:00:44 -05:00
Alexandre Vryghem
3adb81e2c6 118135: Added missing rioxx3 & openaire4 relationship labels 2025-05-09 22:55:22 +02:00
Pierre Lasou
0ae73c7131 Updates on the fr.json5
Random updates based on the "TODO New keys" tags.
2025-05-09 16:06:01 -04:00
Tim Donohue
9926121912 Merge pull request #4310 from lareferencia/fix-orcid-settings-layout
Fix layout of ORCID settings on profile page
2025-05-09 14:01:36 -05:00
Tim Donohue
c1524c0713 Merge pull request #3513 from 4Science/task/main/DSC-1966
Fix navbar wrapping on medium screens
2025-05-09 12:33:21 -05:00
Tim Donohue
dd4aa5ac80 Merge pull request #4311 from 4Science/task/main/DURACOM-363
Fixes wrong wiki reference on labels
2025-05-09 12:18:53 -05:00
Andrea Barbasso
a9dff09300 [DURACOM-363] Fixed wrong wiki reference on translation files 2025-05-09 17:57:40 +02:00
Andrea Barbasso
37cef0fbae [DURACOM-367] fix matomo SSR error on bitstream download page 2025-05-09 16:56:59 +02:00
Jesiel Viana
e28d212789 fix label width 2025-05-09 10:52:54 -03:00
Jesiel Viana
d5c9e694c3 Fix layout of ORCID settings on profile page 2025-05-09 10:46:26 -03:00
Tim Donohue
73bd0d67ea Merge pull request #4249 from atmire/fix-embargoed-thumbnails_contribute-9.0
Show restricted thumbnails to users with access rights
2025-05-08 15:12:36 -05:00
Andrea Barbasso
c0a5420e8b [DURACOM-309] add prod-e2e build script and use it for e2e tests 2025-05-08 12:20:10 +02:00
Art Lowel
bec91c5b8d Merge branch 'fix-embargoed-thumbnails_contribute-8.1' into fix-embargoed-thumbnails_contribute-9.0 2025-05-08 12:07:18 +02:00
Art Lowel
484befafc3 fix lint issues 2025-05-08 12:07:07 +02:00
Art Lowel
7a49802444 Merge branch 'fix-embargoed-thumbnails_contribute-8.1' into fix-embargoed-thumbnails_contribute-9.0 2025-05-08 12:02:43 +02:00
Art Lowel
12a3b4f846 always use thumbnail component for files not supported by the media viewer, and switch to themed version of thumbnail component 2025-05-08 11:34:45 +02:00
Art Lowel
9e8c0dc262 always use thumbnail component for files not supported by the media viewer, and switch to themed version of thumbnail component 2025-05-08 11:20:44 +02:00
Alexandre Vryghem
da6ace1882 130424: Fix validation on the edit metadata tab sometimes wrongly displaying an error message
This only happens when the request that validates the metadata field doesn't return the field with qualifier null on the first page
2025-05-08 01:48:52 +02:00
Tim Donohue
dec05e5cfe Merge pull request #4288 from 4Science/task/main/DURACOM-312
Bitstream upload failure during the workflow editing process when impersonating an editor
2025-05-07 16:41:58 -05:00
FrancescoMolinaro
4c9638150a [DURACOM-344] adapt patterns and example file, fix possible error from YAML 2025-05-07 15:25:08 +02:00
Art Lowel
b1191117fc Merge remote-tracking branch 'upstream/main' into fix-embargoed-thumbnails_contribute-9.0 2025-05-07 12:43:49 +02:00
Giuseppe Digilio
fd23044005 Merge branch 'main' into task/main/DSC-1966
# Conflicts:
#	src/themes/dspace/app/header/header.component.html
2025-05-07 11:18:37 +02:00
Tim Donohue
c2af23b3df Merge pull request #4293 from DSpace/dependabot/npm_and_yarn/core-js-3.42.0
Bump core-js from 3.41.0 to 3.42.0
2025-05-06 17:06:34 -05:00
Jesiel Viana
8757712905 Fix: Improve footer responsiveness and download button on the full item page (#4218)
* fix: footer and download button responsiveness

* Revert styling changes to the download button on the simple item page

* fix: for restrict bitstrream show only lock icon

* ix: display download icon consistently across all item pages

* fix embargo label responsiveness

* Recommit without json5 changes

* Recommit without json5 changes

* Recommit without json5 changes
2025-05-06 12:21:18 -05:00
Art Lowel
149feee93a wait for loading components to disappear before running a11y test 2025-05-06 17:06:37 +02:00
Tim Donohue
b15e9d74d6 Merge pull request #4304 from alexandrevryghem/w2p-117573_remove-observable-function-calls-from-template-main
Fixed broken Pagination of People/Groups on Edit Resource Policy page
2025-05-06 09:59:32 -05:00
Art Lowel
f5ce6301e0 Merge branch 'fix-embargoed-thumbnails_contribute-8.1' into fix-embargoed-thumbnails_contribute-9.0 2025-05-06 15:03:40 +02:00
Art Lowel
c222c446b5 don't show the loading animation when src is set to null 2025-05-06 14:59:15 +02:00
Art Lowel
a84365996b Merge branch 'fix-embargoed-thumbnails_contribute-8.1' into fix-embargoed-thumbnails_contribute-9.0 2025-05-06 14:57:23 +02:00
Art Lowel
a7bcddf597 fix issue where thumbnail would sometimes keep loading indefinitely 2025-05-06 14:41:50 +02:00
Alexandre Vryghem
81fb2e1cbf Merge branch 'w2p-127655_fix-submission-form-getting-stuck-in-loop_contribute-7.6' 2025-05-06 14:35:20 +02:00
Alexandre Vryghem
3d32715d25 127655: Fixed getFirstDataDefinition not always returning a correct SubmitDataResponseDefinitionObject, leading to an infinite loading screen
- Also fixed an issue where the collection switcher could accidentally show the old collection name instead of the new one
- Also updated the WorkspaceItemPageResolver & WorkflowItemPageResolver to embed the collection to use fewer requests
2025-05-06 14:15:34 +02:00
Andreas Awouters
25d6809e1c 119602: Update e2e cookieContent 2025-05-06 10:05:16 +02:00
Andreas Awouters
bcd9bfd607 Revert "click orejime popup accept after login"
This reverts commit 33062214da.
2025-05-06 10:03:07 +02:00
Andreas Awouters
3a419b733a Merge remote-tracking branch 'dspace/main' into accessibility-settings-main 2025-05-06 09:33:03 +02:00
Andrea Barbasso
ea265075c8 [DURACOM-309] disable orejime during e2e tests 2025-05-06 09:25:09 +02:00
Andrea Barbasso
d423f82324 [DURACOM-309] fix tests 2025-05-06 09:20:49 +02:00
Andrea Barbasso
79d03d0433 [DURACOM-309] fix issue after logging in 2025-05-06 09:19:45 +02:00
Andrea Barbasso
bc3d3e4129 [DURACOM-309] fix SSR build 2025-05-06 09:19:44 +02:00
Andrea Barbasso
9d3b298f1a [DURACOM-309] add new orejime cookie for correlation id 2025-05-06 09:19:12 +02:00
Alan Orth
0180b633e2 Merge pull request #4294 from DSpace/dependabot/npm_and_yarn/babel/runtime-7.27.1
Bump @babel/runtime from 7.27.0 to 7.27.1
2025-05-06 07:56:14 +03:00
Alexandre Vryghem
f1444b6edb Merge branch 'w2p-117573_remove-observable-function-calls-from-template-7.6'
# Conflicts:
#	src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html
#	src/app/shared/eperson-group-list/eperson-group-list.component.ts
2025-05-06 00:50:39 +02:00
Alexandre Vryghem
e9061a46b6 117287: Fixed broken pagination on resource policy page 2025-05-06 00:49:01 +02:00
Tim Donohue
052e1c39ab Merge pull request #4290 from alexandrevryghem/w2p-117573_remove-observable-function-calls-from-template-main
Removed observable function calls from template (part 2)
2025-05-05 14:32:25 -05:00
Tim Donohue
19d438040b Merge pull request #4289 from 4Science/task/main/DURACOM-326
[DURACOM-326] fix possible issue on missing value for eperson patch
2025-05-05 11:49:07 -05:00
Kristof De Langhe
4d74d2c6dc Merge branch 'w2p-127655_Fix-submission-infinite-loading-7.6' into w2p-127655_Fix-submission-infinite-loading-main 2025-05-05 13:31:21 +02:00
dependabot[bot]
e40a697dbd Bump @babel/runtime from 7.27.0 to 7.27.1
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.27.0 to 7.27.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.1/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 7.27.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-05 02:37:34 +00:00
dependabot[bot]
c3ba7ef51a Bump core-js from 3.41.0 to 3.42.0
Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.41.0 to 3.42.0.
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/commits/v3.42.0/packages/core-js)

---
updated-dependencies:
- dependency-name: core-js
  dependency-version: 3.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-05 02:31:25 +00:00
Alexandre Vryghem
6d7d3572a2 Merge branch 'w2p-118157_entity-label-fix_contribute-7.6' 2025-05-05 01:20:04 +02:00
Tim Donohue
e7359a3fd2 Merge pull request #4246 from atmire/w2p-127705_cleanup-input-form-spacing_contribute-main
Reduce margins on input fields
2025-05-02 14:51:04 -05:00
Joran De Braekeleer
46a89533f0 127705: Update dynamic list to bootstrap 5 2025-05-02 20:22:12 +02:00
Alexandre Vryghem
f7b77a1c3f Merge branch 'main' 2025-05-02 18:01:33 +02:00
Alexandre Vryghem
2bdb996e1a Merge branch 'theme-fixes_contribute-7.6' 2025-05-02 17:53:02 +02:00
Alexandre Vryghem
56e45a9f08 117287: Fixed various layout issues
- The unthemed home news didn't stick to the header like the dspace theme
- Impersonate user button has additional margin
- Submission form loading icon is not translatable
- Create resource policy page doesn't have the correct heading
2025-05-02 17:51:44 +02:00
Alexandre Vryghem
62f15668b6 122464: Fixed a memory leak when initComponentInstance is called multiple times 2025-05-02 17:11:48 +02:00
Alexandre Vryghem
6c1212de4e Merge branch 'w2p-117573_remove-observable-function-calls-from-template-7.6' 2025-05-02 17:07:56 +02:00
FrancescoMolinaro
b1c5460bbb [DURACOM-344] Adapt SSR page filtering mechanism to a not allowed list 2025-05-02 11:37:41 +02:00
Adamo
c68e5a181d [DURACOM-312] lint fix 2025-05-02 11:04:40 +02:00
Adamo
0574c8ed98 [DURACOM-312] set the X-On-Behalf-Of header with impersonatingID in FileUploader.
(cherry picked from commit 727bcdc2cb23ae7fcff1d9ddfa794f872f1d1b8c)
2025-05-02 10:30:19 +02:00
Adamo
70c6af3630 [DURACOM-312] set the newly created impersonatingID filed in UploaderOptions.
(cherry picked from commit c70fe184208805be8657d4373f50f193e2c6e85f)
2025-05-02 10:29:56 +02:00
Adamo
4b0ab8161f [DURACOM-312] updated UploaderOptions to include impersonatingID.
(cherry picked from commit 2c79be1456c753665e27b58563e56accc87b0383)
2025-05-02 10:29:30 +02:00
Tim Donohue
9a24fb2b64 Merge pull request #4244 from oscar-escire/Issue/4201
Keyboard 'tab' key navigation improved
2025-05-01 15:58:12 -05:00
Tim Donohue
df9c950f1f Merge pull request #4282 from guillermo2519/4195
Fixed Missing Tags in Import Popup from External Sources - 4220
2025-05-01 12:14:46 -05:00
Tim Donohue
af3d68ff71 Fix title tag to have quotes again 2025-05-01 11:20:31 -05:00
Tim Donohue
8c1f50b4d2 Merge pull request #4255 from DSpace/dependabot/npm_and_yarn/sass-f1c32079c7
Bump sass from 1.86.3 to 1.87.0 in the sass group
2025-05-01 10:02:28 -05:00
Tim Donohue
ca22bf327a Merge pull request #4257 from DSpace/dependabot/npm_and_yarn/axios-1.9.0
Bump axios from 1.8.4 to 1.9.0
2025-04-30 17:17:37 -05:00
Tim Donohue
5eda4db743 Merge pull request #4254 from DSpace/dependabot/npm_and_yarn/angular-27236e9c24
Bump the angular group with 3 updates
2025-04-30 17:02:06 -05:00
Tim Donohue
a41a2441df Merge pull request #4057 from saschaszott/saschaszott-patch-11
apply spellCheck configuration to input model
2025-04-30 13:38:19 -05:00
Tim Donohue
f44599a3c9 Merge pull request #4270 from oscar-escire/Issue/4204
fixed flash and translate problems on statistics
2025-04-30 11:40:14 -05:00
Tim Donohue
3370bda789 Merge pull request #4080 from atmire/themed-SubmissionComponents_contribute-main
Themed Submission Components
2025-04-30 09:55:45 -05:00
kshepherd
19078a18fc Merge pull request #4279 from atmire/w2p-130484_bitstreams-tables-keep-being-duplicated-9.0
Fix bitstream tables being duplicated
2025-04-30 16:50:51 +02:00
Andreas Awouters
f9b58ecd06 130484: Filter new bundles 2025-04-30 15:49:59 +02:00
Alexandre Vryghem
dc8b10593c 117287: Removed remaining observable function calls from the HTML templates 2025-04-30 15:37:06 +02:00
FrancescoMolinaro
7b9cd73ee0 [DURACOM-326] fix possible issue on missing value for eperson patch 2025-04-30 14:51:42 +02:00
Andreas Awouters
41afcf9cf6 130484: Correctly update the 'showLoadMoreLink$' observable 2025-04-30 14:18:14 +02:00
Andreas Awouters
d3b48f4ea4 130484: Only add bundles when they are missing from the subject 2025-04-30 14:17:59 +02:00
Zahraa Chreim
0aea4a7666 127047: Remove unnecessary CSS variable from spec setup 2025-04-30 10:22:01 +02:00
guillermo2519
4c26359e29 Fixed Missing Tags in Import Popup from External Sources - 4220 2025-04-29 21:24:59 -06:00
Tim Donohue
5719f06dd1 Merge pull request #4273 from 4Science/task/main/DURACOM-354
Fix missing input fields for integer script parameters
2025-04-29 17:06:48 -05:00
Tim Donohue
67e91e370a Merge pull request #3888 from alexandrevryghem/w2p-119276_fixed-searchservice-returning-stale-requests_contribute-main
Fixed search page still returning stale data after invalidating a request
2025-04-29 13:57:44 -05:00
Tim Donohue
aacedd2db6 Merge pull request #3892 from atmire/w2p-119612_export-item-limit
UI warning for export item limit
2025-04-29 12:42:48 -05:00
Tim Donohue
ea6f640820 Merge pull request #4276 from 4Science/task/main/DURACOM-226
Fix submission footer wrapping on medium screens
2025-04-29 12:21:27 -05:00
Andrea Barbasso
00143d29e7 [DURACOM-354] fix missing input fields for integer script parameters 2025-04-29 16:45:44 +02:00
Tim Donohue
f16a03e226 Merge pull request #4217 from kshepherd/rss-sort-link-fix
Several fixes to RSS component and object lists
2025-04-29 08:58:47 -05:00
Andrea Barbasso
ad7aa36f88 [DURACOM-226] fix submission footer wrapping on medium screens 2025-04-29 15:48:30 +02:00
Jens Vannerum
6232d4e9cf 119612: fix spec test 2025-04-29 12:20:25 +02:00
Jens Vannerum
a6c14e50c2 Merge remote-tracking branch 'origin/main' into w2p-119612_export-item-limit 2025-04-29 10:54:22 +02:00
Kristof De Langhe
c1bd65e8c6 127655: avoid nullpointer 2025-04-29 10:46:45 +02:00
Oscar Chacón
3c1d514807 fixed flash and translate problems on statistics 2025-04-28 23:42:38 -06:00
Kim Shepherd
6cd88ec57b [#4172] Remove unused RSS import from RecentItemListComponent 2025-04-28 22:46:36 +02:00
Tim Donohue
28f865eb2e Merge pull request #4269 from 4Science/task/main/DURACOM-359
Fix matomo downloading its .js even if it's disabled
2025-04-28 11:29:56 -05:00
Tim Donohue
f107573bc1 Merge pull request #4225 from 4Science/task/main/DURACOM-353
[DURACOM-353] fix orejime callbacks
2025-04-28 11:22:36 -05:00
Kristof De Langhe
557879941b Merge branch 'w2p-127655_Fix-submission-infinite-loading-7.6' into w2p-127655_Fix-submission-infinite-loading-main 2025-04-28 17:49:11 +02:00
Kristof De Langhe
0dabc8ed8f 127655: refactor to use buildFromRequestUUID 2025-04-28 17:47:17 +02:00
dependabot[bot]
62822ade31 Bump the angular group with 3 updates
Bumps the angular group with 3 updates: [@angular/ssr](https://github.com/angular/angular-cli), [@angular-devkit/build-angular](https://github.com/angular/angular-cli) and [@angular/cli](https://github.com/angular/angular-cli).


Updates `@angular/ssr` from 18.2.18 to 18.2.19
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/18.2.18...18.2.19)

Updates `@angular-devkit/build-angular` from 18.2.18 to 18.2.19
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/18.2.18...18.2.19)

Updates `@angular/cli` from 18.2.18 to 18.2.19
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/18.2.18...18.2.19)

---
updated-dependencies:
- dependency-name: "@angular/ssr"
  dependency-version: 18.2.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular-devkit/build-angular"
  dependency-version: 18.2.19
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular
- dependency-name: "@angular/cli"
  dependency-version: 18.2.19
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: angular
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 15:30:33 +00:00
dependabot[bot]
a16f1e8248 Bump sass from 1.86.3 to 1.87.0 in the sass group
Bumps the sass group with 1 update: [sass](https://github.com/sass/dart-sass).


Updates `sass` from 1.86.3 to 1.87.0
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.86.3...1.87.0)

---
updated-dependencies:
- dependency-name: sass
  dependency-version: 1.87.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: sass
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 15:27:45 +00:00
Tim Donohue
b123e86760 Merge pull request #4262 from DSpace/dependabot/npm_and_yarn/ngtools/webpack-18.2.19
Bump @ngtools/webpack from 18.2.18 to 18.2.19
2025-04-28 10:18:00 -05:00
Tim Donohue
d664f1f94a Merge pull request #4256 from DSpace/dependabot/npm_and_yarn/webpack-cee3a23860
Bump webpack from 5.99.6 to 5.99.7 in the webpack group
2025-04-28 10:17:30 -05:00
Tim Donohue
0b02dbbb67 Merge pull request #4261 from DSpace/dependabot/npm_and_yarn/isbot-5.1.27
Bump isbot from 5.1.26 to 5.1.27
2025-04-28 10:15:59 -05:00
Andrea Barbasso
16e4ae5583 Merge branch 'main' into task/main/DSC-1966
# Conflicts:
#	src/themes/dspace/app/header/header.component.html
#	src/themes/dspace/app/navbar/navbar.component.html
2025-04-28 17:04:32 +02:00
Andrea Barbasso
cace253d8b [DURACOM-359] fix matomo downloading its .js even if it's disabled 2025-04-28 16:15:10 +02:00
Kim Shepherd
97a4c3c0c0 [#4172] Disable manual ds-rss display in recent items comp
This can be added in easily with:
`      <ds-rss [sortConfig]="sortConfig"></ds-rss>`
2025-04-28 15:37:55 +02:00
Kim Shepherd
72a050c7ae [#4172] Shift enableRSS route data for coll and comm routes
app-routes previously set this but it now needs to be
set in collection-page-routes and community-page-routes
respectively.
2025-04-28 15:31:31 +02:00
Kristof De Langhe
9dc96e7b33 Merge branch 'main' into w2p-127655_Fix-submission-infinite-loading-main 2025-04-28 15:19:50 +02:00
Kristof De Langhe
99e8c1044c 127655: Submission get data stale re-request 2025-04-28 15:15:26 +02:00
Andrea Barbasso
33d75cf217 [DURACOM-353] fix error in SSR 2025-04-28 14:16:13 +02:00
Kristof De Langhe
287d35cb26 127655: Fix submission infinite loading 2025-04-28 13:04:44 +02:00
Ricardo Saraiva
c5d203bbd6 Merge branch 'main' into patch-4 2025-04-28 09:15:58 +01:00
dependabot[bot]
e4d53eddcc Bump @ngtools/webpack from 18.2.18 to 18.2.19
Bumps [@ngtools/webpack](https://github.com/angular/angular-cli) from 18.2.18 to 18.2.19.
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/18.2.18...18.2.19)

---
updated-dependencies:
- dependency-name: "@ngtools/webpack"
  dependency-version: 18.2.19
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 02:49:37 +00:00
dependabot[bot]
25f474386b Bump isbot from 5.1.26 to 5.1.27
Bumps [isbot](https://github.com/omrilotan/isbot) from 5.1.26 to 5.1.27.
- [Changelog](https://github.com/omrilotan/isbot/blob/main/CHANGELOG.md)
- [Commits](https://github.com/omrilotan/isbot/compare/v5.1.26...v5.1.27)

---
updated-dependencies:
- dependency-name: isbot
  dependency-version: 5.1.27
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 02:49:17 +00:00
dependabot[bot]
3d8951db18 Bump axios from 1.8.4 to 1.9.0
Bumps [axios](https://github.com/axios/axios) from 1.8.4 to 1.9.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.8.4...v1.9.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 02:43:22 +00:00
dependabot[bot]
d6875117b0 Bump webpack from 5.99.6 to 5.99.7 in the webpack group
Bumps the webpack group with 1 update: [webpack](https://github.com/webpack/webpack).


Updates `webpack` from 5.99.6 to 5.99.7
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.99.6...v5.99.7)

---
updated-dependencies:
- dependency-name: webpack
  dependency-version: 5.99.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: webpack
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 02:43:08 +00:00
Alexandre Vryghem
b64f69105e Merge branch 'main' into w2p-119276_fixed-searchservice-returning-stale-requests_contribute-main 2025-04-26 14:47:58 +02:00
Tim Donohue
353d660f23 Merge pull request #4212 from jesielviana/fix-bitstream-unable-to-change-format
Fix: prevent bitstream format cache issue by disabling cached version
2025-04-25 16:42:42 -05:00
Art Lowel
80dd89da56 switch to signals 2025-04-25 18:13:46 +02:00
Art Lowel
fa96c8c5ab Merge branch 'fix-embargoed-thumbnails_contribute-8.1' 2025-04-25 17:58:06 +02:00
Tim Donohue
ff3784ab5f Merge pull request #4243 from atmire/w2p-130405_missing-labels-in-journal-volume-search-results_contribute-main
Add translation keys for journal submission volume lookup
2025-04-25 10:31:22 -05:00
Tim Donohue
8d26b1509f Merge pull request #4192 from alanorth/no-proper-noun
Adjust unnecessary capitalization in English i18n strings
2025-04-25 10:05:02 -05:00
Art Lowel
54ed550f4a Merge branch 'w2p-116728_removed-unnecessary-ngvars_contribute-7.6' into fix-embargoed-thumbnails_contribute-8.1 2025-04-25 16:18:21 +02:00
Art Lowel
11f251755b fix issue where thumnails of embargoed bitstreams wouldn't show up for users with access rights 2025-04-25 14:55:52 +02:00
Joran De Braekeleer
b00f489d09 127705: Reduce margins on input fields 2025-04-25 10:00:35 +02:00
Oscar Chacón
4e5b344ce8 hotfix: e2e accessibility tests 2025-04-24 17:11:54 -06:00
Tim Donohue
b1f0ed201a Merge pull request #4234 from 4Science/task/main/DURACOM-350
[DURACOM-350] fix cache issue after mydspace action
2025-04-24 15:07:06 -05:00
Oscar Chacón
f8cfb74555 tab navigation improved 2025-04-24 13:29:01 -06:00
Kim Shepherd
e3c6ad807b #4172 Remove unneeded SearchService from RSS 2025-04-24 16:18:41 +02:00
Tim Donohue
818f60b4d7 Merge pull request #4242 from dataquest-dev/cs-translation-thumbnail-more-accurate
Fix the last update to the Czech Translation
2025-04-24 08:48:53 -05:00
Kim Shepherd
6f878e5c4a #4172 Better boolean comparison of showRSS test 2025-04-24 15:24:58 +02:00
Kim Shepherd
3a347d83b5 #4172 Allow SortOptions override to showRSS input
If a valid, complete SortOptions is passed to
the pagination component showRSS, it will be
used instead of the underlying sortOptions,
otherwise the pagination context sortOptions
will be used.
2025-04-24 15:24:58 +02:00
Kim Shepherd
b89a626ffc #4172 Several fixes to RSS component and object lists
* RSS button displayed on search result pages
* RSS button displayed on Recent Items component
* RSS button NOT displayed in other components
  like top-level community, etc.
* RSS component tracks sort option changes
2025-04-24 15:24:58 +02:00
Joran De Braekeleer
fef16bca14 130405: Add translations for journal submission volume lookup 2025-04-24 15:15:29 +02:00
Jesiel Viana
bb536192c2 fix unit tests for bitstream-data.service.ts 2025-04-24 10:04:41 -03:00
milanmajchrak
00d7e1f97c Use more accurate czech translation for the thumbnail.default.placeholder 2025-04-24 14:56:21 +02:00
Jesiel Viana
4f48f39f7b fix: invalidate cache only for the modified bitstream 2025-04-24 07:43:05 -03:00
FrancescoMolinaro
7998d2d8b9 [DURACOM-350] Fix filter skeleton missing update 2025-04-24 09:28:29 +02:00
Tim Donohue
5fc83782b1 Merge pull request #4231 from atmire/4099-duplicate-view-events_contibute-main
Fix for double view events when using dynamic themes
2025-04-23 16:57:49 -05:00
Tim Donohue
7269f6c36a Merge pull request #4236 from DSpace/backport-4167-to-main
[Port main] Fix unable to remove data from a submission form field of type "name"
2025-04-23 13:44:28 -05:00
abhinav
583bed6164 129621: Add a not empty check
This check is present in the other event handlers above so adding it here as well.

(cherry picked from commit 0b28789e4f)
2025-04-23 12:52:02 -05:00
Tim Donohue
056e923ae8 Merge pull request #4240 from tdonohue/update_solr_in_Docker
Update Docker Compose scripts for Solr 9 compatibility
2025-04-23 12:51:06 -05:00
Tim Donohue
856d701b3e Solr 9 compatibility requires minor updates to how Solr image is initialized 2025-04-23 12:07:46 -05:00
Tim Donohue
7845d37b2e Merge pull request #3968 from VictorDuranEscire/2567
Fix - previously entered qualdrop value field is deleted when adding a new (emtpy) line with the same(!) qualifier and then deleting it
2025-04-23 11:38:21 -05:00
FrancescoMolinaro
1b112dd887 [DURACOM-350] prevent unnecessary reload 2025-04-23 15:47:01 +02:00
FrancescoMolinaro
e84af6ab91 [DURACOM-350] fix cache issue after mydspace action 2025-04-23 15:07:00 +02:00
lotte
bde54ca6fa 129694: fixed lint issues 2025-04-22 18:32:51 +02:00
Andrea Barbasso
4b6ef50611 [DURACOM-353] fix orejime callbacks 2025-04-22 12:58:33 +02:00
lotte
8140ed29e7 129694: PoC #4099 solution with resolvers 2025-04-22 12:43:58 +02:00
Zahraa Chreim
d269d668e5 127047: Remove default value of collectionModifiable and Update overrideComponent to exclude themed components 2025-04-22 09:57:27 +03:00
Tim Donohue
b076738b53 Merge pull request #4219 from michdyk/patch-2
Full update pl translations and minor changes in cz and uk translations
2025-04-21 14:57:08 -05:00
Tim Donohue
09e6452a48 Merge pull request #4223 from DSpace/dependabot/npm_and_yarn/webpack-753b40408c
Bump webpack from 5.99.5 to 5.99.6 in the webpack group
2025-04-21 08:52:41 -05:00
dependabot[bot]
03bcdc37ce Bump webpack from 5.99.5 to 5.99.6 in the webpack group
Bumps the webpack group with 1 update: [webpack](https://github.com/webpack/webpack).


Updates `webpack` from 5.99.5 to 5.99.6
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.99.5...v5.99.6)

---
updated-dependencies:
- dependency-name: webpack
  dependency-version: 5.99.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: webpack
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-21 03:17:20 +00:00
Michał Dykas
8734a07592 Corrected one indentation from pl.json5 2025-04-20 21:52:06 +02:00
Michał Dykas
548fbf54cf Corrected indentations for pl.json5 2025-04-20 21:44:51 +02:00
Michał Dykas
050c007e44 Minor change of commas in pl.json5 2025-04-20 21:28:12 +02:00
Michał Dykas
5346679880 Corrected minor comma/markdown mispellings from json validation for pl.json5 2025-04-20 11:57:44 +02:00
Michał Dykas
fad41b2ddf Added comments to pl.json5 2025-04-20 01:56:17 +02:00
Tim Donohue
fcac66caab Merge pull request #4183 from DSpace/dependabot/npm_and_yarn/bootstrap-5.3.5
Bump bootstrap from 5.3.3 to 5.3.5
2025-04-18 14:19:06 -05:00
Tim Donohue
11493914a7 Merge pull request #4216 from atmire/fix-untyped-item-display-in-edit-item-modal
fix untyped item display in edit item modal
2025-04-18 13:32:01 -05:00
Michał Dykas
e14ed8804c Added missing comma to pl.json5 2025-04-18 12:13:43 +02:00
Michał Dykas
8abd71ab73 Update uk.json5 2025-04-18 10:49:30 +02:00
Michał Dykas
41c4225c0f Update cs.json5 2025-04-18 10:48:18 +02:00
Michał Dykas
d8896f78b8 Update pl.json5 2025-04-18 10:46:01 +02:00
Michał Dykas
3d167acecb Update full translation to pl.json5 2025-04-18 10:37:41 +02:00
Tim Donohue
9b5d008d66 Merge pull request #4215 from pilasou/pilasou-translations-fr
Correct small typo in en.json5
2025-04-17 10:17:38 -05:00
Art Lowel
5ef0a57438 fix untyped item display in edit item modal 2025-04-17 16:57:01 +02:00
Pierre Lasou
f01ccbdae2 Correct typo
Correct type in the external-login.confirm-email-sent.info parameter
2025-04-17 10:32:03 -04:00
Jesiel Viana
6001652101 fix: prevent bitstream format cache issue by disabling cached version 2025-04-17 09:08:19 -03:00
Tim Donohue
6a49cdb54b Merge pull request #2317 from alanorth/eslint-json5
Update ESLint configuration for json5 files
2025-04-16 14:54:30 -05:00
Andreas Awouters
b9e2ec15ed Merge remote-tracking branch 'dspace/main' into accessibility-settings-main
# Conflicts:
#	config/config.example.yml
#	src/config/app-config.interface.ts
#	src/config/default-app-config.ts
#	src/environments/environment.test.ts
2025-04-16 10:56:51 +02:00
Alan Orth
9896eab6ac .eslintrc.json: remove second eslint-plugin-jsonc
This seems to have been added twice at some point.
2025-04-15 23:57:19 +03:00
Alan Orth
5e8571de80 .eslintrc.json: use jsonc/no-irregular-whitespace
The eslint-plugin-jsonc documentation recommends turning ESLint's
own no-irregular-whitespace plugin off for JSON files in favor of
its own jsonc/no-irregular-whitespace plugin.

See: https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-irregular-whitespace.html
2025-04-15 23:55:43 +03:00
Ricardo Saraiva
452493a828 Update pt-PT.json5
Lint corrections.
2025-04-15 21:39:03 +01:00
Alan Orth
3a05733256 .eslintrc.json: use plugin:jsonc/recommended-with-json5
The eslint-plugin-jsonc has a recommended configuration for json5.

See: https://ota-meshi.github.io/eslint-plugin-jsonc/user-guide/#usage
2025-04-15 23:34:46 +03:00
Ricardo Saraiva
71d7a7b659 Update pt-PT.json5
Lint corrections.
2025-04-15 21:31:38 +01:00
Ricardo Saraiva
9f19b481cf Merge branch 'main' into patch-4 2025-04-15 20:48:02 +01:00
Tim Donohue
f95596ba02 Merge pull request #4184 from DSpace/dependabot/npm_and_yarn/http-proxy-middleware-2.0.9
Bump http-proxy-middleware from 2.0.7 to 2.0.9
2025-04-15 14:13:03 -05:00
dependabot[bot]
bd2b5d3bc0 Bump bootstrap from 5.3.3 to 5.3.5
Bumps [bootstrap](https://github.com/twbs/bootstrap) from 5.3.3 to 5.3.5.
- [Release notes](https://github.com/twbs/bootstrap/releases)
- [Commits](https://github.com/twbs/bootstrap/compare/v5.3.3...v5.3.5)

---
updated-dependencies:
- dependency-name: bootstrap
  dependency-version: 5.3.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-15 19:05:14 +00:00
Tim Donohue
fd2e9a49ee Merge pull request #4117 from DSpace/dependabot/npm_and_yarn/babel/runtime-7.27.0
Bump @babel/runtime from 7.26.0 to 7.27.0
2025-04-15 14:03:45 -05:00
dependabot[bot]
8f95d7b5b3 Bump @babel/runtime from 7.26.0 to 7.27.0
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.26.0 to 7.27.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.0/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-15 13:27:14 -05:00
Tim Donohue
67c2b33bb0 Merge pull request #4193 from jesielviana/add-tooltips-to-switch-view-buttons
Fix: adding tooltips to indicate functionality of buttons when switching views of search results
2025-04-15 12:24:24 -05:00
dependabot[bot]
237f183a18 Bump http-proxy-middleware from 2.0.7 to 2.0.9
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.7 to 2.0.9.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.9/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.7...v2.0.9)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-version: 2.0.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-15 15:24:52 +00:00
Tim Donohue
0d78cd57b3 Merge pull request #4185 from DSpace/dependabot/npm_and_yarn/isbot-5.1.26
Bump isbot from 5.1.25 to 5.1.26
2025-04-15 09:51:36 -05:00
Jesiel Viana
5bdf0974fc Adding tooltips to indicate functionality of buttons when switching views of search results 2025-04-15 10:03:49 -03:00
Zahraa Chreim
7864909466 127047: Fix selector of SubmissionSectionContainerComponent in custom theme 2025-04-15 12:03:25 +03:00
kshepherd
d3130bd433 Merge pull request #4189 from tdonohue/more_automated_ssr_tests
Add additional automated server-side rendering (SSR) tests for all Entity Types
2025-04-15 10:31:54 +02:00
Alan Orth
fd9d899574 src/assets/i18n: adjust English capitalization
We don't need to be capitalizing "Community" and "Collection" in
the user interface unless they are at the beginning of a sentence
or in a title, header, or bread crumb. They are not proper nouns
and they look out of place when capitalized.
2025-04-15 10:53:00 +03:00
Tim Donohue
42c52d619b Add additional automated SSR tests for Community, Collection, Publication/Item, Person, OrgUnit and all Journal entities 2025-04-14 14:37:49 -05:00
dependabot[bot]
ef57754e6d Bump isbot from 5.1.25 to 5.1.26
Bumps [isbot](https://github.com/omrilotan/isbot) from 5.1.25 to 5.1.26.
- [Changelog](https://github.com/omrilotan/isbot/blob/main/CHANGELOG.md)
- [Commits](https://github.com/omrilotan/isbot/compare/v5.1.25...v5.1.26)

---
updated-dependencies:
- dependency-name: isbot
  dependency-version: 5.1.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 03:02:45 +00:00
Tim Donohue
0989e28c38 Merge pull request #4158 from DSpace/dependabot/npm_and_yarn/eslint-7904436953
Bump eslint-plugin-jsonc from 2.19.1 to 2.20.0 in the eslint group
2025-04-11 17:13:32 -05:00
dependabot[bot]
45a3c11dfc Bump eslint-plugin-jsonc from 2.19.1 to 2.20.0 in the eslint group
Bumps the eslint group with 1 update: [eslint-plugin-jsonc](https://github.com/ota-meshi/eslint-plugin-jsonc).


Updates `eslint-plugin-jsonc` from 2.19.1 to 2.20.0
- [Release notes](https://github.com/ota-meshi/eslint-plugin-jsonc/releases)
- [Changelog](https://github.com/ota-meshi/eslint-plugin-jsonc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ota-meshi/eslint-plugin-jsonc/compare/v2.19.1...v2.20.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsonc
  dependency-version: 2.20.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: eslint
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 23:15:16 +00:00
Zahraa Chreim
502597aadc 127047: Fix submission-edit.component.spec.ts 2025-04-04 13:20:01 +03:00
Zahraa Chreim
2fc84783a5 127047: Fix submission-form.component.spec.ts 2025-03-28 15:49:37 +02:00
Zahraa Chreim
fa8ffaefd0 127047: Fix imports 2025-03-28 15:08:12 +02:00
Ricardo Saraiva
cadd1520ee Update pt-PT.json5
Minor updates and addition missing translation strings.
2025-03-25 15:52:37 +00:00
Art Lowel
93fff41b8d Merge remote-tracking branch 'upstream/main' into accessibility-settings-main 2025-03-25 10:11:08 +01:00
Art Lowel
d3c0ef501c move intercepts to before the login, otherwise they're not always early enough 2025-03-22 11:32:17 +01:00
Art Lowel
33062214da click orejime popup accept after login 2025-03-21 17:54:23 +01:00
Andreas Awouters
99c5506d7d 119602: Move to control-flow syntax 2025-03-21 13:57:50 +01:00
Andreas Awouters
94dd8e7206 Merge remote-tracking branch 'dspace/main' into accessibility-settings-main
# Conflicts:
#	src/app/footer/footer.component.html
#	src/app/profile-page/profile-page.component.html
2025-03-21 13:03:30 +01:00
Ricardo Saraiva
88d59a2152 Merge branch 'main' into patch-4 2025-03-19 17:35:08 +00:00
Zahraa Chreim
da2c98aa32 Fix lint issues 2025-03-14 13:45:43 +02:00
Zahraa Chreim
681efd9f52 127047: Merge branch 'themed-SubmissionComponents_contribute-7.6' into 'themed-SubmissionComponents_contribute-main' 2025-03-13 16:17:38 +02:00
Ricardo Saraiva
ef8735e5a2 Update pt-PT.json5
Correction of trailing spaces.
2025-03-12 11:24:57 +00:00
Ricardo Saraiva
639bb3e558 Update pt-PT.json5
Revision of trailing spaces.
2025-03-12 11:09:35 +00:00
Ricardo Saraiva
a58456deab Update pt-PT.json5
Addition of missing pt_PT translation keys, improvements and minor revisions to existing translations.
2025-03-12 09:29:56 +00:00
Andreas Awouters
0567ea7a2d Merge remote-tracking branch 'dspace/main' into accessibility-settings-main
# Conflicts:
#	src/app/profile-page/profile-page.component.html
2025-03-07 13:45:37 +01:00
Andreas Awouters
a2882b0644 119602: Set all cookies before e2e specs 2025-03-07 13:09:47 +01:00
Andreas Awouters
4cea196eb2 Revert "119602: Accept cookies in specs where cookie window covers other elements"
This reverts commit c9b2717344.
2025-03-07 13:09:22 +01:00
Andreas Awouters
082018d44d Merge branch 'accessibility-settings-7.6' into accessibility-settings-main
# Conflicts:
#	src/app/accessibility/accessibility-settings.service.ts
2025-03-07 13:04:25 +01:00
Andreas Awouters
122d31b11b 119602: Make KlaroService optional in AccSettingsService 2025-03-07 12:58:31 +01:00
Andreas Awouters
7ab598d571 119602: Reorder footer links 2025-03-07 10:03:57 +01:00
Andreas Awouters
bd06eded8c 119602: Fix small accessibility settings typo 2025-03-07 10:02:57 +01:00
Sascha Szott
13ae42f217 reorganize imports 2025-03-06 14:59:41 +01:00
Sascha Szott
c835b215af add import 2025-03-06 14:43:41 +01:00
Sascha Szott
c0bfc3a739 apply spellCheck configuration to input model 2025-03-06 14:34:25 +01:00
Kristof De Langhe
261af2a2f5 126853: ThemedSubmissionFormComponent 2025-03-03 10:30:16 +01:00
Andreas Awouters
c9b2717344 119602: Accept cookies in specs where cookie window covers other elements 2025-02-25 15:51:12 +01:00
Andreas Awouters
958b3b8c05 119602: Add new cookie setting to e2e tests 2025-02-24 10:14:49 +01:00
Andreas Awouters
cdbfcc7cc4 119602: Fix tests after merge 2025-02-21 16:35:10 +01:00
Andreas Awouters
112eb7b3ce Merge branch 'main' into accessibility-settings-main 2025-02-21 15:52:34 +01:00
Andreas Awouters
08fe940e70 Merge branch 'accessibility-settings-7.6' into accessibility-settings-main
# Conflicts:
#	src/app/accessibility/accessibility-settings.service.spec.ts
#	src/app/accessibility/accessibility-settings.service.ts
#	src/app/info/accessibility-settings/accessibility-settings.component.spec.ts
#	src/app/info/accessibility-settings/accessibility-settings.component.ts
#	src/app/shared/cookies/klaro-configuration.ts
#	src/assets/i18n/en.json5
2025-02-21 15:52:24 +01:00
Andreas Awouters
dcd32c48c3 119602: Rework settings validation 2025-02-21 12:53:44 +01:00
Andreas Awouters
cd825ac0da 119602: Inject AppConfig instead of using environment directly 2025-02-21 10:51:30 +01:00
Andreas Awouters
ca80812d0a 119602: Add additional accessibility-settings.component test 2025-02-21 10:27:44 +01:00
Andreas Awouters
4a74a3ac99 119602: Fix existing accessibility-settings.component tests 2025-02-21 10:10:35 +01:00
Andreas Awouters
62904747fc 119602: Add accessibility-settings.service tests for cookie functionality 2025-02-21 10:03:43 +01:00
Andreas Awouters
4c30e2d0be 119602: Fix existing accessibility-settings.service tests 2025-02-21 09:56:00 +01:00
Andreas Awouters
d05096c015 119602: Add KlaroServiceStub 2025-02-21 09:16:41 +01:00
Kristof De Langhe
858ec87f2f 126853: Themeable SubmissionSectionContainerComponent and SubmissionFormFooterComponent 2025-02-20 14:14:03 +01:00
Andreas Awouters
b837f63b7c 119602: Add 'Accessibility Settings' Klaro option & respect user choice 2025-02-18 10:22:59 +01:00
Andreas Awouters
e838873235 119602: Set default value as actual value instead of placeholder 2025-02-17 13:37:07 +01:00
Andreas Awouters
80948d98f0 119602: Force minimum time out 2025-02-11 09:33:03 +01:00
VictorDuranEscire
1e73fa626d Fix - Validate value on remove item for submission form only for qualdrop component 2025-02-07 21:12:03 -06:00
Andreas Awouters
59cb19ae80 119602: Restructure labels 2025-02-07 14:54:24 +01:00
Jens Vannerum
8eaff78737 119612: aria-label to also include warning message if applicable 2025-02-03 11:20:18 +01:00
Jens Vannerum
daf2dc40d9 Merge remote-tracking branch 'origin/main' into w2p-119612_export-item-limit
# Conflicts:
#	src/assets/i18n/en.json5
2025-02-03 11:13:23 +01:00
Jens Vannerum
e1b773c097 119612: Check if a warning should be shown on changes to the total elements of the search, default to 500 if no value for the configuration property was returned 2025-02-03 10:11:28 +01:00
Jens Vannerum
2a39bd831b Merge branch 'main' into w2p-119612_export-item-limit 2025-01-24 15:59:05 +01:00
Jens Vannerum
b69b21af6c 119612: UI warning that only first part of configured items will be exported 2025-01-24 15:58:25 +01:00
Alexandre Vryghem
1b83f18960 Merge branch 'w2p-119276_fixed-searchservice-returning-stale-requests_contribute-7.6' 2025-01-23 20:54:05 +01:00
Alexandre Vryghem
58ff240c05 119276: Added tests to prove that cached stale requests are not emitted
Also cleaned up test class
2025-01-23 20:48:19 +01:00
Andreas Awouters
888459cb56 Merge remote-tracking branch 'dspace/main' into accessibility-settings-main
# Conflicts:
#	src/app/profile-page/profile-page.component.html
#	src/app/profile-page/profile-page.component.ts
#	src/app/shared/notifications/notifications-board/notifications-board.component.spec.ts
#	src/app/shared/notifications/notifications-board/notifications-board.component.ts
#	src/themes/custom/app/profile-page/profile-page.component.ts
2025-01-23 09:53:46 +01:00
Andreas Awouters
f0ff625b4b Merge branch 'accessibility-settings-7.6' into accessibility-settings-main
# Conflicts:
#	src/app/accessibility/accessibility-settings.service.spec.ts
2025-01-23 09:42:23 +01:00
Andreas Awouters
b55a318676 119602: Reset notificationTimeOut when left on 0 2025-01-22 15:47:13 +01:00
DanGastardelli
fa723c17a9 Adjust thumb text for smaller resolutions 2025-01-14 12:51:07 -03:00
Andreas Awouters
4ffde928d4 122839: Fix vocabulary-treeview component tests 2025-01-02 14:42:17 +01:00
Andreas Awouters
9406f7b085 122839: Retrieve preloadLevel from vocabulary before creating tree 2025-01-02 12:04:40 +01:00
Andreas Awouters
cee9d0422b 122839: Load nodes up to defined preloadLevel 2024-12-24 15:17:27 +01:00
DanGastardelli
829d406808 Key spacing adjustment 2024-12-20 15:51:21 -03:00
DanGastardelli
e06db4cbab Adding treatment for the use of the thumb-font-2 class 2024-12-20 15:50:27 -03:00
Andreas Awouters
ff7c9ba955 Merge branch 'accessibility-settings-7.6' into accessibility-settings-main
# Conflicts:
#	src/app/accessibility/accessibility-settings.service.spec.ts
#	src/app/accessibility/accessibility-settings.service.ts
#	src/app/info/accessibility-settings/accessibility-settings.component.spec.ts
#	src/app/info/accessibility-settings/accessibility-settings.component.ts
#	src/app/info/info.module.ts
#	src/app/shared/live-region/live-region.service.ts
#	src/app/shared/notifications/notifications-board/notifications-board.component.ts
2024-12-11 11:05:18 +01:00
Andreas Awouters
c71c6667e0 119602: Improve notification hiding toggle useability 2024-12-11 10:10:16 +01:00
Andreas Awouters
010b2f9693 119602: Improve types & docs 2024-12-11 09:35:01 +01:00
Alexandre Vryghem
edfaee6aab 119276: Fixed search service first emitting cached stale values instead of waiting for non-stale response
This was problematic for the places that used getFist operators. This is because they only emit data once, and the first value could be the old cached value
2024-12-10 13:54:57 +01:00
Andreas Awouters
cdec4880d2 119602: Compare notifications by id to correctly update store 2024-11-29 10:44:44 +01:00
Andreas Awouters
c38352ed22 119602: Update doc comments 2024-11-25 10:29:16 +01:00
Andreas Awouters
deb4a63c88 119602: Add additional accessibilitySettingsComponent tests 2024-11-25 10:12:39 +01:00
Andreas Awouters
ec016e80fb 119602: Update AccessibilitySettingsService stub 2024-11-25 10:00:04 +01:00
Andreas Awouters
ced163a25f 119602: Remove obsolete tests 2024-11-25 09:58:50 +01:00
Andreas Awouters
5a28e66b2f 119602: Move hints to contextHelp 2024-11-25 09:44:27 +01:00
Andreas Awouters
9ac92e0196 119602: Add unit after input fields 2024-11-22 16:09:12 +01:00
Andreas Awouters
287d028331 119602: Make input boxes smaller 2024-11-22 16:03:52 +01:00
Andreas Awouters
cae13942e2 119602: Allow resetting accessibility settings 2024-11-22 15:54:21 +01:00
Andreas Awouters
b16cec631d 119602: Add automatic notification hiding toggle 2024-11-22 13:56:39 +01:00
Andreas Awouters
dc8a699e94 119602: Rework convertion & form format 2024-11-22 11:55:05 +01:00
Andreas Awouters
fdfa6e2c06 119602: Update values after converters implementation 2024-11-18 14:26:48 +01:00
Andreas Awouters
ecb00a95a0 119602: Implement converting of accessibility settings values 2024-11-18 13:50:46 +01:00
Andreas Awouters
fe90d39943 119602: Add placeholder in form 2024-11-18 11:36:10 +01:00
Andreas Awouters
bb7f0cd3a5 119602: Improve profile page link accessibility 2024-11-07 10:18:11 +01:00
Andreas Awouters
ed0fcd982c Merge branch 'accessibility-settings-7.6' into accessibility-settings-main
# Conflicts:
#	src/app/core/auth/auth.service.ts
#	src/app/info/info-routing-paths.ts
#	src/app/info/info-routing.module.ts
#	src/app/info/info.module.ts
#	src/app/shared/live-region/live-region.service.spec.ts
#	src/app/shared/live-region/live-region.service.ts
#	src/app/shared/notifications/notifications-board/notifications-board.component.spec.ts
#	src/app/shared/notifications/notifications-board/notifications-board.component.ts
#	src/config/app-config.interface.ts
#	src/config/default-app-config.ts
2024-11-06 14:06:26 +01:00
Andreas Awouters
04515591e2 119602: Add link to AccessibilitySettings on profile page 2024-11-06 10:53:41 +01:00
Andreas Awouters
37455a8b6c 119602: Make AccessibilitySettings cookie expiration configurable 2024-11-06 10:53:41 +01:00
Andreas Awouters
82fd9539b7 119602: Add AccessibilitySettingsComponent tests 2024-11-06 10:53:41 +01:00
Andreas Awouters
52eabec70d 119602: Add AccessibilitySettingsService tests 2024-11-06 10:53:41 +01:00
Andreas Awouters
cad086c945 119602: Add AccessibilitySettingsService stub & fix live-region test 2024-11-06 10:53:41 +01:00
Andreas Awouters
6a49df59af 119602: Integrate accessibility settings into notifications-board 2024-11-06 10:53:41 +01:00
Andreas Awouters
d224a2c47d 119602: Integrate accessibility settings into live-region 2024-11-06 09:58:19 +01:00
Andreas Awouters
b72ce73931 119602: Add Accessibility Settings page 2024-11-06 09:58:19 +01:00
Andreas Awouters
8f708d0e28 119602: Add AccessibilitySettingsService 2024-11-06 09:58:19 +01:00
DanGastardelli
3cd5432034 Changed the implementation of changing the text font size when there is no thumbnail. 2024-10-22 10:37:41 -03:00
Andrea Barbasso
49b329edb1 [DSC-1847][DSC-1966] fix navbar ui error 2024-10-22 15:03:30 +02:00
DanGastardelli
c4dfed0e02 Adjust the font size of the 'No thumbnails available' text. 2024-09-30 12:33:00 -03:00
Alexandre Vryghem
27eeefae5f 118135: Fixed missing item page relationship labels 2024-09-25 15:42:20 +02:00
Alexandre Vryghem
286de02158 118135: Made the edit relationship labels unique
And fixed it for all the languages 🍞
2024-09-06 15:19:48 +02:00
1647 changed files with 64270 additions and 20348 deletions

View File

@@ -15,6 +15,10 @@ trim_trailing_whitespace = false
[*.ts]
quote_type = single
ij_typescript_enforce_trailing_comma = whenmultiline
[*.js]
ij_javascript_enforce_trailing_comma = whenmultiline
[*.json5]
ij_json_keep_blank_lines_in_code = 3

View File

@@ -12,7 +12,6 @@
"eslint-plugin-rxjs",
"eslint-plugin-simple-import-sort",
"eslint-plugin-import-newlines",
"eslint-plugin-jsonc",
"dspace-angular-ts",
"dspace-angular-html"
],
@@ -161,6 +160,9 @@
]
}
],
"@angular-eslint/prefer-standalone": [
"error"
],
"@angular-eslint/no-attribute-decorator": "error",
"@angular-eslint/no-output-native": "warn",
"@angular-eslint/no-output-on-prefix": "warn",
@@ -261,9 +263,48 @@
"rxjs/no-nested-subscribe": "off", // todo: go over _all_ cases
// Custom DSpace Angular rules
"dspace-angular-ts/alias-imports": [
"error",
{
"aliases": [
{
"package": "rxjs",
"imported": "of",
"local": "of"
}
]
}
],
"dspace-angular-ts/themed-component-classes": "error",
"dspace-angular-ts/themed-component-selectors": "error",
"dspace-angular-ts/themed-component-usages": "error"
"dspace-angular-ts/themed-component-usages": "error",
"dspace-angular-ts/themed-decorators": [
"off",
{
"decorators": {
"listableObjectComponent": 3,
"rendersSectionForMenu": 2
}
}
],
"dspace-angular-ts/themed-wrapper-no-input-defaults": "error",
"dspace-angular-ts/unique-decorators": [
"off",
{
"decorators": [
"listableObjectComponent"
]
}
],
"dspace-angular-ts/sort-standalone-imports": [
"error",
{
"locale": "en-US",
"maxItems": 0,
"indent": 2,
"trailingComma": true
}
]
}
},
{
@@ -303,10 +344,13 @@
"*.json5"
],
"extends": [
"plugin:jsonc/recommended-with-jsonc"
"plugin:jsonc/recommended-with-json5"
],
"rules": {
"no-irregular-whitespace": "error",
// The ESLint core no-irregular-whitespace rule doesn't work well in JSON
// See: https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-irregular-whitespace.html
"no-irregular-whitespace": "off",
"jsonc/no-irregular-whitespace": "error",
"no-trailing-spaces": "error",
"jsonc/comma-dangle": [
"error",

View File

@@ -29,6 +29,8 @@ jobs:
DSPACE_CACHE_SERVERSIDE_ANONYMOUSCACHE_MAX: 0
# Tell Cypress to run e2e tests using the same UI URL
CYPRESS_BASE_URL: http://127.0.0.1:4000
# Disable the cookie consent banner in e2e tests to avoid errors because of elements hidden by it
DSPACE_INFO_ENABLECOOKIECONSENTPOPUP: false
# 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"
@@ -190,12 +192,115 @@ jobs:
# Get homepage and verify that the <meta name="title"> tag includes "DSpace".
# If it does, then SSR is working, as this tag is created by our MetadataService.
# This step also prints entire HTML of homepage for easier debugging if grep fails.
- name: Verify SSR (server-side rendering)
- name: Verify SSR (server-side rendering) on Homepage
run: |
result=$(wget -O- -q http://127.0.0.1:4000/home)
echo "$result"
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep DSpace
# Get a specific community in our test data and verify that the "<h1>" tag includes "Publications" (the community name).
# If it does, then SSR is working.
- name: Verify SSR on a Community page
run: |
result=$(wget -O- -q http://127.0.0.1:4000/communities/0958c910-2037-42a9-81c7-dca80e3892b4)
echo "$result"
echo "$result" | grep -oE "<h1 [^>]*>[^><]*</h1>" | grep Publications
# Get a specific collection in our test data and verify that the "<h1>" tag includes "Articles" (the collection name).
# If it does, then SSR is working.
- name: Verify SSR on a Collection page
run: |
result=$(wget -O- -q http://127.0.0.1:4000/collections/282164f5-d325-4740-8dd1-fa4d6d3e7200)
echo "$result"
echo "$result" | grep -oE "<h1 [^>]*>[^><]*</h1>" | grep Articles
# Get a specific publication in our test data and verify that the <meta name="title"> tag includes
# the title of this publication. If it does, then SSR is working.
- name: Verify SSR on a Publication page
run: |
result=$(wget -O- -q http://127.0.0.1:4000/entities/publication/6160810f-1e53-40db-81ef-f6621a727398)
echo "$result"
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "An Economic Model of Mortality Salience"
# Get a specific person in our test data and verify that the <meta name="title"> tag includes
# the name of the person. If it does, then SSR is working.
- name: Verify SSR on a Person page
run: |
result=$(wget -O- -q http://127.0.0.1:4000/entities/person/b1b2c768-bda1-448a-a073-fc541e8b24d9)
echo "$result"
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Simmons, Cameron"
# Get a specific project in our test data and verify that the <meta name="title"> tag includes
# the name of the project. If it does, then SSR is working.
- name: Verify SSR on a Project page
run: |
result=$(wget -O- -q http://127.0.0.1:4000/entities/project/46ccb608-a74c-4bf6-bc7a-e29cc7defea9)
echo "$result"
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "University Research Fellowship"
# Get a specific orgunit in our test data and verify that the <meta name="title"> tag includes
# the name of the orgunit. If it does, then SSR is working.
- name: Verify SSR on an OrgUnit page
run: |
result=$(wget -O- -q http://127.0.0.1:4000/entities/orgunit/9851674d-bd9a-467b-8d84-068deb568ccf)
echo "$result"
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Law and Development"
# Get a specific journal in our test data and verify that the <meta name="title"> tag includes
# the name of the journal. If it does, then SSR is working.
- name: Verify SSR on a Journal page
run: |
result=$(wget -O- -q http://127.0.0.1:4000/entities/journal/d4af6c3e-53d0-4757-81eb-566f3b45d63a)
echo "$result"
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Environmental &amp; Architectural Phenomenology"
# Get a specific journal volume in our test data and verify that the <meta name="title"> tag includes
# the name of the volume. If it does, then SSR is working.
- name: Verify SSR on a Journal Volume page
run: |
result=$(wget -O- -q http://127.0.0.1:4000/entities/journalvolume/07c6249f-4bf7-494d-9ce3-6ffdb2aed538)
echo "$result"
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Environmental &amp; Architectural Phenomenology Volume 28 (2017)"
# Get a specific journal issue in our test data and verify that the <meta name="title"> tag includes
# the name of the issue. If it does, then SSR is working.
- name: Verify SSR on a Journal Issue page
run: |
result=$(wget -O- -q http://127.0.0.1:4000/entities/journalissue/44c29473-5de2-48fa-b005-e5029aa1a50b)
echo "$result"
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Environmental &amp; Architectural Phenomenology Vol. 28, No. 1"
# Verify 301 Handle redirect behavior
# Note: /handle/123456789/260 is the same test Publication used by our e2e tests
- name: Verify 301 redirect from '/handle' URLs
run: |
result=$(wget --server-response --quiet http://127.0.0.1:4000/handle/123456789/260 2>&1 | head -1 | awk '{print $2}')
echo "$result"
[[ "$result" -eq "301" ]]
# Verify 403 error code behavior
- name: Verify 403 error code from '/403'
run: |
result=$(wget --server-response --quiet http://127.0.0.1:4000/403 2>&1 | head -1 | awk '{print $2}')
echo "$result"
[[ "$result" -eq "403" ]]
# Verify 404 error code behavior
- name: Verify 404 error code from '/404' and on invalid pages
run: |
result=$(wget --server-response --quiet http://127.0.0.1:4000/404 2>&1 | head -1 | awk '{print $2}')
echo "$result"
result2=$(wget --server-response --quiet http://127.0.0.1:4000/invalidurl 2>&1 | head -1 | awk '{print $2}')
echo "$result2"
[[ "$result" -eq "404" && "$result2" -eq "404" ]]
# Verify 500 error code behavior
- name: Verify 500 error code from '/500'
run: |
result=$(wget --server-response --quiet http://127.0.0.1:4000/500 2>&1 | head -1 | awk '{print $2}')
echo "$result"
[[ "$result" -eq "500" ]]
- name: Stop running app
run: kill -9 $(lsof -t -i:4000)

View File

@@ -23,10 +23,24 @@ ssr:
# Determining which styles are critical is a relatively expensive operation; this option is
# disabled (false) by default to boost server performance at the expense of loading smoothness.
inlineCriticalCss: false
# Path prefixes to enable SSR for. By default these are limited to paths of primary DSpace objects.
# NOTE: The "/handle/" path ensures Handle redirects work via SSR. The "/reload/" path ensures
# hard refreshes (e.g. after login) trigger SSR while fully reloading the page.
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/', '/reload/' ]
# Patterns to be run as regexes against the path of the page to check if SSR is allowed.
# If the path match any of the regexes it will be served directly in CSR.
# By default, excludes community and collection browse, global browse, global search, community list, statistics and various administrative tools.
excludePathPatterns:
- pattern: "^/communities/[a-f0-9-]{36}/browse(/.*)?$"
flag: "i"
- pattern: "^/collections/[a-f0-9-]{36}/browse(/.*)?$"
flag: "i"
- pattern: "^/browse/"
- pattern: "^/search$"
- pattern: "^/community-list$"
- pattern: "^/admin/"
- pattern: "^/processes/?"
- pattern: "^/notifications/"
- pattern: "^/statistics/?"
- pattern: "^/access-control/"
- pattern: "^/health$"
# Whether to enable rendering of Search component on SSR.
# If set to true the component will be included in the HTML returned from the server side rendering.
# If set to false the component will not be included in the HTML returned from the server side rendering.
@@ -260,6 +274,9 @@ languages:
- code: gd
label: Gàidhlig
active: true
- code: gu
label: ગુજરાતી
active: true
- code: hi
label: हिंदी
active: true
@@ -275,6 +292,9 @@ languages:
- code: lv
label: Latviešu
active: true
- code: mr
label: मराठी
active: true
- code: nl
label: Nederlands
active: true
@@ -287,6 +307,9 @@ languages:
- code: pt-BR
label: Português do Brasil
active: true
- code: ru
label: Русский
active: true
- code: sr-lat
label: Srpski (lat)
active: true
@@ -453,6 +476,8 @@ info:
enableEndUserAgreement: true
enablePrivacyStatement: true
enableCOARNotifySupport: true
# Whether to show the cookie consent popup and the cookie settings footer link or not.
enableCookieConsentPopup: true
# Whether to enable Markdown (https://commonmark.org/) and MathJax (https://www.mathjax.org/)
# display in supported metadata fields. By default, only dc.description.abstract is supported.
@@ -593,3 +618,8 @@ geospatialMapViewer:
defaultCentrePoint:
lat: 41.015137
lng: 28.979530
# Configuration for storing accessibility settings, used by the AccessibilitySettingsService
accessibility:
# The duration in days after which the accessibility settings cookie expires
cookieExpirationDuration: 7

View File

@@ -34,6 +34,7 @@ export default defineConfig({
DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME: 'People',
// Account used to test basic submission process
DSPACE_TEST_SUBMIT_USER: 'dspacedemo+submit@gmail.com',
DSPACE_TEST_SUBMIT_USER_UUID: '914955b1-cf2e-4884-8af7-a166aa24cf73',
DSPACE_TEST_SUBMIT_USER_PASSWORD: 'dspace',
// Administrator users group
DSPACE_ADMINISTRATOR_GROUP: 'e59f5659-bff9-451e-b28f-439e7bd467e4'

View File

@@ -15,24 +15,24 @@ describe('Header', () => {
cy.visit('/');
// Click the language switcher (globe) in header
cy.get('a[data-test="lang-switch"]').click();
cy.get('button[data-test="lang-switch"]').click();
// Click on the "Deusch" language in dropdown
cy.get('#language-menu-list li').contains('Deutsch').click();
cy.get('#language-menu-list div[role="option"]').contains('Deutsch').click();
// HTML "lang" attribute should switch to "de"
cy.get('html').invoke('attr', 'lang').should('eq', 'de');
// Login menu should now be in German
cy.get('a[data-test="login-menu"]').contains('Anmelden');
cy.get('[data-test="login-menu"]').contains('Anmelden');
// Change back to English from language switcher
cy.get('a[data-test="lang-switch"]').click();
cy.get('#language-menu-list li').contains('English').click();
cy.get('button[data-test="lang-switch"]').click();
cy.get('#language-menu-list div[role="option"]').contains('English').click();
// HTML "lang" attribute should switch to "en"
cy.get('html').invoke('attr', 'lang').should('eq', 'en');
// Login menu should now be in English
cy.get('a[data-test="login-menu"]').contains('Log In');
cy.get('[data-test="login-menu"]').contains('Log In');
});
});

View File

@@ -4,13 +4,14 @@ import { Options } from 'cypress-axe';
beforeEach(() => {
// Must login as an Admin to see the page
cy.intercept('GET', '/server/actuator/health').as('status');
cy.intercept('GET', '/server/actuator/info').as('info');
cy.visit('/health');
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
});
describe('Health Page > Status Tab', () => {
it('should pass accessibility tests', () => {
cy.intercept('GET', '/server/actuator/health').as('status');
cy.wait('@status');
cy.get('a[data-test="health-page.status-tab"]').click();
@@ -36,7 +37,6 @@ describe('Health Page > Status Tab', () => {
describe('Health Page > Info Tab', () => {
it('should pass accessibility tests', () => {
cy.intercept('GET', '/server/actuator/info').as('info');
cy.wait('@info');
cy.get('a[data-test="health-page.info-tab"]').click();

View File

@@ -26,6 +26,12 @@ describe('Homepage', () => {
// Wait for homepage tag to appear
cy.get('ds-home-page').should('be.visible');
// Wait for at least one loading component to show up
cy.get('ds-loading').should('exist');
// Wait until all loading components have disappeared
cy.get('ds-loading').should('not.exist');
// Analyze <ds-home-page> for accessibility issues
testA11y('ds-home-page');
});

View File

@@ -84,7 +84,7 @@ describe('My DSpace page', () => {
cy.url().should('include', '/mydspace');
// Close any open notifications, to make sure they don't get in the way of next steps
cy.get('[data-dismiss="alert"]').click({ multiple: true });
cy.get('[data-bs-dismiss="alert"]').click({ multiple: true });
// This is the GET command that will actually run the search
cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results');

View File

@@ -95,7 +95,7 @@ describe('New Submission page', () => {
// A success alert should be visible
cy.get('ds-notification div.alert-success').should('be.visible');
// Now, dismiss any open alert boxes (may be multiple, as tests run quickly)
cy.get('[data-dismiss="alert"]').click({ multiple: true });
cy.get('[data-bs-dismiss="alert"]').click({ multiple: true });
// This is the GET command that will actually run the search
cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results');

View File

@@ -56,7 +56,7 @@ before(() => {
beforeEach(() => {
// Pre-agree to all Orejime cookies by setting the orejime-anonymous cookie
// This just ensures it doesn't get in the way of matching other objects in the page.
cy.setCookie('orejime-anonymous', '{"authentication":true,"preferences":true,"acknowledgement":true,"google-analytics":true}');
cy.setCookie('orejime-anonymous', '{"authentication":true,"preferences":true,"acknowledgement":true,"google-analytics":true,"correlation-id":true,"accessibility":true}');
// Remove any CSRF cookies saved from prior tests
cy.clearCookie(DSPACE_XSRF_COOKIE);

View File

@@ -93,7 +93,10 @@ services:
volumes:
# Keep Solr data directory between reboots
- solr_data:/var/solr/data
# Initialize all DSpace Solr cores using the mounted configsets (see above), then start Solr
# NOTE: We are not running Solr as "root", but we need root permissions to copy our cores to the mounted
# /var/solr/data directory. Then we start Solr as the "solr" user.
user: root
# Initialize all DSpace Solr cores, then start Solr
entrypoint:
- /bin/bash
- '-c'
@@ -111,7 +114,8 @@ services:
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
cp -r /opt/solr/server/solr/configsets/suggestion/* suggestion
exec solr -f
chown -R solr:solr /var/solr
runuser -u solr -- solr-foreground
volumes:
assetstore:
pgdata:

View File

@@ -97,11 +97,16 @@ services:
volumes:
# Keep Solr data directory between reboots
- solr_data:/var/solr/data
# NOTE: We are not running Solr as "root", but we need root permissions to copy our cores to the mounted
# /var/solr/data directory. Then we start Solr as the "solr" user.
user: root
# Initialize all DSpace Solr cores using the mounted local configsets (see above), then start Solr
# * First, run precreate-core to create the core (if it doesn't yet exist). If exists already, this is a no-op
# * Second, copy configsets to this core:
# Updates to Solr configs require the container to be rebuilt/restarted:
# `docker compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build dspacesolr`
# * Third, ensure all new folders are owned by "solr" user
# * Finally, start Solr as the "solr" user via the provided solr-foreground script
entrypoint:
- /bin/bash
- '-c'
@@ -119,7 +124,8 @@ services:
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
cp -r /opt/solr/server/solr/configsets/suggestion/* suggestion
exec solr -f
chown -R solr:solr /var/solr
runuser -u solr -- solr-foreground
volumes:
assetstore:
pgdata:

View File

@@ -9,6 +9,8 @@ _______
[Source code](../../../../lint/src/rules/html/no-disabled-attribute-on-button.ts)
### Examples
@@ -19,24 +21,28 @@ _______
```html
<button [dsBtnDisabled]="true">Submit</button>
```
##### disabled attribute is still valid on non-button elements
```html
<input disabled>
```
##### [disabled] attribute is still valid on non-button elements
```html
<input [disabled]="true">
```
##### angular dynamic attributes that use disabled are still valid
```html
<button [class.disabled]="isDisabled">Submit</button>
```
@@ -47,6 +53,9 @@ _______
```html
<button disabled>Submit</button>
```
Will produce the following error(s):
```
@@ -63,6 +72,9 @@ Result of `yarn lint --fix`:
```html
<button [disabled]="true">Submit</button>
```
Will produce the following error(s):
```

View File

@@ -11,6 +11,8 @@ _______
[Source code](../../../../lint/src/rules/html/themed-component-usages.ts)
### Examples
@@ -23,6 +25,7 @@ _______
<ds-test-themeable></ds-test-themeable>
<ds-test-themeable [test]="something"></ds-test-themeable>
```
##### use no-prefix selectors in TypeScript templates
@@ -33,6 +36,7 @@ _______
class Test {
}
```
##### use no-prefix selectors in TypeScript test templates
@@ -45,6 +49,7 @@ Filename: `lint/test/fixture/src/test.spec.ts`
class Test {
}
```
##### base selectors are also allowed in TypeScript test templates
@@ -57,6 +62,7 @@ Filename: `lint/test/fixture/src/test.spec.ts`
class Test {
}
```
@@ -69,6 +75,9 @@ class Test {
<ds-themed-test-themeable/>
<ds-themed-test-themeable></ds-themed-test-themeable>
<ds-themed-test-themeable [test]="something"></ds-themed-test-themeable>
```
Will produce the following error(s):
```
@@ -91,6 +100,9 @@ Result of `yarn lint --fix`:
<ds-base-test-themeable/>
<ds-base-test-themeable></ds-base-test-themeable>
<ds-base-test-themeable [test]="something"></ds-base-test-themeable>
```
Will produce the following error(s):
```

View File

@@ -1,6 +1,11 @@
[DSpace ESLint plugins](../../../lint/README.md) > TypeScript rules
_______
- [`dspace-angular-ts/alias-imports`](./rules/alias-imports.md): Unclear imports should be aliased for clarity
- [`dspace-angular-ts/sort-standalone-imports`](./rules/sort-standalone-imports.md): Sorts the standalone `@Component` imports alphabetically
- [`dspace-angular-ts/themed-component-classes`](./rules/themed-component-classes.md): Formatting rules for themeable component classes
- [`dspace-angular-ts/themed-component-selectors`](./rules/themed-component-selectors.md): Themeable component selectors should follow the DSpace convention
- [`dspace-angular-ts/themed-component-usages`](./rules/themed-component-usages.md): Themeable components should be used via their `ThemedComponent` wrapper class
- [`dspace-angular-ts/themed-decorators`](./rules/themed-decorators.md): Entry components with theme support should declare the correct theme
- [`dspace-angular-ts/themed-wrapper-no-input-defaults`](./rules/themed-wrapper-no-input-defaults.md): ThemedComponent wrappers should not declare input defaults (see [DSpace Angular #2164](https://github.com/DSpace/dspace-angular/pull/2164))
- [`dspace-angular-ts/unique-decorators`](./rules/unique-decorators.md): Some decorators must be called with unique arguments (e.g. when they construct a mapping based on the argument values)

View File

@@ -0,0 +1,148 @@
[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/alias-imports`
_______
Unclear imports should be aliased for clarity
_______
[Source code](../../../../lint/src/rules/ts/alias-imports.ts)
### Options
#### `aliases`
A list of all the imports that you want to alias for clarity. Every alias should be declared in the following format:
```json
{
"package": "rxjs",
"imported": "of",
"local": "observableOf"
}
```
### Examples
#### Valid code
##### correctly aliased imports
```typescript
import { of as observableOf } from 'rxjs';
```
With options:
```json
{
"aliases": [
{
"package": "rxjs",
"imported": "of",
"local": "observableOf"
}
]
}
```
##### enforce unaliased import
```typescript
import { combineLatest } from 'rxjs';
```
With options:
```json
{
"aliases": [
{
"package": "rxjs",
"imported": "combineLatest",
"local": "combineLatest"
}
]
}
```
#### Invalid code &amp; automatic fixes
##### imports without alias
```typescript
import { of } from 'rxjs';
```
Will produce the following error(s):
```
This import must be aliased
```
Result of `yarn lint --fix`:
```typescript
import { of as observableOf } from 'rxjs';
```
##### imports under the wrong alias
```typescript
import { of as ofSomething } from 'rxjs';
```
Will produce the following error(s):
```
This import uses the wrong alias (should be {{ local }})
```
Result of `yarn lint --fix`:
```typescript
import { of as observableOf } from 'rxjs';
```
##### disallow aliasing import
```typescript
import { combineLatest as observableCombineLatest } from 'rxjs';
With options:
```json
{
"aliases": [
{
"package": "rxjs",
"imported": "combineLatest",
"local": "combineLatest"
}
]
}
```
```
Will produce the following error(s):
```
This import should not use an alias
```
Result of `yarn lint --fix`:
```typescript
import { combineLatest } from 'rxjs';
```

View File

@@ -0,0 +1,245 @@
[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/sort-standalone-imports`
_______
Sorts the standalone `@Component` imports alphabetically
_______
[Source code](../../../../lint/src/rules/ts/sort-standalone-imports.ts)
### Options
#### `locale`
The locale used to sort the imports.,
#### `maxItems`
The maximum number of imports that should be displayed before each import is separated onto its own line.,
#### `indent`
The indent used for the project.,
#### `trailingComma`
Whether the last import should have a trailing comma (only applicable for multiline imports).
### Examples
#### Valid code
##### should sort multiple imports on separate lines
```typescript
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
AsyncPipe,
RootComponent,
],
})
export class AppComponent {}
```
##### should not inlines singular imports when maxItems is 0
```typescript
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
RootComponent,
],
})
export class AppComponent {}
```
##### should inline singular imports when maxItems is 1
```typescript
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [RootComponent],
})
export class AppComponent {}
```
With options:
```json
{
"maxItems": 1
}
```
#### Invalid code &amp; automatic fixes
##### should sort multiple imports alphabetically
```typescript
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
RootComponent,
AsyncPipe,
],
})
export class AppComponent {}
```
Will produce the following error(s):
```
Standalone imports should be sorted alphabetically
```
Result of `yarn lint --fix`:
```typescript
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
AsyncPipe,
RootComponent,
],
})
export class AppComponent {}
```
##### should not put singular imports on one line when maxItems is 0
```typescript
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [RootComponent],
})
export class AppComponent {}
```
Will produce the following error(s):
```
Standalone imports should be sorted alphabetically
```
Result of `yarn lint --fix`:
```typescript
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
RootComponent,
],
})
export class AppComponent {}
```
##### should not put singular imports on a separate line when maxItems is 1
```typescript
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
RootComponent,
],
})
export class AppComponent {}
With options:
```json
{
"maxItems": 1
}
```
```
Will produce the following error(s):
```
Standalone imports should be sorted alphabetically
```
Result of `yarn lint --fix`:
```typescript
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [RootComponent],
})
export class AppComponent {}
```
##### should not display multiple imports on the same line
```typescript
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [AsyncPipe, RootComponent],
})
export class AppComponent {}
```
Will produce the following error(s):
```
Standalone imports should be sorted alphabetically
```
Result of `yarn lint --fix`:
```typescript
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
AsyncPipe,
RootComponent,
],
})
export class AppComponent {}
```

View File

@@ -11,6 +11,8 @@ _______
[Source code](../../../../lint/src/rules/ts/themed-component-classes.ts)
### Examples
@@ -26,6 +28,7 @@ _______
class Something {
}
```
##### Base component
@@ -34,9 +37,10 @@ class Something {
selector: 'ds-base-test-themable',
standalone: true,
})
class TestThemeableTomponent {
class TestThemeableComponent {
}
```
##### Wrapper component
@@ -50,9 +54,10 @@ Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
TestThemeableComponent,
],
})
class ThemedTestThemeableTomponent extends ThemedComponent<TestThemeableComponent> {
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
}
```
##### Override component
@@ -66,6 +71,7 @@ Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.t
class Override extends BaseComponent {
}
```
@@ -80,6 +86,9 @@ class Override extends BaseComponent {
})
class TestThemeableComponent {
}
```
Will produce the following error(s):
```
@@ -107,6 +116,9 @@ Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
})
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
}
```
Will produce the following error(s):
```
@@ -137,6 +149,9 @@ Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
})
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
}
```
Will produce the following error(s):
```
@@ -171,6 +186,9 @@ import { SomethingElse } from './somewhere-else';
})
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
}
```
Will produce the following error(s):
```
@@ -207,6 +225,9 @@ import { Something, SomethingElse } from './somewhere-else';
})
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
}
```
Will produce the following error(s):
```
@@ -237,6 +258,9 @@ Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.t
})
class Override extends BaseComponent {
}
```
Will produce the following error(s):
```

View File

@@ -17,6 +17,8 @@ _______
[Source code](../../../../lint/src/rules/ts/themed-component-selectors.ts)
### Examples
@@ -31,6 +33,7 @@ _______
class Something {
}
```
##### Themeable component selector should replace the original version, unthemed version should be changed to ds-base-
@@ -53,6 +56,7 @@ class ThemedSomething extends ThemedComponent<Something> {
class OverrideSomething extends Something {
}
```
##### Other themed component wrappers should not interfere
@@ -69,6 +73,7 @@ class Something {
class ThemedSomethingElse extends ThemedComponent<SomethingElse> {
}
```
@@ -85,6 +90,9 @@ Filename: `lint/test/fixture/src/app/test/test-themeable.component.ts`
})
class TestThemeableComponent {
}
```
Will produce the following error(s):
```
@@ -111,6 +119,9 @@ Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
})
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
}
```
Will produce the following error(s):
```
@@ -137,6 +148,9 @@ Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.t
})
class TestThememeableComponent extends BaseComponent {
}
```
Will produce the following error(s):
```

View File

@@ -15,6 +15,8 @@ _______
[Source code](../../../../lint/src/rules/ts/themed-component-usages.ts)
### Examples
@@ -30,6 +32,7 @@ const config = {
b: ChipsComponent,
}
```
##### allow base class in class declaration
@@ -37,6 +40,7 @@ const config = {
export class TestThemeableComponent {
}
```
##### allow inheriting from base class
@@ -46,6 +50,7 @@ import { TestThemeableComponent } from './app/test/test-themeable.component';
export class ThemedAdminSidebarComponent extends ThemedComponent<TestThemeableComponent> {
}
```
##### allow base class in ViewChild
@@ -56,6 +61,7 @@ export class Something {
@ViewChild(TestThemeableComponent) test: TestThemeableComponent;
}
```
##### allow wrapper selectors in test queries
@@ -65,6 +71,7 @@ Filename: `lint/test/fixture/src/app/test/test.component.spec.ts`
By.css('ds-themeable');
By.css('#test > ds-themeable > #nest');
```
##### allow wrapper selectors in cypress queries
@@ -74,6 +81,7 @@ Filename: `lint/test/fixture/src/app/test/test.component.cy.ts`
By.css('ds-themeable');
By.css('#test > ds-themeable > #nest');
```
@@ -90,6 +98,9 @@ const config = {
a: TestThemeableComponent,
b: TestComponent,
}
```
Will produce the following error(s):
```
@@ -120,6 +131,9 @@ const config = {
b: TestComponent,
c: Something,
}
```
Will produce the following error(s):
```
@@ -150,6 +164,9 @@ const DECLARATIONS = [
Something,
ThemedTestThemeableComponent,
];
```
Will produce the following error(s):
```
@@ -173,6 +190,9 @@ Filename: `lint/test/fixture/src/app/test/test.component.spec.ts`
```typescript
By.css('ds-themed-themeable');
By.css('#test > ds-themed-themeable > #nest');
```
Will produce the following error(s):
```
@@ -194,6 +214,9 @@ Filename: `lint/test/fixture/src/app/test/test.component.spec.ts`
```typescript
By.css('ds-base-themeable');
By.css('#test > ds-base-themeable > #nest');
```
Will produce the following error(s):
```
@@ -215,6 +238,9 @@ Filename: `lint/test/fixture/src/app/test/test.component.cy.ts`
```typescript
cy.get('ds-themed-themeable');
cy.get('#test > ds-themed-themeable > #nest');
```
Will produce the following error(s):
```
@@ -236,6 +262,9 @@ Filename: `lint/test/fixture/src/app/test/test.component.cy.ts`
```typescript
cy.get('ds-base-themeable');
cy.get('#test > ds-base-themeable > #nest');
```
Will produce the following error(s):
```
@@ -266,6 +295,9 @@ import { TestThemeableComponent } from '../../../../app/test/test-themeable.comp
})
export class UsageComponent {
}
```
Will produce the following error(s):
```
@@ -306,6 +338,9 @@ import { ThemedTestThemeableComponent } from '../../../../app/test/themed-test-t
})
export class UsageComponent {
}
```
Will produce the following error(s):
```

View File

@@ -0,0 +1,183 @@
[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/themed-decorators`
_______
Entry components with theme support should declare the correct theme
_______
[Source code](../../../../lint/src/rules/ts/themed-decorators.ts)
### Options
#### `decorators`
A mapping for all the existing themeable decorators, with the decorator name as the key and the index of the `theme` argument as the value.
### Examples
#### Valid code
##### theme file declares the correct theme in @listableObjectComponent
Filename: `lint/test/fixture/src/themes/test/app/dynamic-component/dynamic-component.ts`
```typescript
@listableObjectComponent(something, somethingElse, undefined, 'test')
export class Something extends SomethingElse {
}
```
##### plain file declares no theme in @listableObjectComponent
Filename: `lint/test/fixture/src/app/dynamic-component/dynamic-component.ts`
```typescript
@listableObjectComponent(something, somethingElse, undefined)
export class Something extends SomethingElse {
}
```
##### plain file declares explicit undefined theme in @listableObjectComponent
Filename: `lint/test/fixture/src/app/dynamic-component/dynamic-component.ts`
```typescript
@listableObjectComponent(something, somethingElse, undefined, undefined)
export class Something extends SomethingElse {
}
```
##### test file declares theme outside of theme directory
Filename: `lint/test/fixture/src/app/dynamic-component/dynamic-component.spec.ts`
```typescript
@listableObjectComponent(something, somethingElse, undefined, 'test')
export class Something extends SomethingElse {
}
```
##### only track configured decorators
Filename: `lint/test/fixture/src/app/dynamic-component/dynamic-component.ts`
```typescript
@something('test')
export class Something extends SomethingElse {
}
```
#### Invalid code &amp; automatic fixes
##### theme file declares the wrong theme in @listableObjectComponent
Filename: `lint/test/fixture/src/themes/test/app/dynamic-component/dynamic-component.ts`
```typescript
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
export class Something extends SomethingElse {
}
```
Will produce the following error(s):
```
Wrong theme declaration in decorator
```
Result of `yarn lint --fix`:
```typescript
@listableObjectComponent(something, somethingElse, undefined, 'test')
export class Something extends SomethingElse {
}
```
##### plain file declares a theme in @listableObjectComponent
Filename: `lint/test/fixture/src/app/dynamic-component/dynamic-component.ts`
```typescript
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
export class Something extends SomethingElse {
}
```
Will produce the following error(s):
```
There is a theme declaration in decorator, but this file is not part of a theme
```
Result of `yarn lint --fix`:
```typescript
@listableObjectComponent(something, somethingElse, undefined)
export class Something extends SomethingElse {
}
```
##### theme file declares no theme in @listableObjectComponent
Filename: `lint/test/fixture/src/themes/test-2/app/dynamic-component/dynamic-component.ts`
```typescript
@listableObjectComponent(something, somethingElse, undefined)
export class Something extends SomethingElse {
}
```
Will produce the following error(s):
```
No theme declaration in decorator
```
Result of `yarn lint --fix`:
```typescript
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
export class Something extends SomethingElse {
}
```
##### theme file declares explicit undefined theme in @listableObjectComponent
Filename: `lint/test/fixture/src/themes/test-2/app/dynamic-component/dynamic-component.ts`
```typescript
@listableObjectComponent(something, somethingElse, undefined, undefined)
export class Something extends SomethingElse {
}
```
Will produce the following error(s):
```
No theme declaration in decorator
```
Result of `yarn lint --fix`:
```typescript
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
export class Something extends SomethingElse {
}
```

View File

@@ -0,0 +1,92 @@
[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/themed-wrapper-no-input-defaults`
_______
ThemedComponent wrappers should not declare input defaults (see [DSpace Angular #2164](https://github.com/DSpace/dspace-angular/pull/2164))
_______
[Source code](../../../../lint/src/rules/ts/themed-wrapper-no-input-defaults.ts)
### Examples
#### Valid code
##### ThemedComponent wrapper defines an input without a default value
```typescript
export class TTest extends ThemedComponent<Test> {
@Input()
test;
}
```
##### Regular class defines an input with a default value
```typescript
export class Test {
@Input()
test = 'test';
}
```
#### Invalid code
##### ThemedComponent wrapper defines an input with a default value
```typescript
export class TTest extends ThemedComponent<Test> {
@Input()
test1 = 'test';
@Input()
test2 = true;
@Input()
test2: number = 123;
@Input()
test3: number[] = [1,2,3];
}
```
Will produce the following error(s):
```
ThemedComponent wrapper declares inputs with defaults
ThemedComponent wrapper declares inputs with defaults
ThemedComponent wrapper declares inputs with defaults
ThemedComponent wrapper declares inputs with defaults
```
##### ThemedComponent wrapper defines an input with an undefined default value
```typescript
export class TTest extends ThemedComponent<Test> {
@Input()
test = undefined;
}
```
Will produce the following error(s):
```
ThemedComponent wrapper declares inputs with defaults
```

View File

@@ -0,0 +1,86 @@
[DSpace ESLint plugins](../../../../lint/README.md) > [TypeScript rules](../index.md) > `dspace-angular-ts/unique-decorators`
_______
Some decorators must be called with unique arguments (e.g. when they construct a mapping based on the argument values)
_______
[Source code](../../../../lint/src/rules/ts/unique-decorators.ts)
### Options
#### `decorators`
The list of all the decorators for which you want to enforce this behavior.
### Examples
#### Valid code
##### checked decorator, no repetitions
```typescript
@listableObjectComponent(a)
export class A {
}
@listableObjectComponent(a, 'b')
export class B {
}
@listableObjectComponent(a, 'b', 3)
export class C {
}
@listableObjectComponent(a, 'b', 3, Enum.TEST1)
export class C {
}
@listableObjectComponent(a, 'b', 3, Enum.TEST2)
export class C {
}
```
##### unchecked decorator, some repetitions
```typescript
@something(a)
export class A {
}
@something(a)
export class B {
}
```
#### Invalid code
##### checked decorator, some repetitions
```typescript
@listableObjectComponent(a)
export class A {
}
@listableObjectComponent(a)
export class B {
}
```
Will produce the following error(s):
```
Duplicate decorator call
```

View File

@@ -33,6 +33,7 @@ export const info = {
[Message.USE_DSBTN_DISABLED]: 'Buttons should use the `dsBtnDisabled` directive instead of the `disabled` attribute.',
},
},
optionDocs: [],
defaultOptions: [],
} as DSpaceESLintRuleInfo;

View File

@@ -45,6 +45,7 @@ The only exception to this rule are unit tests, where we may want to use the bas
[Message.WRONG_SELECTOR]: 'Themeable components should be used via their ThemedComponent wrapper\'s selector',
},
},
optionDocs: [],
defaultOptions: [],
} as DSpaceESLintRuleInfo;

View File

@@ -0,0 +1,304 @@
import {
AST_NODE_TYPES,
ESLintUtils,
TSESLint,
TSESTree,
} from '@typescript-eslint/utils';
import { Scope } from '@typescript-eslint/utils/ts-eslint';
import {
DSpaceESLintRuleInfo,
NamedTests,
OptionDoc,
} from '../../util/structure';
export enum Message {
MISSING_ALIAS = 'missingAlias',
WRONG_ALIAS = 'wrongAlias',
MULTIPLE_ALIASES = 'multipleAliases',
UNNECESSARY_ALIAS = 'unnecessaryAlias',
}
interface AliasImportOptions {
aliases: AliasImportOption[];
}
interface AliasImportOption {
package: string;
imported: string;
local: string;
}
interface AliasImportDocOptions {
aliases: OptionDoc;
}
export const info: DSpaceESLintRuleInfo<[AliasImportOptions], [AliasImportDocOptions]> = {
name: 'alias-imports',
meta: {
docs: {
description: 'Unclear imports should be aliased for clarity',
},
messages: {
[Message.MISSING_ALIAS]: 'This import must be aliased',
[Message.WRONG_ALIAS]: 'This import uses the wrong alias (should be {{ local }})',
[Message.MULTIPLE_ALIASES]: 'This import was used twice with a different alias (should be {{ local }})',
[Message.UNNECESSARY_ALIAS]: 'This import should not use an alias',
},
fixable: 'code',
type: 'problem',
schema: {
type: 'array',
items: {
type: 'object',
properties: {
package: { type: 'string' },
imported: { type: 'string' },
local: { type: 'string' },
},
},
},
},
optionDocs: [
{
aliases: {
title: '`aliases`',
description: `A list of all the imports that you want to alias for clarity. Every alias should be declared in the following format:
\`\`\`json
{
"package": "rxjs",
"imported": "of",
"local": "observableOf"
}
\`\`\``,
},
},
],
defaultOptions: [
{
aliases: [
{
package: 'rxjs',
imported: 'of',
local: 'observableOf',
},
],
},
],
};
export const rule = ESLintUtils.RuleCreator.withoutDocs({
...info,
create(context: TSESLint.RuleContext<Message, unknown[]>, options: any) {
return (options[0] as AliasImportOptions).aliases.reduce((selectors: any, option: AliasImportOption) => {
selectors[`ImportDeclaration[source.value = "${option.package}"] > ImportSpecifier[imported.name = "${option.imported}"][local.name != "${option.local}"]`] = (node: TSESTree.ImportSpecifier) => handleUnaliasedImport(context, option, node);
return selectors;
}, {});
},
});
export const tests: NamedTests = {
plugin: info.name,
valid: [
{
name: 'correctly aliased imports',
code: `
import { of as observableOf } from 'rxjs';
`,
options: [
{
aliases: [
{
package: 'rxjs',
imported: 'of',
local: 'observableOf',
},
],
},
],
},
{
name: 'enforce unaliased import',
code: `
import { combineLatest } from 'rxjs';
`,
options: [
{
aliases: [
{
package: 'rxjs',
imported: 'combineLatest',
local: 'combineLatest',
},
],
},
],
},
],
invalid: [
{
name: 'imports without alias',
code: `
import { of } from 'rxjs';
`,
errors: [
{
messageId: 'missingAlias',
},
],
output: `
import { of as observableOf } from 'rxjs';
`,
},
{
name: 'imports under the wrong alias',
code: `
import { of as ofSomething } from 'rxjs';
`,
errors: [
{
messageId: 'wrongAlias',
},
],
output: `
import { of as observableOf } from 'rxjs';
`,
},
{
name: 'disallow aliasing import',
code: `
import { combineLatest as observableCombineLatest } from 'rxjs';
`,
errors: [
{
messageId: 'unnecessaryAlias',
},
],
output: `
import { combineLatest } from 'rxjs';
`,
options: [
{
aliases: [
{
package: 'rxjs',
imported: 'combineLatest',
local: 'combineLatest',
},
],
},
],
},
],
};
/**
* Replaces the incorrectly aliased imports with the ones defined in the defaultOptions
*
* @param context The current {@link TSESLint.RuleContext}
* @param option The current {@link AliasImportOption} that needs to be handled
* @param node The incorrect import node that should be fixed
*/
function handleUnaliasedImport(context: TSESLint.RuleContext<Message, unknown[]>, option: AliasImportOption, node: TSESTree.ImportSpecifier): void {
const hasCorrectAliasedImport: boolean = (node.parent as TSESTree.ImportDeclaration).specifiers.find((specifier: TSESTree.ImportClause) => specifier.local.name === option.local && specifier.type === AST_NODE_TYPES.ImportSpecifier && (specifier as TSESTree.ImportSpecifier).imported.name === option.imported) !== undefined;
if (option.imported === option.local) {
if (hasCorrectAliasedImport) {
context.report({
messageId: Message.MULTIPLE_ALIASES,
data: { local: option.local },
node: node,
fix(fixer: TSESLint.RuleFixer) {
const fixes: TSESLint.RuleFix[] = [];
const commaAfter = context.sourceCode.getTokenAfter(node, {
filter: (token: TSESTree.Token) => token.value === ',',
});
if (commaAfter) {
fixes.push(fixer.removeRange([node.range[0], commaAfter.range[1]]));
} else {
fixes.push(fixer.remove(node));
}
fixes.push(...retrieveUsageReplacementFixes(context, fixer, node, option.local));
return fixes;
},
});
} else {
context.report({
messageId: Message.UNNECESSARY_ALIAS,
data: { local: option.local },
node: node,
fix(fixer: TSESLint.RuleFixer) {
const fixes: TSESLint.RuleFix[] = [];
fixes.push(fixer.replaceText(node, option.imported));
fixes.push(...retrieveUsageReplacementFixes(context, fixer, node, option.local));
return fixes;
},
});
}
} else {
if (hasCorrectAliasedImport) {
context.report({
messageId: Message.MULTIPLE_ALIASES,
data: { local: option.local },
node: node,
fix(fixer: TSESLint.RuleFixer) {
const fixes: TSESLint.RuleFix[] = [];
const commaAfter = context.sourceCode.getTokenAfter(node, {
filter: (token: TSESTree.Token) => token.value === ',',
});
if (commaAfter) {
fixes.push(fixer.removeRange([node.range[0], commaAfter.range[1]]));
} else {
fixes.push(fixer.remove(node));
}
fixes.push(...retrieveUsageReplacementFixes(context, fixer, node, option.local));
return fixes;
},
});
} else if (node.local.name === node.imported.name) {
context.report({
messageId: Message.MISSING_ALIAS,
node: node,
fix(fixer: TSESLint.RuleFixer) {
const fixes: TSESLint.RuleFix[] = [];
fixes.push(fixer.replaceText(node.local, `${option.imported} as ${option.local}`));
fixes.push(...retrieveUsageReplacementFixes(context, fixer, node, option.local));
return fixes;
},
});
} else {
context.report({
messageId: Message.WRONG_ALIAS,
data: { local: option.local },
node: node,
fix(fixer: TSESLint.RuleFixer) {
const fixes: TSESLint.RuleFix[] = [];
fixes.push(fixer.replaceText(node.local, option.local));
fixes.push(...retrieveUsageReplacementFixes(context, fixer, node, option.local));
return fixes;
},
});
}
}
}
/**
* Generates the {@link TSESLint.RuleFix}s for all the usages of the incorrect import.
*
* @param context The current {@link TSESLint.RuleContext}
* @param fixer The instance {@link TSESLint.RuleFixer}
* @param node The node which needs to be replaced
* @param newAlias The new import name
*/
function retrieveUsageReplacementFixes(context: TSESLint.RuleContext<Message, unknown[]>, fixer: TSESLint.RuleFixer, node: TSESTree.ImportSpecifier, newAlias: string): TSESLint.RuleFix[] {
return context.sourceCode.getDeclaredVariables(node)[0].references.map((reference: Scope.Reference) => fixer.replaceText(reference.identifier, newAlias));
}

View File

@@ -10,14 +10,24 @@ import {
RuleExports,
} from '../../util/structure';
/* eslint-disable import/no-namespace */
import * as aliasImports from './alias-imports';
import * as sortStandaloneImports from './sort-standalone-imports';
import * as themedComponentClasses from './themed-component-classes';
import * as themedComponentSelectors from './themed-component-selectors';
import * as themedComponentUsages from './themed-component-usages';
import * as themedDecorators from './themed-decorators';
import * as themedWrapperNoInputDefaults from './themed-wrapper-no-input-defaults';
import * as uniqueDecorators from './unique-decorators';
const index = [
aliasImports,
sortStandaloneImports,
themedComponentClasses,
themedComponentSelectors,
themedComponentUsages,
themedDecorators,
themedWrapperNoInputDefaults,
uniqueDecorators,
] as unknown as RuleExports[];
export = {

View File

@@ -0,0 +1,306 @@
import {
ASTUtils as TSESLintASTUtils,
ESLintUtils,
TSESLint,
TSESTree,
} from '@typescript-eslint/utils';
import {
DSpaceESLintRuleInfo,
NamedTests,
OptionDoc,
} from '../../util/structure';
const DEFAULT_LOCALE = 'en-US';
const DEFAULT_MAX_SIZE = 0;
const DEFAULT_SPACE_INDENT_AMOUNT = 2;
const DEFAULT_TRAILING_COMMA = true;
export enum Message {
SORT_STANDALONE_IMPORTS_ARRAYS = 'sortStandaloneImportsArrays',
}
export interface UniqueDecoratorsOptions {
locale: string;
maxItems: number;
indent: number;
trailingComma: boolean;
}
export interface UniqueDecoratorsDocOptions {
locale: OptionDoc;
maxItems: OptionDoc;
indent: OptionDoc;
trailingComma: OptionDoc;
}
export const info: DSpaceESLintRuleInfo<[UniqueDecoratorsOptions], [UniqueDecoratorsDocOptions]> = {
name: 'sort-standalone-imports',
meta: {
docs: {
description: 'Sorts the standalone `@Component` imports alphabetically',
},
messages: {
[Message.SORT_STANDALONE_IMPORTS_ARRAYS]: 'Standalone imports should be sorted alphabetically',
},
fixable: 'code',
type: 'problem',
schema: [
{
type: 'object',
properties: {
locale: {
type: 'string',
},
maxItems: {
type: 'number',
},
indent: {
type: 'number',
},
trailingComma: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
},
optionDocs: [
{
locale: {
title: '`locale`',
description: 'The locale used to sort the imports.',
},
maxItems: {
title: '`maxItems`',
description: 'The maximum number of imports that should be displayed before each import is separated onto its own line.',
},
indent: {
title: '`indent`',
description: 'The indent used for the project.',
},
trailingComma: {
title: '`trailingComma`',
description: 'Whether the last import should have a trailing comma (only applicable for multiline imports).',
},
},
],
defaultOptions: [
{
locale: DEFAULT_LOCALE,
maxItems: DEFAULT_MAX_SIZE,
indent: DEFAULT_SPACE_INDENT_AMOUNT,
trailingComma: DEFAULT_TRAILING_COMMA,
},
],
};
export const rule = ESLintUtils.RuleCreator.withoutDocs({
...info,
create(context: TSESLint.RuleContext<Message, unknown[]>, [{ locale, maxItems, indent, trailingComma }]: any) {
return {
['ClassDeclaration > Decorator > CallExpression[callee.name="Component"] > ObjectExpression > Property[key.name="imports"] > ArrayExpression']: (node: TSESTree.ArrayExpression) => {
const elements = node.elements.filter((element) => element !== null && (TSESLintASTUtils.isIdentifier(element) || element?.type === TSESTree.AST_NODE_TYPES.CallExpression));
const sortedNames: string[] = elements
.map((element) => context.sourceCode.getText(element!))
.sort((a: string, b: string) => a.localeCompare(b, locale));
const isSorted: boolean = elements.every((identifier, index) => context.sourceCode.getText(identifier!) === sortedNames[index]);
const requiresMultiline: boolean = maxItems < node.elements.length;
const isMultiline: boolean = /\n/.test(context.sourceCode.getText(node));
const incorrectFormat: boolean = requiresMultiline !== isMultiline;
if (isSorted && !incorrectFormat) {
return;
}
context.report({
node: node.parent,
messageId: Message.SORT_STANDALONE_IMPORTS_ARRAYS,
fix: (fixer: TSESLint.RuleFixer) => {
if (requiresMultiline) {
const multilineImports: string = sortedNames
.map((name: string) => `${' '.repeat(2 * indent)}${name}${trailingComma ? ',' : ''}`)
.join(trailingComma ? '\n' : ',\n');
return fixer.replaceText(node, `[\n${multilineImports}\n${' '.repeat(indent)}]`);
} else {
return fixer.replaceText(node, `[${sortedNames.join(', ')}]`);
}
},
});
},
};
},
});
export const tests: NamedTests = {
plugin: info.name,
valid: [
{
name: 'should sort multiple imports on separate lines',
code: `
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
AsyncPipe,
RootComponent,
],
})
export class AppComponent {}`,
},
{
name: 'should not inlines singular imports when maxItems is 0',
code: `
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
RootComponent,
],
})
export class AppComponent {}`,
},
{
name: 'should inline singular imports when maxItems is 1',
options: [{ maxItems: 1 }],
code: `
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [RootComponent],
})
export class AppComponent {}`,
},
],
invalid: [
{
name: 'should sort multiple imports alphabetically',
code: `
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
RootComponent,
AsyncPipe,
],
})
export class AppComponent {}`,
errors: [
{
messageId: Message.SORT_STANDALONE_IMPORTS_ARRAYS,
},
],
output: `
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
AsyncPipe,
RootComponent,
],
})
export class AppComponent {}`,
},
{
name: 'should not put singular imports on one line when maxItems is 0',
code: `
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [RootComponent],
})
export class AppComponent {}`,
errors: [
{
messageId: Message.SORT_STANDALONE_IMPORTS_ARRAYS,
},
],
output: `
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
RootComponent,
],
})
export class AppComponent {}`,
},
{
name: 'should not put singular imports on a separate line when maxItems is 1',
options: [{ maxItems: 1 }],
code: `
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
RootComponent,
],
})
export class AppComponent {}`,
errors: [
{
messageId: Message.SORT_STANDALONE_IMPORTS_ARRAYS,
},
],
output: `
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [RootComponent],
})
export class AppComponent {}`,
},
{
name: 'should not display multiple imports on the same line',
code: `
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [AsyncPipe, RootComponent],
})
export class AppComponent {}`,
errors: [
{
messageId: Message.SORT_STANDALONE_IMPORTS_ARRAYS,
},
],
output: `
@Component({
selector: 'ds-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
imports: [
AsyncPipe,
RootComponent,
],
})
export class AppComponent {}`,
},
],
};

View File

@@ -52,6 +52,7 @@ export const info = {
[Message.WRAPPER_IMPORTS_BASE]: 'Themed component wrapper classes must only import the base class',
},
},
optionDocs: [],
defaultOptions: [],
} as DSpaceESLintRuleInfo;
@@ -180,7 +181,7 @@ class Something {
selector: 'ds-base-test-themable',
standalone: true,
})
class TestThemeableTomponent {
class TestThemeableComponent {
}
`,
},
@@ -195,7 +196,7 @@ class TestThemeableTomponent {
TestThemeableComponent,
],
})
class ThemedTestThemeableTomponent extends ThemedComponent<TestThemeableComponent> {
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
}
`,
},

View File

@@ -53,6 +53,7 @@ Unit tests are exempt from this rule, because they may redefine components using
[Message.THEMED]: 'Theme override of themeable component should have a selector starting with \'ds-themed-\'',
},
},
optionDocs: [],
defaultOptions: [],
} as DSpaceESLintRuleInfo;

View File

@@ -63,6 +63,7 @@ There are a few exceptions where the base class can still be used:
[Message.BASE_IN_MODULE]: 'Base themeable components shouldn\'t be declared in modules',
},
},
optionDocs: [],
defaultOptions: [],
} as DSpaceESLintRuleInfo;

View File

@@ -0,0 +1,280 @@
import {
AST_NODE_TYPES,
ESLintUtils,
TSESLint,
TSESTree,
} from '@typescript-eslint/utils';
import { fixture } from '../../../test/fixture';
import { isTestFile } from '../../util/filter';
import {
DSpaceESLintRuleInfo,
NamedTests,
OptionDoc,
} from '../../util/structure';
import { getFileTheme } from '../../util/theme-support';
export enum Message {
NO_THEME_DECLARED_IN_THEME_FILE = 'noThemeDeclaredInThemeFile',
THEME_DECLARED_IN_NON_THEME_FILE = 'themeDeclaredInNonThemeFile',
WRONG_THEME_DECLARED_IN_THEME_FILE = 'wrongThemeDeclaredInThemeFile',
}
interface ThemedDecoratorsOption {
decorators: { [name: string]: number };
}
interface ThemedDecoratorsDocsOption {
decorators: OptionDoc;
}
export const info: DSpaceESLintRuleInfo<[ThemedDecoratorsOption], [ThemedDecoratorsDocsOption]> = {
name: 'themed-decorators',
meta: {
docs: {
description: 'Entry components with theme support should declare the correct theme',
},
fixable: 'code',
messages: {
[Message.NO_THEME_DECLARED_IN_THEME_FILE]: 'No theme declaration in decorator',
[Message.THEME_DECLARED_IN_NON_THEME_FILE]: 'There is a theme declaration in decorator, but this file is not part of a theme',
[Message.WRONG_THEME_DECLARED_IN_THEME_FILE]: 'Wrong theme declaration in decorator',
},
type: 'problem',
schema: [
{
type: 'object',
properties: {
decorators: {
type: 'object',
},
},
},
],
},
optionDocs: [
{
decorators: {
title: '`decorators`',
description: 'A mapping for all the existing themeable decorators, with the decorator name as the key and the index of the `theme` argument as the value.',
},
},
],
defaultOptions: [
{
decorators: {
listableObjectComponent: 3,
rendersSectionForMenu: 2,
},
},
],
};
export const rule = ESLintUtils.RuleCreator.withoutDocs({
...info,
create(context: TSESLint.RuleContext<Message, unknown[]>, options: any) {
return {
[`ClassDeclaration > Decorator > CallExpression[callee.name=/^(${Object.keys(options[0].decorators).join('|')})$/]`]: (node: TSESTree.CallExpression) => {
if (isTestFile(context)) {
return;
}
if (node.callee.type !== AST_NODE_TYPES.Identifier) {
// We only support regular method identifiers
return;
}
const fileTheme = getFileTheme(context);
const themeDeclaration = getDeclaredTheme(options, node as TSESTree.CallExpression);
if (themeDeclaration === undefined) {
if (fileTheme !== undefined) {
context.report({
messageId: Message.NO_THEME_DECLARED_IN_THEME_FILE,
node: node,
fix(fixer) {
return fixer.insertTextAfter(node.arguments[node.arguments.length - 1], `, '${fileTheme as string}'`);
},
});
}
} else if (themeDeclaration?.type === AST_NODE_TYPES.Literal) {
if (fileTheme === undefined) {
context.report({
messageId: Message.THEME_DECLARED_IN_NON_THEME_FILE,
node: themeDeclaration,
fix(fixer) {
const idx = node.arguments.findIndex((v) => v.range === themeDeclaration.range);
if (idx === 0) {
return fixer.remove(themeDeclaration);
} else {
const previousArgument = node.arguments[idx - 1];
return fixer.removeRange([previousArgument.range[1], themeDeclaration.range[1]]); // todo: comma?
}
},
});
} else if (fileTheme !== themeDeclaration?.value) {
context.report({
messageId: Message.WRONG_THEME_DECLARED_IN_THEME_FILE,
node: themeDeclaration,
fix(fixer) {
return fixer.replaceText(themeDeclaration, `'${fileTheme as string}'`);
},
});
}
} else if (themeDeclaration?.type === AST_NODE_TYPES.Identifier && themeDeclaration.name === 'undefined') {
if (fileTheme !== undefined) {
context.report({
messageId: Message.NO_THEME_DECLARED_IN_THEME_FILE,
node: node,
fix(fixer) {
return fixer.replaceText(node.arguments[node.arguments.length - 1], `'${fileTheme as string}'`);
},
});
}
} else {
throw new Error('Unexpected theme declaration');
}
},
};
},
});
export const tests: NamedTests = {
plugin: info.name,
valid: [
{
name: 'theme file declares the correct theme in @listableObjectComponent',
code: `
@listableObjectComponent(something, somethingElse, undefined, 'test')
export class Something extends SomethingElse {
}
`,
filename: fixture('src/themes/test/app/dynamic-component/dynamic-component.ts'),
},
{
name: 'plain file declares no theme in @listableObjectComponent',
code: `
@listableObjectComponent(something, somethingElse, undefined)
export class Something extends SomethingElse {
}
`,
filename: fixture('src/app/dynamic-component/dynamic-component.ts'),
},
{
name: 'plain file declares explicit undefined theme in @listableObjectComponent',
code: `
@listableObjectComponent(something, somethingElse, undefined, undefined)
export class Something extends SomethingElse {
}
`,
filename: fixture('src/app/dynamic-component/dynamic-component.ts'),
},
{
name: 'test file declares theme outside of theme directory',
code: `
@listableObjectComponent(something, somethingElse, undefined, 'test')
export class Something extends SomethingElse {
}
`,
filename: fixture('src/app/dynamic-component/dynamic-component.spec.ts'),
},
{
name: 'only track configured decorators',
code: `
@something('test')
export class Something extends SomethingElse {
}
`,
filename: fixture('src/app/dynamic-component/dynamic-component.ts'),
},
],
invalid: [
{
name: 'theme file declares the wrong theme in @listableObjectComponent',
code: `
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
export class Something extends SomethingElse {
}
`,
filename: fixture('src/themes/test/app/dynamic-component/dynamic-component.ts'),
errors: [
{
messageId: 'wrongThemeDeclaredInThemeFile',
},
],
output: `
@listableObjectComponent(something, somethingElse, undefined, 'test')
export class Something extends SomethingElse {
}
`,
},
{
name: 'plain file declares a theme in @listableObjectComponent',
code: `
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
export class Something extends SomethingElse {
}
`,
filename: fixture('src/app/dynamic-component/dynamic-component.ts'),
errors: [
{
messageId: 'themeDeclaredInNonThemeFile',
},
],
output: `
@listableObjectComponent(something, somethingElse, undefined)
export class Something extends SomethingElse {
}
`,
},
{
name: 'theme file declares no theme in @listableObjectComponent',
code: `
@listableObjectComponent(something, somethingElse, undefined)
export class Something extends SomethingElse {
}
`,
filename: fixture('src/themes/test-2/app/dynamic-component/dynamic-component.ts'),
errors: [
{
messageId: 'noThemeDeclaredInThemeFile',
},
],
output: `
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
export class Something extends SomethingElse {
}
`,
},
{
name: 'theme file declares explicit undefined theme in @listableObjectComponent',
code: `
@listableObjectComponent(something, somethingElse, undefined, undefined)
export class Something extends SomethingElse {
}
`,
filename: fixture('src/themes/test-2/app/dynamic-component/dynamic-component.ts'),
errors: [
{
messageId: 'noThemeDeclaredInThemeFile',
},
],
output: `
@listableObjectComponent(something, somethingElse, undefined, 'test-2')
export class Something extends SomethingElse {
}
`,
},
],
};
function getDeclaredTheme(options: [ThemedDecoratorsOption], decoratorCall: TSESTree.CallExpression): TSESTree.Node | undefined {
const index: number = options[0].decorators[(decoratorCall.callee as TSESTree.Identifier).name];
if (decoratorCall.arguments.length >= index + 1) {
return decoratorCall.arguments[index];
}
return undefined;
}

View File

@@ -0,0 +1,158 @@
import {
ESLintUtils,
TSESTree,
} from '@typescript-eslint/utils';
import { RuleContext } from '@typescript-eslint/utils/ts-eslint';
import {
DSpaceESLintRuleInfo,
NamedTests,
} from '../../util/structure';
import { isThemedComponentWrapper } from '../../util/theme-support';
export enum Message {
WRAPPER_HAS_INPUT_DEFAULTS = 'wrapperHasInputDefaults',
}
export const info: DSpaceESLintRuleInfo = {
name: 'themed-wrapper-no-input-defaults',
meta: {
docs: {
description: 'ThemedComponent wrappers should not declare input defaults (see [DSpace Angular #2164](https://github.com/DSpace/dspace-angular/pull/2164))',
},
messages: {
[Message.WRAPPER_HAS_INPUT_DEFAULTS]: 'ThemedComponent wrapper declares inputs with defaults',
},
type: 'problem',
schema: [],
},
optionDocs: [],
defaultOptions: [],
};
export const rule = ESLintUtils.RuleCreator.withoutDocs({
...info,
create(context: RuleContext<any, any>, options: any) {
return {
'ClassBody > PropertyDefinition > Decorator > CallExpression[callee.name=\'Input\']': (node: TSESTree.CallExpression) => {
const classDeclaration = (node?.parent?.parent?.parent as TSESTree.Decorator); // todo: clean this up
if (!isThemedComponentWrapper(classDeclaration)) {
return;
}
const propertyDefinition: TSESTree.PropertyDefinition = (node.parent.parent as any); // todo: clean this up
if (propertyDefinition.value !== null) {
context.report({
messageId: Message.WRAPPER_HAS_INPUT_DEFAULTS,
node: propertyDefinition.value,
// fix(fixer) {
// // todo: don't strip type annotations!
// // todo: replace default with appropriate type annotation if not present!
// return fixer.removeRange([propertyDefinition.key.range[1], (propertyDefinition.value as any).range[1]]);
// }
});
}
},
};
},
});
export const tests: NamedTests = {
plugin: info.name,
valid: [
{
name: 'ThemedComponent wrapper defines an input without a default value',
code: `
export class TTest extends ThemedComponent<Test> {
@Input()
test;
}
`,
},
{
name: 'Regular class defines an input with a default value',
code: `
export class Test {
@Input()
test = 'test';
}
`,
},
],
invalid: [
{
name: 'ThemedComponent wrapper defines an input with a default value',
code: `
export class TTest extends ThemedComponent<Test> {
@Input()
test1 = 'test';
@Input()
test2 = true;
@Input()
test2: number = 123;
@Input()
test3: number[] = [1,2,3];
}
`,
errors: [
{
messageId: 'wrapperHasInputDefaults',
},
{
messageId: 'wrapperHasInputDefaults',
},
{
messageId: 'wrapperHasInputDefaults',
},
{
messageId: 'wrapperHasInputDefaults',
},
],
// output: `
// export class TTest extends ThemedComponent<Test> {
//
// @Input()
// test1: string;
//
// @Input()
// test2: boolean;
//
// @Input()
// test2: number;
//
// @Input()
// test3: number[];
// }
// `,
},
{
name: 'ThemedComponent wrapper defines an input with an undefined default value',
code: `
export class TTest extends ThemedComponent<Test> {
@Input()
test = undefined;
}
`,
errors: [
{
messageId: 'wrapperHasInputDefaults',
},
],
// output: `
// export class TTest extends ThemedComponent<Test> {
//
// @Input()
// test;
// }
// `,
},
],
};

View File

@@ -0,0 +1,226 @@
import {
AST_NODE_TYPES,
ESLintUtils,
TSESLint,
TSESTree,
} from '@typescript-eslint/utils';
import { isTestFile } from '../../util/filter';
import {
DSpaceESLintRuleInfo,
NamedTests,
OptionDoc,
} from '../../util/structure';
export enum Message {
DUPLICATE_DECORATOR_CALL = 'duplicateDecoratorCall',
}
/**
* Saves the decorators by decoratorName → file → Set<String>
*/
const decoratorCalls: Map<string, Map<string, Set<string>>> = new Map();
/**
* Keep a list of the files wo contain a decorator. This is done in order to prevent the `Program` selector from being
* run for every file.
*/
const fileWithDecorators: Set<string> = new Set();
export interface UniqueDecoratorsOptions {
decorators: string[];
}
export interface UniqueDecoratorsDocOptions {
decorators: OptionDoc;
}
export const info: DSpaceESLintRuleInfo<[UniqueDecoratorsOptions], [UniqueDecoratorsDocOptions]> = {
name: 'unique-decorators',
meta: {
docs: {
description: 'Some decorators must be called with unique arguments (e.g. when they construct a mapping based on the argument values)',
},
messages: {
[Message.DUPLICATE_DECORATOR_CALL]: 'Duplicate decorator call',
},
type: 'problem',
schema: [
{
type: 'object',
properties: {
decorators: {
type: 'array',
items: {
type: 'string',
},
},
},
},
],
},
optionDocs: [
{
decorators: {
title: '`decorators`',
description: 'The list of all the decorators for which you want to enforce this behavior.',
},
},
],
defaultOptions: [
{
decorators: [
'listableObjectComponent', // todo: must take default arguments into account!
],
},
],
};
export const rule = ESLintUtils.RuleCreator.withoutDocs({
...info,
create(context: TSESLint.RuleContext<Message, unknown[]>, options: any) {
return {
['Program']: () => {
if (fileWithDecorators.has(context.physicalFilename)) {
for (const decorator of options[0].decorators) {
decoratorCalls.get(decorator)?.get(context.physicalFilename)?.clear();
}
}
},
[`ClassDeclaration > Decorator > CallExpression[callee.name=/^(${options[0].decorators.join('|')})$/]`]: (node: TSESTree.CallExpression) => {
if (isTestFile(context)) {
return;
}
if (node.callee.type !== AST_NODE_TYPES.Identifier) {
// We only support regular method identifiers
return;
}
fileWithDecorators.add(context.physicalFilename);
if (!isUnique(node, context.physicalFilename)) {
context.report({
messageId: Message.DUPLICATE_DECORATOR_CALL,
node: node,
});
}
},
};
},
});
export const tests: NamedTests = {
plugin: info.name,
valid: [
{
name: 'checked decorator, no repetitions',
code: `
@listableObjectComponent(a)
export class A {
}
@listableObjectComponent(a, 'b')
export class B {
}
@listableObjectComponent(a, 'b', 3)
export class C {
}
@listableObjectComponent(a, 'b', 3, Enum.TEST1)
export class C {
}
@listableObjectComponent(a, 'b', 3, Enum.TEST2)
export class C {
}
`,
},
{
name: 'unchecked decorator, some repetitions',
code: `
@something(a)
export class A {
}
@something(a)
export class B {
}
`,
},
],
invalid: [
{
name: 'checked decorator, some repetitions',
code: `
@listableObjectComponent(a)
export class A {
}
@listableObjectComponent(a)
export class B {
}
`,
errors: [
{
messageId: 'duplicateDecoratorCall',
},
],
},
],
};
function callKey(node: TSESTree.CallExpression): string {
let key = '';
for (const arg of node.arguments) {
switch ((arg as TSESTree.Node).type) {
// todo: can we make this more generic somehow?
case AST_NODE_TYPES.Identifier:
key += (arg as TSESTree.Identifier).name;
break;
case AST_NODE_TYPES.Literal:
// eslint-disable-next-line @typescript-eslint/no-base-to-string
key += (arg as TSESTree.Literal).value;
break;
case AST_NODE_TYPES.MemberExpression:
key += (arg as any).object.name + '.' + (arg as any).property.name;
break;
default:
throw new Error(`Unrecognized decorator argument type: ${arg.type}`);
}
key += ', ';
}
return key;
}
function isUnique(node: TSESTree.CallExpression, filePath: string): boolean {
const decorator = (node.callee as TSESTree.Identifier).name;
if (!decoratorCalls.has(decorator)) {
decoratorCalls.set(decorator, new Map());
}
if (!decoratorCalls.get(decorator)!.has(filePath)) {
decoratorCalls.get(decorator)!.set(filePath, new Set());
}
const key = callKey(node);
let unique = true;
for (const decoratorCallsByFile of decoratorCalls.get(decorator)!.values()) {
if (decoratorCallsByFile.has(key)) {
unique = !unique;
break;
}
}
decoratorCalls.get(decorator)?.get(filePath)?.add(key);
return unique;
}

10
lint/src/util/filter.ts Normal file
View File

@@ -0,0 +1,10 @@
import { RuleContext } from '@typescript-eslint/utils/ts-eslint';
/**
* Determine whether the current file is a test file
* @param context the current ESLint rule context
*/
export function isTestFile(context: RuleContext<any, any>): boolean {
// note: shouldn't use plain .filename (doesn't work in DSpace Angular 7.4)
return context.getFilename()?.endsWith('.spec.ts') ;
}

View File

@@ -17,10 +17,16 @@ export type Meta = RuleMetaData<string, unknown[]>;
export type Valid = ValidTestCase<unknown[]>;
export type Invalid = InvalidTestCase<string, unknown[]>;
export interface DSpaceESLintRuleInfo {
export interface DSpaceESLintRuleInfo<T = unknown[], D = unknown[]> {
name: string;
meta: Meta,
defaultOptions: unknown[],
optionDocs: D,
defaultOptions: T,
}
export interface OptionDoc {
title: string;
description: string;
}
export interface NamedTests {

View File

@@ -7,6 +7,11 @@ _______
[Source code](../../../../lint/src/rules/<%- plugin.name.replace('dspace-angular-', '') %>/<%- rule.name %>.ts)
<% if (rule.optionDocs?.length > 0) { %>
### Options
<%- rule.optionDocs.map(optionDoc => Object.keys(optionDoc).map(option => '\n#### ' + optionDoc[option].title + '\n\n' + optionDoc[option].description)) %>
<% } %>
### Examples
<% if (tests.valid) {%>
@@ -19,6 +24,13 @@ Filename: `<%- test.filename %>`
```<%- plugin.language.toLowerCase() %>
<%- test.code.trim() %>
```
<% if (test?.options?.length > 0) { %>
With options:
```json
<%- JSON.stringify(test.options[0], null, 2) %>
```
<% }%>
<% }) %>
<% } %>
@@ -31,6 +43,15 @@ Filename: `<%- test.filename %>`
<% } %>
```<%- plugin.language.toLowerCase() %>
<%- test.code.trim() %>
<% if (test?.options?.length > 0) { %>
With options:
```json
<%- JSON.stringify(test.options[0], null, 2) %>
```
<% }%>
```
Will produce the following error(s):
```

View File

@@ -7,6 +7,7 @@
*/
import { TSESTree } from '@typescript-eslint/utils';
import { RuleContext } from '@typescript-eslint/utils/ts-eslint';
import { readFileSync } from 'fs';
import { basename } from 'path';
import ts, { Identifier } from 'typescript';
@@ -263,3 +264,18 @@ export const DISALLOWED_THEME_SELECTORS = 'ds-(base|themed)-';
export function fixSelectors(text: string): string {
return text.replaceAll(/ds-(base|themed)-/g, 'ds-');
}
/**
* Determine the theme of the current file based on its path in the project.
* @param context the current ESLint rule context
*/
export function getFileTheme(context: RuleContext<any, any>): string | undefined {
// note: shouldn't use plain .filename (doesn't work in DSpace Angular 7.4)
const m = context.getFilename()?.match(/\/src\/themes\/([^/]+)\//);
if (m?.length === 2) {
return m[1];
}
return undefined;
}

View File

@@ -6,6 +6,8 @@
* http://www.dspace.org/license/
*/
import { RuleMetaData } from '@typescript-eslint/utils/ts-eslint';
import { default as html } from '../src/rules/html';
import { default as ts } from '../src/rules/ts';
@@ -69,6 +71,16 @@ describe('plugin structure', () => {
expect(ruleExports.tests.valid.length).toBeGreaterThan(0);
expect(ruleExports.tests.invalid.length).toBeGreaterThan(0);
});
it('should contain a valid ESLint rule', () => {
// we don't have a better way to enforce this, but it's something at least
expect((ruleExports.rule as any).name).toBeUndefined(
'Rules should be passed to RuleCreator, omitting info.name since it is not part of the RuleWithMeta interface',
);
expect(ruleExports.rule.create).toBeTruthy();
expect(ruleExports.rule.meta).toEqual(ruleExports.info.meta as RuleMetaData<string, []>);
});
});
}
});

595
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "dspace-angular",
"version": "9.0.0-next",
"version": "9.1.0",
"scripts": {
"ng": "ng",
"config:watch": "nodemon",
@@ -102,8 +102,8 @@
"@angular/platform-browser-dynamic": "^18.2.12",
"@angular/platform-server": "^18.2.12",
"@angular/router": "^18.2.12",
"@angular/ssr": "^18.2.18",
"@babel/runtime": "7.26.0",
"@angular/ssr": "^18.2.20",
"@babel/runtime": "7.27.6",
"@kolkov/ngx-gallery": "^2.0.1",
"@ng-bootstrap/ng-bootstrap": "^12.0.0",
"@ng-dynamic-forms/core": "^16.0.0",
@@ -117,14 +117,14 @@
"@terraformer/wkt": "^2.2.1",
"altcha": "^0.9.0",
"angulartics2": "^12.2.0",
"axios": "^1.8.4",
"axios": "^1.10.0",
"bootstrap": "^5.3",
"cerialize": "0.1.18",
"cli-progress": "^3.12.0",
"colors": "^1.4.0",
"compression": "^1.7.5",
"cookie-parser": "1.4.7",
"core-js": "^3.41.0",
"core-js": "^3.42.0",
"date-fns": "^2.29.3",
"date-fns-tz": "^1.3.7",
"deepmerge": "^4.3.1",
@@ -133,9 +133,9 @@
"express-rate-limit": "^5.1.3",
"fast-json-patch": "^3.1.1",
"filesize": "^10.1.6",
"http-proxy-middleware": "^2.0.7",
"http-proxy-middleware": "^2.0.9",
"http-terminator": "^3.2.0",
"isbot": "^5.1.25",
"isbot": "^5.1.28",
"js-cookie": "2.2.1",
"js-yaml": "^4.1.0",
"json5": "^2.2.3",
@@ -168,7 +168,7 @@
},
"devDependencies": {
"@angular-builders/custom-webpack": "~18.0.0",
"@angular-devkit/build-angular": "^18.2.18",
"@angular-devkit/build-angular": "^18.2.20",
"@angular-eslint/builder": "^18.4.1",
"@angular-eslint/bundled-angular-compiler": "^18.4.1",
"@angular-eslint/eslint-plugin": "^18.4.1",
@@ -176,13 +176,13 @@
"@angular-eslint/schematics": "^18.4.1",
"@angular-eslint/template-parser": "^18.4.1",
"@angular-eslint/utils": "^18.4.1",
"@angular/cli": "^18.2.18",
"@angular/cli": "^18.2.20",
"@angular/compiler-cli": "^18.2.12",
"@angular/language-service": "^18.2.12",
"@cypress/schematic": "^1.5.0",
"@fortawesome/fontawesome-free": "^6.7.2",
"@ngrx/store-devtools": "^18.1.1",
"@ngtools/webpack": "^18.2.18",
"@ngtools/webpack": "^18.2.19",
"@types/deep-freeze": "0.1.5",
"@types/ejs": "^3.1.2",
"@types/express": "^4.17.17",
@@ -209,7 +209,7 @@
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-import-newlines": "^1.3.1",
"eslint-plugin-jsdoc": "^45.0.0",
"eslint-plugin-jsonc": "^2.19.1",
"eslint-plugin-jsonc": "^2.20.1",
"eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-rxjs": "^5.0.3",
"eslint-plugin-simple-import-sort": "^10.0.0",
@@ -224,7 +224,7 @@
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"karma-mocha-reporter": "2.2.5",
"ng-mocks": "^14.13.4",
"ng-mocks": "^14.13.5",
"ngx-mask": "14.2.4",
"nodemon": "^2.0.22",
"postcss": "^8.5",
@@ -232,12 +232,12 @@
"postcss-loader": "^4.0.3",
"postcss-preset-env": "^7.4.2",
"rimraf": "^3.0.2",
"sass": "~1.86.3",
"sass": "~1.89.2",
"sass-loader": "^12.6.0",
"sass-resources-loader": "^2.2.5",
"ts-node": "^8.10.2",
"typescript": "~5.4.5",
"webpack": "5.99.5",
"webpack": "5.99.9",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
}

View File

@@ -58,6 +58,7 @@ import {
REQUEST,
RESPONSE,
} from './src/express.tokens';
import { SsrExcludePatterns } from "./src/config/ssr-config.interface";
/*
* Set path for the browser application's dist folder
@@ -221,7 +222,7 @@ export function app() {
* The callback function to serve server side angular
*/
function ngApp(req, res, next) {
if (environment.ssr.enabled && req.method === 'GET' && (req.path === '/' || environment.ssr.paths.some(pathPrefix => req.path.startsWith(pathPrefix)))) {
if (environment.ssr.enabled && req.method === 'GET' && (req.path === '/' || !isExcludedFromSsr(req.path, environment.ssr.excludePathPatterns))) {
// Render the page to user via SSR (server side rendering)
serverSideRender(req, res, next);
} else {
@@ -627,6 +628,21 @@ function start() {
}
}
/**
* Check if SSR should be skipped for path
*
* @param path
* @param excludePathPattern
*/
function isExcludedFromSsr(path: string, excludePathPattern: SsrExcludePatterns[]): boolean {
const patterns = excludePathPattern.map(p =>
new RegExp(p.pattern, p.flag || '')
);
return patterns.some((regex) => {
return regex.test(path)
});
}
/*
* The callback function to serve health check requests
*/

View File

@@ -51,16 +51,16 @@ import { BrowserOnlyPipe } from '../../../shared/utils/browser-only.pipe';
},
],
imports: [
PaginationComponent,
AsyncPipe,
NgbAccordionModule,
TranslateModule,
NgbNavModule,
ThemedSearchComponent,
BrowserOnlyPipe,
NgxPaginationModule,
SelectableListItemControlComponent,
ListableObjectComponentLoaderComponent,
NgbAccordionModule,
NgbNavModule,
NgxPaginationModule,
PaginationComponent,
SelectableListItemControlComponent,
ThemedSearchComponent,
TranslateModule,
],
standalone: true,
})

View File

@@ -26,10 +26,10 @@ import { BulkAccessSettingsComponent } from './settings/bulk-access-settings.com
templateUrl: './bulk-access.component.html',
styleUrls: ['./bulk-access.component.scss'],
imports: [
TranslateModule,
BulkAccessSettingsComponent,
BulkAccessBrowseComponent,
BtnDisabledDirective,
BulkAccessBrowseComponent,
BulkAccessSettingsComponent,
TranslateModule,
],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,

View File

@@ -14,9 +14,9 @@ import { AccessControlFormContainerComponent } from '../../../shared/access-cont
styleUrls: ['./bulk-access-settings.component.scss'],
exportAs: 'dsBulkSettings',
imports: [
AccessControlFormContainerComponent,
NgbAccordionModule,
TranslateModule,
AccessControlFormContainerComponent,
],
standalone: true,
})

View File

@@ -27,7 +27,7 @@ import {
import { TranslateModule } from '@ngx-translate/core';
import {
Observable,
of as observableOf,
of,
} from 'rxjs';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
@@ -85,7 +85,7 @@ describe('EPeopleRegistryComponent', () => {
}), this.allEpeople));
},
getActiveEPerson(): Observable<EPerson> {
return observableOf(this.activeEPerson);
return of(this.activeEPerson);
},
searchByScope(scope: string, query: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<EPerson>>> {
if (scope === 'email') {
@@ -129,7 +129,7 @@ describe('EPeopleRegistryComponent', () => {
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {
return (ePerson2.uuid !== ePerson.uuid);
});
return observableOf(true);
return of(true);
},
editEPerson(ePerson: EPerson) {
this.activeEPerson = ePerson;
@@ -145,7 +145,7 @@ describe('EPeopleRegistryComponent', () => {
},
};
authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: observableOf(true),
isAuthorized: of(true),
});
builderService = getMockFormBuilderService();
@@ -180,7 +180,7 @@ describe('EPeopleRegistryComponent', () => {
fixture = TestBed.createComponent(EPeopleRegistryComponent);
component = fixture.componentInstance;
modalService = TestBed.inject(NgbModal);
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: observableOf(true) }) }));
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: of(true) }) }));
fixture.detectChanges();
});
@@ -261,7 +261,7 @@ describe('EPeopleRegistryComponent', () => {
it('should hide delete EPerson button when the isAuthorized returns false', () => {
spyOn(authorizationService, 'isAuthorized').and.returnValue(observableOf(false));
spyOn(authorizationService, 'isAuthorized').and.returnValue(of(false));
component.initialisePage();
fixture.detectChanges();

View File

@@ -67,14 +67,14 @@ import { EPersonFormComponent } from './eperson-form/eperson-form.component';
selector: 'ds-epeople-registry',
templateUrl: './epeople-registry.component.html',
imports: [
TranslateModule,
RouterModule,
AsyncPipe,
EPersonFormComponent,
ReactiveFormsModule,
ThemedLoadingComponent,
PaginationComponent,
NgClass,
PaginationComponent,
ReactiveFormsModule,
RouterModule,
ThemedLoadingComponent,
TranslateModule,
],
standalone: true,
})

View File

@@ -30,7 +30,7 @@
</div>
}
@if (canImpersonate$ | async) {
<div between class="btn-group ms-1">
<div between class="btn-group">
@if (!isImpersonated) {
<button class="btn btn-primary" type="button" (click)="impersonate()">
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.impersonate' | translate}}

View File

@@ -25,7 +25,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import {
Observable,
of as observableOf,
of,
} from 'rxjs';
import { AuthService } from '../../../core/auth/auth.service';
@@ -91,7 +91,7 @@ describe('EPersonFormComponent', () => {
activeEPerson: null,
allEpeople: mockEPeople,
getActiveEPerson(): Observable<EPerson> {
return observableOf(this.activeEPerson);
return of(this.activeEPerson);
},
searchByScope(scope: string, query: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<EPerson>>> {
if (scope === 'email') {
@@ -115,7 +115,7 @@ describe('EPersonFormComponent', () => {
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {
return (ePerson2.uuid !== ePerson.uuid);
});
return observableOf(true);
return of(true);
},
create(ePerson: EPerson): Observable<RemoteData<EPerson>> {
this.allEpeople = [...this.allEpeople, ePerson];
@@ -210,7 +210,7 @@ describe('EPersonFormComponent', () => {
});
authService = new AuthServiceStub();
authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: observableOf(true),
isAuthorized: of(true),
});
groupsDataService = jasmine.createSpyObj('groupsDataService', {
@@ -389,7 +389,7 @@ describe('EPersonFormComponent', () => {
});
describe('without active EPerson', () => {
beforeEach(() => {
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(observableOf(undefined));
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(of(undefined));
component.onSubmit();
fixture.detectChanges();
});
@@ -429,7 +429,7 @@ describe('EPersonFormComponent', () => {
},
},
});
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(observableOf(expectedWithId));
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(of(expectedWithId));
component.ngOnInit();
component.onSubmit();
fixture.detectChanges();
@@ -485,10 +485,10 @@ describe('EPersonFormComponent', () => {
spyOn(authService, 'impersonate').and.callThrough();
eperson = EPersonMock;
component.epersonInitial = eperson;
component.canDelete$ = observableOf(true);
spyOn(component.epersonService, 'getActiveEPerson').and.returnValue(observableOf(eperson));
component.canDelete$ = of(true);
spyOn(component.epersonService, 'getActiveEPerson').and.returnValue(of(eperson));
modalService = (component as any).modalService;
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: observableOf(true) }) }));
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: of(true) }) }));
component.ngOnInit();
fixture.detectChanges();
});
@@ -499,7 +499,7 @@ describe('EPersonFormComponent', () => {
});
it('the delete button should be hidden if the ePerson cannot be deleted', () => {
component.canDelete$ = observableOf(false);
component.canDelete$ = of(false);
fixture.detectChanges();
const deleteButton = fixture.debugElement.query(By.css('.delete-button'));
expect(deleteButton).toBeNull();

View File

@@ -1,7 +1,4 @@
import {
AsyncPipe,
NgClass,
} from '@angular/common';
import { AsyncPipe } from '@angular/common';
import {
ChangeDetectorRef,
Component,
@@ -30,7 +27,7 @@ import {
import {
combineLatest as observableCombineLatest,
Observable,
of as observableOf,
of,
Subscription,
} from 'rxjs';
import {
@@ -81,15 +78,14 @@ import { ValidateEmailNotTaken } from './validators/email-taken.validator';
selector: 'ds-eperson-form',
templateUrl: './eperson-form.component.html',
imports: [
FormComponent,
AsyncPipe,
TranslateModule,
NgClass,
ThemedLoadingComponent,
BtnDisabledDirective,
FormComponent,
HasNoValuePipe,
PaginationComponent,
RouterLink,
HasNoValuePipe,
BtnDisabledDirective,
ThemedLoadingComponent,
TranslateModule,
],
standalone: true,
})
@@ -361,7 +357,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
this.groups$ = this.activeEPerson$.pipe(
switchMap((eperson) => {
return observableCombineLatest([observableOf(eperson), this.paginationService.getFindListOptions(this.config.id, {
return observableCombineLatest([of(eperson), this.paginationService.getFindListOptions(this.config.id, {
currentPage: 1,
elementsPerPage: this.config.pageSize,
})]);
@@ -370,7 +366,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
if (eperson != null) {
return this.groupsDataService.findListByHref(eperson._links.groups.href, findListOptions, true, true, followLink('object'));
}
return observableOf(undefined);
return of(undefined);
}),
);
@@ -383,14 +379,14 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
if (hasValue(eperson)) {
return this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, eperson.self);
} else {
return observableOf(false);
return of(false);
}
}),
);
this.canDelete$ = this.activeEPerson$.pipe(
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined)),
);
this.canReset$ = observableOf(true);
this.canReset$ = of(true);
}
/**
@@ -544,16 +540,16 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
take(1),
switchMap((confirm: boolean) => {
if (confirm && hasValue(eperson.id)) {
this.canDelete$ = observableOf(false);
this.canDelete$ = of(false);
return this.epersonService.deleteEPerson(eperson).pipe(
getFirstCompletedRemoteData(),
map((restResponse: RemoteData<NoContent>) => ({ restResponse, eperson })),
);
} else {
return observableOf(null);
return of(null);
}
}),
finalize(() => this.canDelete$ = observableOf(true)),
finalize(() => this.canDelete$ = of(true)),
);
}),
).subscribe(({ restResponse, eperson }: { restResponse: RemoteData<NoContent> | null, eperson: EPerson }) => {

View File

@@ -27,7 +27,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { Operation } from 'fast-json-patch';
import {
Observable,
of as observableOf,
of,
} from 'rxjs';
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
@@ -116,7 +116,7 @@ describe('GroupFormComponent', () => {
activeGroup: null,
createdGroup: null,
getActiveGroup(): Observable<Group> {
return observableOf(this.activeGroup);
return of(this.activeGroup);
},
getGroupRegistryRouterLink(): string {
return '/access-control/groups';
@@ -137,7 +137,7 @@ describe('GroupFormComponent', () => {
this.activeGroup = null;
},
findById(id: string) {
return observableOf({ payload: null, hasSucceeded: true });
return of({ payload: null, hasSucceeded: true });
},
findByHref(href: string) {
return createSuccessfulRemoteDataObject$(this.createdGroup);
@@ -164,7 +164,7 @@ describe('GroupFormComponent', () => {
},
};
authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: observableOf(true),
isAuthorized: of(true),
});
dsoDataServiceStub = {
findByHref(href: string): Observable<RemoteData<DSpaceObject>> {
@@ -330,7 +330,7 @@ describe('GroupFormComponent', () => {
},
},
});
spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(observableOf(expected));
spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(of(expected));
spyOn(groupsDataServiceStub, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(expected2));
component.ngOnInit();
});
@@ -417,7 +417,7 @@ describe('GroupFormComponent', () => {
},
});
spyOn(component.submitForm, 'emit');
spyOn(dsoDataServiceStub, 'findByHref').and.returnValue(observableOf(expected));
spyOn(dsoDataServiceStub, 'findByHref').and.returnValue(of(expected));
fixture.detectChanges();
component.initialisePage();
@@ -471,11 +471,11 @@ describe('GroupFormComponent', () => {
beforeEach(async () => {
spyOn(groupsDataServiceStub, 'delete').and.callThrough();
component.activeGroup$ = observableOf({
component.activeGroup$ = of({
id: 'active-group',
permanent: false,
} as Group);
component.canEdit$ = observableOf(true);
component.canEdit$ = of(true);
component.initialisePage();

View File

@@ -87,13 +87,13 @@ import { ValidateGroupExists } from './validators/group-exists.validator';
selector: 'ds-group-form',
templateUrl: './group-form.component.html',
imports: [
FormComponent,
AlertComponent,
AsyncPipe,
TranslateModule,
ContextHelpDirective,
FormComponent,
MembersListComponent,
SubgroupsListComponent,
TranslateModule,
],
standalone: true,
})

View File

@@ -83,7 +83,7 @@
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div class="flex-grow-1 me-3">
<div class="form-group input-group me-3">
<div class="mb-3 input-group me-3">
<input type="text" name="query" id="query" formControlName="query"
class="form-control" aria-label="Search input">
<span class="input-group-append">

View File

@@ -32,7 +32,7 @@ import {
} from '@ngx-translate/core';
import {
Observable,
of as observableOf,
of,
} from 'rxjs';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
@@ -113,7 +113,7 @@ describe('MembersListComponent', () => {
epersonMembers: epersonMembers,
epersonNonMembers: epersonNonMembers,
getActiveGroup(): Observable<Group> {
return observableOf(activeGroup);
return of(activeGroup);
},
getEPersonMembers() {
return this.epersonMembers;
@@ -127,7 +127,7 @@ describe('MembersListComponent', () => {
this.epersonNonMembers.splice(index, 1);
}
});
return observableOf(new RestResponse(true, 200, 'Success'));
return of(new RestResponse(true, 200, 'Success'));
},
clearGroupsRequests() {
// empty
@@ -147,7 +147,7 @@ describe('MembersListComponent', () => {
});
// Add eperson to list of non-members
this.epersonNonMembers = [...this.epersonNonMembers, epersonToDelete];
return observableOf(new RestResponse(true, 200, 'Success'));
return of(new RestResponse(true, 200, 'Success'));
},
};
builderService = getMockFormBuilderService();

View File

@@ -25,7 +25,7 @@ import {
combineLatest as observableCombineLatest,
Observable,
ObservedValueOf,
of as observableOf,
of,
Subscription,
} from 'rxjs';
import {
@@ -103,14 +103,14 @@ export interface EPersonListActionConfig {
selector: 'ds-members-list',
templateUrl: './members-list.component.html',
imports: [
TranslateModule,
ContextHelpDirective,
ReactiveFormsModule,
PaginationComponent,
AsyncPipe,
RouterLink,
NgClass,
BtnDisabledDirective,
ContextHelpDirective,
NgClass,
PaginationComponent,
ReactiveFormsModule,
RouterLink,
TranslateModule,
],
standalone: true,
})
@@ -260,7 +260,7 @@ export class MembersListComponent implements OnInit, OnDestroy {
* @param possibleMember EPerson that is a possible member (being tested) of the group currently being edited
*/
isMemberOfGroup(possibleMember: EPerson): Observable<boolean> {
return observableOf(true);
return of(true);
}
/**

View File

@@ -31,7 +31,7 @@ import {
} from '@ngx-translate/core';
import {
Observable,
of as observableOf,
of,
} from 'rxjs';
import { EPersonMock2 } from 'src/app/shared/testing/eperson.mock';
@@ -108,7 +108,7 @@ describe('SubgroupsListComponent', () => {
subgroups: subgroups,
groupNonMembers: groupNonMembers,
getActiveGroup(): Observable<Group> {
return observableOf(this.activeGroup);
return of(this.activeGroup);
},
getSubgroups(): Group {
return this.subgroups;
@@ -138,7 +138,7 @@ describe('SubgroupsListComponent', () => {
this.groupNonMembers.splice(index, 1);
}
});
return observableOf(new RestResponse(true, 200, 'Success'));
return of(new RestResponse(true, 200, 'Success'));
},
clearGroupsRequests() {
// empty
@@ -155,7 +155,7 @@ describe('SubgroupsListComponent', () => {
});
// Add group to list of non-members
this.groupNonMembers = [...this.groupNonMembers, subgroupToDelete];
return observableOf(new RestResponse(true, 200, 'Success'));
return of(new RestResponse(true, 200, 'Success'));
},
};
routerStub = new RouterMock();

View File

@@ -59,12 +59,12 @@ enum SubKey {
selector: 'ds-subgroups-list',
templateUrl: './subgroups-list.component.html',
imports: [
RouterLink,
AsyncPipe,
ContextHelpDirective,
TranslateModule,
ReactiveFormsModule,
PaginationComponent,
ReactiveFormsModule,
RouterLink,
TranslateModule,
],
standalone: true,
})

View File

@@ -9,7 +9,7 @@ import {
} from '@angular/router';
import {
Observable,
of as observableOf,
of,
} from 'rxjs';
import { AuthService } from '../../core/auth/auth.service';
@@ -37,7 +37,7 @@ describe('GroupPageGuard', () => {
function init() {
halEndpointService = jasmine.createSpyObj(['getEndpoint']);
( halEndpointService as any ).getEndpoint.and.returnValue(observableOf(groupsEndpointUrl));
( halEndpointService as any ).getEndpoint.and.returnValue(of(groupsEndpointUrl));
authorizationService = jasmine.createSpyObj(['isAuthorized']);
// NOTE: value is set in beforeEach
@@ -46,7 +46,7 @@ describe('GroupPageGuard', () => {
( router as any ).parseUrl.and.returnValue = {};
authService = jasmine.createSpyObj(['isAuthenticated']);
( authService as any ).isAuthenticated.and.returnValue(observableOf(true));
( authService as any ).isAuthenticated.and.returnValue(of(true));
TestBed.configureTestingModule({
providers: [
@@ -69,7 +69,7 @@ describe('GroupPageGuard', () => {
describe('canActivate', () => {
describe('when the current user can manage the group', () => {
beforeEach(() => {
( authorizationService as any ).isAuthorized.and.returnValue(observableOf(true));
( authorizationService as any ).isAuthorized.and.returnValue(of(true));
});
it('should return true', (done) => {
@@ -89,7 +89,7 @@ describe('GroupPageGuard', () => {
describe('when the current user can not manage the group', () => {
beforeEach(() => {
(authorizationService as any).isAuthorized.and.returnValue(observableOf(false));
(authorizationService as any).isAuthorized.and.returnValue(of(false));
});
it('should not return true', (done) => {

View File

@@ -6,7 +6,7 @@ import {
} from '@angular/router';
import {
Observable,
of as observableOf,
of,
} from 'rxjs';
import { map } from 'rxjs/operators';
@@ -33,6 +33,6 @@ export const groupPageGuard = (
getObjectUrl = defaultGroupPageGetObjectUrl,
getEPersonUuid?: StringGuardParamFn,
): CanActivateFn => someFeatureAuthorizationGuard(
() => observableOf([FeatureID.CanManageGroup]),
() => of([FeatureID.CanManageGroup]),
getObjectUrl,
getEPersonUuid);

View File

@@ -85,7 +85,7 @@
}
@if (!groupDto.group?.permanent && groupDto.ableToDelete) {
<button
(click)="deleteGroup(groupDto)" class="btn btn-outline-danger btn-sm btn-delete"
(click)="confirmDelete(groupDto)" class="btn btn-outline-danger btn-sm btn-delete"
title="{{messagePrefix + 'table.edit.buttons.remove' | translate: {name: dsoNameService.getName(groupDto.group) } }}">
<i class="fas fa-trash-alt fa-fw"></i>
</button>

View File

@@ -25,7 +25,6 @@ import { provideMockStore } from '@ngrx/store/testing';
import { TranslateModule } from '@ngx-translate/core';
import {
Observable,
of as observableOf,
of,
} from 'rxjs';
@@ -95,11 +94,11 @@ describe('GroupsRegistryComponent', () => {
(authorizationService as any).isAuthorized.and.callFake((featureId?: FeatureID) => {
switch (featureId) {
case FeatureID.AdministratorOf:
return observableOf(isAdmin);
return of(isAdmin);
case FeatureID.CanManageGroup:
return observableOf(canManageGroup);
return of(canManageGroup);
case FeatureID.CanDelete:
return observableOf(true);
return of(true);
default:
throw new Error(`setIsAuthorized: this fake implementation does not support ${featureId}.`);
}
@@ -382,6 +381,8 @@ describe('GroupsRegistryComponent', () => {
deleteButton.click();
fixture.detectChanges();
(document as any).querySelector('.modal-footer .confirm').click();
expect(groupsDataServiceStub.delete).toHaveBeenCalledWith(mockGroups[0].id);
});
});

View File

@@ -9,7 +9,10 @@ import {
UntypedFormBuilder,
} from '@angular/forms';
import { RouterLink } from '@angular/router';
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
import {
NgbModal,
NgbTooltipModule,
} from '@ng-bootstrap/ng-bootstrap';
import {
TranslateModule,
TranslateService,
@@ -19,7 +22,7 @@ import {
combineLatest as observableCombineLatest,
EMPTY,
Observable,
of as observableOf,
of,
Subscription,
} from 'rxjs';
import {
@@ -27,6 +30,7 @@ import {
defaultIfEmpty,
map,
switchMap,
takeUntil,
tap,
} from 'rxjs/operators';
@@ -57,6 +61,7 @@ import {
} from '../../core/shared/operators';
import { PageInfo } from '../../core/shared/page-info.model';
import { BtnDisabledDirective } from '../../shared/btn-disabled.directive';
import { ConfirmationModalComponent } from '../../shared/confirmation-modal/confirmation-modal.component';
import { hasValue } from '../../shared/empty.util';
import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component';
import { NotificationsService } from '../../shared/notifications/notifications.service';
@@ -68,14 +73,14 @@ import { followLink } from '../../shared/utils/follow-link-config.model';
selector: 'ds-groups-registry',
templateUrl: './groups-registry.component.html',
imports: [
AsyncPipe,
BtnDisabledDirective,
NgbTooltipModule,
PaginationComponent,
ReactiveFormsModule,
RouterLink,
ThemedLoadingComponent,
TranslateModule,
RouterLink,
ReactiveFormsModule,
AsyncPipe,
PaginationComponent,
NgbTooltipModule,
BtnDisabledDirective,
],
standalone: true,
})
@@ -142,6 +147,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
private paginationService: PaginationService,
public requestService: RequestService,
public dsoNameService: DSONameService,
private modalService: NgbModal,
) {
this.currentSearchQuery = '';
this.searchForm = this.formBuilder.group(({
@@ -179,7 +185,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
getRemoteDataPayload(),
switchMap((groups: PaginatedList<Group>) => {
if (groups.page.length === 0) {
return observableOf(buildPaginatedList(groups.pageInfo, []));
return of(buildPaginatedList(groups.pageInfo, []));
}
return this.authorizationService.isAuthorized(FeatureID.AdministratorOf).pipe(
switchMap((isSiteAdmin: boolean) => {
@@ -224,7 +230,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
canManageGroup$(isSiteAdmin: boolean, group: Group): Observable<boolean> {
if (isSiteAdmin) {
return observableOf(true);
return of(true);
} else {
return this.authorizationService.isAuthorized(FeatureID.CanManageGroup, group.self);
}
@@ -283,7 +289,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
return this.dSpaceObjectDataService.findByHref(group._links.object.href).pipe(
getFirstSucceededRemoteData(),
map((rd: RemoteData<DSpaceObject>) => hasValue(rd) && hasValue(rd.payload)),
catchError(() => observableOf(false)),
catchError(() => of(false)),
);
}
@@ -314,4 +320,30 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
this.paginationService.clearPagination(this.config.id);
}
confirmDelete(group: GroupDtoModel): void {
const modalRef = this.modalService.open(ConfirmationModalComponent);
modalRef.componentInstance.name = this.dsoNameService.getName(group.group);
modalRef.componentInstance.headerLabel = 'admin.access-control.epeople.table.edit.buttons.remove.modal.header';
modalRef.componentInstance.infoLabel = 'admin.access-control.epeople.table.edit.buttons.remove.modal.info';
modalRef.componentInstance.cancelLabel = 'admin.access-control.epeople.table.edit.buttons.remove.modal.cancel';
modalRef.componentInstance.confirmLabel = 'admin.access-control.epeople.table.edit.buttons.remove.modal.confirm';
modalRef.componentInstance.brandColor = 'danger';
modalRef.componentInstance.confirmIcon = 'fas fa-trash';
const modalSub: Subscription = modalRef.componentInstance.response.pipe(
takeUntil(modalRef.closed),
).subscribe((result: boolean) => {
if (result === true) {
this.deleteGroup(group);
}
});
void modalRef.result.then().finally(() => {
modalRef.close();
if (modalSub && !modalSub.closed) {
modalSub.unsubscribe();
}
});
}
}

View File

@@ -0,0 +1,11 @@
import { Config } from '../../config/config.interface';
/**
* Configuration interface used by the AccessibilitySettingsService
*/
export class AccessibilitySettingsConfig implements Config {
/**
* The duration in days after which the accessibility settings cookie expires
*/
cookieExpirationDuration: number;
}

View File

@@ -0,0 +1,419 @@
import {
fakeAsync,
flush,
} from '@angular/core/testing';
import { of } from 'rxjs';
import { AppConfig } from '../../config/app-config.interface';
import { AuthService } from '../core/auth/auth.service';
import { EPersonDataService } from '../core/eperson/eperson-data.service';
import { EPerson } from '../core/eperson/models/eperson.model';
import { CookieService } from '../core/services/cookie.service';
import { OrejimeServiceStub } from '../shared/cookies/orejime.service.stub';
import { CookieServiceMock } from '../shared/mocks/cookie.service.mock';
import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject$,
} from '../shared/remote-data.utils';
import { AuthServiceStub } from '../shared/testing/auth-service.stub';
import {
ACCESSIBILITY_COOKIE,
ACCESSIBILITY_SETTINGS_METADATA_KEY,
AccessibilitySettings,
AccessibilitySettingsFormValues,
AccessibilitySettingsService,
FullAccessibilitySettings,
} from './accessibility-settings.service';
describe('accessibilitySettingsService', () => {
let service: AccessibilitySettingsService;
let cookieService: CookieServiceMock;
let authService: AuthServiceStub;
let ePersonService: EPersonDataService;
let orejimeService: OrejimeServiceStub;
let appConfig: AppConfig;
beforeEach(() => {
cookieService = new CookieServiceMock();
authService = new AuthServiceStub();
orejimeService = new OrejimeServiceStub();
appConfig = { accessibility: { cookieExpirationDuration: 10 } } as AppConfig;
orejimeService.getSavedPreferences.and.returnValue(of({ accessibility: true }));
ePersonService = jasmine.createSpyObj('ePersonService', {
createPatchFromCache: of([{
op: 'add',
value: null,
}]),
patch: of({}),
});
service = new AccessibilitySettingsService(
cookieService as unknown as CookieService,
authService as unknown as AuthService,
ePersonService,
orejimeService,
appConfig,
);
});
describe('get', () => {
it('should return the setting if it is set', () => {
const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};
service.getAll = jasmine.createSpy('getAll').and.returnValue(of(settings));
service.get('notificationTimeOut', 'default').subscribe(value =>
expect(value).toEqual('1000'),
);
});
it('should return the default value if the setting is not set', () => {
const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};
service.getAll = jasmine.createSpy('getAll').and.returnValue(of(settings));
service.get('liveRegionTimeOut', 'default').subscribe(value =>
expect(value).toEqual('default'),
);
});
});
describe('getAsNumber', () => {
it('should return the setting as number if the value for the setting can be parsed to a number', () => {
service.get = jasmine.createSpy('get').and.returnValue(of('1000'));
service.getAsNumber('notificationTimeOut').subscribe(value =>
expect(value).toEqual(1000),
);
});
it('should return the default value if no value is set for the setting', () => {
service.get = jasmine.createSpy('get').and.returnValue(of(null));
service.getAsNumber('notificationTimeOut', 123).subscribe(value =>
expect(value).toEqual(123),
);
});
it('should return the default value if the value for the setting can not be parsed to a number', () => {
service.get = jasmine.createSpy('get').and.returnValue(of('text'));
service.getAsNumber('notificationTimeOut', 123).subscribe(value =>
expect(value).toEqual(123),
);
});
});
describe('getAll', () => {
it('should attempt to get the settings from metadata first', () => {
service.getAllSettingsFromAuthenticatedUserMetadata =
jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata').and.returnValue(of({ }));
service.getAll().subscribe();
expect(service.getAllSettingsFromAuthenticatedUserMetadata).toHaveBeenCalled();
});
it('should attempt to get the settings from the cookie if the settings from metadata are empty', () => {
service.getAllSettingsFromAuthenticatedUserMetadata =
jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata').and.returnValue(of({ }));
service.getAllSettingsFromCookie = jasmine.createSpy('getAllSettingsFromCookie').and.returnValue({ });
service.getAll().subscribe();
expect(service.getAllSettingsFromCookie).toHaveBeenCalled();
});
it('should not attempt to get the settings from the cookie if the settings from metadata are not empty', () => {
const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};
service.getAllSettingsFromAuthenticatedUserMetadata =
jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata').and.returnValue(of(settings));
service.getAllSettingsFromCookie = jasmine.createSpy('getAllSettingsFromCookie').and.returnValue({ });
service.getAll().subscribe();
expect(service.getAllSettingsFromCookie).not.toHaveBeenCalled();
});
it('should return an empty object if both are empty', () => {
service.getAllSettingsFromAuthenticatedUserMetadata =
jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata').and.returnValue(of({ }));
service.getAllSettingsFromCookie = jasmine.createSpy('getAllSettingsFromCookie').and.returnValue({ });
service.getAll().subscribe(value => expect(value).toEqual({}));
});
});
describe('getAllSettingsFromCookie', () => {
it('should retrieve the settings from the cookie', () => {
cookieService.get = jasmine.createSpy();
service.getAllSettingsFromCookie();
expect(cookieService.get).toHaveBeenCalledWith(ACCESSIBILITY_COOKIE);
});
});
describe('getAllSettingsFromAuthenticatedUserMetadata', () => {
it('should retrieve all settings from the user\'s metadata', () => {
const settings = { 'liveRegionTimeOut': '1000' };
const user = new EPerson();
user.setMetadata(ACCESSIBILITY_SETTINGS_METADATA_KEY, null, JSON.stringify(settings));
authService.getAuthenticatedUserFromStoreIfAuthenticated =
jasmine.createSpy('getAuthenticatedUserFromStoreIfAuthenticated').and.returnValue(of(user));
service.getAllSettingsFromAuthenticatedUserMetadata().subscribe(value =>
expect(value).toEqual(settings),
);
});
});
describe('set', () => {
it('should correctly update the chosen setting', () => {
service.updateSettings = jasmine.createSpy('updateSettings');
service.set('liveRegionTimeOut', '500');
expect(service.updateSettings).toHaveBeenCalledWith({ liveRegionTimeOut: '500' });
});
});
describe('setSettings', () => {
beforeEach(() => {
service.setSettingsInCookie = jasmine.createSpy('setSettingsInCookie').and.returnValue(of('cookie'));
});
it('should attempt to set settings in metadata', () => {
service.setSettingsInAuthenticatedUserMetadata =
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of('failed'));
const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};
service.setSettings(settings).subscribe();
expect(service.setSettingsInAuthenticatedUserMetadata).toHaveBeenCalledWith(settings);
});
it('should set settings in cookie if metadata failed', () => {
service.setSettingsInAuthenticatedUserMetadata =
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(false));
const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};
service.setSettings(settings).subscribe();
expect(service.setSettingsInCookie).toHaveBeenCalled();
});
it('should not set settings in cookie if metadata succeeded', () => {
service.setSettingsInAuthenticatedUserMetadata =
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of('metadata'));
const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};
service.setSettings(settings).subscribe();
expect(service.setSettingsInCookie).not.toHaveBeenCalled();
});
it('should return \'metadata\' if settings are stored in metadata', () => {
service.setSettingsInAuthenticatedUserMetadata =
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of('metadata'));
const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};
service.setSettings(settings).subscribe(value =>
expect(value).toEqual('metadata'),
);
});
it('should return \'cookie\' if settings are stored in cookie', () => {
service.setSettingsInAuthenticatedUserMetadata =
jasmine.createSpy('setSettingsInAuthenticatedUserMetadata').and.returnValue(of(false));
const settings: AccessibilitySettings = {
notificationTimeOut: '1000',
};
service.setSettings(settings).subscribe(value =>
expect(value).toEqual('cookie'),
);
});
});
describe('updateSettings', () => {
it('should call setSettings with the updated settings', () => {
const beforeSettings: AccessibilitySettings = {
notificationTimeOut: '1000',
};
service.getAll = jasmine.createSpy('getAll').and.returnValue(of(beforeSettings));
service.setSettings = jasmine.createSpy('setSettings').and.returnValue(of('cookie'));
const newSettings: AccessibilitySettings = {
liveRegionTimeOut: '2000',
};
const combinedSettings: AccessibilitySettings = {
notificationTimeOut: '1000',
liveRegionTimeOut: '2000',
};
service.updateSettings(newSettings).subscribe();
expect(service.setSettings).toHaveBeenCalledWith(combinedSettings);
});
});
describe('setSettingsInAuthenticatedUserMetadata', () => {
beforeEach(() => {
service.setSettingsInMetadata = jasmine.createSpy('setSettingsInMetadata').and.returnValue(of(null));
});
it('should store settings in metadata when the user is authenticated', fakeAsync(() => {
const user = new EPerson();
authService.getAuthenticatedUserFromStoreIfAuthenticated = jasmine.createSpy().and.returnValue(of(user));
service.setSettingsInAuthenticatedUserMetadata({}).subscribe();
flush();
expect(service.setSettingsInMetadata).toHaveBeenCalled();
}));
it('should emit "failed" when the user is not authenticated', fakeAsync(() => {
authService.getAuthenticatedUserFromStoreIfAuthenticated = jasmine.createSpy().and.returnValue(of(null));
service.setSettingsInAuthenticatedUserMetadata({})
.subscribe(value => expect(value).toEqual('failed'));
flush();
expect(service.setSettingsInMetadata).not.toHaveBeenCalled();
}));
});
describe('setSettingsInMetadata', () => {
const ePerson = new EPerson();
beforeEach(() => {
ePerson.setMetadata = jasmine.createSpy('setMetadata');
ePerson.removeMetadata = jasmine.createSpy('removeMetadata');
});
it('should set the settings in metadata', () => {
service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' }).subscribe();
expect(ePerson.setMetadata).toHaveBeenCalled();
});
it('should remove the metadata when the settings are emtpy', () => {
service.setSettingsInMetadata(ePerson, {}).subscribe();
expect(ePerson.setMetadata).not.toHaveBeenCalled();
expect(ePerson.removeMetadata).toHaveBeenCalled();
});
it('should create a patch with the metadata changes', () => {
service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' }).subscribe();
expect(ePersonService.createPatchFromCache).toHaveBeenCalled();
});
it('should send the patch request', () => {
service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' }).subscribe();
expect(ePersonService.patch).toHaveBeenCalled();
});
it('should emit "metadata" when the update succeeded', fakeAsync(() => {
ePersonService.patch = jasmine.createSpy().and.returnValue(createSuccessfulRemoteDataObject$({}));
service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' })
.subscribe(value => {
expect(value).toEqual('metadata');
});
flush();
}));
it('should emit "failed" when the update failed', fakeAsync(() => {
ePersonService.patch = jasmine.createSpy().and.returnValue(createFailedRemoteDataObject$());
service.setSettingsInMetadata(ePerson, { ['liveRegionTimeOut']: '500' })
.subscribe(value => {
expect(value).toEqual('failed');
});
flush();
}));
});
describe('setSettingsInCookie', () => {
beforeEach(() => {
cookieService.set = jasmine.createSpy('set');
cookieService.remove = jasmine.createSpy('remove');
});
it('should fail to store settings in the cookie when the user has not accepted the cookie', fakeAsync(() => {
orejimeService.getSavedPreferences.and.returnValue(of({ accessibility: false }));
service.setSettingsInCookie({ ['liveRegionTimeOut']: '500' }).subscribe(value => {
expect(value).toEqual('failed');
});
flush();
expect(cookieService.set).not.toHaveBeenCalled();
}));
it('should store the settings in a cookie', fakeAsync(() => {
service.setSettingsInCookie({ ['liveRegionTimeOut']: '500' }).subscribe(value => {
expect(value).toEqual('cookie');
});
flush();
expect(cookieService.set).toHaveBeenCalled();
}));
it('should remove the cookie when the settings are empty', fakeAsync(() => {
service.setSettingsInCookie({}).subscribe(value => {
expect(value).toEqual('cookie');
});
flush();
expect(cookieService.set).not.toHaveBeenCalled();
expect(cookieService.remove).toHaveBeenCalled();
}));
});
describe('convertFormValuesToStoredValues', () => {
it('should reset the notificationTimeOut when timeOut is enabled but set to "0"', () => {
const formValues: AccessibilitySettingsFormValues = {
notificationTimeOutEnabled: true,
notificationTimeOut: '0',
liveRegionTimeOut: null,
};
const storedValues: FullAccessibilitySettings = service.convertFormValuesToStoredValues(formValues);
expect('notificationTimeOut' in storedValues).toBeFalse();
});
});
it('should keep the notificationTimeOut when timeOut is enabled and differs from "0"', () => {
const formValues: AccessibilitySettingsFormValues = {
notificationTimeOutEnabled: true,
notificationTimeOut: '3',
liveRegionTimeOut: null,
};
const storedValues: FullAccessibilitySettings = service.convertFormValuesToStoredValues(formValues);
expect('notificationTimeOut' in storedValues).toBeTrue();
});
});

View File

@@ -0,0 +1,45 @@
import { of } from 'rxjs';
import { AccessibilitySettingsService } from './accessibility-settings.service';
export function getAccessibilitySettingsServiceStub(): AccessibilitySettingsService {
return new AccessibilitySettingsServiceStub() as unknown as AccessibilitySettingsService;
}
export class AccessibilitySettingsServiceStub {
getAllAccessibilitySettingKeys = jasmine.createSpy('getAllAccessibilitySettingKeys').and.returnValue([]);
get = jasmine.createSpy('get').and.returnValue(of(null));
getAsNumber = jasmine.createSpy('getAsNumber').and.returnValue(of(0));
getAll = jasmine.createSpy('getAll').and.returnValue(of({}));
getAllSettingsFromCookie = jasmine.createSpy('getAllSettingsFromCookie').and.returnValue({});
getAllSettingsFromAuthenticatedUserMetadata = jasmine.createSpy('getAllSettingsFromAuthenticatedUserMetadata')
.and.returnValue(of({}));
set = jasmine.createSpy('setSettings').and.returnValue(of('cookie'));
updateSettings = jasmine.createSpy('updateSettings').and.returnValue(of('cookie'));
setSettingsInAuthenticatedUserMetadata = jasmine.createSpy('setSettingsInAuthenticatedUserMetadata')
.and.returnValue(of(false));
setSettingsInMetadata = jasmine.createSpy('setSettingsInMetadata').and.returnValue(of(false));
setSettingsInCookie = jasmine.createSpy('setSettingsInCookie');
getInputType = jasmine.createSpy('getInputType').and.returnValue('text');
convertFormValuesToStoredValues = jasmine.createSpy('convertFormValuesToStoredValues').and.returnValue({});
convertStoredValuesToFormValues = jasmine.createSpy('convertStoredValuesToFormValues').and.returnValue({});
getDefaultValue = jasmine.createSpy('getPlaceholder').and.returnValue('placeholder');
isValid = jasmine.createSpy('isValid').and.returnValue(true);
formValuesValid = jasmine.createSpy('allValid').and.returnValue(true);
}

View File

@@ -0,0 +1,381 @@
import {
Inject,
Injectable,
Optional,
} from '@angular/core';
import cloneDeep from 'lodash/cloneDeep';
import {
combineLatest,
Observable,
of,
switchMap,
} from 'rxjs';
import {
map,
take,
} from 'rxjs/operators';
import {
APP_CONFIG,
AppConfig,
} from '../../config/app-config.interface';
import { environment } from '../../environments/environment';
import { AuthService } from '../core/auth/auth.service';
import { EPersonDataService } from '../core/eperson/eperson-data.service';
import { EPerson } from '../core/eperson/models/eperson.model';
import { CookieService } from '../core/services/cookie.service';
import { getFirstCompletedRemoteData } from '../core/shared/operators';
import { OrejimeService } from '../shared/cookies/orejime.service';
import {
hasNoValue,
hasValue,
isNotEmpty,
} from '../shared/empty.util';
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
/**
* Name of the cookie used to store the settings locally
*/
export const ACCESSIBILITY_COOKIE = 'dsAccessibilityCookie';
/**
* Name of the metadata field to store settings on the ePerson
*/
export const ACCESSIBILITY_SETTINGS_METADATA_KEY = 'dspace.accessibility.settings';
/**
* Array containing all possible accessibility settings.
* When adding new settings, make sure to add the new setting to the accessibility-settings component form.
* The converter methods to convert from stored format to form format (and vice-versa) need to be updated as well.
*/
export const accessibilitySettingKeys = ['notificationTimeOut', 'liveRegionTimeOut'] as const;
/**
* Type representing the possible accessibility settings
*/
export type AccessibilitySetting = typeof accessibilitySettingKeys[number];
/**
* Type representing an object that contains accessibility settings values for all accessibility settings.
*/
export type FullAccessibilitySettings = { [key in AccessibilitySetting]: string };
/**
* Type representing an object that contains accessibility settings values for some accessibility settings.
*/
export type AccessibilitySettings = Partial<FullAccessibilitySettings>;
/**
* The accessibility settings object format used by the accessibility-settings component form.
*/
export interface AccessibilitySettingsFormValues {
notificationTimeOutEnabled: boolean,
notificationTimeOut: string,
liveRegionTimeOut: string,
}
/**
* Service handling the retrieval and configuration of accessibility settings.
*
* This service stores the configured settings in either a cookie or on the user's metadata depending on whether
* the user is authenticated.
*/
@Injectable({
providedIn: 'root',
})
export class AccessibilitySettingsService {
constructor(
protected cookieService: CookieService,
protected authService: AuthService,
protected ePersonService: EPersonDataService,
@Optional() protected orejimeService: OrejimeService,
@Inject(APP_CONFIG) protected appConfig: AppConfig,
) {
}
/**
* Get the stored value for the provided {@link AccessibilitySetting}. If the value does not exist or if it is empty,
* the provided defaultValue is emitted instead.
*/
get(setting: AccessibilitySetting, defaultValue: string = null): Observable<string> {
return this.getAll().pipe(
map(settings => settings[setting]),
map(value => isNotEmpty(value) ? value : defaultValue),
);
}
/**
* Get the stored value for the provided {@link AccessibilitySetting} as a number. If the stored value
* could not be converted to a number, the value of the defaultValue parameter is emitted instead.
*/
getAsNumber(setting: AccessibilitySetting, defaultValue: number = null): Observable<number> {
return this.get(setting).pipe(
map(value => hasValue(value) ? parseInt(value, 10) : NaN),
map(number => !isNaN(number) ? number : defaultValue),
);
}
/**
* Get all currently stored accessibility settings
*/
getAll(): Observable<AccessibilitySettings> {
return this.getAllSettingsFromAuthenticatedUserMetadata().pipe(
map(value => isNotEmpty(value) ? value : this.getAllSettingsFromCookie()),
map(value => isNotEmpty(value) ? value : {}),
);
}
/**
* Get all settings from the accessibility settings cookie
*/
getAllSettingsFromCookie(): AccessibilitySettings {
return this.cookieService.get(ACCESSIBILITY_COOKIE);
}
/**
* Attempts to retrieve all settings from the authenticated user's metadata.
* Returns an empty object when no user is authenticated.
*/
getAllSettingsFromAuthenticatedUserMetadata(): Observable<AccessibilitySettings> {
return this.authService.getAuthenticatedUserFromStoreIfAuthenticated().pipe(
take(1),
map(user => hasValue(user) && hasValue(user.firstMetadataValue(ACCESSIBILITY_SETTINGS_METADATA_KEY)) ?
JSON.parse(user.firstMetadataValue(ACCESSIBILITY_SETTINGS_METADATA_KEY)) :
{},
),
);
}
/**
* Set a single accessibility setting value, leaving all other settings unchanged.
* When setting all values, {@link AccessibilitySettingsService#setSettings} should be used.
* When updating multiple values, {@link AccessibilitySettingsService#updateSettings} should be used.
*
* Returns 'cookie' when the changes were stored in the cookie.
* Returns 'metadata' when the changes were stored in metadata.
* Returns 'failed' when both options failed.
*/
set(setting: AccessibilitySetting, value: string): Observable<'metadata' | 'cookie' | 'failed'> {
return this.updateSettings({ [setting]: value });
}
/**
* Set all accessibility settings simultaneously.
* This method removes existing settings if they are missing from the provided {@link AccessibilitySettings} object.
* Removes all settings if the provided object is empty.
*
* Returns 'cookie' when the changes were stored in the cookie.
* Returns 'metadata' when the changes were stored in metadata.
* Returns 'failed' when both options failed.
*/
setSettings(settings: AccessibilitySettings): Observable<'metadata' | 'cookie' | 'failed'> {
return this.setSettingsInAuthenticatedUserMetadata(settings).pipe(
take(1),
map(saveLocation => saveLocation === 'metadata'),
switchMap((savedInMetadata) =>
savedInMetadata ? ofMetadata() : this.setSettingsInCookie(settings),
),
);
}
/**
* Update multiple accessibility settings simultaneously.
* This method does not change the settings that are missing from the provided {@link AccessibilitySettings} object.
*
* Returns 'cookie' when the changes were stored in the cookie.
* Returns 'metadata' when the changes were stored in metadata.
* Returns 'failed' when both options failed.
*/
updateSettings(settings: AccessibilitySettings): Observable<'metadata' | 'cookie' | 'failed'> {
return this.getAll().pipe(
take(1),
map(currentSettings => Object.assign({}, currentSettings, settings)),
switchMap(newSettings => this.setSettings(newSettings)),
);
}
/**
* Attempts to set the provided settings on the currently authorized user's metadata.
* Emits false when no user is authenticated or when the metadata update failed.
* Emits 'metadata' when the metadata update succeeded, and 'failed' otherwise.
*/
setSettingsInAuthenticatedUserMetadata(settings: AccessibilitySettings): Observable<'metadata' | 'failed'> {
return this.authService.getAuthenticatedUserFromStoreIfAuthenticated().pipe(
take(1),
switchMap(user => {
if (hasValue(user)) {
// EPerson has to be cloned, otherwise the EPerson's metadata can't be modified
const clonedUser = cloneDeep(user);
return this.setSettingsInMetadata(clonedUser, settings);
} else {
return ofFailed();
}
}),
);
}
/**
* Attempts to set the provided settings on the user's metadata.
* Emits false when the update failed, true when the update succeeded.
*/
setSettingsInMetadata(
user: EPerson,
settings: AccessibilitySettings,
): Observable<'metadata' | 'failed'> {
if (isNotEmpty(settings)) {
user.setMetadata(ACCESSIBILITY_SETTINGS_METADATA_KEY, null, JSON.stringify(settings));
} else {
user.removeMetadata(ACCESSIBILITY_SETTINGS_METADATA_KEY);
}
return this.ePersonService.createPatchFromCache(user).pipe(
take(1),
switchMap(operations =>
isNotEmpty(operations) ? this.ePersonService.patch(user, operations) : createSuccessfulRemoteDataObject$({})),
getFirstCompletedRemoteData(),
switchMap(rd => rd.hasSucceeded ? ofMetadata() : ofFailed()),
);
}
/**
* Attempts to set the provided settings in a cookie.
* Emits 'failed' when setting in a cookie failed due to the cookie not being accepted, 'cookie' when it succeeded.
*/
setSettingsInCookie(settings: AccessibilitySettings): Observable<'cookie' | 'failed'> {
if (hasNoValue(this.orejimeService)) {
return of('failed');
}
return this.orejimeService.getSavedPreferences().pipe(
map(preferences => preferences.accessibility),
map((accessibilityCookieAccepted: boolean) => {
if (accessibilityCookieAccepted) {
if (isNotEmpty(settings)) {
this.cookieService.set(ACCESSIBILITY_COOKIE, settings, { expires: this.appConfig.accessibility.cookieExpirationDuration });
} else {
this.cookieService.remove(ACCESSIBILITY_COOKIE);
}
return 'cookie';
} else {
return 'failed';
}
}),
);
}
/**
* Clears all settings in the cookie and attempts to clear settings in metadata.
* Emits an array mentioning which settings succeeded or failed.
*/
clearSettings(): Observable<['cookie' | 'failed', 'metadata' | 'failed']> {
return combineLatest([
this.setSettingsInCookie({}),
this.setSettingsInAuthenticatedUserMetadata({}),
]);
}
/**
* Retrieve the default value to be used for the provided AccessibilitySetting.
* Returns an empty string when no default value is specified for the provided setting.
*/
getDefaultValue(setting: AccessibilitySetting): string {
switch (setting) {
case 'notificationTimeOut':
return millisecondsToSeconds(environment.notifications.timeOut.toString());
case 'liveRegionTimeOut':
return millisecondsToSeconds(environment.liveRegion.messageTimeOutDurationMs.toString());
default:
return '';
}
}
/**
* Convert values in the provided accessibility settings object to values ready to be stored.
*/
convertFormValuesToStoredValues(settings: AccessibilitySettingsFormValues): FullAccessibilitySettings {
const storedValues = {
notificationTimeOut: settings.notificationTimeOutEnabled ?
secondsToMilliseconds(settings.notificationTimeOut) : '0',
liveRegionTimeOut: secondsToMilliseconds(settings.liveRegionTimeOut),
};
// When the user enables the timeout but does not change the timeout duration from 0,
// it is removed from the values to be stored so the default value is used.
// Keeping it at 0 would mean the notifications are not automatically removed.
if (settings.notificationTimeOutEnabled && settings.notificationTimeOut === '0') {
delete storedValues.notificationTimeOut;
}
return storedValues;
}
/**
* Convert values in the provided accessibility settings object to values ready to show in the form.
*/
convertStoredValuesToFormValues(settings: AccessibilitySettings): AccessibilitySettingsFormValues {
return {
notificationTimeOutEnabled: parseFloat(settings.notificationTimeOut) !== 0,
notificationTimeOut: millisecondsToSeconds(settings.notificationTimeOut),
liveRegionTimeOut: millisecondsToSeconds(settings.liveRegionTimeOut),
};
}
/**
* Returns true if the provided AccessibilitySetting is valid in regard to the provided formValues.
*/
isValid(setting: AccessibilitySetting, formValues: AccessibilitySettingsFormValues): boolean {
switch (setting) {
case 'notificationTimeOut':
return formValues.notificationTimeOutEnabled ?
hasNoValue(formValues.notificationTimeOut) || parseFloat(formValues.notificationTimeOut) > 0 :
true;
case 'liveRegionTimeOut':
return hasNoValue(formValues.liveRegionTimeOut) || parseFloat(formValues.liveRegionTimeOut) > 0;
default:
throw new Error(`Unhandled accessibility setting during validity check: ${setting}`);
}
}
/**
* Returns true if all settings in the provided AccessibilitySettingsFormValues object are valid
*/
formValuesValid(formValues: AccessibilitySettingsFormValues) {
return accessibilitySettingKeys.every(setting => this.isValid(setting, formValues));
}
}
/**
* Converts a string representing seconds to a string representing milliseconds
* Returns null if the input could not be parsed to a float
*/
function secondsToMilliseconds(secondsStr: string): string {
const seconds = parseFloat(secondsStr);
if (isNaN(seconds)) {
return null;
} else {
return (seconds * 1000).toString();
}
}
/**
* Converts a string representing milliseconds to a string representing seconds
* Returns null if the input could not be parsed to a float
*/
function millisecondsToSeconds(millisecondsStr: string): string {
const milliseconds = parseFloat(millisecondsStr);
if (isNaN(milliseconds)) {
return null;
} else {
return (milliseconds / 1000).toString();
}
}
function ofMetadata(): Observable<'metadata'> {
return of('metadata');
}
function ofFailed(): Observable<'failed'> {
return of('failed');
}

View File

@@ -33,10 +33,10 @@ import { FileDropzoneNoUploaderComponent } from '../../shared/upload/file-dropzo
selector: 'ds-batch-import-page',
templateUrl: './batch-import-page.component.html',
imports: [
TranslateModule,
FormsModule,
UiSwitchModule,
FileDropzoneNoUploaderComponent,
FormsModule,
TranslateModule,
UiSwitchModule,
],
standalone: true,
})

View File

@@ -10,7 +10,9 @@ import { MetadataImportPageComponent } from './metadata-import-page.component';
selector: 'ds-metadata-import-page',
templateUrl: '../../shared/theme-support/themed.component.html',
standalone: true,
imports: [MetadataImportPageComponent],
imports: [
MetadataImportPageComponent,
],
})
export class ThemedMetadataImportPageComponent extends ThemedComponent<MetadataImportPageComponent> {
protected getComponentName(): string {

View File

@@ -6,7 +6,7 @@
<!-- In the toggle section -->
@if (!isNewService) {
<div class="toggle-switch-container">
<label class="status-label font-weight-bold" for="enabled">{{ 'ldn-service-status' | translate }}</label>
<label class="status-label fw-bold" for="enabled">{{ 'ldn-service-status' | translate }}</label>
<div>
<input formControlName="enabled" hidden id="enabled" name="enabled" type="checkbox">
<div (click)="toggleEnabled()" [class.checked]="formModel.get('enabled').value" class="toggle-switch">
@@ -17,7 +17,7 @@
}
<!-- In the Name section -->
<div class="mb-5">
<label for="name" class="font-weight-bold">{{ 'ldn-new-service.form.label.name' | translate }}</label>
<label for="name" class="fw-bold">{{ 'ldn-new-service.form.label.name' | translate }}</label>
<input [class.invalid-field]="formModel.get('name').invalid && formModel.get('name').touched"
[placeholder]="'ldn-new-service.form.placeholder.name' | translate" class="form-control"
formControlName="name"
@@ -33,7 +33,7 @@
<!-- In the description section -->
<div class="mb-5 mt-5 d-flex flex-column">
<label for="description" class="font-weight-bold">{{ 'ldn-new-service.form.label.description' | translate }}</label>
<label for="description" class="fw-bold">{{ 'ldn-new-service.form.label.description' | translate }}</label>
<textarea [placeholder]="'ldn-new-service.form.placeholder.description' | translate"
class="form-control" formControlName="description" id="description" name="description"></textarea>
</div>
@@ -42,7 +42,7 @@
<!-- In the url section -->
<div class="d-flex align-items-center">
<div class="d-flex flex-column w-50 me-2">
<label for="url" class="font-weight-bold">{{ 'ldn-new-service.form.label.url' | translate }}</label>
<label for="url" class="fw-bold">{{ 'ldn-new-service.form.label.url' | translate }}</label>
<input [class.invalid-field]="formModel.get('url').invalid && formModel.get('url').touched"
[placeholder]="'ldn-new-service.form.placeholder.url' | translate" class="form-control"
formControlName="url"
@@ -57,7 +57,7 @@
</div>
<div class="d-flex flex-column w-50">
<label for="score" class="font-weight-bold">{{ 'ldn-new-service.form.label.score' | translate }}</label>
<label for="score" class="fw-bold">{{ 'ldn-new-service.form.label.score' | translate }}</label>
<input [class.invalid-field]="formModel.get('score').invalid && formModel.get('score').touched"
[placeholder]="'ldn-new-service.form.placeholder.score' | translate" formControlName="score"
id="score"
@@ -78,7 +78,7 @@
<!-- In the IP range section -->
<div class="mb-5 mt-5">
<label for="lowerIp" class="font-weight-bold">{{ 'ldn-new-service.form.label.ip-range' | translate }}</label>
<label for="lowerIp" class="fw-bold">{{ 'ldn-new-service.form.label.ip-range' | translate }}</label>
<div class="d-flex">
<input [class.invalid-field]="formModel.get('lowerIp').invalid && formModel.get('lowerIp').touched"
[placeholder]="'ldn-new-service.form.placeholder.lowerIp' | translate" class="form-control me-2"
@@ -105,7 +105,7 @@
<!-- In the ldnUrl section -->
<div class="mb-5 mt-5">
<label for="ldnUrl" class="font-weight-bold">{{ 'ldn-new-service.form.label.ldnUrl' | translate }}</label>
<label for="ldnUrl" class="fw-bold">{{ 'ldn-new-service.form.label.ldnUrl' | translate }}</label>
<input [class.invalid-field]="formModel.get('ldnUrl').invalid && formModel.get('ldnUrl').touched"
[placeholder]="'ldn-new-service.form.placeholder.ldnUrl' | translate" class="form-control"
formControlName="ldnUrl"
@@ -130,7 +130,7 @@
<!-- In the usesActorEmailId section -->
<div class="mb-5 mt-5">
<label class="status-label font-weight-bold" for="usesActorEmailId">{{ 'ldn-service-usesActorEmailId' | translate }}</label>
<label class="status-label fw-bold" for="usesActorEmailId">{{ 'ldn-service-usesActorEmailId' | translate }}</label>
<div>
<input formControlName="usesActorEmailId" hidden id="usesActorEmailId"
name="usesActorEmailId" type="checkbox">
@@ -149,14 +149,14 @@
@if (areControlsInitialized) {
<div class="row mb-1 mt-5">
<div class="col">
<label class="font-weight-bold">{{ 'ldn-new-service.form.label.inboundPattern' | translate }} </label>
<label class="fw-bold">{{ 'ldn-new-service.form.label.inboundPattern' | translate }} </label>
</div>
@if (formModel.get('notifyServiceInboundPatterns')['controls'][0]?.value?.pattern) {
<div class="col">
<label class="font-weight-bold">{{ 'ldn-new-service.form.label.ItemFilter' | translate }}</label>
<label class="fw-bold">{{ 'ldn-new-service.form.label.ItemFilter' | translate }}</label>
</div>
<div class="col-sm-1">
<label class="font-weight-bold">{{ 'ldn-new-service.form.label.automatic' | translate }}</label>
<label class="fw-bold">{{ 'ldn-new-service.form.label.automatic' | translate }}</label>
</div>
}
<div class="col-sm-2">
@@ -295,8 +295,8 @@
<span (click)="addInboundPattern()"
class="add-pattern-link mb-2">{{ 'ldn-new-service.form.label.addPattern' | translate }}</span>
<hr>
<div class="form-group row">
<div class="col text-right space-children-mr">
<div class="mb-3 row">
<div class="col text-end space-children-mr">
<ng-content select="[before]"></ng-content>
<button (click)="resetFormAndLeave()" class="btn btn-outline-secondary" type="button">
<span>&nbsp;{{ 'submission.general.back.submit' | translate }}</span>
@@ -317,9 +317,7 @@
@if (isNewService) {
<h4>{{'service.overview.create.modal' | translate }}</h4>
}
<button (click)="closeModal()" aria-label="Close"
class="close" type="button">
<span aria-hidden="true">×</span>
<button (click)="closeModal()" aria-label="Close" class="btn-close" type="button">
</button>
</div>

View File

@@ -30,10 +30,7 @@ import {
TranslateService,
} from '@ngx-translate/core';
import { PaginationService } from 'ngx-pagination';
import {
of as observableOf,
of,
} from 'rxjs';
import { of } from 'rxjs';
import { RouteService } from '../../../core/services/route.service';
import { MockActivatedRoute } from '../../../shared/mocks/active-router.mock';
@@ -94,8 +91,8 @@ describe('LdnServiceFormEditComponent', () => {
beforeEach(async () => {
ldnServicesService = jasmine.createSpyObj('ldnServicesService', {
create: observableOf(null),
update: observableOf(null),
create: of(null),
update: of(null),
findById: createSuccessfulRemoteDataObject$({}),
});

View File

@@ -71,10 +71,10 @@ import { notifyPatterns } from '../ldn-services-patterns/ldn-service-coar-patter
]),
],
imports: [
AsyncPipe,
NgbDropdownModule,
ReactiveFormsModule,
TranslateModule,
NgbDropdownModule,
AsyncPipe,
],
})
export class LdnServiceFormComponent implements OnInit, OnDestroy {

View File

@@ -2,7 +2,7 @@ import {
cold,
getTestScheduler,
} from 'jasmine-marbles';
import { of as observableOf } from 'rxjs';
import { of } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
@@ -71,12 +71,12 @@ describe('LdnServicesService test', () => {
generateRequestId: requestUUID,
send: true,
removeByHrefSubstring: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: observableOf(responseCacheEntry),
getByHref: of(responseCacheEntry),
getByUUID: of(responseCacheEntry),
});
halService = jasmine.createSpyObj('halService', {
getEndpoint: observableOf(endpointURL),
getEndpoint: of(endpointURL),
});
rdbService = jasmine.createSpyObj('rdbService', {
@@ -107,7 +107,7 @@ describe('LdnServicesService test', () => {
it('should find service by inbound pattern', (done) => {
const params = [new RequestParam('pattern', 'testPattern')];
const findListOptions = Object.assign(new FindListOptions(), {}, { searchParams: params });
spyOn(service, 'searchBy').and.returnValue(observableOf(null));
spyOn(service, 'searchBy').and.returnValue(of(null));
spyOn((service as any).searchData, 'searchBy').and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([mockLdnService])));
service.findByInboundPattern('testPattern').subscribe(() => {
@@ -120,7 +120,7 @@ describe('LdnServicesService test', () => {
const constraints = [{ void: true }];
const files = [new File([],'fileName')];
spyOn(service as any, 'getInvocationFormData');
spyOn(service, 'getBrowseEndpoint').and.returnValue(observableOf('testEndpoint'));
spyOn(service, 'getBrowseEndpoint').and.returnValue(of('testEndpoint'));
service.invoke('serviceName', 'serviceId', constraints, files).subscribe(result => {
expect((service as any).getInvocationFormData).toHaveBeenCalledWith(constraints, files);
expect(service.getBrowseEndpoint).toHaveBeenCalled();

View File

@@ -77,8 +77,7 @@
</div>
<button (click)="closeModal()" aria-label="Close"
[attr.aria-label]="'ldn-service-overview-close-modal' | translate"
class="close" type="button">
<span aria-hidden="true">×</span>
class="btn-close" type="button">
</button>
</div>
@@ -86,7 +85,7 @@
<div>
{{ 'service.overview.delete.body' | translate }}
</div>
<div class="mt-4 text-right">
<div class="mt-4 text-end">
<button (click)="closeModal()"
[attr.aria-label]="'ldn-service-overview-close-modal' | translate"
class="btn btn-outline-secondary me-2">{{ 'service.detail.delete.cancel' | translate }}</button>

View File

@@ -52,13 +52,13 @@ import { LdnService } from '../ldn-services-model/ldn-services.model';
styleUrls: ['./ldn-services-directory.component.scss'],
changeDetection: ChangeDetectionStrategy.Default,
imports: [
TranslateModule,
AsyncPipe,
NgClass,
PaginationComponent,
RouterLink,
TranslateModule,
TruncatableComponent,
TruncatablePartComponent,
NgClass,
RouterLink,
],
standalone: true,
})

View File

@@ -6,7 +6,9 @@ import { SuggestionSourcesComponent } from '../../../notifications/suggestions/s
selector: 'ds-admin-notifications-publication-claim-page',
templateUrl: './admin-notifications-publication-claim-page.component.html',
styleUrls: ['./admin-notifications-publication-claim-page.component.scss'],
imports: [ SuggestionSourcesComponent ],
imports: [
SuggestionSourcesComponent,
],
standalone: true,
})
export class AdminNotificationsPublicationClaimPageComponent {

View File

@@ -42,9 +42,9 @@ import {
standalone: true,
imports: [
AdminNotifyMetricsComponent,
AsyncPipe,
RouterLink,
TranslateModule,
AsyncPipe,
],
})

View File

@@ -1,15 +1,14 @@
<div class="modal-header">
<h4 class="modal-title">{{'notify-message-modal.title' | translate}}</h4>
<button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
<span aria-hidden="true">&times;</span>
<button type="button" class="btn-close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
</button>
</div>
<div class="modal-body p-4">
@for (key of notifyMessageKeys; track key) {
<div>
<div class="row mb-4">
<div class="font-weight-bold col">{{ key + '.notify-detail-modal' | translate}}</div>
<div class="col text-right">{{'notify-detail-modal.' + notifyMessage[key] | translate: {default: notifyMessage[key] ?? "n/a" } }}</div>
<div class="fw-bold col">{{ key + '.notify-detail-modal' | translate}}</div>
<div class="col text-end">{{'notify-detail-modal.' + notifyMessage[key] | translate: {default: notifyMessage[key] ?? "n/a" } }}</div>
</div>
</div>
}

View File

@@ -20,8 +20,8 @@ import { AdminNotifyLogsResultComponent } from '../admin-notify-logs-result/admi
],
standalone: true,
imports: [
RouterLink,
AdminNotifyLogsResultComponent,
RouterLink,
TranslateModule,
],
})

View File

@@ -1,6 +1,6 @@
<div class="container my-4">
<div class="row">
<div class="col-12 col-md-3 text-left h4">{{((isInbound$ | async) ? 'admin.notify.dashboard.inbound' : 'admin.notify.dashboard.outbound') | translate}}</div>
<div class="col-12 col-md-3 text-start h4">{{((isInbound$ | async) ? 'admin.notify.dashboard.inbound' : 'admin.notify.dashboard.outbound') | translate}}</div>
<div class="col-md-9">
<div class="h4">
@if ((selectedSearchConfig$ | async) !== defaultConfiguration) {

View File

@@ -32,9 +32,9 @@ import { ThemedSearchComponent } from '../../../../shared/search/themed-search.c
],
standalone: true,
imports: [
AsyncPipe,
SearchLabelsComponent,
ThemedSearchComponent,
AsyncPipe,
TranslateModule,
],
})

View File

@@ -20,8 +20,8 @@ import { AdminNotifyLogsResultComponent } from '../admin-notify-logs-result/admi
],
standalone: true,
imports: [
RouterLink,
AdminNotifyLogsResultComponent,
RouterLink,
TranslateModule,
],
})

View File

@@ -7,10 +7,7 @@ import {
import { RouterModule } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import {
of as observableOf,
of,
} from 'rxjs';
import { of } from 'rxjs';
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-configuration.service';
@@ -120,7 +117,7 @@ describe('AdminNotifySearchResultComponent', () => {
fixture = TestBed.createComponent(AdminNotifySearchResultComponent);
component = fixture.componentInstance;
modalService = TestBed.inject(NgbModal);
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: observableOf(true) }) }));
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: of(true) }) }));
fixture.detectChanges();
});

View File

@@ -39,12 +39,12 @@ import { AdminNotifyMessagesService } from '../services/admin-notify-messages.se
],
standalone: true,
imports: [
TranslateModule,
DatePipe,
AsyncPipe,
DatePipe,
RouterLink,
TranslateModule,
TruncatableComponent,
TruncatablePartComponent,
RouterLink,
],
})
/**

View File

@@ -9,7 +9,7 @@ import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
import { of } from 'rxjs';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
@@ -51,7 +51,7 @@ describe('AddBitstreamFormatComponent', () => {
notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
createBitstreamFormat: createSuccessfulRemoteDataObject$({}),
clearBitStreamFormatRequests: observableOf(null),
clearBitStreamFormatRequests: of(null),
});
TestBed.configureTestingModule({
@@ -98,7 +98,7 @@ describe('AddBitstreamFormatComponent', () => {
notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
createBitstreamFormat: createFailedRemoteDataObject$('Error', 500),
clearBitStreamFormatRequests: observableOf(null),
clearBitStreamFormatRequests: of(null),
});
TestBed.configureTestingModule({

View File

@@ -9,7 +9,7 @@ import { RouterModule } from '@angular/router';
import { provideMockStore } from '@ngrx/store/testing';
import { TranslateModule } from '@ngx-translate/core';
import { hot } from 'jasmine-marbles';
import { of as observableOf } from 'rxjs';
import { of } from 'rxjs';
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
import { PaginationService } from '../../../core/pagination/pagination.service';
@@ -88,14 +88,14 @@ describe('BitstreamFormatsComponent', () => {
notificationsServiceStub = new NotificationsServiceStub();
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: observableOf(mockFormatsRD),
findAll: of(mockFormatsRD),
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
getSelectedBitstreamFormats: hot('a', { a: mockFormatsList }),
selectBitstreamFormat: {},
deselectBitstreamFormat: {},
deselectAllBitstreamFormats: {},
delete: createSuccessfulRemoteDataObject$({}),
clearBitStreamFormatRequests: observableOf('cleared'),
clearBitStreamFormatRequests: of('cleared'),
});
paginationService = new PaginationServiceStub();
@@ -225,14 +225,14 @@ describe('BitstreamFormatsComponent', () => {
notificationsServiceStub = new NotificationsServiceStub();
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: observableOf(mockFormatsRD),
findAll: of(mockFormatsRD),
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
getSelectedBitstreamFormats: observableOf(mockFormatsList),
getSelectedBitstreamFormats: of(mockFormatsList),
selectBitstreamFormat: {},
deselectBitstreamFormat: {},
deselectAllBitstreamFormats: {},
delete: createNoContentRemoteDataObject$(),
clearBitStreamFormatRequests: observableOf('cleared'),
clearBitStreamFormatRequests: of('cleared'),
});
paginationService = new PaginationServiceStub();
@@ -282,14 +282,14 @@ describe('BitstreamFormatsComponent', () => {
notificationsServiceStub = new NotificationsServiceStub();
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: observableOf(mockFormatsRD),
findAll: of(mockFormatsRD),
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
getSelectedBitstreamFormats: observableOf(mockFormatsList),
getSelectedBitstreamFormats: of(mockFormatsList),
selectBitstreamFormat: {},
deselectBitstreamFormat: {},
deselectAllBitstreamFormats: {},
delete: createFailedRemoteDataObject$(),
clearBitStreamFormatRequests: observableOf('cleared'),
clearBitStreamFormatRequests: of('cleared'),
});
paginationService = new PaginationServiceStub();

View File

@@ -38,9 +38,9 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
templateUrl: './bitstream-formats.component.html',
imports: [
AsyncPipe,
PaginationComponent,
RouterLink,
TranslateModule,
PaginationComponent,
],
standalone: true,
})

View File

@@ -12,7 +12,7 @@ import {
import { RouterTestingModule } from '@angular/router/testing';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
import { of } from 'rxjs';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RemoteData } from '../../../../core/data/remote-data';
@@ -44,7 +44,7 @@ describe('EditBitstreamFormatComponent', () => {
bitstreamFormat.extensions = null;
const routeStub = {
data: observableOf({
data: of({
bitstreamFormat: createSuccessfulRemoteDataObject(bitstreamFormat),
}),
};

View File

@@ -30,9 +30,9 @@ import { FormatFormComponent } from '../format-form/format-form.component';
selector: 'ds-edit-bitstream-format',
templateUrl: './edit-bitstream-format.component.html',
imports: [
AsyncPipe,
FormatFormComponent,
TranslateModule,
AsyncPipe,
],
standalone: true,
})

View File

@@ -14,7 +14,7 @@ import { RouterLink } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
import { of } from 'rxjs';
import { FormBuilderService } from 'src/app/shared/form/builder/form-builder.service';
import { ConfigurationDataService } from '../../../core/data/configuration-data.service';
@@ -178,7 +178,7 @@ describe('MetadataRegistryComponent', () => {
}));
it('should cancel editing the selected schema when clicked again', waitForAsync(() => {
comp.activeMetadataSchema$ = observableOf(mockSchemasList[0] as MetadataSchema);
comp.activeMetadataSchema$ = of(mockSchemasList[0] as MetadataSchema);
spyOn(registryService, 'cancelEditMetadataSchema');
row.click();
fixture.detectChanges();
@@ -193,7 +193,7 @@ describe('MetadataRegistryComponent', () => {
beforeEach(() => {
spyOn(registryService, 'deleteMetadataSchema').and.callThrough();
comp.selectedMetadataSchemaIDs$ = observableOf(selectedSchemas.map((selectedSchema: MetadataSchema) => selectedSchema.id));
comp.selectedMetadataSchemaIDs$ = of(selectedSchemas.map((selectedSchema: MetadataSchema) => selectedSchema.id));
comp.deleteSchemas();
fixture.detectChanges();
});

View File

@@ -43,12 +43,12 @@ import { MetadataSchemaFormComponent } from './metadata-schema-form/metadata-sch
templateUrl: './metadata-registry.component.html',
styleUrls: ['./metadata-registry.component.scss'],
imports: [
MetadataSchemaFormComponent,
TranslateModule,
AsyncPipe,
PaginationComponent,
MetadataSchemaFormComponent,
NgClass,
PaginationComponent,
RouterLink,
TranslateModule,
],
standalone: true,
})

View File

@@ -9,7 +9,7 @@ import {
import { RouterTestingModule } from '@angular/router/testing';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
import { of } from 'rxjs';
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
import { RegistryService } from '../../../../core/registry/registry.service';
@@ -72,7 +72,7 @@ describe('MetadataSchemaFormComponent', () => {
describe('without an active schema', () => {
beforeEach(() => {
component.activeMetadataSchema$ = observableOf(undefined);
component.activeMetadataSchema$ = of(undefined);
component.onSubmit();
fixture.detectChanges();
});
@@ -91,7 +91,7 @@ describe('MetadataSchemaFormComponent', () => {
} as MetadataSchema);
beforeEach(() => {
component.activeMetadataSchema$ = observableOf(expectedWithId);
component.activeMetadataSchema$ = of(expectedWithId);
component.onSubmit();
fixture.detectChanges();
});

View File

@@ -37,8 +37,8 @@ import { FormComponent } from '../../../../shared/form/form.component';
templateUrl: './metadata-schema-form.component.html',
imports: [
AsyncPipe,
TranslateModule,
FormComponent,
TranslateModule,
],
standalone: true,
})

View File

@@ -1,4 +1,4 @@
@if (registryService.getActiveMetadataField() | async) {
@if (activeMetadataField$ | async) {
<h2>{{messagePrefix + '.edit' | translate}}</h2>
} @else {
<h2>{{messagePrefix + '.create' | translate}}</h2>

View File

@@ -9,7 +9,7 @@ import {
import { RouterTestingModule } from '@angular/router/testing';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
import { of } from 'rxjs';
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
@@ -86,7 +86,7 @@ describe('MetadataFieldFormComponent', () => {
describe('without an active field', () => {
beforeEach(() => {
spyOn(registryService, 'getActiveMetadataField').and.returnValue(observableOf(undefined));
spyOn(registryService, 'getActiveMetadataField').and.returnValue(of(undefined));
component.onSubmit();
fixture.detectChanges();
});
@@ -107,7 +107,7 @@ describe('MetadataFieldFormComponent', () => {
});
beforeEach(() => {
spyOn(registryService, 'getActiveMetadataField').and.returnValue(observableOf(expectedWithId));
spyOn(registryService, 'getActiveMetadataField').and.returnValue(of(expectedWithId));
component.onSubmit();
fixture.detectChanges();
});

View File

@@ -19,7 +19,7 @@ import {
TranslateModule,
TranslateService,
} from '@ngx-translate/core';
import { combineLatest } from 'rxjs';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
@@ -32,9 +32,9 @@ import { FormComponent } from '../../../../shared/form/form.component';
selector: 'ds-metadata-field-form',
templateUrl: './metadata-field-form.component.html',
imports: [
AsyncPipe,
FormComponent,
TranslateModule,
AsyncPipe,
],
standalone: true,
})
@@ -109,6 +109,8 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
*/
@Output() submitForm: EventEmitter<any> = new EventEmitter();
activeMetadataField$: Observable<MetadataField>;
constructor(public registryService: RegistryService,
private formBuilderService: FormBuilderService,
private translateService: TranslateService) {
@@ -117,71 +119,65 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
/**
* Initialize the component, setting up the necessary Models for the dynamic form
*/
ngOnInit() {
combineLatest([
this.translateService.get(`${this.messagePrefix}.element`),
this.translateService.get(`${this.messagePrefix}.qualifier`),
this.translateService.get(`${this.messagePrefix}.scopenote`),
]).subscribe(([element, qualifier, scopenote]) => {
this.element = new DynamicInputModel({
id: 'element',
label: element,
name: 'element',
validators: {
required: null,
pattern: '^[^. ,]*$',
maxLength: 64,
},
required: true,
errorMessages: {
pattern: 'error.validation.metadata.element.invalid-pattern',
maxLength: 'error.validation.metadata.element.max-length',
},
});
this.qualifier = new DynamicInputModel({
id: 'qualifier',
label: qualifier,
name: 'qualifier',
validators: {
pattern: '^[^. ,]*$',
maxLength: 64,
},
required: false,
errorMessages: {
pattern: 'error.validation.metadata.qualifier.invalid-pattern',
maxLength: 'error.validation.metadata.qualifier.max-length',
},
});
this.scopeNote = new DynamicTextAreaModel({
id: 'scopeNote',
label: scopenote,
name: 'scopeNote',
required: false,
rows: 5,
});
this.formModel = [
new DynamicFormGroupModel(
{
id: 'metadatadatafieldgroup',
group:[this.element, this.qualifier, this.scopeNote],
}),
];
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
this.registryService.getActiveMetadataField().subscribe((field: MetadataField): void => {
if (field == null) {
this.clearFields();
} else {
this.formGroup.patchValue({
metadatadatafieldgroup: {
element: field.element,
qualifier: field.qualifier,
scopeNote: field.scopeNote,
},
});
this.element.disabled = true;
this.qualifier.disabled = true;
}
});
ngOnInit(): void {
this.activeMetadataField$ = this.registryService.getActiveMetadataField();
this.element = new DynamicInputModel({
id: 'element',
label: this.translateService.instant(`${this.messagePrefix}.element`),
name: 'element',
validators: {
required: null,
pattern: '^[^. ,]*$',
maxLength: 64,
},
required: true,
errorMessages: {
pattern: 'error.validation.metadata.element.invalid-pattern',
maxLength: 'error.validation.metadata.element.max-length',
},
});
this.qualifier = new DynamicInputModel({
id: 'qualifier',
label: this.translateService.instant(`${this.messagePrefix}.qualifier`),
name: 'qualifier',
validators: {
pattern: '^[^. ,]*$',
maxLength: 64,
},
required: false,
errorMessages: {
pattern: 'error.validation.metadata.qualifier.invalid-pattern',
maxLength: 'error.validation.metadata.qualifier.max-length',
},
});
this.scopeNote = new DynamicTextAreaModel({
id: 'scopeNote',
label: this.translateService.instant(`${this.messagePrefix}.scopenote`),
name: 'scopeNote',
required: false,
rows: 5,
});
this.formModel = [
new DynamicFormGroupModel({
id: 'metadatadatafieldgroup',
group:[this.element, this.qualifier, this.scopeNote],
}),
];
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
this.registryService.getActiveMetadataField().subscribe((field: MetadataField): void => {
if (field == null) {
this.clearFields();
} else {
this.formGroup.patchValue({
metadatadatafieldgroup: {
element: field.element,
qualifier: field.qualifier,
scopeNote: field.scopeNote,
},
});
this.element.disabled = true;
this.qualifier.disabled = true;
}
});
}

Some files were not shown because too many files have changed in this diff Show More