mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Compare commits
252 Commits
dspace-9.0
...
dspace-8.2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
38e0fe2f5d | ||
![]() |
215dd37c89 | ||
![]() |
2b9d3a0dc2 | ||
![]() |
2e26b4a50d | ||
![]() |
df8859d976 | ||
![]() |
c36c8d726e | ||
![]() |
93200d6b3c | ||
![]() |
aabc0a199e | ||
![]() |
3c99183504 | ||
![]() |
c3edf911ac | ||
![]() |
b815737710 | ||
![]() |
524de36043 | ||
![]() |
86b4ce2cf9 | ||
![]() |
2f986bb0a6 | ||
![]() |
9070ad62a4 | ||
![]() |
9e39b01c6c | ||
![]() |
991dc0a0e5 | ||
![]() |
31fcfda622 | ||
![]() |
1ec2df1017 | ||
![]() |
763871a5c0 | ||
![]() |
510c0ae367 | ||
![]() |
c05586a7ea | ||
![]() |
33b3f1a8e6 | ||
![]() |
39ddebede7 | ||
![]() |
e048d3bc7d | ||
![]() |
d6cccf1c1d | ||
![]() |
5d8785e804 | ||
![]() |
05d0743c0e | ||
![]() |
f525461099 | ||
![]() |
6b38f5c0ac | ||
![]() |
7a5cad9ff7 | ||
![]() |
76486e285c | ||
![]() |
a1ef2e9e84 | ||
![]() |
84ad762b32 | ||
![]() |
d1fdd6110e | ||
![]() |
ea81165fc5 | ||
![]() |
c2c23dfa99 | ||
![]() |
8a3a2026da | ||
![]() |
c608ba6ea3 | ||
![]() |
2ad5c98a00 | ||
![]() |
fde7e464b0 | ||
![]() |
2956f4ae67 | ||
![]() |
8143114996 | ||
![]() |
418bf7d4ea | ||
![]() |
4855772489 | ||
![]() |
bd6a5db73e | ||
![]() |
9e8bc95acc | ||
![]() |
29a13ef518 | ||
![]() |
0e12c5bdec | ||
![]() |
2699e81684 | ||
![]() |
46394d4bba | ||
![]() |
3f413af65e | ||
![]() |
bb17ce42ed | ||
![]() |
4b7c95a766 | ||
![]() |
1c6e54909e | ||
![]() |
aca2b3370a | ||
![]() |
cfec1c8b8a | ||
![]() |
8208f886f2 | ||
![]() |
af29e5e3df | ||
![]() |
5de271ef94 | ||
![]() |
fb28098ece | ||
![]() |
83d5400c1c | ||
![]() |
7c45c78082 | ||
![]() |
c38711d8a9 | ||
![]() |
b18260f8ae | ||
![]() |
97bf3c2b79 | ||
![]() |
cd4de478bc | ||
![]() |
72a3872b51 | ||
![]() |
65c47fbdfc | ||
![]() |
eb6759817f | ||
![]() |
d2cc002af0 | ||
![]() |
09182f9cee | ||
![]() |
3ee1983426 | ||
![]() |
ed4dfdad1d | ||
![]() |
fdeb1d0963 | ||
![]() |
ff95c2303b | ||
![]() |
94097dd636 | ||
![]() |
5c5aef86c6 | ||
![]() |
8c38380bbd | ||
![]() |
c2246d50fe | ||
![]() |
c9338ad362 | ||
![]() |
6e63e8e372 | ||
![]() |
3ef7adff79 | ||
![]() |
c7d5611a4e | ||
![]() |
c58b398e43 | ||
![]() |
2428dfeb4b | ||
![]() |
2a5905d7f4 | ||
![]() |
67563e37ff | ||
![]() |
e579725dfb | ||
![]() |
490bf758ac | ||
![]() |
6e9018a01d | ||
![]() |
2eed8f13d0 | ||
![]() |
be2bf9897a | ||
![]() |
d6f75af9d3 | ||
![]() |
3d7ba529c1 | ||
![]() |
e5047f52ac | ||
![]() |
ce44a89f8e | ||
![]() |
5cbec3719b | ||
![]() |
d4231e0e3e | ||
![]() |
4bb7b54be9 | ||
![]() |
c6bb829ac2 | ||
![]() |
499b49e6bf | ||
![]() |
d6c8be6983 | ||
![]() |
4a4c01b80b | ||
![]() |
40e213f926 | ||
![]() |
d92aeb6d09 | ||
![]() |
6148a66ab6 | ||
![]() |
fb6626904a | ||
![]() |
577d241379 | ||
![]() |
0c564cb9c2 | ||
![]() |
0439d07374 | ||
![]() |
5792c4f32d | ||
![]() |
4fa6a7e7df | ||
![]() |
1683905053 | ||
![]() |
dbdf84fc15 | ||
![]() |
25004d3578 | ||
![]() |
d02c06d8db | ||
![]() |
dd4c736d5e | ||
![]() |
3d95a7138b | ||
![]() |
d95974a042 | ||
![]() |
eeb4009664 | ||
![]() |
78b230b82f | ||
![]() |
a1cbc7c83e | ||
![]() |
977a6334b6 | ||
![]() |
ca61d55c03 | ||
![]() |
2b2aebffd9 | ||
![]() |
50f7ebf1b9 | ||
![]() |
7f5000f840 | ||
![]() |
2ee74041a0 | ||
![]() |
e2a06f8a73 | ||
![]() |
fce9382260 | ||
![]() |
a4c77ea51e | ||
![]() |
ad93c22fb8 | ||
![]() |
7ad05559b7 | ||
![]() |
4e538447eb | ||
![]() |
783d0add3a | ||
![]() |
d3c1f93215 | ||
![]() |
2787baa176 | ||
![]() |
416bfedc66 | ||
![]() |
a7a7fa5511 | ||
![]() |
535653fff5 | ||
![]() |
7088d78b93 | ||
![]() |
05da2056f9 | ||
![]() |
32a4c4a070 | ||
![]() |
67d71e6da4 | ||
![]() |
44f46a9a8a | ||
![]() |
208e7791f1 | ||
![]() |
fcef3c6d60 | ||
![]() |
564a0f41a7 | ||
![]() |
e9a32721af | ||
![]() |
eefc502d8c | ||
![]() |
a71b3442ae | ||
![]() |
494f0fb987 | ||
![]() |
5e7309986b | ||
![]() |
24899229b2 | ||
![]() |
5382315ca5 | ||
![]() |
a07b7b1f70 | ||
![]() |
4c1220df30 | ||
![]() |
8eb98ce914 | ||
![]() |
005655a461 | ||
![]() |
e1430cdf0d | ||
![]() |
a20594abfc | ||
![]() |
6263a39a91 | ||
![]() |
b0daab23bb | ||
![]() |
f74ffcee7a | ||
![]() |
17c974fbbd | ||
![]() |
d32d64040a | ||
![]() |
bdf62ab52e | ||
![]() |
345a995b51 | ||
![]() |
9fe2ad988b | ||
![]() |
9f3b941e8e | ||
![]() |
dbb8748190 | ||
![]() |
11d09d6033 | ||
![]() |
9aac463e94 | ||
![]() |
881fc4d39f | ||
![]() |
19e14a3d2a | ||
![]() |
60f1007915 | ||
![]() |
e9a1da5bb8 | ||
![]() |
847a74eac2 | ||
![]() |
275e848aa9 | ||
![]() |
5bade1b73e | ||
![]() |
d2a9894e64 | ||
![]() |
fe7773d915 | ||
![]() |
0e9de91280 | ||
![]() |
437234b2ac | ||
![]() |
7e41f75666 | ||
![]() |
6fd6313608 | ||
![]() |
120e767f51 | ||
![]() |
72c630af47 | ||
![]() |
0c0081581a | ||
![]() |
4a337beeb8 | ||
![]() |
03703a69c5 | ||
![]() |
442668d866 | ||
![]() |
42b3773919 | ||
![]() |
4670496695 | ||
![]() |
1b2a0e19ad | ||
![]() |
8b82bb5a7a | ||
![]() |
7099d42767 | ||
![]() |
1ea30ecb0a | ||
![]() |
8ad50df607 | ||
![]() |
0b28789e4f | ||
![]() |
f3065bcbfc | ||
![]() |
e5f41d9a31 | ||
![]() |
edac96a064 | ||
![]() |
70e6e515a2 | ||
![]() |
a8ac4f21ba | ||
![]() |
682fd99ebb | ||
![]() |
c20526aa8b | ||
![]() |
9633fa875c | ||
![]() |
08ec6e000e | ||
![]() |
be3e6ef7bd | ||
![]() |
c0402bd540 | ||
![]() |
a5357c7083 | ||
![]() |
1b0e9927a8 | ||
![]() |
a1037d8b0e | ||
![]() |
e2bea80d3f | ||
![]() |
3364e6e2cb | ||
![]() |
44f28ecf43 | ||
![]() |
957d4bc770 | ||
![]() |
a5f68a3626 | ||
![]() |
da5a4f3a0d | ||
![]() |
bb6ddcbeab | ||
![]() |
8f7e9aac5b | ||
![]() |
3908253fca | ||
![]() |
e32d9feaf5 | ||
![]() |
b2af22014d | ||
![]() |
dc8590a69a | ||
![]() |
e898216844 | ||
![]() |
3d9774a86a | ||
![]() |
72e0f48267 | ||
![]() |
733cbb4a27 | ||
![]() |
3555dccdfd | ||
![]() |
6a5d7afcbf | ||
![]() |
e90759d74b | ||
![]() |
fa6120c42f | ||
![]() |
7d046d404c | ||
![]() |
cd810c52e9 | ||
![]() |
4430d48452 | ||
![]() |
0d39406f60 | ||
![]() |
7949330071 | ||
![]() |
5b3c7cec88 | ||
![]() |
b8fcc27504 | ||
![]() |
cfcbef2ec1 | ||
![]() |
f4fb0b312d | ||
![]() |
9052509fd2 | ||
![]() |
64fa1e455e | ||
![]() |
4ffde928d4 | ||
![]() |
9406f7b085 | ||
![]() |
cee9d0422b | ||
![]() |
fe4dcf0b24 | ||
![]() |
477ca5e712 | ||
![]() |
6319c9b300 |
@@ -25,6 +25,4 @@ npm-debug.log.*
|
|||||||
|
|
||||||
# Webpack files
|
# Webpack files
|
||||||
webpack.records.json
|
webpack.records.json
|
||||||
|
package-lock.json
|
||||||
# Yarn no longer used
|
|
||||||
yarn.lock
|
|
||||||
|
@@ -15,10 +15,6 @@ trim_trailing_whitespace = false
|
|||||||
|
|
||||||
[*.ts]
|
[*.ts]
|
||||||
quote_type = single
|
quote_type = single
|
||||||
ij_typescript_enforce_trailing_comma = whenmultiline
|
|
||||||
|
|
||||||
[*.js]
|
|
||||||
ij_javascript_enforce_trailing_comma = whenmultiline
|
|
||||||
|
|
||||||
[*.json5]
|
[*.json5]
|
||||||
ij_json_keep_blank_lines_in_code = 3
|
ij_json_keep_blank_lines_in_code = 3
|
||||||
|
@@ -160,9 +160,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@angular-eslint/prefer-standalone": [
|
|
||||||
"error"
|
|
||||||
],
|
|
||||||
"@angular-eslint/no-attribute-decorator": "error",
|
"@angular-eslint/no-attribute-decorator": "error",
|
||||||
"@angular-eslint/no-output-native": "warn",
|
"@angular-eslint/no-output-native": "warn",
|
||||||
"@angular-eslint/no-output-on-prefix": "warn",
|
"@angular-eslint/no-output-on-prefix": "warn",
|
||||||
@@ -263,48 +260,9 @@
|
|||||||
"rxjs/no-nested-subscribe": "off", // todo: go over _all_ cases
|
"rxjs/no-nested-subscribe": "off", // todo: go over _all_ cases
|
||||||
|
|
||||||
// Custom DSpace Angular rules
|
// 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-classes": "error",
|
||||||
"dspace-angular-ts/themed-component-selectors": "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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -335,8 +293,7 @@
|
|||||||
"rules": {
|
"rules": {
|
||||||
// Custom DSpace Angular rules
|
// Custom DSpace Angular rules
|
||||||
"dspace-angular-html/themed-component-usages": "error",
|
"dspace-angular-html/themed-component-usages": "error",
|
||||||
"dspace-angular-html/no-disabled-attribute-on-button": "error",
|
"dspace-angular-html/no-disabled-attribute-on-button": "error"
|
||||||
"@angular-eslint/template/prefer-control-flow": "error"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
8
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -7,16 +7,16 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Describe the bug
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is. Include the version(s) of DSpace where you've seen this problem & what *web browser* you were using. Link to examples if they are public.
|
A clear and concise description of what the bug is. Include the version(s) of DSpace where you've seen this problem & what *web browser* you were using. Link to examples if they are public.
|
||||||
|
|
||||||
## To Reproduce
|
**To Reproduce**
|
||||||
Steps to reproduce the behavior:
|
Steps to reproduce the behavior:
|
||||||
1. Do this
|
1. Do this
|
||||||
2. Then this...
|
2. Then this...
|
||||||
|
|
||||||
## Expected behavior
|
**Expected behavior**
|
||||||
A clear and concise description of what you expected to happen.
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
## Related work
|
**Related work**
|
||||||
Link to any related tickets or PRs here.
|
Link to any related tickets or PRs here.
|
||||||
|
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -7,14 +7,14 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Is your feature request related to a problem? Please describe.
|
**Is your feature request related to a problem? Please describe.**
|
||||||
A clear and concise description of what the problem or use case is. For example, I'm always frustrated when [...]
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
## Describe the solution you'd like
|
**Describe the solution you'd like**
|
||||||
A clear and concise description of what you want to happen.
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
## Describe alternatives or workarounds you've considered
|
**Describe alternatives or workarounds you've considered**
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
## Additional information
|
**Additional context**
|
||||||
Add any other information, related tickets or screenshots about the feature request here.
|
Add any other context or screenshots about the feature request here.
|
||||||
|
298
.github/dependabot.yml
vendored
298
.github/dependabot.yml
vendored
@@ -1,298 +0,0 @@
|
|||||||
#-------------------
|
|
||||||
# DSpace's dependabot rules. Enables npm updates for all dependencies on a weekly basis
|
|
||||||
# for main and any maintenance branches. Security updates only apply to main.
|
|
||||||
#-------------------
|
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
###############
|
|
||||||
## Main branch
|
|
||||||
###############
|
|
||||||
# NOTE: At this time, "security-updates" rules only apply if "target-branch" is unspecified
|
|
||||||
# So, only this first section can include "applies-to: security-updates"
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
# Allow up to 10 open PRs for dependencies
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
# Group together Angular package upgrades
|
|
||||||
groups:
|
|
||||||
# Group together all minor/patch version updates for Angular in a single PR
|
|
||||||
angular:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "@angular*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together all security updates for Angular. Only accept minor/patch types.
|
|
||||||
angular-security:
|
|
||||||
applies-to: security-updates
|
|
||||||
patterns:
|
|
||||||
- "@angular*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together all minor/patch version updates for NgRx in a single PR
|
|
||||||
ngrx:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "@ngrx*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together all security updates for NgRx. Only accept minor/patch types.
|
|
||||||
ngrx-security:
|
|
||||||
applies-to: security-updates
|
|
||||||
patterns:
|
|
||||||
- "@ngrx*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together all patch version updates for eslint in a single PR
|
|
||||||
eslint:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "@typescript-eslint*"
|
|
||||||
- "eslint*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together all security updates for eslint.
|
|
||||||
eslint-security:
|
|
||||||
applies-to: security-updates
|
|
||||||
patterns:
|
|
||||||
- "@typescript-eslint*"
|
|
||||||
- "eslint*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any testing related version updates
|
|
||||||
testing:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "@cypress*"
|
|
||||||
- "axe-*"
|
|
||||||
- "cypress*"
|
|
||||||
- "jasmine*"
|
|
||||||
- "karma*"
|
|
||||||
- "ng-mocks"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any testing related security updates
|
|
||||||
testing-security:
|
|
||||||
applies-to: security-updates
|
|
||||||
patterns:
|
|
||||||
- "@cypress*"
|
|
||||||
- "axe-*"
|
|
||||||
- "cypress*"
|
|
||||||
- "jasmine*"
|
|
||||||
- "karma*"
|
|
||||||
- "ng-mocks"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any postcss related version updates
|
|
||||||
postcss:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "postcss*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any postcss related security updates
|
|
||||||
postcss-security:
|
|
||||||
applies-to: security-updates
|
|
||||||
patterns:
|
|
||||||
- "postcss*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any sass related version updates
|
|
||||||
sass:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "sass*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any sass related security updates
|
|
||||||
sass-security:
|
|
||||||
applies-to: security-updates
|
|
||||||
patterns:
|
|
||||||
- "sass*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any webpack related version updates
|
|
||||||
webpack:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "webpack*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any webpack related seurity updates
|
|
||||||
webpack-security:
|
|
||||||
applies-to: security-updates
|
|
||||||
patterns:
|
|
||||||
- "webpack*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
ignore:
|
|
||||||
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
|
|
||||||
- dependency-name: "*"
|
|
||||||
update-types: ["version-update:semver-major"]
|
|
||||||
#####################
|
|
||||||
## dspace-8_x branch
|
|
||||||
#####################
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/"
|
|
||||||
target-branch: dspace-8_x
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
# Allow up to 10 open PRs for dependencies
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
# Group together Angular package upgrades
|
|
||||||
groups:
|
|
||||||
# Group together all patch version updates for Angular in a single PR
|
|
||||||
angular:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "@angular*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together all minor/patch version updates for NgRx in a single PR
|
|
||||||
ngrx:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "@ngrx*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together all patch version updates for eslint in a single PR
|
|
||||||
eslint:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "@typescript-eslint*"
|
|
||||||
- "eslint*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any testing related version updates
|
|
||||||
testing:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "@cypress*"
|
|
||||||
- "axe-*"
|
|
||||||
- "cypress*"
|
|
||||||
- "jasmine*"
|
|
||||||
- "karma*"
|
|
||||||
- "ng-mocks"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any postcss related version updates
|
|
||||||
postcss:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "postcss*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any sass related version updates
|
|
||||||
sass:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "sass*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any webpack related version updates
|
|
||||||
webpack:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "webpack*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
ignore:
|
|
||||||
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
|
|
||||||
- dependency-name: "*"
|
|
||||||
update-types: ["version-update:semver-major"]
|
|
||||||
#####################
|
|
||||||
## dspace-7_x branch
|
|
||||||
#####################
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/"
|
|
||||||
target-branch: dspace-7_x
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
# Allow up to 10 open PRs for dependencies
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
# Group together Angular package upgrades
|
|
||||||
groups:
|
|
||||||
# Group together all minor/patch version updates for Angular in a single PR
|
|
||||||
angular:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "@angular*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together all minor/patch version updates for NgRx in a single PR
|
|
||||||
ngrx:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "@ngrx*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together all patch version updates for eslint in a single PR
|
|
||||||
eslint:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "@typescript-eslint*"
|
|
||||||
- "eslint*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any testing related version updates
|
|
||||||
testing:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "@cypress*"
|
|
||||||
- "axe-*"
|
|
||||||
- "cypress*"
|
|
||||||
- "jasmine*"
|
|
||||||
- "karma*"
|
|
||||||
- "ng-mocks"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any postcss related version updates
|
|
||||||
postcss:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "postcss*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
# Group together any sass related version updates
|
|
||||||
sass:
|
|
||||||
applies-to: version-updates
|
|
||||||
patterns:
|
|
||||||
- "sass*"
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
ignore:
|
|
||||||
# 7.x Cannot update Webpack past v5.76.1 as later versions not supported by Angular 15
|
|
||||||
# See also https://github.com/DSpace/dspace-angular/pull/3283#issuecomment-2372488489
|
|
||||||
- dependency-name: "webpack"
|
|
||||||
# Ignore all major version updates for all dependencies. We'll only automate minor/patch updates.
|
|
||||||
- dependency-name: "*"
|
|
||||||
update-types: ["version-update:semver-major"]
|
|
21
.github/pull_request_template.md
vendored
21
.github/pull_request_template.md
vendored
@@ -1,7 +1,7 @@
|
|||||||
## References
|
## References
|
||||||
_Add references/links to any related issues or PRs. These may include:_
|
_Add references/links to any related issues or PRs. These may include:_
|
||||||
* Fixes #issue-number (if this fixes an issue ticket)
|
* Fixes #`issue-number` (if this fixes an issue ticket)
|
||||||
* Requires DSpace/DSpace#pr-number (if a REST API PR is required to test this)
|
* Requires DSpace/DSpace#`pr-number` (if a REST API PR is required to test this)
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
Short summary of changes (1-2 sentences).
|
Short summary of changes (1-2 sentences).
|
||||||
@@ -16,18 +16,13 @@ List of changes in this PR:
|
|||||||
**Include guidance for how to test or review your PR.** This may include: steps to reproduce a bug, screenshots or description of a new feature, or reasons behind specific changes.
|
**Include guidance for how to test or review your PR.** This may include: steps to reproduce a bug, screenshots or description of a new feature, or reasons behind specific changes.
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
_This checklist provides a reminder of what we are going to look for when reviewing your PR. You do not need to complete this checklist prior creating your PR (draft PRs are always welcome).
|
_This checklist provides a reminder of what we are going to look for when reviewing your PR. You need not complete this checklist prior to creating your PR (draft PRs are always welcome). If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!_
|
||||||
However, reviewers may request that you complete any actions in this list if you have not done so. If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!_
|
|
||||||
|
|
||||||
- [ ] My PR is **created against the `main` branch** of code (unless it is a backport or is fixing an issue specific to an older branch).
|
- [ ] My PR is small in size (e.g. less than 1,000 lines of code, not including comments & specs/tests), or I have provided reasons as to why that's not possible.
|
||||||
- [ ] My PR is **small in size** (e.g. less than 1,000 lines of code, not including comments & specs/tests), or I have provided reasons as to why that's not possible.
|
- [ ] My PR passes [ESLint](https://eslint.org/) validation using `yarn lint`
|
||||||
- [ ] My PR **passes [ESLint](https://eslint.org/)** validation using `npm run lint`
|
- [ ] My PR doesn't introduce circular dependencies (verified via `yarn check-circ-deps`)
|
||||||
- [ ] My PR **doesn't introduce circular dependencies** (verified via `npm run check-circ-deps`)
|
- [ ] My PR includes [TypeDoc](https://typedoc.org/) comments for _all new (or modified) public methods and classes_. It also includes TypeDoc for large or complex private methods.
|
||||||
- [ ] My PR **includes [TypeDoc](https://typedoc.org/) comments** for _all new (or modified) public methods and classes_. It also includes TypeDoc for large or complex private methods.
|
- [ ] My PR passes all specs/tests and includes new/updated specs or tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
|
||||||
- [ ] My PR **passes all specs/tests and includes new/updated specs or tests** based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
|
|
||||||
- [ ] My PR **aligns with [Accessibility guidelines](https://wiki.lyrasis.org/display/DSDOC8x/Accessibility)** if it makes changes to the user interface.
|
|
||||||
- [ ] My PR **uses i18n (internationalization) keys** instead of hardcoded English text, to allow for translations.
|
|
||||||
- [ ] My PR **includes details on how to test it**. I've provided clear instructions to reviewers on how to successfully test this fix or feature.
|
|
||||||
- [ ] If my PR includes new libraries/dependencies (in `package.json`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
- [ ] If my PR includes new libraries/dependencies (in `package.json`), I've made sure their licenses align with the [DSpace BSD License](https://github.com/DSpace/DSpace/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
||||||
- [ ] If my PR includes new features or configurations, I've provided basic technical documentation in the PR itself.
|
- [ ] If my PR includes new features or configurations, I've provided basic technical documentation in the PR itself.
|
||||||
- [ ] If my PR fixes an issue ticket, I've [linked them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
|
- [ ] If my PR fixes an issue ticket, I've [linked them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
|
||||||
|
40
.github/workflows/build.yml
vendored
40
.github/workflows/build.yml
vendored
@@ -75,39 +75,39 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
google-chrome --version
|
google-chrome --version
|
||||||
|
|
||||||
# https://github.com/actions/cache/blob/main/examples.md#node---npm
|
# https://github.com/actions/cache/blob/main/examples.md#node---yarn
|
||||||
- name: Get NPM cache directory
|
- name: Get Yarn cache directory
|
||||||
id: npm-cache-dir
|
id: yarn-cache-dir-path
|
||||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||||
- name: Cache NPM dependencies
|
- name: Cache Yarn dependencies
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
# Cache entire NPM cache directory (see previous step)
|
# Cache entire Yarn cache directory (see previous step)
|
||||||
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
# Cache key is hash of package-lock.json. Therefore changes to package-lock.json will invalidate cache
|
# Cache key is hash of yarn.lock. Therefore changes to yarn.lock will invalidate cache
|
||||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
restore-keys: ${{ runner.os }}-npm-
|
restore-keys: ${{ runner.os }}-yarn-
|
||||||
|
|
||||||
- name: Install NPM dependencies
|
- name: Install Yarn dependencies
|
||||||
run: npm clean-install
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build lint plugins
|
- name: Build lint plugins
|
||||||
run: npm run build:lint
|
run: yarn run build:lint
|
||||||
|
|
||||||
- name: Run lint plugin tests
|
- name: Run lint plugin tests
|
||||||
run: npm run test:lint:nobuild
|
run: yarn run test:lint:nobuild
|
||||||
|
|
||||||
- name: Run lint
|
- name: Run lint
|
||||||
run: npm run lint:nobuild -- --quiet
|
run: yarn run lint:nobuild --quiet
|
||||||
|
|
||||||
- name: Check for circular dependencies
|
- name: Check for circular dependencies
|
||||||
run: npm run check-circ-deps
|
run: yarn run check-circ-deps
|
||||||
|
|
||||||
- name: Run build
|
- name: Run build
|
||||||
run: npm run build:prod
|
run: yarn run build:prod
|
||||||
|
|
||||||
- name: Run specs (unit tests)
|
- name: Run specs (unit tests)
|
||||||
run: npm run test:headless
|
run: yarn run test:headless
|
||||||
|
|
||||||
# Upload code coverage report to artifact (for one version of Node only),
|
# Upload code coverage report to artifact (for one version of Node only),
|
||||||
# so that it can be shared with the 'codecov' job (see below)
|
# so that it can be shared with the 'codecov' job (see below)
|
||||||
@@ -145,7 +145,7 @@ jobs:
|
|||||||
# Run tests in Chrome, headless mode (default)
|
# Run tests in Chrome, headless mode (default)
|
||||||
browser: chrome
|
browser: chrome
|
||||||
# Start app before running tests (will be stopped automatically after tests finish)
|
# Start app before running tests (will be stopped automatically after tests finish)
|
||||||
start: npm run serve:ssr
|
start: yarn run serve:ssr
|
||||||
# Wait for backend & frontend to be available
|
# Wait for backend & frontend to be available
|
||||||
# NOTE: We use the 'sites' REST endpoint to also ensure the database is ready
|
# NOTE: We use the 'sites' REST endpoint to also ensure the database is ready
|
||||||
wait-on: http://127.0.0.1:8080/server/api/core/sites, http://127.0.0.1:4000
|
wait-on: http://127.0.0.1:8080/server/api/core/sites, http://127.0.0.1:4000
|
||||||
@@ -181,7 +181,7 @@ jobs:
|
|||||||
# Start up the app with SSR enabled (run in background)
|
# Start up the app with SSR enabled (run in background)
|
||||||
- name: Start app in SSR (server-side rendering) mode
|
- name: Start app in SSR (server-side rendering) mode
|
||||||
run: |
|
run: |
|
||||||
nohup npm run serve:ssr &
|
nohup yarn run serve:ssr &
|
||||||
printf 'Waiting for app to start'
|
printf 'Waiting for app to start'
|
||||||
until curl --output /dev/null --silent --head --fail http://127.0.0.1:4000/home; do
|
until curl --output /dev/null --silent --head --fail http://127.0.0.1:4000/home; do
|
||||||
printf '.'
|
printf '.'
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -28,12 +28,12 @@ webpack.records.json
|
|||||||
|
|
||||||
morgan.log
|
morgan.log
|
||||||
|
|
||||||
# Yarn no longer used
|
|
||||||
yarn.lock
|
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
|
||||||
*.css
|
*.css
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
.java-version
|
.java-version
|
||||||
|
|
||||||
.env
|
.env
|
||||||
|
@@ -10,7 +10,7 @@ DSpace is a community built and supported project. We do not have a centralized
|
|||||||
## Contribute new code via a Pull Request
|
## Contribute new code via a Pull Request
|
||||||
|
|
||||||
We accept [GitHub Pull Requests (PRs)](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) at any time from anyone.
|
We accept [GitHub Pull Requests (PRs)](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) at any time from anyone.
|
||||||
Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC8x/Release+Notes).
|
Contributors to each release are recognized in our [Release Notes](https://wiki.lyrasis.org/display/DSDOC7x/Release+Notes).
|
||||||
|
|
||||||
Code Contribution Checklist
|
Code Contribution Checklist
|
||||||
- [ ] PRs _should_ be smaller in size (ideally less than 1,000 lines of code, not including comments & tests)
|
- [ ] PRs _should_ be smaller in size (ideally less than 1,000 lines of code, not including comments & tests)
|
||||||
@@ -18,9 +18,6 @@ Code Contribution Checklist
|
|||||||
- [ ] PRs **must** not introduce circular dependencies (verified via `yarn check-circ-deps`)
|
- [ ] PRs **must** not introduce circular dependencies (verified via `yarn check-circ-deps`)
|
||||||
- [ ] PRs **must** include [TypeDoc](https://typedoc.org/) comments for _all new (or modified) public methods and classes_. Large or complex private methods should also have TypeDoc.
|
- [ ] PRs **must** include [TypeDoc](https://typedoc.org/) comments for _all new (or modified) public methods and classes_. Large or complex private methods should also have TypeDoc.
|
||||||
- [ ] PRs **must** pass all automated pecs/tests and includes new/updated specs or tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
|
- [ ] PRs **must** pass all automated pecs/tests and includes new/updated specs or tests based on the [Code Testing Guide](https://wiki.lyrasis.org/display/DSPACE/Code+Testing+Guide).
|
||||||
- [ ] User interface changes **must** align with [Accessibility guidelines](https://wiki.lyrasis.org/display/DSDOC8x/Accessibility)
|
|
||||||
- [ ] PRs **must** use i18n (internationalization) keys instead of hardcoded English text, to allow for translations.
|
|
||||||
- [ ] Details on how to test the PR **must** be provided. Reviewers must be aware of any steps they need to take to successfully test your fix or feature.
|
|
||||||
- [ ] If a PR includes new libraries/dependencies (in `package.json`), then their software licenses **must** align with the [DSpace BSD License](https://github.com/DSpace/dspace-angular/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
- [ ] If a PR includes new libraries/dependencies (in `package.json`), then their software licenses **must** align with the [DSpace BSD License](https://github.com/DSpace/dspace-angular/blob/main/LICENSE) based on the [Licensing of Contributions](https://wiki.lyrasis.org/display/DSPACE/Code+Contribution+Guidelines#CodeContributionGuidelines-LicensingofContributions) documentation.
|
||||||
- [ ] Basic technical documentation _should_ be provided for any new features or configuration, either in the PR itself or in the DSpace Wiki documentation.
|
- [ ] Basic technical documentation _should_ be provided for any new features or configuration, either in the PR itself or in the DSpace Wiki documentation.
|
||||||
- [ ] If a PR fixes an issue ticket, please [link them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
|
- [ ] If a PR fixes an issue ticket, please [link them together](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
|
||||||
@@ -29,7 +26,7 @@ Additional details on the code contribution process can be found in our [Code Co
|
|||||||
|
|
||||||
## Contribute documentation
|
## Contribute documentation
|
||||||
|
|
||||||
DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC
|
DSpace Documentation is a collaborative effort in a shared Wiki. The latest documentation is at https://wiki.lyrasis.org/display/DSDOC7x
|
||||||
|
|
||||||
If you find areas of the DSpace Documentation which you wish to improve, please request a Wiki account by emailing wikihelp@lyrasis.org.
|
If you find areas of the DSpace Documentation which you wish to improve, please request a Wiki account by emailing wikihelp@lyrasis.org.
|
||||||
Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) or email) for access to edit our Documentation.
|
Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) or email) for access to edit our Documentation.
|
||||||
@@ -37,7 +34,7 @@ Once you have an account setup, contact @tdonohue (via [Slack](https://wiki.lyra
|
|||||||
## Help others on mailing lists or Slack
|
## Help others on mailing lists or Slack
|
||||||
|
|
||||||
DSpace has our own [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) community and [Mailing Lists](https://wiki.lyrasis.org/display/DSPACE/Mailing+Lists) where discussions take place and questions are answered.
|
DSpace has our own [Slack](https://wiki.lyrasis.org/display/DSPACE/Slack) community and [Mailing Lists](https://wiki.lyrasis.org/display/DSPACE/Mailing+Lists) where discussions take place and questions are answered.
|
||||||
Anyone is welcome to join and help others. We just ask you to follow our [Code of Conduct](https://www.lyrasis.org/about/Pages/Code-of-Conduct.aspx) (adopted via Lyrasis).
|
Anyone is welcome to join and help others. We just ask you to follow our [Code of Conduct](https://www.lyrasis.org/about/Pages/Code-of-Conduct.aspx) (adopted via LYRASIS).
|
||||||
|
|
||||||
## Join a working or interest group
|
## Join a working or interest group
|
||||||
|
|
||||||
@@ -45,5 +42,5 @@ Most of the work in building/improving DSpace comes via [Working Groups](https:/
|
|||||||
|
|
||||||
All working/interest groups are open to anyone to join and participate. A few key groups to be aware of include:
|
All working/interest groups are open to anyone to join and participate. A few key groups to be aware of include:
|
||||||
|
|
||||||
* [DSpace Developer Team](https://wiki.lyrasis.org/display/DSPACE/Developer+Meetings) - This is the primary, volunteer development team. We meet weekly to review our current development [project board](https://github.com/orgs/DSpace/projects), assigning tickets and/or PRs. This is also were discussions of the next release or major issues occur. Anyone is welcome to attend.
|
* [DSpace 7 Working Group](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+Working+Group) - This is the main (mostly volunteer) development team. We meet weekly to review our current development [project board](https://github.com/orgs/DSpace/projects), assigning tickets and/or PRs.
|
||||||
* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team) - This is an interest group for repository managers/administrators. We meet monthly to discuss DSpace, share tips & provide feedback back to developers. Anyone is welcome to attend.
|
* [DSpace Community Advisory Team (DCAT)](https://wiki.lyrasis.org/display/cmtygp/DSpace+Community+Advisory+Team) - This is an interest group for repository managers/administrators. We meet monthly to discuss DSpace, share tips & provide feedback back to developers.
|
@@ -11,7 +11,9 @@ WORKDIR /app
|
|||||||
ADD . /app/
|
ADD . /app/
|
||||||
EXPOSE 4000
|
EXPOSE 4000
|
||||||
|
|
||||||
RUN npm install
|
# We run yarn install with an increased network timeout (5min) to avoid "ESOCKETTIMEDOUT" errors from hub.docker.com
|
||||||
|
# See, for example https://github.com/yarnpkg/yarn/issues/5540
|
||||||
|
RUN yarn install --network-timeout 300000
|
||||||
|
|
||||||
# When running in dev mode, 4GB of memory is required to build & launch the app.
|
# When running in dev mode, 4GB of memory is required to build & launch the app.
|
||||||
# This default setting can be overridden as needed in your shell, via an env file or in docker-compose.
|
# This default setting can be overridden as needed in your shell, via an env file or in docker-compose.
|
||||||
@@ -23,4 +25,4 @@ ENV NODE_OPTIONS="--max_old_space_size=4096"
|
|||||||
# NOTE: At this time it is only possible to run Docker container in Production mode
|
# NOTE: At this time it is only possible to run Docker container in Production mode
|
||||||
# if you have a public URL. See https://github.com/DSpace/dspace-angular/issues/1485
|
# if you have a public URL. See https://github.com/DSpace/dspace-angular/issues/1485
|
||||||
ENV NODE_ENV=development
|
ENV NODE_ENV=development
|
||||||
CMD npm run serve -- --host 0.0.0.0
|
CMD yarn serve --host 0.0.0.0
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
# See https://github.com/DSpace/dspace-angular/tree/main/docker for usage details
|
# See https://github.com/DSpace/dspace-angular/tree/main/docker for usage details
|
||||||
|
|
||||||
# Test build:
|
# Test build:
|
||||||
# docker build -f Dockerfile.dist -t dspace/dspace-angular:latest-dist .
|
# docker build -f Dockerfile.dist -t dspace/dspace-angular:dspace-8_x-dist .
|
||||||
|
|
||||||
FROM docker.io/node:18-alpine AS build
|
FROM docker.io/node:18-alpine AS build
|
||||||
|
|
||||||
@@ -11,11 +11,11 @@ FROM docker.io/node:18-alpine AS build
|
|||||||
RUN apk add --update python3 make g++ && rm -rf /var/cache/apk/*
|
RUN apk add --update python3 make g++ && rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json yarn.lock ./
|
||||||
RUN npm install
|
RUN yarn install --network-timeout 300000
|
||||||
|
|
||||||
ADD . /app/
|
ADD . /app/
|
||||||
RUN npm run build:prod
|
RUN yarn build:prod
|
||||||
|
|
||||||
FROM node:18-alpine
|
FROM node:18-alpine
|
||||||
RUN npm install --global pm2
|
RUN npm install --global pm2
|
||||||
|
86
README.md
86
README.md
@@ -35,7 +35,7 @@ https://wiki.lyrasis.org/display/DSDOC7x/Installing+DSpace
|
|||||||
Quick start
|
Quick start
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
**Ensure you're running [Node](https://nodejs.org) `v18.x` or `v20.x`, [npm](https://www.npmjs.com/) >= `v10.x`**
|
**Ensure you're running [Node](https://nodejs.org) `v18.x` or `v20.x`, [npm](https://www.npmjs.com/) >= `v10.x` and [yarn](https://yarnpkg.com) == `v1.x`**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# clone the repo
|
# clone the repo
|
||||||
@@ -45,10 +45,10 @@ git clone https://github.com/DSpace/dspace-angular.git
|
|||||||
cd dspace-angular
|
cd dspace-angular
|
||||||
|
|
||||||
# install the local dependencies
|
# install the local dependencies
|
||||||
npm install
|
yarn install
|
||||||
|
|
||||||
# start the server
|
# start the server
|
||||||
npm start
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
Then go to [http://localhost:4000](http://localhost:4000) in your browser
|
Then go to [http://localhost:4000](http://localhost:4000) in your browser
|
||||||
@@ -77,7 +77,7 @@ Table of Contents
|
|||||||
- [Recommended Editors/IDEs](#recommended-editorsides)
|
- [Recommended Editors/IDEs](#recommended-editorsides)
|
||||||
- [Collaborating](#collaborating)
|
- [Collaborating](#collaborating)
|
||||||
- [File Structure](#file-structure)
|
- [File Structure](#file-structure)
|
||||||
- [Managing Dependencies (via npm)](#managing-dependencies-via-npm)
|
- [Managing Dependencies (via yarn)](#managing-dependencies-via-yarn)
|
||||||
- [Frequently asked questions](#frequently-asked-questions)
|
- [Frequently asked questions](#frequently-asked-questions)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
@@ -89,15 +89,15 @@ You can find more information on the technologies used in this project (Angular.
|
|||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org)
|
- [Node.js](https://nodejs.org) and [yarn](https://yarnpkg.com)
|
||||||
- Ensure you're running node `v18.x` or `v20.x`
|
- Ensure you're running node `v18.x` or `v20.x` and yarn == `v1.x`
|
||||||
|
|
||||||
If you have [`nvm`](https://github.com/creationix/nvm#install-script) or [`nvm-windows`](https://github.com/coreybutler/nvm-windows) installed, which is highly recommended, you can run `nvm install --lts && nvm use` to install and start using the latest Node LTS.
|
If you have [`nvm`](https://github.com/creationix/nvm#install-script) or [`nvm-windows`](https://github.com/coreybutler/nvm-windows) installed, which is highly recommended, you can run `nvm install --lts && nvm use` to install and start using the latest Node LTS.
|
||||||
|
|
||||||
Installing
|
Installing
|
||||||
----------
|
----------
|
||||||
|
|
||||||
- `npm install` to install the local dependencies
|
- `yarn install` to install the local dependencies
|
||||||
|
|
||||||
### Configuring
|
### Configuring
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ import { environment } from '../environment.ts';
|
|||||||
Running the app
|
Running the app
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
After you have installed all dependencies you can now run the app. Run `npm run start:dev` to start a local server which will watch for changes, rebuild the code, and reload the server for you. You can visit it at `http://localhost:4000`.
|
After you have installed all dependencies you can now run the app. Run `yarn run start:dev` to start a local server which will watch for changes, rebuild the code, and reload the server for you. You can visit it at `http://localhost:4000`.
|
||||||
|
|
||||||
### Running in production mode
|
### Running in production mode
|
||||||
|
|
||||||
@@ -211,20 +211,20 @@ When building for production we're using Ahead of Time (AoT) compilation. With A
|
|||||||
To build the app for production and start the server (in one command) run:
|
To build the app for production and start the server (in one command) run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm start
|
yarn start
|
||||||
```
|
```
|
||||||
This will run the application in an instance of the Express server, which is included.
|
This will run the application in an instance of the Express server, which is included.
|
||||||
|
|
||||||
If you only want to build for production, without starting, run:
|
If you only want to build for production, without starting, run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build:prod
|
yarn run build:prod
|
||||||
```
|
```
|
||||||
This will build the application and put the result in the `dist` folder. You can copy this folder to wherever you need it for your application server. If you will be using the built-in Express server, you'll also need a copy of the `node_modules` folder tucked inside your copy of `dist`.
|
This will build the application and put the result in the `dist` folder. You can copy this folder to wherever you need it for your application server. If you will be using the built-in Express server, you'll also need a copy of the `node_modules` folder tucked inside your copy of `dist`.
|
||||||
|
|
||||||
After building the app for production, it can be started by running:
|
After building the app for production, it can be started by running:
|
||||||
```bash
|
```bash
|
||||||
npm run serve:ssr
|
yarn run serve:ssr
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running the application with Docker
|
### Running the application with Docker
|
||||||
@@ -238,14 +238,14 @@ Cleaning
|
|||||||
--------
|
--------
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# clean everything, including node_modules. You'll need to run npm install again afterwards.
|
# clean everything, including node_modules. You'll need to run yarn install again afterwards.
|
||||||
npm run clean
|
yarn run clean
|
||||||
|
|
||||||
# clean files generated by the production build (.ngfactory files, css files, etc)
|
# clean files generated by the production build (.ngfactory files, css files, etc)
|
||||||
npm run clean:prod
|
yarn run clean:prod
|
||||||
|
|
||||||
# cleans the distribution directory
|
# cleans the distribution directory
|
||||||
npm run clean:dist
|
yarn run clean:dist
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -259,9 +259,9 @@ If you would like to contribute by testing a Pull Request (PR), here's how to do
|
|||||||
1. Pull down the branch that the Pull Request was built from. Easy instructions for doing so can be found on the Pull Request itself.
|
1. Pull down the branch that the Pull Request was built from. Easy instructions for doing so can be found on the Pull Request itself.
|
||||||
* Next to the "Merge" button, you'll see a link that says "command line instructions".
|
* Next to the "Merge" button, you'll see a link that says "command line instructions".
|
||||||
* Click it, and follow "Step 1" of those instructions to checkout the pull down the PR branch.
|
* Click it, and follow "Step 1" of those instructions to checkout the pull down the PR branch.
|
||||||
2. `npm run clean` (This resets your local dependencies to ensure you are up-to-date with this PR)
|
2. `yarn run clean` (This resets your local dependencies to ensure you are up-to-date with this PR)
|
||||||
3. `npm install` (Updates your local dependencies to those in the PR)
|
3. `yarn install` (Updates your local dependencies to those in the PR)
|
||||||
4. `npm start` (Rebuilds the project, and deploys to localhost:4000, by default)
|
4. `yarn start` (Rebuilds the project, and deploys to localhost:4000, by default)
|
||||||
5. At this point, the code from the PR will be deployed to http://localhost:4000. Test it out, and ensure that it does what is described in the PR (or fixes the bug described in the ticket linked to the PR).
|
5. At this point, the code from the PR will be deployed to http://localhost:4000. Test it out, and ensure that it does what is described in the PR (or fixes the bug described in the ticket linked to the PR).
|
||||||
|
|
||||||
Once you have tested the Pull Request, please add a comment and/or approval to the PR to let us know whether you found it to be successful (or not). Thanks!
|
Once you have tested the Pull Request, please add a comment and/or approval to the PR to let us know whether you found it to be successful (or not). Thanks!
|
||||||
@@ -271,13 +271,13 @@ Once you have tested the Pull Request, please add a comment and/or approval to t
|
|||||||
|
|
||||||
Unit tests use the [Jasmine test framework](https://jasmine.github.io/), and are run via [Karma](https://karma-runner.github.io/).
|
Unit tests use the [Jasmine test framework](https://jasmine.github.io/), and are run via [Karma](https://karma-runner.github.io/).
|
||||||
|
|
||||||
You can find the Karma configuration file at the same level of this README file:`./karma.conf.js` If you are going to use a remote test environment you need to edit the `./karma.conf.js`. Follow the instructions you will find inside it. To executing tests whenever any file changes you can modify the 'autoWatch' option to 'true' and 'singleRun' option to 'false'. A coverage report is also available at: http://localhost:9876/ after you run: `npm run coverage`.
|
You can find the Karma configuration file at the same level of this README file:`./karma.conf.js` If you are going to use a remote test environment you need to edit the `./karma.conf.js`. Follow the instructions you will find inside it. To executing tests whenever any file changes you can modify the 'autoWatch' option to 'true' and 'singleRun' option to 'false'. A coverage report is also available at: http://localhost:9876/ after you run: `yarn run coverage`.
|
||||||
|
|
||||||
The default browser is Google Chrome.
|
The default browser is Google Chrome.
|
||||||
|
|
||||||
Place your tests in the same location of the application source code files that they test, e.g. ending with `*.component.spec.ts`
|
Place your tests in the same location of the application source code files that they test, e.g. ending with `*.component.spec.ts`
|
||||||
|
|
||||||
and run: `npm test`
|
and run: `yarn test`
|
||||||
|
|
||||||
If you run into odd test errors, see the Angular guide to debugging tests: https://angular.io/guide/test-debugging
|
If you run into odd test errors, see the Angular guide to debugging tests: https://angular.io/guide/test-debugging
|
||||||
|
|
||||||
@@ -330,9 +330,9 @@ All E2E tests must be created under the `./cypress/integration/` folder, and mus
|
|||||||
* In the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner), you'll Cypress automatically visit the page. This first test will succeed, as all you are doing is making sure the _page exists_.
|
* In the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner), you'll Cypress automatically visit the page. This first test will succeed, as all you are doing is making sure the _page exists_.
|
||||||
* From here, you can use the [Selector Playground](https://docs.cypress.io/guides/core-concepts/test-runner#Selector-Playground) in the Cypress Test Runner window to determine how to tell Cypress to interact with a specific HTML element on that page.
|
* From here, you can use the [Selector Playground](https://docs.cypress.io/guides/core-concepts/test-runner#Selector-Playground) in the Cypress Test Runner window to determine how to tell Cypress to interact with a specific HTML element on that page.
|
||||||
* Most commands start by telling Cypress to [get()](https://docs.cypress.io/api/commands/get) a specific element, using a CSS or jQuery style selector
|
* Most commands start by telling Cypress to [get()](https://docs.cypress.io/api/commands/get) a specific element, using a CSS or jQuery style selector
|
||||||
* It's generally best not to rely on attributes like `class` and `id` in tests, as those are likely to change later on. Instead, you can add a `data-test` attribute to makes it clear that it's required for a test.
|
* It's generally best not to rely on attributes like `class` and `id` in tests, as those are likely to change later on. Instead, you can add a `data-test` attribute to makes it clear that it's required for a test.
|
||||||
* Cypress can then do actions like [click()](https://docs.cypress.io/api/commands/click) an element, or [type()](https://docs.cypress.io/api/commands/type) text in an input field, etc.
|
* Cypress can then do actions like [click()](https://docs.cypress.io/api/commands/click) an element, or [type()](https://docs.cypress.io/api/commands/type) text in an input field, etc.
|
||||||
* When running with server-side rendering enabled, the client first receives HTML without the JS; only once the page is rendered client-side do some elements (e.g. a button that toggles a Bootstrap dropdown) become fully interactive. This can trip up Cypress in some cases as it may try to `click` or `type` in an element that's not fully loaded yet, causing tests to fail.
|
* When running with server-side rendering enabled, the client first receives HTML without the JS; only once the page is rendered client-side do some elements (e.g. a button that toggles a Bootstrap dropdown) become fully interactive. This can trip up Cypress in some cases as it may try to `click` or `type` in an element that's not fully loaded yet, causing tests to fail.
|
||||||
* To work around this issue, define the attributes you use for Cypress selectors as `[attr.data-test]="'button' | ngBrowserOnly"`. This will only show the attribute in CSR HTML, forcing Cypress to wait until CSR is complete before interacting with the element.
|
* To work around this issue, define the attributes you use for Cypress selectors as `[attr.data-test]="'button' | ngBrowserOnly"`. This will only show the attribute in CSR HTML, forcing Cypress to wait until CSR is complete before interacting with the element.
|
||||||
* Cypress can also validate that something occurs, using [should()](https://docs.cypress.io/api/commands/should) assertions.
|
* Cypress can also validate that something occurs, using [should()](https://docs.cypress.io/api/commands/should) assertions.
|
||||||
* Any time you save your test file, the Cypress Test Runner will reload & rerun it. This allows you can see your results quickly as you write the tests & correct any broken tests rapidly.
|
* Any time you save your test file, the Cypress Test Runner will reload & rerun it. This allows you can see your results quickly as you write the tests & correct any broken tests rapidly.
|
||||||
@@ -357,14 +357,14 @@ Some UI specific configuration documentation is also found in the [`./docs`](doc
|
|||||||
|
|
||||||
To build the code documentation we use [TYPEDOC](http://typedoc.org). TYPEDOC is a documentation generator for TypeScript projects. It extracts information from properly formatted comments that can be written within the code files. Follow the instructions [here](http://typedoc.org/guides/doccomments/) to know how to make those comments.
|
To build the code documentation we use [TYPEDOC](http://typedoc.org). TYPEDOC is a documentation generator for TypeScript projects. It extracts information from properly formatted comments that can be written within the code files. Follow the instructions [here](http://typedoc.org/guides/doccomments/) to know how to make those comments.
|
||||||
|
|
||||||
Run:`npm run docs` to produce the documentation that will be available in the 'doc' folder.
|
Run:`yarn run docs` to produce the documentation that will be available in the 'doc' folder.
|
||||||
|
|
||||||
Other commands
|
Other commands
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
There are many more commands in the `scripts` section of `package.json`. Most of these are executed by one of the commands mentioned above.
|
There are many more commands in the `scripts` section of `package.json`. Most of these are executed by one of the commands mentioned above.
|
||||||
|
|
||||||
A command with a name that starts with `pre` or `post` will be executed automatically before or after the script with the matching name. e.g. if you type `npm run start` the `prestart` script will run first, then the `start` script will trigger.
|
A command with a name that starts with `pre` or `post` will be executed automatically before or after the script with the matching name. e.g. if you type `yarn run start` the `prestart` script will run first, then the `start` script will trigger.
|
||||||
|
|
||||||
Recommended Editors/IDEs
|
Recommended Editors/IDEs
|
||||||
------------------------
|
------------------------
|
||||||
@@ -456,7 +456,6 @@ dspace-angular
|
|||||||
├── LICENSES_THIRD_PARTY *
|
├── LICENSES_THIRD_PARTY *
|
||||||
├── nodemon.json * Nodemon (https://nodemon.io/) configuration
|
├── nodemon.json * Nodemon (https://nodemon.io/) configuration
|
||||||
├── package.json * This file describes the npm package for this project, its dependencies, scripts, etc.
|
├── package.json * This file describes the npm package for this project, its dependencies, scripts, etc.
|
||||||
├── package-lock.json * npm lockfile (https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json)
|
|
||||||
├── postcss.config.js * PostCSS (http://postcss.org/) configuration
|
├── postcss.config.js * PostCSS (http://postcss.org/) configuration
|
||||||
├── README.md * This document
|
├── README.md * This document
|
||||||
├── SECURITY.md *
|
├── SECURITY.md *
|
||||||
@@ -467,29 +466,30 @@ dspace-angular
|
|||||||
├── tsconfig.spec.json * TypeScript config for tests
|
├── tsconfig.spec.json * TypeScript config for tests
|
||||||
├── tsconfig.ts-node.json * TypeScript config for using ts-node directly
|
├── tsconfig.ts-node.json * TypeScript config for using ts-node directly
|
||||||
├── tslint.json * TSLint (https://palantir.github.io/tslint/) configuration
|
├── tslint.json * TSLint (https://palantir.github.io/tslint/) configuration
|
||||||
└── typedoc.json * TYPEDOC configuration
|
├── typedoc.json * TYPEDOC configuration
|
||||||
|
└── yarn.lock * Yarn lockfile (https://yarnpkg.com/en/docs/yarn-lock)
|
||||||
```
|
```
|
||||||
|
|
||||||
Managing Dependencies (via npm)
|
Managing Dependencies (via yarn)
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
This project makes use of [`npm`](https://docs.npmjs.com/about-npm) to ensure that the exact same dependency versions are used every time you install it.
|
This project makes use of [`yarn`](https://yarnpkg.com/en/) to ensure that the exact same dependency versions are used every time you install it.
|
||||||
|
|
||||||
* `npm` creates a [`package-lock.json`](https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json) to track those versions. That file is updated automatically by whenever dependencies are added/updated/removed via npm.
|
* `yarn` creates a [`yarn.lock`](https://yarnpkg.com/en/docs/yarn-lock) to track those versions. That file is updated automatically by whenever dependencies are added/updated/removed via yarn.
|
||||||
* **Adding new dependencies**: To install/add a new dependency (third party library), use [`npm install`](https://docs.npmjs.com/cli/v10/commands/npm-install). For example: `npm install some-lib`.
|
* **Adding new dependencies**: To install/add a new dependency (third party library), use [`yarn add`](https://yarnpkg.com/en/docs/cli/add). For example: `yarn add some-lib`.
|
||||||
* If you are adding a new build tool dependency (to `devDependencies`), use `npm install some-lib --save--dev`
|
* If you are adding a new build tool dependency (to `devDependencies`), use `yarn add some-lib --dev`
|
||||||
* **Upgrading existing dependencies**: To upgrade existing dependencies, you can use [`npm update`](https://docs.npmjs.com/cli/v10/commands/npm-update). For example: `npm update some-lib` or `npm update some-lib@version`
|
* **Upgrading existing dependencies**: To upgrade existing dependencies, you can use [`yarn upgrade`](https://yarnpkg.com/en/docs/cli/upgrade). For example: `yarn upgrade some-lib` or `yarn upgrade some-lib@version`
|
||||||
* **Removing dependencies**: If a dependency is no longer needed, or replaced, use [`npm uninstall`](https://docs.npmjs.com/cli/v10/commands/npm-uninstall) to remove it.
|
* **Removing dependencies**: If a dependency is no longer needed, or replaced, use [`yarn remove`](https://yarnpkg.com/en/docs/cli/remove) to remove it.
|
||||||
|
|
||||||
As you can see above, using `npm` commandline tools means that you should never need to modify the `package.json` manually. *We recommend always using `npm` to keep dependencies updated / in sync.*
|
As you can see above, using `yarn` commandline tools means that you should never need to modify the `package.json` manually. *We recommend always using `yarn` to keep dependencies updated / in sync.*
|
||||||
|
|
||||||
### Adding Typings for libraries
|
### Adding Typings for libraries
|
||||||
|
|
||||||
If the library does not include typings, you can install them using npm:
|
If the library does not include typings, you can install them using yarn:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install d3
|
yarn add d3
|
||||||
npm install @types/d3 --save-dev
|
yarn add @types/d3 --dev
|
||||||
```
|
```
|
||||||
|
|
||||||
If the library doesn't have typings available at `@types/`, you can still use it by manually adding typings for it:
|
If the library doesn't have typings available at `@types/`, you can still use it by manually adding typings for it:
|
||||||
@@ -527,13 +527,13 @@ Frequently asked questions
|
|||||||
- What are the naming conventions for Angular?
|
- What are the naming conventions for Angular?
|
||||||
- See [the official angular style guide](https://angular.io/styleguide)
|
- See [the official angular style guide](https://angular.io/styleguide)
|
||||||
- Why is the size of my app larger in development?
|
- Why is the size of my app larger in development?
|
||||||
- The production build uses a whole host of techniques (ahead-of-time compilation, rollup to remove unreachable code, minification, etc.) to reduce the size, that aren't used during development in the interest of build speed.
|
- The production build uses a whole host of techniques (ahead-of-time compilation, rollup to remove unreachable code, minification, etc.) to reduce the size, that aren't used during development in the intrest of build speed.
|
||||||
- node-pre-gyp ERR in npm install (Windows)
|
- node-pre-gyp ERR in yarn install (Windows)
|
||||||
- install Python x86 version between 2.5 and 3.0 on windows. See [this issue](https://github.com/AngularClass/angular2-webpack-starter/issues/626)
|
- install Python x86 version between 2.5 and 3.0 on windows. See [this issue](https://github.com/AngularClass/angular2-webpack-starter/issues/626)
|
||||||
- How do I handle merge conflicts in package-lock.json?
|
- How do I handle merge conflicts in yarn.lock?
|
||||||
- first check out the package-lock.json file from the branch you're merging in to yours: e.g. `git checkout --theirs package-lock.json`
|
- first check out the yarn.lock file from the branch you're merging in to yours: e.g. `git checkout --theirs yarn.lock`
|
||||||
- now run `npm install` again. NPM will create a new lockfile that contains both sets of changes.
|
- now run `yarn install` again. Yarn will create a new lockfile that contains both sets of changes.
|
||||||
- then run `git add package-lock.json` to stage the lockfile for commit
|
- then run `git add yarn.lock` to stage the lockfile for commit
|
||||||
- and `git commit` to conclude the merge
|
- and `git commit` to conclude the merge
|
||||||
|
|
||||||
Getting Help
|
Getting Help
|
||||||
|
@@ -58,10 +58,7 @@
|
|||||||
"input": "src/themes/dspace/styles/theme.scss",
|
"input": "src/themes/dspace/styles/theme.scss",
|
||||||
"inject": false,
|
"inject": false,
|
||||||
"bundleName": "dspace-theme"
|
"bundleName": "dspace-theme"
|
||||||
},
|
}
|
||||||
"node_modules/leaflet/dist/leaflet.css",
|
|
||||||
"node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css",
|
|
||||||
"node_modules/leaflet.markercluster/dist/MarkerCluster.css"
|
|
||||||
],
|
],
|
||||||
"scripts": [],
|
"scripts": [],
|
||||||
"baseHref": "/"
|
"baseHref": "/"
|
||||||
|
@@ -101,7 +101,7 @@ cache:
|
|||||||
# Set to true to see all cache hits/misses/refreshes in your console logs. Useful for debugging SSR caching issues.
|
# Set to true to see all cache hits/misses/refreshes in your console logs. Useful for debugging SSR caching issues.
|
||||||
debug: false
|
debug: false
|
||||||
# When enabled (i.e. max > 0), known bots will be sent pages from a server side cache specific for bots.
|
# When enabled (i.e. max > 0), known bots will be sent pages from a server side cache specific for bots.
|
||||||
# (Keep in mind, bot detection cannot be guaranteed. It is possible some bots will bypass this cache.)
|
# (Keep in mind, bot detection cannot be guarranteed. It is possible some bots will bypass this cache.)
|
||||||
botCache:
|
botCache:
|
||||||
# Maximum number of pages to cache for known bots. Set to zero (0) to disable server side caching for bots.
|
# Maximum number of pages to cache for known bots. Set to zero (0) to disable server side caching for bots.
|
||||||
# Default is 1000, which means the 1000 most recently accessed public pages will be cached.
|
# Default is 1000, which means the 1000 most recently accessed public pages will be cached.
|
||||||
@@ -364,8 +364,6 @@ item:
|
|||||||
# Rounded to the nearest size in the list of selectable sizes on the
|
# Rounded to the nearest size in the list of selectable sizes on the
|
||||||
# settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'.
|
# settings menu. See pageSizeOptions in 'pagination-component-options.model.ts'.
|
||||||
pageSize: 5
|
pageSize: 5
|
||||||
# Show the bitstream access status label on the item page
|
|
||||||
showAccessStatuses: false
|
|
||||||
|
|
||||||
# Community Page Config
|
# Community Page Config
|
||||||
community:
|
community:
|
||||||
@@ -467,8 +465,6 @@ info:
|
|||||||
enableEndUserAgreement: true
|
enableEndUserAgreement: true
|
||||||
enablePrivacyStatement: true
|
enablePrivacyStatement: true
|
||||||
enableCOARNotifySupport: 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/)
|
# 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.
|
# display in supported metadata fields. By default, only dc.description.abstract is supported.
|
||||||
@@ -562,6 +558,7 @@ notifyMetrics:
|
|||||||
config: 'NOTIFY.outgoing.delivered'
|
config: 'NOTIFY.outgoing.delivered'
|
||||||
description: 'admin-notify-dashboard.NOTIFY.outgoing.delivered.description'
|
description: 'admin-notify-dashboard.NOTIFY.outgoing.delivered.description'
|
||||||
|
|
||||||
|
|
||||||
# Live Region configuration
|
# Live Region configuration
|
||||||
# Live Region as defined by w3c, https://www.w3.org/TR/wai-aria-1.1/#terms:
|
# Live Region as defined by w3c, https://www.w3.org/TR/wai-aria-1.1/#terms:
|
||||||
# Live regions are perceivable regions of a web page that are typically updated as a
|
# Live regions are perceivable regions of a web page that are typically updated as a
|
||||||
@@ -576,40 +573,6 @@ liveRegion:
|
|||||||
# The visibility of the live region. Setting this to true is only useful for debugging purposes.
|
# The visibility of the live region. Setting this to true is only useful for debugging purposes.
|
||||||
isVisible: false
|
isVisible: false
|
||||||
|
|
||||||
# Geospatial Map display options
|
|
||||||
geospatialMapViewer:
|
|
||||||
# Which fields to use for parsing as geospatial points in search maps
|
|
||||||
# (note, the item page field component allows any field(s) to be used
|
|
||||||
# and is set as an input when declaring the component)
|
|
||||||
spatialMetadataFields:
|
|
||||||
- 'dcterms.spatial'
|
|
||||||
# Which discovery configuration to use for 'geospatial search', used
|
|
||||||
# in the browse map
|
|
||||||
spatialFacetDiscoveryConfiguration: 'geospatial'
|
|
||||||
# Which filter / facet name to use for faceted geospatial search
|
|
||||||
# used in the browse map
|
|
||||||
spatialPointFilterName: 'point'
|
|
||||||
# Whether item page geospatial metadata should be displayed
|
|
||||||
# (assumes they are wrapped in a test for this config in the template as
|
|
||||||
# per the default templates supplied with DSpace for untyped-item and publication)
|
|
||||||
enableItemPageFields: false
|
|
||||||
# Whether the browse map should be enabled and included in the browse menu
|
|
||||||
enableBrowseMap: false
|
|
||||||
# Whether a 'map view' mode should be included alongside list and grid views
|
|
||||||
# in search result pages
|
|
||||||
enableSearchViewMode: false
|
|
||||||
# The tile provider(s) to use for the map tiles drawn in the leaflet maps.
|
|
||||||
# (see https://leaflet-extras.github.io/leaflet-providers/preview/) for a full list
|
|
||||||
tileProviders:
|
|
||||||
- 'OpenStreetMap.Mapnik'
|
|
||||||
# Starting centre point for the map, as lat and lng coordinates. This is useful
|
|
||||||
# to set the centre of the map when the map is first loaded and if there are no
|
|
||||||
# points, shapes or markers to display.
|
|
||||||
# Defaults to the centre of Istanbul
|
|
||||||
defaultCentrePoint:
|
|
||||||
lat: 41.015137
|
|
||||||
lng: 28.979530
|
|
||||||
|
|
||||||
# Configuration for storing accessibility settings, used by the AccessibilitySettingsService
|
# Configuration for storing accessibility settings, used by the AccessibilitySettingsService
|
||||||
accessibility:
|
accessibility:
|
||||||
# The duration in days after which the accessibility settings cookie expires
|
# The duration in days after which the accessibility settings cookie expires
|
||||||
|
@@ -34,7 +34,6 @@ export default defineConfig({
|
|||||||
DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME: 'People',
|
DSPACE_TEST_SUBMIT_PERSON_COLLECTION_NAME: 'People',
|
||||||
// Account used to test basic submission process
|
// Account used to test basic submission process
|
||||||
DSPACE_TEST_SUBMIT_USER: 'dspacedemo+submit@gmail.com',
|
DSPACE_TEST_SUBMIT_USER: 'dspacedemo+submit@gmail.com',
|
||||||
DSPACE_TEST_SUBMIT_USER_UUID: '914955b1-cf2e-4884-8af7-a166aa24cf73',
|
|
||||||
DSPACE_TEST_SUBMIT_USER_PASSWORD: 'dspace',
|
DSPACE_TEST_SUBMIT_USER_PASSWORD: 'dspace',
|
||||||
// Administrator users group
|
// Administrator users group
|
||||||
DSPACE_ADMINISTRATOR_GROUP: 'e59f5659-bff9-451e-b28f-439e7bd467e4'
|
DSPACE_ADMINISTRATOR_GROUP: 'e59f5659-bff9-451e-b28f-439e7bd467e4'
|
||||||
|
@@ -9,12 +9,12 @@ describe('Admin Add New Modals', () => {
|
|||||||
|
|
||||||
it('Add new Community modal should pass accessibility tests', () => {
|
it('Add new Community modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
cy.get('[data-test="admin-menu-section-new-title"]').should('be.visible');
|
cy.get('#admin-menu-section-new-title').should('be.visible');
|
||||||
cy.get('[data-test="admin-menu-section-new-title"]').click();
|
cy.get('#admin-menu-section-new-title').click();
|
||||||
|
|
||||||
cy.get('a[data-test="menu.section.new_community"]').click();
|
cy.get('a[data-test="menu.section.new_community"]').click();
|
||||||
|
|
||||||
@@ -24,12 +24,12 @@ describe('Admin Add New Modals', () => {
|
|||||||
|
|
||||||
it('Add new Collection modal should pass accessibility tests', () => {
|
it('Add new Collection modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
cy.get('[data-test="admin-menu-section-new-title"]').should('be.visible');
|
cy.get('#admin-menu-section-new-title').should('be.visible');
|
||||||
cy.get('[data-test="admin-menu-section-new-title"]').click();
|
cy.get('#admin-menu-section-new-title').click();
|
||||||
|
|
||||||
cy.get('a[data-test="menu.section.new_collection"]').click();
|
cy.get('a[data-test="menu.section.new_collection"]').click();
|
||||||
|
|
||||||
@@ -39,12 +39,12 @@ describe('Admin Add New Modals', () => {
|
|||||||
|
|
||||||
it('Add new Item modal should pass accessibility tests', () => {
|
it('Add new Item modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
cy.get('[data-test="admin-menu-section-new-title"]').should('be.visible');
|
cy.get('#admin-menu-section-new-title').should('be.visible');
|
||||||
cy.get('[data-test="admin-menu-section-new-title"]').click();
|
cy.get('#admin-menu-section-new-title').click();
|
||||||
|
|
||||||
cy.get('a[data-test="menu.section.new_item"]').click();
|
cy.get('a[data-test="menu.section.new_item"]').click();
|
||||||
|
|
||||||
|
@@ -9,12 +9,12 @@ describe('Admin Edit Modals', () => {
|
|||||||
|
|
||||||
it('Edit Community modal should pass accessibility tests', () => {
|
it('Edit Community modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
cy.get('[data-test="admin-menu-section-edit-title"]').should('be.visible');
|
cy.get('#admin-menu-section-edit-title').should('be.visible');
|
||||||
cy.get('[data-test="admin-menu-section-edit-title"]').click();
|
cy.get('#admin-menu-section-edit-title').click();
|
||||||
|
|
||||||
cy.get('a[data-test="menu.section.edit_community"]').click();
|
cy.get('a[data-test="menu.section.edit_community"]').click();
|
||||||
|
|
||||||
@@ -24,12 +24,12 @@ describe('Admin Edit Modals', () => {
|
|||||||
|
|
||||||
it('Edit Collection modal should pass accessibility tests', () => {
|
it('Edit Collection modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
cy.get('[data-test="admin-menu-section-edit-title"]').should('be.visible');
|
cy.get('#admin-menu-section-edit-title').should('be.visible');
|
||||||
cy.get('[data-test="admin-menu-section-edit-title"]').click();
|
cy.get('#admin-menu-section-edit-title').click();
|
||||||
|
|
||||||
cy.get('a[data-test="menu.section.edit_collection"]').click();
|
cy.get('a[data-test="menu.section.edit_collection"]').click();
|
||||||
|
|
||||||
@@ -39,12 +39,12 @@ describe('Admin Edit Modals', () => {
|
|||||||
|
|
||||||
it('Edit Item modal should pass accessibility tests', () => {
|
it('Edit Item modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
cy.get('[data-test="admin-menu-section-edit-title"]').should('be.visible');
|
cy.get('#admin-menu-section-edit-title').should('be.visible');
|
||||||
cy.get('[data-test="admin-menu-section-edit-title"]').click();
|
cy.get('#admin-menu-section-edit-title').click();
|
||||||
|
|
||||||
cy.get('a[data-test="menu.section.edit_item"]').click();
|
cy.get('a[data-test="menu.section.edit_item"]').click();
|
||||||
|
|
||||||
|
@@ -9,12 +9,12 @@ describe('Admin Export Modals', () => {
|
|||||||
|
|
||||||
it('Export metadata modal should pass accessibility tests', () => {
|
it('Export metadata modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
cy.get('[data-test="admin-menu-section-export-title"]').should('be.visible');
|
cy.get('#admin-menu-section-export-title').should('be.visible');
|
||||||
cy.get('[data-test="admin-menu-section-export-title"]').click();
|
cy.get('#admin-menu-section-export-title').click();
|
||||||
|
|
||||||
cy.get('a[data-test="menu.section.export_metadata"]').click();
|
cy.get('a[data-test="menu.section.export_metadata"]').click();
|
||||||
|
|
||||||
@@ -24,12 +24,12 @@ describe('Admin Export Modals', () => {
|
|||||||
|
|
||||||
it('Export batch modal should pass accessibility tests', () => {
|
it('Export batch modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
cy.get('#sidebar-collapse-toggle').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
cy.get('[data-test="admin-menu-section-export-title"]').should('be.visible');
|
cy.get('#admin-menu-section-export-title').should('be.visible');
|
||||||
cy.get('[data-test="admin-menu-section-export-title"]').click();
|
cy.get('#admin-menu-section-export-title').click();
|
||||||
|
|
||||||
cy.get('a[data-test="menu.section.export_batch"]').click();
|
cy.get('a[data-test="menu.section.export_batch"]').click();
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
import { Options } from 'cypress-axe';
|
||||||
|
|
||||||
describe('Admin Sidebar', () => {
|
describe('Admin Sidebar', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -9,12 +10,19 @@ describe('Admin Sidebar', () => {
|
|||||||
|
|
||||||
it('should be pinnable and pass accessibility tests', () => {
|
it('should be pinnable and pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('#sidebar-collapse-toggle').click();
|
||||||
|
|
||||||
// Click on every expandable section to open all menus
|
// Click on every expandable section to open all menus
|
||||||
cy.get('ds-expandable-admin-sidebar-section').click({ multiple: true });
|
cy.get('ds-expandable-admin-sidebar-section').click({ multiple: true });
|
||||||
|
|
||||||
// Analyze <ds-admin-sidebar> for accessibility
|
// Analyze <ds-admin-sidebar> for accessibility
|
||||||
testA11y('ds-admin-sidebar');
|
testA11y('ds-admin-sidebar',
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
// Currently all expandable sections have nested interactive elements
|
||||||
|
// See https://github.com/DSpace/dspace-angular/issues/2178
|
||||||
|
'nested-interactive': { enabled: false },
|
||||||
|
},
|
||||||
|
} as Options);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -12,13 +12,6 @@ describe('Community List Page', () => {
|
|||||||
cy.get('[data-test="expand-button"]').click({ multiple: true });
|
cy.get('[data-test="expand-button"]').click({ multiple: true });
|
||||||
|
|
||||||
// Analyze <ds-community-list-page> for accessibility issues
|
// Analyze <ds-community-list-page> for accessibility issues
|
||||||
testA11y('ds-community-list-page', {
|
testA11y('ds-community-list-page');
|
||||||
rules: {
|
|
||||||
// When expanding a cdk node on the community-list page, the 'aria-posinset' property becomes 0.
|
|
||||||
// 0 is not a valid value for 'aria-posinset' so the test fails.
|
|
||||||
// see https://github.com/DSpace/dspace-angular/issues/4068
|
|
||||||
'aria-valid-attr-value': { enabled: false },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -4,14 +4,13 @@ import { Options } from 'cypress-axe';
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Must login as an Admin to see the page
|
// 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.visit('/health');
|
||||||
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
cy.loginViaForm(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Health Page > Status Tab', () => {
|
describe('Health Page > Status Tab', () => {
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.intercept('GET', '/server/actuator/health').as('status');
|
||||||
cy.wait('@status');
|
cy.wait('@status');
|
||||||
|
|
||||||
cy.get('a[data-test="health-page.status-tab"]').click();
|
cy.get('a[data-test="health-page.status-tab"]').click();
|
||||||
@@ -37,6 +36,7 @@ describe('Health Page > Status Tab', () => {
|
|||||||
|
|
||||||
describe('Health Page > Info Tab', () => {
|
describe('Health Page > Info Tab', () => {
|
||||||
it('should pass accessibility tests', () => {
|
it('should pass accessibility tests', () => {
|
||||||
|
cy.intercept('GET', '/server/actuator/info').as('info');
|
||||||
cy.wait('@info');
|
cy.wait('@info');
|
||||||
|
|
||||||
cy.get('a[data-test="health-page.info-tab"]').click();
|
cy.get('a[data-test="health-page.info-tab"]').click();
|
||||||
|
@@ -17,7 +17,7 @@ describe('Site Statistics Page', () => {
|
|||||||
|
|
||||||
cy.visit('/statistics');
|
cy.visit('/statistics');
|
||||||
|
|
||||||
// <ds-site-statistics-page> tag must be visible
|
// <ds-site-statistics-page> tag must be visable
|
||||||
cy.get('ds-site-statistics-page').should('be.visible');
|
cy.get('ds-site-statistics-page').should('be.visible');
|
||||||
|
|
||||||
// Verify / wait until "Total Visits" table's *last* label is non-empty
|
// Verify / wait until "Total Visits" table's *last* label is non-empty
|
||||||
|
@@ -26,12 +26,6 @@ describe('Homepage', () => {
|
|||||||
// Wait for homepage tag to appear
|
// Wait for homepage tag to appear
|
||||||
cy.get('ds-home-page').should('be.visible');
|
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
|
// Analyze <ds-home-page> for accessibility issues
|
||||||
testA11y('ds-home-page');
|
testA11y('ds-home-page');
|
||||||
});
|
});
|
||||||
|
@@ -7,7 +7,7 @@ describe('Item Statistics Page', () => {
|
|||||||
it('should load if you click on "Statistics" from an Item/Entity page', () => {
|
it('should load if you click on "Statistics" from an Item/Entity page', () => {
|
||||||
cy.visit('/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')));
|
cy.visit('/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')));
|
||||||
cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click();
|
cy.get('ds-navbar ds-link-menu-item a[data-test="link-menu-item.menu.section.statistics"]').click();
|
||||||
cy.location('pathname').should('eq', '/statistics/entities/publication/'.concat(Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION')));
|
cy.location('pathname').should('eq', ITEMSTATISTICSPAGE);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain element ds-item-statistics-page when navigating to an item statistics page', () => {
|
it('should contain element ds-item-statistics-page when navigating to an item statistics page', () => {
|
||||||
|
@@ -142,7 +142,7 @@ describe('Login Modal', () => {
|
|||||||
page.submitLoginAndPasswordByPressingButton(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
page.submitLoginAndPasswordByPressingButton(Cypress.env('DSPACE_TEST_ADMIN_USER'), Cypress.env('DSPACE_TEST_ADMIN_PASSWORD'));
|
||||||
cy.get('ds-log-in').should('not.exist');
|
cy.get('ds-log-in').should('not.exist');
|
||||||
|
|
||||||
// Open user menu, verify user menu accessibility
|
// Open user menu, verify user menu accesibility
|
||||||
page.openUserMenu();
|
page.openUserMenu();
|
||||||
cy.get('ds-user-menu').should('be.visible');
|
cy.get('ds-user-menu').should('be.visible');
|
||||||
testA11y('ds-user-menu');
|
testA11y('ds-user-menu');
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { testA11y } from 'cypress/support/utils';
|
import { testA11y } from 'cypress/support/utils';
|
||||||
|
|
||||||
describe('PageNotFound', () => {
|
describe('PageNotFound', () => {
|
||||||
it('should contain element ds-pagenotfound when navigating to page that does not exist', () => {
|
it('should contain element ds-pagenotfound when navigating to page that doesnt exist', () => {
|
||||||
// request an invalid page (UUIDs at root path aren't valid)
|
// request an invalid page (UUIDs at root path aren't valid)
|
||||||
cy.visit('/e9019a69-d4f1-4773-b6a3-bd362caa46f2', { failOnStatusCode: false });
|
cy.visit('/e9019a69-d4f1-4773-b6a3-bd362caa46f2', { failOnStatusCode: false });
|
||||||
cy.get('ds-pagenotfound').should('be.visible');
|
cy.get('ds-pagenotfound').should('be.visible');
|
||||||
|
@@ -34,7 +34,7 @@ describe('New Submission page', () => {
|
|||||||
// Author & Subject fields have invalid "aria-multiline" attrs.
|
// Author & Subject fields have invalid "aria-multiline" attrs.
|
||||||
// See https://github.com/DSpace/dspace-angular/issues/1272
|
// See https://github.com/DSpace/dspace-angular/issues/1272
|
||||||
'aria-allowed-attr': { enabled: false },
|
'aria-allowed-attr': { enabled: false },
|
||||||
// All panels are accordions & fail "aria-required-children" and "nested-interactive".
|
// All panels are accordians & fail "aria-required-children" and "nested-interactive".
|
||||||
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
|
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
|
||||||
'aria-required-children': { enabled: false },
|
'aria-required-children': { enabled: false },
|
||||||
'nested-interactive': { enabled: false },
|
'nested-interactive': { enabled: false },
|
||||||
@@ -192,7 +192,7 @@ describe('New Submission page', () => {
|
|||||||
testA11y('ds-submission-edit',
|
testA11y('ds-submission-edit',
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
// All panels are accordions & fail "aria-required-children" and "nested-interactive".
|
// All panels are accordians & fail "aria-required-children" and "nested-interactive".
|
||||||
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
|
// Seem to require updating ng-bootstrap and https://github.com/DSpace/dspace-angular/issues/2216
|
||||||
'aria-required-children': { enabled: false },
|
'aria-required-children': { enabled: false },
|
||||||
'nested-interactive': { enabled: false },
|
'nested-interactive': { enabled: false },
|
||||||
@@ -217,7 +217,7 @@ describe('New Submission page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Close popup window
|
// Close popup window
|
||||||
cy.get('ds-dynamic-lookup-relation-modal button.btn-close').click();
|
cy.get('ds-dynamic-lookup-relation-modal button.close').click();
|
||||||
|
|
||||||
// Back on the form, click the discard button to remove new submission
|
// Back on the form, click the discard button to remove new submission
|
||||||
// Clicking it will display a confirmation, which we will confirm with another click
|
// Clicking it will display a confirmation, which we will confirm with another click
|
||||||
|
@@ -54,9 +54,9 @@ before(() => {
|
|||||||
|
|
||||||
// Runs once before the first test in each "block"
|
// Runs once before the first test in each "block"
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Pre-agree to all Orejime cookies by setting the orejime-anonymous cookie
|
// Pre-agree to all Klaro cookies by setting the klaro-anonymous cookie
|
||||||
// This just ensures it doesn't get in the way of matching other objects in the page.
|
// 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,"correlation-id":true,"accessibility":true}');
|
cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true%2C%22google-recaptcha%22:true}');
|
||||||
|
|
||||||
// Remove any CSRF cookies saved from prior tests
|
// Remove any CSRF cookies saved from prior tests
|
||||||
cy.clearCookie(DSPACE_XSRF_COOKIE);
|
cy.clearCookie(DSPACE_XSRF_COOKIE);
|
||||||
|
@@ -1,16 +1,10 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.json",
|
"extends": "../tsconfig.json",
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts"
|
||||||
"../cypress.config.ts"
|
|
||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"typeRoots": [
|
|
||||||
"../node_modules",
|
|
||||||
"../node_modules/@types",
|
|
||||||
"../src/typings.d.ts"
|
|
||||||
],
|
|
||||||
"types": [
|
"types": [
|
||||||
"cypress",
|
"cypress",
|
||||||
"cypress-axe",
|
"cypress-axe",
|
||||||
|
@@ -23,14 +23,14 @@ the Docker compose scripts in this 'docker' folder.
|
|||||||
This Dockerfile is used to build a *development* DSpace Angular UI image, published as 'dspace/dspace-angular'
|
This Dockerfile is used to build a *development* DSpace Angular UI image, published as 'dspace/dspace-angular'
|
||||||
|
|
||||||
```
|
```
|
||||||
docker build -t dspace/dspace-angular:latest .
|
docker build -t dspace/dspace-angular:dspace-8_x .
|
||||||
```
|
```
|
||||||
|
|
||||||
This image is built *automatically* after each commit is made to the `main` branch.
|
This image is built *automatically* after each commit is made to the `main` branch.
|
||||||
|
|
||||||
Admins to our DockerHub repo can manually publish with the following command.
|
Admins to our DockerHub repo can manually publish with the following command.
|
||||||
```
|
```
|
||||||
docker push dspace/dspace-angular:latest
|
docker push dspace/dspace-angular:dspace-8_x
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dockerfile.dist
|
### Dockerfile.dist
|
||||||
@@ -39,7 +39,7 @@ The `Dockerfile.dist` is used to generate a *production* build and runtime envir
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# build the latest image
|
# build the latest image
|
||||||
docker build -f Dockerfile.dist -t dspace/dspace-angular:latest-dist .
|
docker build -f Dockerfile.dist -t dspace/dspace-angular:dspace-8_x-dist .
|
||||||
```
|
```
|
||||||
|
|
||||||
A default/demo version of this image is built *automatically*.
|
A default/demo version of this image is built *automatically*.
|
||||||
|
@@ -14,14 +14,14 @@
|
|||||||
# Therefore, it should be kept in sync with that file
|
# Therefore, it should be kept in sync with that file
|
||||||
networks:
|
networks:
|
||||||
# Default to using network named 'dspacenet' from docker-compose-rest.yml.
|
# Default to using network named 'dspacenet' from docker-compose-rest.yml.
|
||||||
# Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet")
|
# Its full name will be prepended with the project name (e.g. "-p d8" means it will be named "d8_dspacenet")
|
||||||
# If COMPOSITE_PROJECT_NAME is missing, default value will be "docker" (name of folder this file is in)
|
# If COMPOSITE_PROJECT_NAME is missing, default value will be "docker" (name of folder this file is in)
|
||||||
default:
|
default:
|
||||||
name: ${COMPOSE_PROJECT_NAME:-docker}_dspacenet
|
name: ${COMPOSE_PROJECT_NAME:-docker}_dspacenet
|
||||||
external: true
|
external: true
|
||||||
services:
|
services:
|
||||||
dspace-cli:
|
dspace-cli:
|
||||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-latest}"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-8_x}"
|
||||||
container_name: dspace-cli
|
container_name: dspace-cli
|
||||||
environment:
|
environment:
|
||||||
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
# Below syntax may look odd, but it is how to override dspace.cfg settings via env variables.
|
||||||
|
@@ -14,10 +14,11 @@
|
|||||||
# # Therefore, it should be kept in sync with that file
|
# # Therefore, it should be kept in sync with that file
|
||||||
services:
|
services:
|
||||||
dspacedb:
|
dspacedb:
|
||||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}-loadsql"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-8_x}-loadsql"
|
||||||
environment:
|
environment:
|
||||||
# This LOADSQL should be kept in sync with the URL in DSpace/DSpace
|
# This LOADSQL should be kept in sync with the URL in DSpace/DSpace
|
||||||
# This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
# This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
||||||
|
# NOTE: currently there is no dspace8 version
|
||||||
- LOADSQL=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql
|
- LOADSQL=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql
|
||||||
dspace:
|
dspace:
|
||||||
### OVERRIDE default 'entrypoint' in 'docker-compose-rest.yml' ####
|
### OVERRIDE default 'entrypoint' in 'docker-compose-rest.yml' ####
|
||||||
|
@@ -33,7 +33,7 @@ services:
|
|||||||
# This allows us to generate statistics in e2e tests so that statistics pages can be tested thoroughly.
|
# This allows us to generate statistics in e2e tests so that statistics pages can be tested thoroughly.
|
||||||
solr__D__statistics__P__autoCommit: 'false'
|
solr__D__statistics__P__autoCommit: 'false'
|
||||||
LOGGING_CONFIG: /dspace/config/log4j2-container.xml
|
LOGGING_CONFIG: /dspace/config/log4j2-container.xml
|
||||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-latest-test}"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-8_x-test}"
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
networks:
|
networks:
|
||||||
@@ -60,11 +60,12 @@ services:
|
|||||||
# NOTE: This is customized to use our loadsql image, so that we are using a database with existing test data
|
# NOTE: This is customized to use our loadsql image, so that we are using a database with existing test data
|
||||||
dspacedb:
|
dspacedb:
|
||||||
container_name: dspacedb
|
container_name: dspacedb
|
||||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}-loadsql"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-8_x}-loadsql"
|
||||||
environment:
|
environment:
|
||||||
# This LOADSQL should be kept in sync with the LOADSQL in
|
# This LOADSQL should be kept in sync with the LOADSQL in
|
||||||
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml
|
# https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml
|
||||||
# This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
# This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
||||||
|
# NOTE: currently there is no dspace8 version
|
||||||
LOADSQL: https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql
|
LOADSQL: https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql
|
||||||
PGDATA: /pgdata
|
PGDATA: /pgdata
|
||||||
POSTGRES_PASSWORD: dspace
|
POSTGRES_PASSWORD: dspace
|
||||||
@@ -81,7 +82,7 @@ services:
|
|||||||
# DSpace Solr container
|
# DSpace Solr container
|
||||||
dspacesolr:
|
dspacesolr:
|
||||||
container_name: dspacesolr
|
container_name: dspacesolr
|
||||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-8_x}"
|
||||||
networks:
|
networks:
|
||||||
- dspacenet
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
@@ -93,10 +94,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
# Keep Solr data directory between reboots
|
# Keep Solr data directory between reboots
|
||||||
- solr_data:/var/solr/data
|
- 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
|
# Initialize all DSpace Solr cores using the mounted configsets (see above), then start Solr
|
||||||
# /var/solr/data directory. Then we start Solr as the "solr" user.
|
|
||||||
user: root
|
|
||||||
# Initialize all DSpace Solr cores, then start Solr
|
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- /bin/bash
|
- /bin/bash
|
||||||
- '-c'
|
- '-c'
|
||||||
@@ -114,8 +112,7 @@ services:
|
|||||||
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
|
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
|
||||||
precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
|
precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
|
||||||
cp -r /opt/solr/server/solr/configsets/suggestion/* suggestion
|
cp -r /opt/solr/server/solr/configsets/suggestion/* suggestion
|
||||||
chown -R solr:solr /var/solr
|
exec solr -f
|
||||||
runuser -u solr -- solr-foreground
|
|
||||||
volumes:
|
volumes:
|
||||||
assetstore:
|
assetstore:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
@@ -26,7 +26,7 @@ services:
|
|||||||
DSPACE_REST_HOST: sandbox.dspace.org
|
DSPACE_REST_HOST: sandbox.dspace.org
|
||||||
DSPACE_REST_PORT: 443
|
DSPACE_REST_PORT: 443
|
||||||
DSPACE_REST_NAMESPACE: /server
|
DSPACE_REST_NAMESPACE: /server
|
||||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-latest}-dist"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-dspace-8_x}-dist"
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: Dockerfile.dist
|
dockerfile: Dockerfile.dist
|
||||||
|
@@ -40,7 +40,7 @@ services:
|
|||||||
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
|
# from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above.
|
||||||
proxies__P__trusted__P__ipranges: '172.23.0'
|
proxies__P__trusted__P__ipranges: '172.23.0'
|
||||||
LOGGING_CONFIG: /dspace/config/log4j2-container.xml
|
LOGGING_CONFIG: /dspace/config/log4j2-container.xml
|
||||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-latest-test}"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-8_x-test}"
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
networks:
|
networks:
|
||||||
@@ -68,7 +68,7 @@ services:
|
|||||||
dspacedb:
|
dspacedb:
|
||||||
container_name: dspacedb
|
container_name: dspacedb
|
||||||
# Uses a custom Postgres image with pgcrypto installed
|
# Uses a custom Postgres image with pgcrypto installed
|
||||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-dspace-8_x}"
|
||||||
environment:
|
environment:
|
||||||
PGDATA: /pgdata
|
PGDATA: /pgdata
|
||||||
POSTGRES_PASSWORD: dspace
|
POSTGRES_PASSWORD: dspace
|
||||||
@@ -85,7 +85,7 @@ services:
|
|||||||
# DSpace Solr container
|
# DSpace Solr container
|
||||||
dspacesolr:
|
dspacesolr:
|
||||||
container_name: dspacesolr
|
container_name: dspacesolr
|
||||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-dspace-8_x}"
|
||||||
networks:
|
networks:
|
||||||
- dspacenet
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
@@ -97,16 +97,11 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
# Keep Solr data directory between reboots
|
# Keep Solr data directory between reboots
|
||||||
- solr_data:/var/solr/data
|
- 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
|
# 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
|
# * 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:
|
# * Second, copy configsets to this core:
|
||||||
# Updates to Solr configs require the container to be rebuilt/restarted:
|
# Updates to Solr configs require the container to be rebuilt/restarted:
|
||||||
# `docker compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build dspacesolr`
|
# `docker compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build dspacesolr`
|
||||||
# * Third, ensure all new folders are owned by "solr" user
|
|
||||||
# * Finally, start Solr as the "solr" user via the provided solr-foreground script
|
|
||||||
entrypoint:
|
entrypoint:
|
||||||
- /bin/bash
|
- /bin/bash
|
||||||
- '-c'
|
- '-c'
|
||||||
@@ -124,8 +119,7 @@ services:
|
|||||||
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
|
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
|
||||||
precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
|
precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
|
||||||
cp -r /opt/solr/server/solr/configsets/suggestion/* suggestion
|
cp -r /opt/solr/server/solr/configsets/suggestion/* suggestion
|
||||||
chown -R solr:solr /var/solr
|
exec solr -f
|
||||||
runuser -u solr -- solr-foreground
|
|
||||||
volumes:
|
volumes:
|
||||||
assetstore:
|
assetstore:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
@@ -23,7 +23,7 @@ services:
|
|||||||
DSPACE_REST_HOST: localhost
|
DSPACE_REST_HOST: localhost
|
||||||
DSPACE_REST_PORT: 8080
|
DSPACE_REST_PORT: 8080
|
||||||
DSPACE_REST_NAMESPACE: /server
|
DSPACE_REST_NAMESPACE: /server
|
||||||
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-latest}"
|
image: "${DOCKER_REGISTRY:-docker.io}/${DOCKER_OWNER:-dspace}/dspace-angular:${DSPACE_VER:-dspace-8_x}"
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
@@ -15,7 +15,7 @@ DSPACE_APP_CONFIG_PATH=/usr/local/dspace/config/config.yml
|
|||||||
Configuration options can be overridden by setting environment variables.
|
Configuration options can be overridden by setting environment variables.
|
||||||
|
|
||||||
## Nodejs server
|
## Nodejs server
|
||||||
When you start dspace-angular on node, it spins up an http server on which it listens for incoming connections. You can define the ip address and port the server should bind itself to, and if ssl should be enabled not. By default it listens on `localhost:4000`. If you want it to listen on all your network connections, configure it to bind itself to `0.0.0.0`.
|
When you start dspace-angular on node, it spins up an http server on which it listens for incoming connections. You can define the ip address and port the server should bind itsself to, and if ssl should be enabled not. By default it listens on `localhost:4000`. If you want it to listen on all your network connections, configure it to bind itself to `0.0.0.0`.
|
||||||
|
|
||||||
To change this configuration, change the options `ui.host`, `ui.port` and `ui.ssl` in the appropriate configuration file (see above):
|
To change this configuration, change the options `ui.host`, `ui.port` and `ui.ssl` in the appropriate configuration file (see above):
|
||||||
|
|
||||||
|
@@ -9,8 +9,6 @@ _______
|
|||||||
|
|
||||||
[Source code](../../../../lint/src/rules/html/no-disabled-attribute-on-button.ts)
|
[Source code](../../../../lint/src/rules/html/no-disabled-attribute-on-button.ts)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
@@ -21,28 +19,24 @@ _______
|
|||||||
```html
|
```html
|
||||||
<button [dsBtnDisabled]="true">Submit</button>
|
<button [dsBtnDisabled]="true">Submit</button>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### disabled attribute is still valid on non-button elements
|
##### disabled attribute is still valid on non-button elements
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<input disabled>
|
<input disabled>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### [disabled] attribute is still valid on non-button elements
|
##### [disabled] attribute is still valid on non-button elements
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<input [disabled]="true">
|
<input [disabled]="true">
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### angular dynamic attributes that use disabled are still valid
|
##### angular dynamic attributes that use disabled are still valid
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<button [class.disabled]="isDisabled">Submit</button>
|
<button [class.disabled]="isDisabled">Submit</button>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -53,9 +47,6 @@ _______
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<button disabled>Submit</button>
|
<button disabled>Submit</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -72,9 +63,6 @@ Result of `yarn lint --fix`:
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<button [disabled]="true">Submit</button>
|
<button [disabled]="true">Submit</button>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
|
@@ -11,8 +11,6 @@ _______
|
|||||||
|
|
||||||
[Source code](../../../../lint/src/rules/html/themed-component-usages.ts)
|
[Source code](../../../../lint/src/rules/html/themed-component-usages.ts)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
@@ -25,7 +23,6 @@ _______
|
|||||||
<ds-test-themeable></ds-test-themeable>
|
<ds-test-themeable></ds-test-themeable>
|
||||||
<ds-test-themeable [test]="something"></ds-test-themeable>
|
<ds-test-themeable [test]="something"></ds-test-themeable>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### use no-prefix selectors in TypeScript templates
|
##### use no-prefix selectors in TypeScript templates
|
||||||
|
|
||||||
@@ -36,7 +33,6 @@ _______
|
|||||||
class Test {
|
class Test {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### use no-prefix selectors in TypeScript test templates
|
##### use no-prefix selectors in TypeScript test templates
|
||||||
|
|
||||||
@@ -49,7 +45,6 @@ Filename: `lint/test/fixture/src/test.spec.ts`
|
|||||||
class Test {
|
class Test {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### base selectors are also allowed in TypeScript test templates
|
##### base selectors are also allowed in TypeScript test templates
|
||||||
|
|
||||||
@@ -62,7 +57,6 @@ Filename: `lint/test/fixture/src/test.spec.ts`
|
|||||||
class Test {
|
class Test {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -75,9 +69,6 @@ class Test {
|
|||||||
<ds-themed-test-themeable/>
|
<ds-themed-test-themeable/>
|
||||||
<ds-themed-test-themeable></ds-themed-test-themeable>
|
<ds-themed-test-themeable></ds-themed-test-themeable>
|
||||||
<ds-themed-test-themeable [test]="something"></ds-themed-test-themeable>
|
<ds-themed-test-themeable [test]="something"></ds-themed-test-themeable>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -100,9 +91,6 @@ Result of `yarn lint --fix`:
|
|||||||
<ds-base-test-themeable/>
|
<ds-base-test-themeable/>
|
||||||
<ds-base-test-themeable></ds-base-test-themeable>
|
<ds-base-test-themeable></ds-base-test-themeable>
|
||||||
<ds-base-test-themeable [test]="something"></ds-base-test-themeable>
|
<ds-base-test-themeable [test]="something"></ds-base-test-themeable>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
|
@@ -1,11 +1,6 @@
|
|||||||
[DSpace ESLint plugins](../../../lint/README.md) > TypeScript rules
|
[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-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-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-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)
|
|
||||||
|
@@ -1,148 +0,0 @@
|
|||||||
[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 & 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';
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -1,245 +0,0 @@
|
|||||||
[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 & 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 {}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -11,8 +11,6 @@ _______
|
|||||||
|
|
||||||
[Source code](../../../../lint/src/rules/ts/themed-component-classes.ts)
|
[Source code](../../../../lint/src/rules/ts/themed-component-classes.ts)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
@@ -28,7 +26,6 @@ _______
|
|||||||
class Something {
|
class Something {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### Base component
|
##### Base component
|
||||||
|
|
||||||
@@ -37,10 +34,9 @@ class Something {
|
|||||||
selector: 'ds-base-test-themable',
|
selector: 'ds-base-test-themable',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
class TestThemeableComponent {
|
class TestThemeableTomponent {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### Wrapper component
|
##### Wrapper component
|
||||||
|
|
||||||
@@ -54,10 +50,9 @@ Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
|||||||
TestThemeableComponent,
|
TestThemeableComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableTomponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### Override component
|
##### Override component
|
||||||
|
|
||||||
@@ -71,7 +66,6 @@ Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.t
|
|||||||
class Override extends BaseComponent {
|
class Override extends BaseComponent {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -86,9 +80,6 @@ class Override extends BaseComponent {
|
|||||||
})
|
})
|
||||||
class TestThemeableComponent {
|
class TestThemeableComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -116,9 +107,6 @@ Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
|||||||
})
|
})
|
||||||
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -149,9 +137,6 @@ Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
|||||||
})
|
})
|
||||||
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -186,9 +171,6 @@ import { SomethingElse } from './somewhere-else';
|
|||||||
})
|
})
|
||||||
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -225,9 +207,6 @@ import { Something, SomethingElse } from './somewhere-else';
|
|||||||
})
|
})
|
||||||
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -258,9 +237,6 @@ Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.t
|
|||||||
})
|
})
|
||||||
class Override extends BaseComponent {
|
class Override extends BaseComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
|
@@ -17,8 +17,6 @@ _______
|
|||||||
|
|
||||||
[Source code](../../../../lint/src/rules/ts/themed-component-selectors.ts)
|
[Source code](../../../../lint/src/rules/ts/themed-component-selectors.ts)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
@@ -33,7 +31,6 @@ _______
|
|||||||
class Something {
|
class Something {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### Themeable component selector should replace the original version, unthemed version should be changed to ds-base-
|
##### Themeable component selector should replace the original version, unthemed version should be changed to ds-base-
|
||||||
|
|
||||||
@@ -56,7 +53,6 @@ class ThemedSomething extends ThemedComponent<Something> {
|
|||||||
class OverrideSomething extends Something {
|
class OverrideSomething extends Something {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### Other themed component wrappers should not interfere
|
##### Other themed component wrappers should not interfere
|
||||||
|
|
||||||
@@ -73,7 +69,6 @@ class Something {
|
|||||||
class ThemedSomethingElse extends ThemedComponent<SomethingElse> {
|
class ThemedSomethingElse extends ThemedComponent<SomethingElse> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -90,9 +85,6 @@ Filename: `lint/test/fixture/src/app/test/test-themeable.component.ts`
|
|||||||
})
|
})
|
||||||
class TestThemeableComponent {
|
class TestThemeableComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -119,9 +111,6 @@ Filename: `lint/test/fixture/src/app/test/themed-test-themeable.component.ts`
|
|||||||
})
|
})
|
||||||
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -148,9 +137,6 @@ Filename: `lint/test/fixture/src/themes/test/app/test/test-themeable.component.t
|
|||||||
})
|
})
|
||||||
class TestThememeableComponent extends BaseComponent {
|
class TestThememeableComponent extends BaseComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
|
@@ -15,8 +15,6 @@ _______
|
|||||||
|
|
||||||
[Source code](../../../../lint/src/rules/ts/themed-component-usages.ts)
|
[Source code](../../../../lint/src/rules/ts/themed-component-usages.ts)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
|
|
||||||
@@ -32,7 +30,6 @@ const config = {
|
|||||||
b: ChipsComponent,
|
b: ChipsComponent,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### allow base class in class declaration
|
##### allow base class in class declaration
|
||||||
|
|
||||||
@@ -40,7 +37,6 @@ const config = {
|
|||||||
export class TestThemeableComponent {
|
export class TestThemeableComponent {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### allow inheriting from base class
|
##### allow inheriting from base class
|
||||||
|
|
||||||
@@ -50,7 +46,6 @@ import { TestThemeableComponent } from './app/test/test-themeable.component';
|
|||||||
export class ThemedAdminSidebarComponent extends ThemedComponent<TestThemeableComponent> {
|
export class ThemedAdminSidebarComponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### allow base class in ViewChild
|
##### allow base class in ViewChild
|
||||||
|
|
||||||
@@ -61,7 +56,6 @@ export class Something {
|
|||||||
@ViewChild(TestThemeableComponent) test: TestThemeableComponent;
|
@ViewChild(TestThemeableComponent) test: TestThemeableComponent;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### allow wrapper selectors in test queries
|
##### allow wrapper selectors in test queries
|
||||||
|
|
||||||
@@ -71,7 +65,6 @@ Filename: `lint/test/fixture/src/app/test/test.component.spec.ts`
|
|||||||
By.css('ds-themeable');
|
By.css('ds-themeable');
|
||||||
By.css('#test > ds-themeable > #nest');
|
By.css('#test > ds-themeable > #nest');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
##### allow wrapper selectors in cypress queries
|
##### allow wrapper selectors in cypress queries
|
||||||
|
|
||||||
@@ -81,7 +74,6 @@ Filename: `lint/test/fixture/src/app/test/test.component.cy.ts`
|
|||||||
By.css('ds-themeable');
|
By.css('ds-themeable');
|
||||||
By.css('#test > ds-themeable > #nest');
|
By.css('#test > ds-themeable > #nest');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -98,9 +90,6 @@ const config = {
|
|||||||
a: TestThemeableComponent,
|
a: TestThemeableComponent,
|
||||||
b: TestComponent,
|
b: TestComponent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -131,9 +120,6 @@ const config = {
|
|||||||
b: TestComponent,
|
b: TestComponent,
|
||||||
c: Something,
|
c: Something,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -164,9 +150,6 @@ const DECLARATIONS = [
|
|||||||
Something,
|
Something,
|
||||||
ThemedTestThemeableComponent,
|
ThemedTestThemeableComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -190,9 +173,6 @@ Filename: `lint/test/fixture/src/app/test/test.component.spec.ts`
|
|||||||
```typescript
|
```typescript
|
||||||
By.css('ds-themed-themeable');
|
By.css('ds-themed-themeable');
|
||||||
By.css('#test > ds-themed-themeable > #nest');
|
By.css('#test > ds-themed-themeable > #nest');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -214,9 +194,6 @@ Filename: `lint/test/fixture/src/app/test/test.component.spec.ts`
|
|||||||
```typescript
|
```typescript
|
||||||
By.css('ds-base-themeable');
|
By.css('ds-base-themeable');
|
||||||
By.css('#test > ds-base-themeable > #nest');
|
By.css('#test > ds-base-themeable > #nest');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -238,9 +215,6 @@ Filename: `lint/test/fixture/src/app/test/test.component.cy.ts`
|
|||||||
```typescript
|
```typescript
|
||||||
cy.get('ds-themed-themeable');
|
cy.get('ds-themed-themeable');
|
||||||
cy.get('#test > ds-themed-themeable > #nest');
|
cy.get('#test > ds-themed-themeable > #nest');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -262,9 +236,6 @@ Filename: `lint/test/fixture/src/app/test/test.component.cy.ts`
|
|||||||
```typescript
|
```typescript
|
||||||
cy.get('ds-base-themeable');
|
cy.get('ds-base-themeable');
|
||||||
cy.get('#test > ds-base-themeable > #nest');
|
cy.get('#test > ds-base-themeable > #nest');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -295,9 +266,6 @@ import { TestThemeableComponent } from '../../../../app/test/test-themeable.comp
|
|||||||
})
|
})
|
||||||
export class UsageComponent {
|
export class UsageComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
@@ -338,9 +306,6 @@ import { ThemedTestThemeableComponent } from '../../../../app/test/themed-test-t
|
|||||||
})
|
})
|
||||||
export class UsageComponent {
|
export class UsageComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
|
@@ -1,183 +0,0 @@
|
|||||||
[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 & 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 {
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -1,92 +0,0 @@
|
|||||||
[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
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -1,86 +0,0 @@
|
|||||||
[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
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -33,7 +33,6 @@ export const info = {
|
|||||||
[Message.USE_DSBTN_DISABLED]: 'Buttons should use the `dsBtnDisabled` directive instead of the `disabled` attribute.',
|
[Message.USE_DSBTN_DISABLED]: 'Buttons should use the `dsBtnDisabled` directive instead of the `disabled` attribute.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
optionDocs: [],
|
|
||||||
defaultOptions: [],
|
defaultOptions: [],
|
||||||
} as DSpaceESLintRuleInfo;
|
} as DSpaceESLintRuleInfo;
|
||||||
|
|
||||||
|
@@ -45,7 +45,6 @@ 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',
|
[Message.WRONG_SELECTOR]: 'Themeable components should be used via their ThemedComponent wrapper\'s selector',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
optionDocs: [],
|
|
||||||
defaultOptions: [],
|
defaultOptions: [],
|
||||||
} as DSpaceESLintRuleInfo;
|
} as DSpaceESLintRuleInfo;
|
||||||
|
|
||||||
|
@@ -1,304 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
@@ -10,24 +10,14 @@ import {
|
|||||||
RuleExports,
|
RuleExports,
|
||||||
} from '../../util/structure';
|
} from '../../util/structure';
|
||||||
/* eslint-disable import/no-namespace */
|
/* 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 themedComponentClasses from './themed-component-classes';
|
||||||
import * as themedComponentSelectors from './themed-component-selectors';
|
import * as themedComponentSelectors from './themed-component-selectors';
|
||||||
import * as themedComponentUsages from './themed-component-usages';
|
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 = [
|
const index = [
|
||||||
aliasImports,
|
|
||||||
sortStandaloneImports,
|
|
||||||
themedComponentClasses,
|
themedComponentClasses,
|
||||||
themedComponentSelectors,
|
themedComponentSelectors,
|
||||||
themedComponentUsages,
|
themedComponentUsages,
|
||||||
themedDecorators,
|
|
||||||
themedWrapperNoInputDefaults,
|
|
||||||
uniqueDecorators,
|
|
||||||
] as unknown as RuleExports[];
|
] as unknown as RuleExports[];
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
|
@@ -1,306 +0,0 @@
|
|||||||
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 {}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
@@ -52,7 +52,6 @@ export const info = {
|
|||||||
[Message.WRAPPER_IMPORTS_BASE]: 'Themed component wrapper classes must only import the base class',
|
[Message.WRAPPER_IMPORTS_BASE]: 'Themed component wrapper classes must only import the base class',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
optionDocs: [],
|
|
||||||
defaultOptions: [],
|
defaultOptions: [],
|
||||||
} as DSpaceESLintRuleInfo;
|
} as DSpaceESLintRuleInfo;
|
||||||
|
|
||||||
@@ -181,7 +180,7 @@ class Something {
|
|||||||
selector: 'ds-base-test-themable',
|
selector: 'ds-base-test-themable',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
class TestThemeableComponent {
|
class TestThemeableTomponent {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
@@ -196,7 +195,7 @@ class TestThemeableComponent {
|
|||||||
TestThemeableComponent,
|
TestThemeableComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
class ThemedTestThemeableComponent extends ThemedComponent<TestThemeableComponent> {
|
class ThemedTestThemeableTomponent extends ThemedComponent<TestThemeableComponent> {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
@@ -53,7 +53,6 @@ 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-\'',
|
[Message.THEMED]: 'Theme override of themeable component should have a selector starting with \'ds-themed-\'',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
optionDocs: [],
|
|
||||||
defaultOptions: [],
|
defaultOptions: [],
|
||||||
} as DSpaceESLintRuleInfo;
|
} as DSpaceESLintRuleInfo;
|
||||||
|
|
||||||
|
@@ -63,7 +63,6 @@ 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',
|
[Message.BASE_IN_MODULE]: 'Base themeable components shouldn\'t be declared in modules',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
optionDocs: [],
|
|
||||||
defaultOptions: [],
|
defaultOptions: [],
|
||||||
} as DSpaceESLintRuleInfo;
|
} as DSpaceESLintRuleInfo;
|
||||||
|
|
||||||
|
@@ -1,280 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
@@ -1,158 +0,0 @@
|
|||||||
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;
|
|
||||||
// }
|
|
||||||
// `,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
@@ -1,226 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
@@ -1,10 +0,0 @@
|
|||||||
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') ;
|
|
||||||
}
|
|
@@ -17,16 +17,10 @@ export type Meta = RuleMetaData<string, unknown[]>;
|
|||||||
export type Valid = ValidTestCase<unknown[]>;
|
export type Valid = ValidTestCase<unknown[]>;
|
||||||
export type Invalid = InvalidTestCase<string, unknown[]>;
|
export type Invalid = InvalidTestCase<string, unknown[]>;
|
||||||
|
|
||||||
export interface DSpaceESLintRuleInfo<T = unknown[], D = unknown[]> {
|
export interface DSpaceESLintRuleInfo {
|
||||||
name: string;
|
name: string;
|
||||||
meta: Meta,
|
meta: Meta,
|
||||||
optionDocs: D,
|
defaultOptions: unknown[],
|
||||||
defaultOptions: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OptionDoc {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NamedTests {
|
export interface NamedTests {
|
||||||
|
@@ -7,11 +7,6 @@ _______
|
|||||||
|
|
||||||
[Source code](../../../../lint/src/rules/<%- plugin.name.replace('dspace-angular-', '') %>/<%- rule.name %>.ts)
|
[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
|
### Examples
|
||||||
|
|
||||||
<% if (tests.valid) {%>
|
<% if (tests.valid) {%>
|
||||||
@@ -24,13 +19,6 @@ Filename: `<%- test.filename %>`
|
|||||||
```<%- plugin.language.toLowerCase() %>
|
```<%- plugin.language.toLowerCase() %>
|
||||||
<%- test.code.trim() %>
|
<%- test.code.trim() %>
|
||||||
```
|
```
|
||||||
<% if (test?.options?.length > 0) { %>
|
|
||||||
With options:
|
|
||||||
|
|
||||||
```json
|
|
||||||
<%- JSON.stringify(test.options[0], null, 2) %>
|
|
||||||
```
|
|
||||||
<% }%>
|
|
||||||
<% }) %>
|
<% }) %>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
@@ -43,15 +31,6 @@ Filename: `<%- test.filename %>`
|
|||||||
<% } %>
|
<% } %>
|
||||||
```<%- plugin.language.toLowerCase() %>
|
```<%- plugin.language.toLowerCase() %>
|
||||||
<%- test.code.trim() %>
|
<%- test.code.trim() %>
|
||||||
|
|
||||||
<% if (test?.options?.length > 0) { %>
|
|
||||||
With options:
|
|
||||||
|
|
||||||
```json
|
|
||||||
<%- JSON.stringify(test.options[0], null, 2) %>
|
|
||||||
```
|
|
||||||
<% }%>
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Will produce the following error(s):
|
Will produce the following error(s):
|
||||||
```
|
```
|
||||||
|
@@ -7,7 +7,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { TSESTree } from '@typescript-eslint/utils';
|
import { TSESTree } from '@typescript-eslint/utils';
|
||||||
import { RuleContext } from '@typescript-eslint/utils/ts-eslint';
|
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { basename } from 'path';
|
import { basename } from 'path';
|
||||||
import ts, { Identifier } from 'typescript';
|
import ts, { Identifier } from 'typescript';
|
||||||
@@ -264,18 +263,3 @@ export const DISALLOWED_THEME_SELECTORS = 'ds-(base|themed)-';
|
|||||||
export function fixSelectors(text: string): string {
|
export function fixSelectors(text: string): string {
|
||||||
return text.replaceAll(/ds-(base|themed)-/g, 'ds-');
|
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;
|
|
||||||
}
|
|
||||||
|
@@ -6,8 +6,6 @@
|
|||||||
* http://www.dspace.org/license/
|
* http://www.dspace.org/license/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RuleMetaData } from '@typescript-eslint/utils/ts-eslint';
|
|
||||||
|
|
||||||
import { default as html } from '../src/rules/html';
|
import { default as html } from '../src/rules/html';
|
||||||
import { default as ts } from '../src/rules/ts';
|
import { default as ts } from '../src/rules/ts';
|
||||||
|
|
||||||
@@ -71,16 +69,6 @@ describe('plugin structure', () => {
|
|||||||
expect(ruleExports.tests.valid.length).toBeGreaterThan(0);
|
expect(ruleExports.tests.valid.length).toBeGreaterThan(0);
|
||||||
expect(ruleExports.tests.invalid.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, []>);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
23477
package-lock.json
generated
23477
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
185
package.json
185
package.json
@@ -1,31 +1,30 @@
|
|||||||
{
|
{
|
||||||
"name": "dspace-angular",
|
"name": "dspace-angular",
|
||||||
"version": "9.0.0",
|
"version": "8.2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"config:watch": "nodemon",
|
"config:watch": "nodemon",
|
||||||
"test:rest": "ts-node --project ./tsconfig.ts-node.json scripts/test-rest.ts",
|
"test:rest": "ts-node --project ./tsconfig.ts-node.json scripts/test-rest.ts",
|
||||||
"start": "npm run start:prod",
|
"start": "yarn run start:prod",
|
||||||
"start:dev": "nodemon --exec \"cross-env NODE_ENV=development npm run serve\"",
|
"start:dev": "nodemon --exec \"cross-env NODE_ENV=development yarn run serve\"",
|
||||||
"start:prod": "npm run build:prod && cross-env NODE_ENV=production npm run serve:ssr",
|
"start:prod": "yarn run build:prod && cross-env NODE_ENV=production yarn run serve:ssr",
|
||||||
"start:mirador:prod": "npm run build:mirador && npm run start:prod",
|
"start:mirador:prod": "yarn run build:mirador && yarn run start:prod",
|
||||||
"preserve": "npm run base-href",
|
"preserve": "yarn base-href",
|
||||||
"serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts",
|
"serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts",
|
||||||
"serve:ssr": "node dist/server/main",
|
"serve:ssr": "node dist/server/main",
|
||||||
"analyze": "webpack-bundle-analyzer dist/browser/stats.json",
|
|
||||||
"build": "ng build --configuration development",
|
"build": "ng build --configuration development",
|
||||||
"build:stats": "ng build --stats-json",
|
"build:stats": "ng build --stats-json",
|
||||||
"build:prod": "cross-env NODE_ENV=production npm run build:ssr",
|
"build:prod": "cross-env NODE_ENV=production yarn run build:ssr",
|
||||||
"build:ssr": "ng build --configuration production && ng run dspace-angular:server:production",
|
"build:ssr": "ng build --configuration production && ng run dspace-angular:server:production",
|
||||||
"build:lint": "rimraf 'lint/dist/**/*.js' 'lint/dist/**/*.js.map' && tsc -b lint/tsconfig.json",
|
"build:lint": "rimraf 'lint/dist/**/*.js' 'lint/dist/**/*.js.map' && tsc -b lint/tsconfig.json",
|
||||||
"test": "ng test --source-map=true --watch=false --configuration test",
|
"test": "ng test --source-map=true --watch=false --configuration test",
|
||||||
"test:watch": "nodemon --exec \"ng test --source-map=true --watch=true --configuration test\"",
|
"test:watch": "nodemon --exec \"ng test --source-map=true --watch=true --configuration test\"",
|
||||||
"test:headless": "ng test --source-map=true --watch=false --configuration test --browsers=ChromeHeadless --code-coverage",
|
"test:headless": "ng test --source-map=true --watch=false --configuration test --browsers=ChromeHeadless --code-coverage",
|
||||||
"test:lint": "npm run build:lint && npm run test:lint:nobuild",
|
"test:lint": "yarn build:lint && yarn test:lint:nobuild",
|
||||||
"test:lint:nobuild": "jasmine --config=lint/jasmine.json",
|
"test:lint:nobuild": "jasmine --config=lint/jasmine.json",
|
||||||
"lint": "npm run build:lint && npm run lint:nobuild",
|
"lint": "yarn build:lint && yarn lint:nobuild",
|
||||||
"lint:nobuild": "ng lint",
|
"lint:nobuild": "ng lint",
|
||||||
"lint-fix": "npm run build:lint && ng lint --fix=true",
|
"lint-fix": "yarn build:lint && ng lint --fix=true",
|
||||||
"docs:lint": "ts-node --project ./lint/tsconfig.json ./lint/generate-docs.ts",
|
"docs:lint": "ts-node --project ./lint/tsconfig.json ./lint/generate-docs.ts",
|
||||||
"e2e": "cross-env NODE_ENV=production ng e2e",
|
"e2e": "cross-env NODE_ENV=production ng e2e",
|
||||||
"clean:dev:config": "rimraf src/assets/config.json",
|
"clean:dev:config": "rimraf src/assets/config.json",
|
||||||
@@ -36,8 +35,8 @@
|
|||||||
"clean:json": "rimraf *.records.json",
|
"clean:json": "rimraf *.records.json",
|
||||||
"clean:node": "rimraf node_modules",
|
"clean:node": "rimraf node_modules",
|
||||||
"clean:cli": "rimraf .angular/cache",
|
"clean:cli": "rimraf .angular/cache",
|
||||||
"clean:prod": "npm run clean:dist && npm run clean:log && npm run clean:doc && npm run clean:coverage && npm run clean:json",
|
"clean:prod": "yarn run clean:dist && yarn run clean:log && yarn run clean:doc && yarn run clean:coverage && yarn run clean:json",
|
||||||
"clean": "npm run clean:prod && npm run clean:dev:config && npm run clean:cli && npm run clean:node",
|
"clean": "yarn run clean:prod && yarn run clean:dev:config && yarn run clean:cli && yarn run clean:node",
|
||||||
"sync-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/sync-i18n-files.ts",
|
"sync-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/sync-i18n-files.ts",
|
||||||
"build:mirador": "webpack --config webpack/webpack.mirador.config.ts",
|
"build:mirador": "webpack --config webpack/webpack.mirador.config.ts",
|
||||||
"merge-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts",
|
"merge-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts",
|
||||||
@@ -46,7 +45,7 @@
|
|||||||
"env:yaml": "ts-node --project ./tsconfig.ts-node.json scripts/env-to-yaml.ts",
|
"env:yaml": "ts-node --project ./tsconfig.ts-node.json scripts/env-to-yaml.ts",
|
||||||
"base-href": "ts-node --project ./tsconfig.ts-node.json scripts/base-href.ts",
|
"base-href": "ts-node --project ./tsconfig.ts-node.json scripts/base-href.ts",
|
||||||
"check-circ-deps": "npx madge --exclude '(bitstream|bundle|collection|config-submission-form|eperson|item|version)\\.model\\.ts$' --circular --extensions ts ./",
|
"check-circ-deps": "npx madge --exclude '(bitstream|bundle|collection|config-submission-form|eperson|item|version)\\.model\\.ts$' --circular --extensions ts ./",
|
||||||
"postinstall": "npm run build:lint || echo 'Skipped DSpace ESLint plugins.'"
|
"postinstall": "yarn build:lint || echo 'Skipped DSpace ESLint plugins.'"
|
||||||
},
|
},
|
||||||
"browser": {
|
"browser": {
|
||||||
"fs": false,
|
"fs": false,
|
||||||
@@ -55,74 +54,36 @@
|
|||||||
"https": false
|
"https": false
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"overrides": {
|
|
||||||
"@kolkov/ngx-gallery": {
|
|
||||||
"@angular/animations": "^18.2.12",
|
|
||||||
"@angular/common": "^18.2.12",
|
|
||||||
"@angular/core": "^18.2.12"
|
|
||||||
},
|
|
||||||
"@ng-bootstrap/ng-bootstrap": {
|
|
||||||
"@angular/common": "^18.2.12",
|
|
||||||
"@angular/core": "^18.2.12",
|
|
||||||
"@angular/forms": "^18.2.12",
|
|
||||||
"@angular/localize": "^18.2.12"
|
|
||||||
},
|
|
||||||
"@ng-dynamic-forms/core": {
|
|
||||||
"@angular/common": "^18.2.12",
|
|
||||||
"@angular/core": "^18.2.12",
|
|
||||||
"@angular/forms": "^18.2.12"
|
|
||||||
},
|
|
||||||
"@ng-dynamic-forms/ui-ng-bootstrap": {
|
|
||||||
"ngx-mask": "14.2.4",
|
|
||||||
"@ng-bootstrap/ng-bootstrap": "^12.0.0",
|
|
||||||
"bootstrap": "^5.3"
|
|
||||||
},
|
|
||||||
"@nicky-lenaers/ngx-scroll-to": {
|
|
||||||
"@angular/common": "^18.2.12",
|
|
||||||
"@angular/core": "^18.2.12"
|
|
||||||
},
|
|
||||||
"eslint-plugin-unused-imports": {
|
|
||||||
"@typescript-eslint/eslint-plugin": "^7.2.0"
|
|
||||||
},
|
|
||||||
"ngx-infinite-scroll": {
|
|
||||||
"@angular/common": "^18.2.12",
|
|
||||||
"@angular/core": "^18.2.12"
|
|
||||||
},
|
|
||||||
"notistack": "3.0.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^18.2.12",
|
"@angular/animations": "^17.3.11",
|
||||||
"@angular/cdk": "^18.2.12",
|
"@angular/cdk": "^17.3.10",
|
||||||
"@angular/common": "^18.2.12",
|
"@angular/common": "^17.3.11",
|
||||||
"@angular/compiler": "^18.2.12",
|
"@angular/compiler": "^17.3.11",
|
||||||
"@angular/core": "^18.2.12",
|
"@angular/core": "^17.3.11",
|
||||||
"@angular/forms": "^18.2.12",
|
"@angular/forms": "^17.3.11",
|
||||||
"@angular/localize": "^18.2.12",
|
"@angular/localize": "17.3.12",
|
||||||
"@angular/platform-browser": "^18.2.12",
|
"@angular/platform-browser": "^17.3.11",
|
||||||
"@angular/platform-browser-dynamic": "^18.2.12",
|
"@angular/platform-browser-dynamic": "^17.3.11",
|
||||||
"@angular/platform-server": "^18.2.12",
|
"@angular/platform-server": "^17.3.11",
|
||||||
"@angular/router": "^18.2.12",
|
"@angular/router": "^17.3.11",
|
||||||
"@angular/ssr": "^18.2.19",
|
"@angular/ssr": "^17.3.17",
|
||||||
"@babel/runtime": "7.27.1",
|
"@babel/runtime": "7.27.6",
|
||||||
"@kolkov/ngx-gallery": "^2.0.1",
|
"@kolkov/ngx-gallery": "^2.0.1",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^12.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^11.0.0",
|
||||||
"@ng-dynamic-forms/core": "^16.0.0",
|
"@ng-dynamic-forms/core": "^16.0.0",
|
||||||
"@ng-dynamic-forms/ui-ng-bootstrap": "^16.0.0",
|
"@ng-dynamic-forms/ui-ng-bootstrap": "^16.0.0",
|
||||||
"@ngrx/effects": "^18.1.1",
|
"@ngrx/effects": "^17.1.1",
|
||||||
"@ngrx/operators": "^18.0.0",
|
"@ngrx/router-store": "^17.1.1",
|
||||||
"@ngrx/router-store": "^18.1.1",
|
"@ngrx/store": "^17.1.1",
|
||||||
"@ngrx/store": "^18.1.1",
|
"@ngx-translate/core": "^14.0.0",
|
||||||
"@ngx-translate/core": "^16.0.3",
|
|
||||||
"@nicky-lenaers/ngx-scroll-to": "^14.0.0",
|
"@nicky-lenaers/ngx-scroll-to": "^14.0.0",
|
||||||
"@terraformer/wkt": "^2.2.1",
|
|
||||||
"altcha": "^0.9.0",
|
|
||||||
"angulartics2": "^12.2.0",
|
"angulartics2": "^12.2.0",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.10.0",
|
||||||
"bootstrap": "^5.3",
|
"bootstrap": "^4.6.1",
|
||||||
"cerialize": "0.1.18",
|
"cerialize": "0.1.18",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"compression": "^1.7.5",
|
"compression": "^1.8.0",
|
||||||
"cookie-parser": "1.4.7",
|
"cookie-parser": "1.4.7",
|
||||||
"core-js": "^3.42.0",
|
"core-js": "^3.42.0",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
@@ -132,7 +93,7 @@
|
|||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"express-rate-limit": "^5.1.3",
|
"express-rate-limit": "^5.1.3",
|
||||||
"fast-json-patch": "^3.1.1",
|
"fast-json-patch": "^3.1.1",
|
||||||
"filesize": "^10.1.6",
|
"filesize": "^6.1.0",
|
||||||
"http-proxy-middleware": "^2.0.9",
|
"http-proxy-middleware": "^2.0.9",
|
||||||
"http-terminator": "^3.2.0",
|
"http-terminator": "^3.2.0",
|
||||||
"isbot": "^5.1.28",
|
"isbot": "^5.1.28",
|
||||||
@@ -141,71 +102,69 @@
|
|||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"jsonschema": "1.5.0",
|
"jsonschema": "1.5.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"leaflet": "^1.9.4",
|
"klaro": "^0.7.18",
|
||||||
"leaflet-providers": "^2.0.0",
|
"lodash": "^4.17.21",
|
||||||
"leaflet.markercluster": "^1.5.3",
|
|
||||||
"lodash-es": "^4.17.21",
|
|
||||||
"lru-cache": "^7.14.1",
|
"lru-cache": "^7.14.1",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"mirador": "^3.4.3",
|
"mirador": "^3.4.3",
|
||||||
"mirador-dl-plugin": "^0.13.0",
|
"mirador-dl-plugin": "^0.13.0",
|
||||||
"mirador-share-plugin": "^0.16.0",
|
"mirador-share-plugin": "^0.16.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ng2-file-upload": "7.0.1",
|
"ng2-file-upload": "5.0.0",
|
||||||
"ng2-nouislider": "^2.0.0",
|
"ng2-nouislider": "^2.0.0",
|
||||||
"ngx-infinite-scroll": "^18.0.0",
|
"ngx-infinite-scroll": "^16.0.0",
|
||||||
"ngx-matomo-client": "^6.4.1",
|
|
||||||
"ngx-pagination": "6.0.3",
|
"ngx-pagination": "6.0.3",
|
||||||
"ngx-skeleton-loader": "^9.0.0",
|
"ngx-skeleton-loader": "^9.0.0",
|
||||||
"ngx-ui-switch": "^15.0.0",
|
"ngx-ui-switch": "^14.1.0",
|
||||||
"nouislider": "^15.7.1",
|
"nouislider": "^15.7.1",
|
||||||
"orejime": "^2.3.1",
|
|
||||||
"pem": "1.14.8",
|
"pem": "1.14.8",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.0",
|
"rxjs": "^7.8.2",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"zone.js": "~0.14.10"
|
"zone.js": "~0.14.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-builders/custom-webpack": "~18.0.0",
|
"@angular-builders/custom-webpack": "~17.0.2",
|
||||||
"@angular-devkit/build-angular": "^18.2.19",
|
"@angular-devkit/build-angular": "^17.3.17",
|
||||||
"@angular-eslint/builder": "^18.4.1",
|
"@angular-eslint/builder": "17.5.3",
|
||||||
"@angular-eslint/bundled-angular-compiler": "^18.4.1",
|
"@angular-eslint/bundled-angular-compiler": "17.5.3",
|
||||||
"@angular-eslint/eslint-plugin": "^18.4.1",
|
"@angular-eslint/eslint-plugin": "17.5.3",
|
||||||
"@angular-eslint/eslint-plugin-template": "^18.4.1",
|
"@angular-eslint/eslint-plugin-template": "17.5.3",
|
||||||
"@angular-eslint/schematics": "^18.4.1",
|
"@angular-eslint/schematics": "17.5.3",
|
||||||
"@angular-eslint/template-parser": "^18.4.1",
|
"@angular-eslint/template-parser": "17.5.3",
|
||||||
"@angular-eslint/utils": "^18.4.1",
|
"@angular/cli": "^17.3.17",
|
||||||
"@angular/cli": "^18.2.19",
|
"@angular/compiler-cli": "^17.3.11",
|
||||||
"@angular/compiler-cli": "^18.2.12",
|
"@angular/language-service": "^17.3.11",
|
||||||
"@angular/language-service": "^18.2.12",
|
|
||||||
"@cypress/schematic": "^1.5.0",
|
"@cypress/schematic": "^1.5.0",
|
||||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
"@ngrx/store-devtools": "^18.1.1",
|
"@material-ui/core": "^4.12.4",
|
||||||
"@ngtools/webpack": "^18.2.19",
|
"@material-ui/icons": "^4.11.3",
|
||||||
|
"@ngrx/store-devtools": "^17.1.1",
|
||||||
|
"@ngtools/webpack": "^16.2.16",
|
||||||
"@types/deep-freeze": "0.1.5",
|
"@types/deep-freeze": "0.1.5",
|
||||||
"@types/ejs": "^3.1.2",
|
"@types/ejs": "^3.1.2",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/grecaptcha": "^3.0.9",
|
"@types/grecaptcha": "^3.0.9",
|
||||||
"@types/jasmine": "~3.6.0",
|
"@types/jasmine": "~3.6.0",
|
||||||
"@types/js-cookie": "2.2.6",
|
"@types/js-cookie": "2.2.6",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash": "^4.17.17",
|
||||||
"@types/node": "^14.14.9",
|
"@types/node": "^14.14.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||||
"@typescript-eslint/parser": "^7.18.0",
|
"@typescript-eslint/parser": "^7.2.0",
|
||||||
"@typescript-eslint/rule-tester": "^7.18.0",
|
"@typescript-eslint/rule-tester": "^7.2.0",
|
||||||
"@typescript-eslint/utils": "^7.18.0",
|
"@typescript-eslint/utils": "^7.2.0",
|
||||||
"axe-core": "^4.10.2",
|
"axe-core": "^4.10.3",
|
||||||
"compression-webpack-plugin": "^9.2.0",
|
"compression-webpack-plugin": "^9.2.0",
|
||||||
"copy-webpack-plugin": "^6.4.1",
|
"copy-webpack-plugin": "^6.4.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
"csstype": "^3.1.3",
|
||||||
"cypress": "^13.17.0",
|
"cypress": "^13.17.0",
|
||||||
"cypress-axe": "^1.6.0",
|
"cypress-axe": "^1.6.0",
|
||||||
"deep-freeze": "0.0.1",
|
"deep-freeze": "0.0.1",
|
||||||
"eslint": "^8.39.0",
|
"eslint": "^8.39.0",
|
||||||
"eslint-plugin-deprecation": "^1.4.1",
|
"eslint-plugin-deprecation": "^1.4.1",
|
||||||
"eslint-plugin-dspace-angular-html": "file:./lint/dist/src/rules/html",
|
"eslint-plugin-dspace-angular-html": "link:./lint/dist/src/rules/html",
|
||||||
"eslint-plugin-dspace-angular-ts": "file:./lint/dist/src/rules/ts",
|
"eslint-plugin-dspace-angular-ts": "link:./lint/dist/src/rules/ts",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-import-newlines": "^1.3.1",
|
"eslint-plugin-import-newlines": "^1.3.1",
|
||||||
"eslint-plugin-jsdoc": "^45.0.0",
|
"eslint-plugin-jsdoc": "^45.0.0",
|
||||||
@@ -218,26 +177,30 @@
|
|||||||
"jasmine": "^3.8.0",
|
"jasmine": "^3.8.0",
|
||||||
"jasmine-core": "^3.8.0",
|
"jasmine-core": "^3.8.0",
|
||||||
"jasmine-marbles": "0.9.2",
|
"jasmine-marbles": "0.9.2",
|
||||||
"karma": "^6.4.4",
|
"karma": "^6.4.2",
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
"karma-coverage-istanbul-reporter": "~3.0.3",
|
"karma-coverage-istanbul-reporter": "~3.0.3",
|
||||||
"karma-jasmine": "~4.0.0",
|
"karma-jasmine": "~4.0.0",
|
||||||
"karma-jasmine-html-reporter": "^1.5.0",
|
"karma-jasmine-html-reporter": "^1.5.0",
|
||||||
"karma-mocha-reporter": "2.2.5",
|
"karma-mocha-reporter": "2.2.5",
|
||||||
"ng-mocks": "^14.13.4",
|
"ng-mocks": "^14.13.5",
|
||||||
"ngx-mask": "14.2.4",
|
"ngx-mask": "14.2.4",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"postcss": "^8.5",
|
"postcss": "^8.5",
|
||||||
"postcss-import": "^14.0.0",
|
"postcss-import": "^14.0.0",
|
||||||
"postcss-loader": "^4.0.3",
|
"postcss-loader": "^4.0.3",
|
||||||
"postcss-preset-env": "^7.4.2",
|
"postcss-preset-env": "^7.4.2",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react": "^16.14.0",
|
||||||
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
|
"react-dom": "^16.14.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"sass": "~1.89.0",
|
"sass": "~1.89.2",
|
||||||
"sass-loader": "^12.6.0",
|
"sass-loader": "^12.6.0",
|
||||||
"sass-resources-loader": "^2.2.5",
|
"sass-resources-loader": "^2.2.5",
|
||||||
"ts-node": "^8.10.2",
|
"ts-node": "^8.10.2",
|
||||||
"typescript": "~5.4.5",
|
"typescript": "~5.4.5",
|
||||||
"webpack": "5.99.8",
|
"webpack": "5.99.9",
|
||||||
"webpack-cli": "^5.1.4",
|
"webpack-cli": "^5.1.4",
|
||||||
"webpack-dev-server": "^4.15.1"
|
"webpack-dev-server": "^4.15.1"
|
||||||
}
|
}
|
||||||
|
@@ -38,7 +38,7 @@ parseCliInput();
|
|||||||
function parseCliInput() {
|
function parseCliInput() {
|
||||||
program
|
program
|
||||||
.option('-d, --output-dir <output-dir>', 'output dir when running script on all language files', projectRoot(LANGUAGE_FILES_LOCATION))
|
.option('-d, --output-dir <output-dir>', 'output dir when running script on all language files', projectRoot(LANGUAGE_FILES_LOCATION))
|
||||||
.option('-s, --source-dir <source-dir>', 'source dir of translations to be merged')
|
.option('-s, --source-dir <source-dir>', 'source dir of transalations to be merged')
|
||||||
.usage('(-s <source-dir> [-d <output-dir>])')
|
.usage('(-s <source-dir> [-d <output-dir>])')
|
||||||
.parse(process.argv);
|
.parse(process.argv);
|
||||||
|
|
||||||
|
@@ -20,10 +20,10 @@ import 'reflect-metadata';
|
|||||||
|
|
||||||
/* eslint-disable import/no-namespace */
|
/* eslint-disable import/no-namespace */
|
||||||
import * as morgan from 'morgan';
|
import * as morgan from 'morgan';
|
||||||
import express from 'express';
|
import * as express from 'express';
|
||||||
import * as ejs from 'ejs';
|
import * as ejs from 'ejs';
|
||||||
import * as compression from 'compression';
|
import * as compression from 'compression';
|
||||||
import expressStaticGzip from 'express-static-gzip';
|
import * as expressStaticGzip from 'express-static-gzip';
|
||||||
/* eslint-enable import/no-namespace */
|
/* eslint-enable import/no-namespace */
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import LRU from 'lru-cache';
|
import LRU from 'lru-cache';
|
||||||
@@ -103,7 +103,7 @@ export function app() {
|
|||||||
* If production mode is enabled in the environment file:
|
* If production mode is enabled in the environment file:
|
||||||
* - Enable Angular's production mode
|
* - Enable Angular's production mode
|
||||||
* - Initialize caching of SSR rendered pages (if enabled in config.yml)
|
* - Initialize caching of SSR rendered pages (if enabled in config.yml)
|
||||||
* - Enable compression for SSR responses. See [compression](https://github.com/expressjs/compression)
|
* - Enable compression for SSR reponses. See [compression](https://github.com/expressjs/compression)
|
||||||
*/
|
*/
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
enableProdMode();
|
enableProdMode();
|
||||||
@@ -437,7 +437,7 @@ function checkCacheForRequest(cacheName: string, cache: LRU<string, any>, req, r
|
|||||||
if (environment.cache.serverSide.debug) { console.log(`CACHE EXPIRED FOR ${key} in ${cacheName} cache. Re-rendering...`); }
|
if (environment.cache.serverSide.debug) { console.log(`CACHE EXPIRED FOR ${key} in ${cacheName} cache. Re-rendering...`); }
|
||||||
// Update cached copy by rerendering server-side
|
// Update cached copy by rerendering server-side
|
||||||
// NOTE: In this scenario the currently cached copy will be returned to the current user.
|
// NOTE: In this scenario the currently cached copy will be returned to the current user.
|
||||||
// This re-render is performed behind the scenes to update cached copy for next user.
|
// This re-render is peformed behind the scenes to update cached copy for next user.
|
||||||
serverSideRender(req, res, next, false);
|
serverSideRender(req, res, next, false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@@ -1,13 +1,19 @@
|
|||||||
<ngb-accordion #acc="ngbAccordion" [activeIds]="'browse'">
|
<ngb-accordion #acc="ngbAccordion" [activeIds]="'browse'">
|
||||||
<ngb-panel [id]="'browse'">
|
<ngb-panel [id]="'browse'">
|
||||||
<ng-template ngbPanelTitle>
|
<ng-template ngbPanelHeader>
|
||||||
<div class="w-100 d-flex gap-3 justify-content-between collapse-toggle" (click)="acc.toggle('browse')"
|
<div class="w-100 d-flex gap-3 justify-content-between collapse-toggle" ngbPanelToggle (click)="acc.toggle('browse')"
|
||||||
data-test="browse">
|
data-test="browse">
|
||||||
<button type="button" class="btn btn-link p-0" (click)="$event.preventDefault()"
|
<button type="button" class="btn btn-link p-0" (click)="$event.preventDefault()"
|
||||||
[attr.aria-expanded]="acc.isExpanded('browse')"
|
[attr.aria-expanded]="acc.isExpanded('browse')"
|
||||||
aria-controls="bulk-access-browse-panel-content">
|
aria-controls="bulk-access-browse-panel-content">
|
||||||
{{ 'admin.access-control.bulk-access-browse.header' | translate }}
|
{{ 'admin.access-control.bulk-access-browse.header' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
<div class="text-right d-flex gap-2">
|
||||||
|
<div class="d-flex my-auto">
|
||||||
|
<span *ngIf="acc.isExpanded('browse')" class="fas fa-chevron-up fa-fw"></span>
|
||||||
|
<span *ngIf="!acc.isExpanded('browse')" class="fas fa-chevron-down fa-fw"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template ngbPanelContent>
|
<ng-template ngbPanelContent>
|
||||||
@@ -16,11 +22,11 @@
|
|||||||
<li [ngbNavItem]="'search'" role="presentation">
|
<li [ngbNavItem]="'search'" role="presentation">
|
||||||
<a ngbNavLink>{{'admin.access-control.bulk-access-browse.search.header' | translate}}</a>
|
<a ngbNavLink>{{'admin.access-control.bulk-access-browse.search.header' | translate}}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<div class="bulk-access-search">
|
<div class="mx-n3">
|
||||||
<ds-search [configuration]="'administrativeBulkAccess'"
|
<ds-search [configuration]="'administrativeBulkAccess'"
|
||||||
[selectable]="true"
|
[selectable]="true"
|
||||||
[selectionConfig]="{ repeatable: true, listId: listId }"
|
[selectionConfig]="{ repeatable: true, listId: listId }"
|
||||||
[showThumbnails]="false"></ds-search>
|
[showThumbnails]="false"></ds-search>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
@@ -36,25 +42,21 @@
|
|||||||
[showPaginator]="false"
|
[showPaginator]="false"
|
||||||
(prev)="pagePrev()"
|
(prev)="pagePrev()"
|
||||||
(next)="pageNext()">
|
(next)="pageNext()">
|
||||||
@if ((objectsSelected$|async)?.hasSucceeded) {
|
<ul *ngIf="(objectsSelected$|async)?.hasSucceeded" class="list-unstyled ml-4">
|
||||||
<ul class="list-unstyled ms-4">
|
<li *ngFor='let object of (objectsSelected$|async)?.payload?.page | paginate: { itemsPerPage: (paginationOptions$ | async).pageSize,
|
||||||
@for (object of (objectsSelected$|async)?.payload?.page | paginate: { itemsPerPage: (paginationOptions$ | async).pageSize,
|
currentPage: (paginationOptions$ | async).currentPage, totalItems: (objectsSelected$|async)?.payload?.page.length }; let i = index; let last = last '
|
||||||
currentPage: (paginationOptions$ | async).currentPage, totalItems: (objectsSelected$|async)?.payload?.page.length }; track object; let i = $index; let last = $last) {
|
class="mt-4 mb-4 d-flex"
|
||||||
<li
|
[attr.data-test]="'list-object' | dsBrowserOnly">
|
||||||
class="mt-4 mb-4 d-flex"
|
<ds-selectable-list-item-control [index]="i"
|
||||||
[attr.data-test]="'list-object' | dsBrowserOnly">
|
[object]="object"
|
||||||
<ds-selectable-list-item-control [index]="i"
|
[selectionConfig]="{ repeatable: true, listId: listId }"></ds-selectable-list-item-control>
|
||||||
[object]="object"
|
<ds-listable-object-component-loader [listID]="listId"
|
||||||
[selectionConfig]="{ repeatable: true, listId: listId }"></ds-selectable-list-item-control>
|
[index]="i"
|
||||||
<ds-listable-object-component-loader [listID]="listId"
|
[object]="object"
|
||||||
[index]="i"
|
[showThumbnails]="false"
|
||||||
[object]="object"
|
[viewMode]="'list'"></ds-listable-object-component-loader>
|
||||||
[showThumbnails]="false"
|
</li>
|
||||||
[viewMode]="'list'"></ds-listable-object-component-loader>
|
</ul>
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
</ds-pagination>
|
</ds-pagination>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
@@ -1,4 +0,0 @@
|
|||||||
.bulk-access-search {
|
|
||||||
margin-right: calc(var(--bs-gutter-x, 1.5rem) / -2);
|
|
||||||
margin-left: calc(var(--bs-gutter-x, 1.5rem) / -2);
|
|
||||||
}
|
|
||||||
|
@@ -1,4 +1,8 @@
|
|||||||
import { AsyncPipe } from '@angular/common';
|
import {
|
||||||
|
AsyncPipe,
|
||||||
|
NgForOf,
|
||||||
|
NgIf,
|
||||||
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
Input,
|
Input,
|
||||||
@@ -51,16 +55,18 @@ import { BrowserOnlyPipe } from '../../../shared/utils/browser-only.pipe';
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
AsyncPipe,
|
|
||||||
BrowserOnlyPipe,
|
|
||||||
ListableObjectComponentLoaderComponent,
|
|
||||||
NgbAccordionModule,
|
|
||||||
NgbNavModule,
|
|
||||||
NgxPaginationModule,
|
|
||||||
PaginationComponent,
|
PaginationComponent,
|
||||||
SelectableListItemControlComponent,
|
AsyncPipe,
|
||||||
ThemedSearchComponent,
|
NgbAccordionModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
|
NgIf,
|
||||||
|
NgbNavModule,
|
||||||
|
ThemedSearchComponent,
|
||||||
|
BrowserOnlyPipe,
|
||||||
|
NgForOf,
|
||||||
|
NgxPaginationModule,
|
||||||
|
SelectableListItemControlComponent,
|
||||||
|
ListableObjectComponentLoaderComponent,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
<button class="btn btn-outline-primary me-3" (click)="reset()">
|
<button class="btn btn-outline-primary mr-3" (click)="reset()">
|
||||||
{{ 'access-control-cancel' | translate }}
|
{{ 'access-control-cancel' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary" [dsBtnDisabled]="!canExport()" (click)="submit()">
|
<button class="btn btn-primary" [dsBtnDisabled]="!canExport()" (click)="submit()">
|
||||||
|
@@ -26,10 +26,10 @@ import { BulkAccessSettingsComponent } from './settings/bulk-access-settings.com
|
|||||||
templateUrl: './bulk-access.component.html',
|
templateUrl: './bulk-access.component.html',
|
||||||
styleUrls: ['./bulk-access.component.scss'],
|
styleUrls: ['./bulk-access.component.scss'],
|
||||||
imports: [
|
imports: [
|
||||||
BtnDisabledDirective,
|
|
||||||
BulkAccessBrowseComponent,
|
|
||||||
BulkAccessSettingsComponent,
|
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
|
BulkAccessSettingsComponent,
|
||||||
|
BulkAccessBrowseComponent,
|
||||||
|
BtnDisabledDirective,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
@@ -1,11 +1,17 @@
|
|||||||
<ngb-accordion #acc="ngbAccordion" [activeIds]="'settings'">
|
<ngb-accordion #acc="ngbAccordion" [activeIds]="'settings'">
|
||||||
<ngb-panel [id]="'settings'">
|
<ngb-panel [id]="'settings'">
|
||||||
<ng-template ngbPanelTitle>
|
<ng-template ngbPanelHeader>
|
||||||
<div class="w-100 d-flex gap-3 justify-content-between collapse-toggle" ngbPanelToggle (click)="acc.toggle('settings')" data-test="settings">
|
<div class="w-100 d-flex gap-3 justify-content-between collapse-toggle" ngbPanelToggle (click)="acc.toggle('settings')" data-test="settings">
|
||||||
<button type="button" class="btn btn-link p-0" (click)="$event.preventDefault()" [attr.aria-expanded]="acc.isExpanded('settings')"
|
<button type="button" class="btn btn-link p-0" (click)="$event.preventDefault()" [attr.aria-expanded]="acc.isExpanded('settings')"
|
||||||
aria-controls="bulk-access-settings-panel-content">
|
aria-controls="bulk-access-settings-panel-content">
|
||||||
{{ 'admin.access-control.bulk-access-settings.header' | translate }}
|
{{ 'admin.access-control.bulk-access-settings.header' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
<div class="text-right d-flex gap-2">
|
||||||
|
<div class="d-flex my-auto">
|
||||||
|
<span *ngIf="acc.isExpanded('settings')" class="fas fa-chevron-up fa-fw"></span>
|
||||||
|
<span *ngIf="!acc.isExpanded('settings')" class="fas fa-chevron-down fa-fw"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template ngbPanelContent>
|
<ng-template ngbPanelContent>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
|
import { NgIf } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
@@ -14,9 +14,10 @@ import { AccessControlFormContainerComponent } from '../../../shared/access-cont
|
|||||||
styleUrls: ['./bulk-access-settings.component.scss'],
|
styleUrls: ['./bulk-access-settings.component.scss'],
|
||||||
exportAs: 'dsBulkSettings',
|
exportAs: 'dsBulkSettings',
|
||||||
imports: [
|
imports: [
|
||||||
AccessControlFormContainerComponent,
|
|
||||||
NgbAccordionModule,
|
NgbAccordionModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
|
NgIf,
|
||||||
|
AccessControlFormContainerComponent,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -5,10 +5,10 @@
|
|||||||
<h1 id="header" class="pb-2">{{labelPrefix + 'head' | translate}}</h1>
|
<h1 id="header" class="pb-2">{{labelPrefix + 'head' | translate}}</h1>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button class="me-auto btn btn-success addEPerson-button"
|
<button class="mr-auto btn btn-success addEPerson-button"
|
||||||
[routerLink]="'create'">
|
[routerLink]="'create'">
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus"></i>
|
||||||
<span class="d-none d-sm-inline ms-1">{{labelPrefix + 'button.add' | translate}}</span>
|
<span class="d-none d-sm-inline ml-1">{{labelPrefix + 'button.add' | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,84 +18,77 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
|
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
|
||||||
<div>
|
<div>
|
||||||
<select name="scope" id="scope" formControlName="scope" class="form-select" aria-label="Search scope">
|
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
|
||||||
<option value="metadata">{{labelPrefix + 'search.scope.metadata' | translate}}</option>
|
<option value="metadata">{{labelPrefix + 'search.scope.metadata' | translate}}</option>
|
||||||
<option value="email">{{labelPrefix + 'search.scope.email' | translate}}</option>
|
<option value="email">{{labelPrefix + 'search.scope.email' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow-1 me-3 ms-3">
|
<div class="flex-grow-1 mr-3 ml-3">
|
||||||
<div class="mb-3 input-group">
|
<div class="form-group input-group">
|
||||||
<input type="text" name="query" id="query" formControlName="query"
|
<input type="text" name="query" id="query" formControlName="query"
|
||||||
class="form-control" [attr.aria-label]="labelPrefix + 'search.placeholder' | translate"
|
class="form-control" [attr.aria-label]="labelPrefix + 'search.placeholder' | translate"
|
||||||
[placeholder]="(labelPrefix + 'search.placeholder' | translate)">
|
[placeholder]="(labelPrefix + 'search.placeholder' | translate)">
|
||||||
<span class="input-group-append">
|
<span class="input-group-append">
|
||||||
<button type="submit" class="search-button btn btn-primary">
|
<button type="submit" class="search-button btn btn-primary">
|
||||||
<i class="fas fa-search"></i> {{ labelPrefix + 'search.button' | translate }}
|
<i class="fas fa-search"></i> {{ labelPrefix + 'search.button' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button (click)="clearFormAndResetResult();"
|
<button (click)="clearFormAndResetResult();"
|
||||||
class="search-button btn btn-secondary">{{labelPrefix + 'button.see-all' | translate}}</button>
|
class="search-button btn btn-secondary">{{labelPrefix + 'button.see-all' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@if (searching$ | async) {
|
<ds-loading *ngIf="searching$ | async"></ds-loading>
|
||||||
<ds-loading></ds-loading>
|
<ds-pagination
|
||||||
}
|
*ngIf="(pageInfoState$ | async)?.totalElements > 0 && (searching$ | async) !== true"
|
||||||
@if ((pageInfoState$ | async)?.totalElements > 0 && (searching$ | async) !== true) {
|
[paginationOptions]="config"
|
||||||
<ds-pagination
|
[collectionSize]="(pageInfoState$ | async)?.totalElements"
|
||||||
[paginationOptions]="config"
|
[hideGear]="true"
|
||||||
[collectionSize]="(pageInfoState$ | async)?.totalElements"
|
[hidePagerWhenSinglePage]="true">
|
||||||
[hideGear]="true"
|
|
||||||
[hidePagerWhenSinglePage]="true">
|
<div class="table-responsive">
|
||||||
<div class="table-responsive">
|
<table id="epeople" class="table table-striped table-hover table-bordered">
|
||||||
<table id="epeople" class="table table-striped table-hover table-bordered">
|
<thead>
|
||||||
<thead>
|
<tr>
|
||||||
<tr>
|
<th scope="col">{{labelPrefix + 'table.id' | translate}}</th>
|
||||||
<th scope="col">{{labelPrefix + 'table.id' | translate}}</th>
|
<th scope="col">{{labelPrefix + 'table.name' | translate}}</th>
|
||||||
<th scope="col">{{labelPrefix + 'table.name' | translate}}</th>
|
<th scope="col">{{labelPrefix + 'table.email' | translate}}</th>
|
||||||
<th scope="col">{{labelPrefix + 'table.email' | translate}}</th>
|
<th>{{labelPrefix + 'table.edit' | translate}}</th>
|
||||||
<th>{{labelPrefix + 'table.edit' | translate}}</th>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
</thead>
|
<tbody>
|
||||||
<tbody>
|
<tr *ngFor="let epersonDto of (ePeopleDto$ | async)?.page"
|
||||||
@for (epersonDto of (ePeopleDto$ | async)?.page; track epersonDto) {
|
[ngClass]="{'table-primary' : (activeEPerson$ | async) === epersonDto.eperson}">
|
||||||
<tr
|
<td>{{epersonDto.eperson.id}}</td>
|
||||||
[ngClass]="{'table-primary' : (activeEPerson$ | async) === epersonDto.eperson}">
|
<td>{{ dsoNameService.getName(epersonDto.eperson) }}</td>
|
||||||
<td>{{epersonDto.eperson.id}}</td>
|
<td>{{epersonDto.eperson.email}}</td>
|
||||||
<td>{{ dsoNameService.getName(epersonDto.eperson) }}</td>
|
<td>
|
||||||
<td>{{epersonDto.eperson.email}}</td>
|
<div class="btn-group edit-field">
|
||||||
<td>
|
<button [routerLink]="getEditEPeoplePage(epersonDto.eperson.id)"
|
||||||
<div class="btn-group edit-field">
|
|
||||||
<button [routerLink]="getEditEPeoplePage(epersonDto.eperson.id)"
|
|
||||||
class="btn btn-outline-primary btn-sm access-control-editEPersonButton"
|
class="btn btn-outline-primary btn-sm access-control-editEPersonButton"
|
||||||
title="{{labelPrefix + 'table.edit.buttons.edit' | translate: { name: dsoNameService.getName(epersonDto.eperson) } }}">
|
title="{{labelPrefix + 'table.edit.buttons.edit' | translate: { name: dsoNameService.getName(epersonDto.eperson) } }}">
|
||||||
<i class="fas fa-edit fa-fw"></i>
|
<i class="fas fa-edit fa-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
@if (epersonDto.ableToDelete) {
|
<button *ngIf="epersonDto.ableToDelete" (click)="deleteEPerson(epersonDto.eperson)"
|
||||||
<button (click)="deleteEPerson(epersonDto.eperson)"
|
class="delete-button btn btn-outline-danger btn-sm access-control-deleteEPersonButton"
|
||||||
class="delete-button btn btn-outline-danger btn-sm access-control-deleteEPersonButton"
|
title="{{labelPrefix + 'table.edit.buttons.remove' | translate: { name: dsoNameService.getName(epersonDto.eperson) } }}">
|
||||||
title="{{labelPrefix + 'table.edit.buttons.remove' | translate: { name: dsoNameService.getName(epersonDto.eperson) } }}">
|
<i class="fas fa-trash-alt fa-fw"></i>
|
||||||
<i class="fas fa-trash-alt fa-fw"></i>
|
</button>
|
||||||
</button>
|
</div>
|
||||||
}
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
</td>
|
</tbody>
|
||||||
</tr>
|
</table>
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</ds-pagination>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if ((pageInfoState$ | async)?.totalElements === 0) {
|
|
||||||
<div class="alert alert-info w-100 mb-2" role="alert">
|
|
||||||
{{labelPrefix + 'no-items' | translate}}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(pageInfoState$ | async)?.totalElements === 0" class="alert alert-info w-100 mb-2" role="alert">
|
||||||
|
{{labelPrefix + 'no-items' | translate}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -27,7 +27,7 @@ import {
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of as observableOf,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
@@ -85,7 +85,7 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
}), this.allEpeople));
|
}), this.allEpeople));
|
||||||
},
|
},
|
||||||
getActiveEPerson(): Observable<EPerson> {
|
getActiveEPerson(): Observable<EPerson> {
|
||||||
return of(this.activeEPerson);
|
return observableOf(this.activeEPerson);
|
||||||
},
|
},
|
||||||
searchByScope(scope: string, query: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<EPerson>>> {
|
searchByScope(scope: string, query: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
if (scope === 'email') {
|
if (scope === 'email') {
|
||||||
@@ -129,7 +129,7 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {
|
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {
|
||||||
return (ePerson2.uuid !== ePerson.uuid);
|
return (ePerson2.uuid !== ePerson.uuid);
|
||||||
});
|
});
|
||||||
return of(true);
|
return observableOf(true);
|
||||||
},
|
},
|
||||||
editEPerson(ePerson: EPerson) {
|
editEPerson(ePerson: EPerson) {
|
||||||
this.activeEPerson = ePerson;
|
this.activeEPerson = ePerson;
|
||||||
@@ -145,7 +145,7 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
isAuthorized: of(true),
|
isAuthorized: observableOf(true),
|
||||||
});
|
});
|
||||||
builderService = getMockFormBuilderService();
|
builderService = getMockFormBuilderService();
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
fixture = TestBed.createComponent(EPeopleRegistryComponent);
|
fixture = TestBed.createComponent(EPeopleRegistryComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
modalService = TestBed.inject(NgbModal);
|
modalService = TestBed.inject(NgbModal);
|
||||||
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: of(true) }) }));
|
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: observableOf(true) }) }));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -261,7 +261,7 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
|
|
||||||
|
|
||||||
it('should hide delete EPerson button when the isAuthorized returns false', () => {
|
it('should hide delete EPerson button when the isAuthorized returns false', () => {
|
||||||
spyOn(authorizationService, 'isAuthorized').and.returnValue(of(false));
|
spyOn(authorizationService, 'isAuthorized').and.returnValue(observableOf(false));
|
||||||
component.initialisePage();
|
component.initialisePage();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
NgClass,
|
NgClass,
|
||||||
|
NgForOf,
|
||||||
|
NgIf,
|
||||||
} from '@angular/common';
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
@@ -67,14 +69,16 @@ import { EPersonFormComponent } from './eperson-form/eperson-form.component';
|
|||||||
selector: 'ds-epeople-registry',
|
selector: 'ds-epeople-registry',
|
||||||
templateUrl: './epeople-registry.component.html',
|
templateUrl: './epeople-registry.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
AsyncPipe,
|
|
||||||
EPersonFormComponent,
|
|
||||||
NgClass,
|
|
||||||
PaginationComponent,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
RouterModule,
|
|
||||||
ThemedLoadingComponent,
|
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
|
RouterModule,
|
||||||
|
AsyncPipe,
|
||||||
|
NgIf,
|
||||||
|
EPersonFormComponent,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
ThemedLoadingComponent,
|
||||||
|
PaginationComponent,
|
||||||
|
NgClass,
|
||||||
|
NgForOf,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -2,111 +2,97 @@
|
|||||||
<div class="group-form row">
|
<div class="group-form row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|
||||||
@if (activeEPerson$ | async) {
|
<div *ngIf="activeEPerson$ | async; then editHeader; else createHeader"></div>
|
||||||
<h1 class="border-bottom pb-2">{{messagePrefix + '.edit' | translate}}</h1>
|
|
||||||
} @else {
|
<ng-template #createHeader>
|
||||||
<h1 class="border-bottom pb-2">{{messagePrefix + '.create' | translate}}</h1>
|
<h1 class="border-bottom pb-2">{{messagePrefix + '.create' | translate}}</h1>
|
||||||
}
|
</ng-template>
|
||||||
|
|
||||||
|
|
||||||
|
<ng-template #editHeader>
|
||||||
|
<h1 class="border-bottom pb-2">{{messagePrefix + '.edit' | translate}}</h1>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<ds-form [formId]="formId"
|
<ds-form [formId]="formId"
|
||||||
[formModel]="formModel"
|
[formModel]="formModel"
|
||||||
[formGroup]="formGroup"
|
[formGroup]="formGroup"
|
||||||
[formLayout]="formLayout"
|
[formLayout]="formLayout"
|
||||||
[displayCancel]="false"
|
[displayCancel]="false"
|
||||||
[submitLabel]="submitLabel"
|
[submitLabel]="submitLabel"
|
||||||
(submitForm)="onSubmit()">
|
(submitForm)="onSubmit()">
|
||||||
<div before class="btn-group">
|
<div before class="btn-group">
|
||||||
<button (click)="onCancel()" type="button" class="btn btn-outline-secondary">
|
<button (click)="onCancel()" type="button" class="btn btn-outline-secondary">
|
||||||
<i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}
|
<i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@if (displayResetPassword) {
|
<div *ngIf="displayResetPassword" between class="btn-group">
|
||||||
<div between class="btn-group">
|
<button class="btn btn-primary" [dsBtnDisabled]="(canReset$ | async) !== true" type="button" (click)="resetPassword()">
|
||||||
<button class="btn btn-primary" [dsBtnDisabled]="(canReset$ | async) !== true" type="button" (click)="resetPassword()">
|
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
|
||||||
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (canImpersonate$ | async) {
|
|
||||||
<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}}
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
@if (isImpersonated) {
|
|
||||||
<button class="btn btn-primary" type="button" (click)="stopImpersonating()">
|
|
||||||
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.stop-impersonating' | translate}}
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (canDelete$ | async) {
|
|
||||||
<button after class="btn btn-danger delete-button" type="button" (click)="delete()">
|
|
||||||
<i class="fas fa-trash"></i> {{'admin.access-control.epeople.actions.delete' | translate}}
|
|
||||||
</button>
|
</button>
|
||||||
}
|
</div>
|
||||||
|
<div *ngIf="canImpersonate$ | async" between class="btn-group">
|
||||||
|
<button *ngIf="!isImpersonated" class="btn btn-primary" type="button" (click)="impersonate()">
|
||||||
|
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.impersonate' | translate}}
|
||||||
|
</button>
|
||||||
|
<button *ngIf="isImpersonated" class="btn btn-primary" type="button" (click)="stopImpersonating()">
|
||||||
|
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.stop-impersonating' | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button *ngIf="canDelete$ | async" after class="btn btn-danger delete-button" type="button" (click)="delete()">
|
||||||
|
<i class="fas fa-trash"></i> {{'admin.access-control.epeople.actions.delete' | translate}}
|
||||||
|
</button>
|
||||||
</ds-form>
|
</ds-form>
|
||||||
|
|
||||||
@if (!formGroup) {
|
<ds-loading [showMessage]="false" *ngIf="!formGroup"></ds-loading>
|
||||||
<ds-loading [showMessage]="false"></ds-loading>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (activeEPerson$ | async) {
|
<div *ngIf="activeEPerson$ | async">
|
||||||
<div>
|
<h2>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h2>
|
||||||
<h2>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h2>
|
|
||||||
@if (groups$ | async | dsHasNoValue) {
|
<ds-loading [showMessage]="false" *ngIf="groups$ | async | dsHasNoValue"></ds-loading>
|
||||||
<ds-loading [showMessage]="false"></ds-loading>
|
|
||||||
}
|
<ds-pagination
|
||||||
@if ((groups$ | async)?.payload?.totalElements > 0) {
|
*ngIf="(groups$ | async)?.payload?.totalElements > 0"
|
||||||
<ds-pagination
|
[paginationOptions]="config"
|
||||||
[paginationOptions]="config"
|
[collectionSize]="(groups$ | async)?.payload?.totalElements"
|
||||||
[collectionSize]="(groups$ | async)?.payload?.totalElements"
|
[hideGear]="true"
|
||||||
[hideGear]="true"
|
[hidePagerWhenSinglePage]="true"
|
||||||
[hidePagerWhenSinglePage]="true"
|
(pageChange)="onPageChange($event)">
|
||||||
(pageChange)="onPageChange($event)">
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table id="groups" class="table table-striped table-hover table-bordered">
|
<table id="groups" class="table table-striped table-hover table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@for (group of (groups$ | async)?.payload?.page; track group) {
|
<tr *ngFor="let group of (groups$ | async)?.payload?.page">
|
||||||
<tr>
|
<td class="align-middle">{{group.id}}</td>
|
||||||
<td class="align-middle">{{group.id}}</td>
|
<td class="align-middle">
|
||||||
<td class="align-middle">
|
<a (click)="groupsDataService.startEditingNewGroup(group)"
|
||||||
<a (click)="groupsDataService.startEditingNewGroup(group)"
|
[routerLink]="[groupsDataService.getGroupEditPageRouterLink(group)]">
|
||||||
[routerLink]="[groupsDataService.getGroupEditPageRouterLink(group)]">
|
{{ dsoNameService.getName(group) }}
|
||||||
{{ dsoNameService.getName(group) }}
|
</a>
|
||||||
</a>
|
</td>
|
||||||
</td>
|
<td class="align-middle">
|
||||||
<td class="align-middle">
|
{{ dsoNameService.getName((group.object | async)?.payload) }}
|
||||||
{{ dsoNameService.getName((group.object | async)?.payload) }}
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
</tbody>
|
||||||
}
|
</table>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
|
||||||
</div>
|
</ds-pagination>
|
||||||
</ds-pagination>
|
|
||||||
}
|
<div *ngIf="(groups$ | async)?.payload?.totalElements === 0" class="alert alert-info w-100 mb-2" role="alert">
|
||||||
@if ((groups$ | async)?.payload?.totalElements === 0) {
|
<div>{{messagePrefix + '.memberOfNoGroups' | translate}}</div>
|
||||||
<div class="alert alert-info w-100 mb-2" role="alert">
|
<div>
|
||||||
<div>{{messagePrefix + '.memberOfNoGroups' | translate}}</div>
|
<button [routerLink]="[groupsDataService.getGroupRegistryRouterLink()]"
|
||||||
<div>
|
class="btn btn-primary">{{messagePrefix + '.goToGroups' | translate}}</button>
|
||||||
<button [routerLink]="[groupsDataService.getGroupRegistryRouterLink()]"
|
</div>
|
||||||
class="btn btn-primary">{{messagePrefix + '.goToGroups' | translate}}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -25,7 +25,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of as observableOf,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
import { AuthService } from '../../../core/auth/auth.service';
|
import { AuthService } from '../../../core/auth/auth.service';
|
||||||
@@ -91,7 +91,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
activeEPerson: null,
|
activeEPerson: null,
|
||||||
allEpeople: mockEPeople,
|
allEpeople: mockEPeople,
|
||||||
getActiveEPerson(): Observable<EPerson> {
|
getActiveEPerson(): Observable<EPerson> {
|
||||||
return of(this.activeEPerson);
|
return observableOf(this.activeEPerson);
|
||||||
},
|
},
|
||||||
searchByScope(scope: string, query: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<EPerson>>> {
|
searchByScope(scope: string, query: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
if (scope === 'email') {
|
if (scope === 'email') {
|
||||||
@@ -115,7 +115,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {
|
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {
|
||||||
return (ePerson2.uuid !== ePerson.uuid);
|
return (ePerson2.uuid !== ePerson.uuid);
|
||||||
});
|
});
|
||||||
return of(true);
|
return observableOf(true);
|
||||||
},
|
},
|
||||||
create(ePerson: EPerson): Observable<RemoteData<EPerson>> {
|
create(ePerson: EPerson): Observable<RemoteData<EPerson>> {
|
||||||
this.allEpeople = [...this.allEpeople, ePerson];
|
this.allEpeople = [...this.allEpeople, ePerson];
|
||||||
@@ -210,7 +210,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
});
|
});
|
||||||
authService = new AuthServiceStub();
|
authService = new AuthServiceStub();
|
||||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
isAuthorized: of(true),
|
isAuthorized: observableOf(true),
|
||||||
|
|
||||||
});
|
});
|
||||||
groupsDataService = jasmine.createSpyObj('groupsDataService', {
|
groupsDataService = jasmine.createSpyObj('groupsDataService', {
|
||||||
@@ -389,7 +389,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
});
|
});
|
||||||
describe('without active EPerson', () => {
|
describe('without active EPerson', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(of(undefined));
|
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(observableOf(undefined));
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@@ -429,7 +429,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(of(expectedWithId));
|
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(observableOf(expectedWithId));
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -485,10 +485,10 @@ describe('EPersonFormComponent', () => {
|
|||||||
spyOn(authService, 'impersonate').and.callThrough();
|
spyOn(authService, 'impersonate').and.callThrough();
|
||||||
eperson = EPersonMock;
|
eperson = EPersonMock;
|
||||||
component.epersonInitial = eperson;
|
component.epersonInitial = eperson;
|
||||||
component.canDelete$ = of(true);
|
component.canDelete$ = observableOf(true);
|
||||||
spyOn(component.epersonService, 'getActiveEPerson').and.returnValue(of(eperson));
|
spyOn(component.epersonService, 'getActiveEPerson').and.returnValue(observableOf(eperson));
|
||||||
modalService = (component as any).modalService;
|
modalService = (component as any).modalService;
|
||||||
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: of(true) }) }));
|
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: observableOf(true) }) }));
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@@ -499,7 +499,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('the delete button should be hidden if the ePerson cannot be deleted', () => {
|
it('the delete button should be hidden if the ePerson cannot be deleted', () => {
|
||||||
component.canDelete$ = of(false);
|
component.canDelete$ = observableOf(false);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const deleteButton = fixture.debugElement.query(By.css('.delete-button'));
|
const deleteButton = fixture.debugElement.query(By.css('.delete-button'));
|
||||||
expect(deleteButton).toBeNull();
|
expect(deleteButton).toBeNull();
|
||||||
|
@@ -1,4 +1,8 @@
|
|||||||
import { AsyncPipe } from '@angular/common';
|
import {
|
||||||
|
AsyncPipe,
|
||||||
|
NgFor,
|
||||||
|
NgIf,
|
||||||
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
@@ -27,7 +31,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of as observableOf,
|
||||||
Subscription,
|
Subscription,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
@@ -78,14 +82,16 @@ import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
|||||||
selector: 'ds-eperson-form',
|
selector: 'ds-eperson-form',
|
||||||
templateUrl: './eperson-form.component.html',
|
templateUrl: './eperson-form.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
AsyncPipe,
|
|
||||||
BtnDisabledDirective,
|
|
||||||
FormComponent,
|
FormComponent,
|
||||||
HasNoValuePipe,
|
NgIf,
|
||||||
|
NgFor,
|
||||||
|
AsyncPipe,
|
||||||
|
TranslateModule,
|
||||||
|
ThemedLoadingComponent,
|
||||||
PaginationComponent,
|
PaginationComponent,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
ThemedLoadingComponent,
|
HasNoValuePipe,
|
||||||
TranslateModule,
|
BtnDisabledDirective,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
@@ -357,7 +363,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.groups$ = this.activeEPerson$.pipe(
|
this.groups$ = this.activeEPerson$.pipe(
|
||||||
switchMap((eperson) => {
|
switchMap((eperson) => {
|
||||||
return observableCombineLatest([of(eperson), this.paginationService.getFindListOptions(this.config.id, {
|
return observableCombineLatest([observableOf(eperson), this.paginationService.getFindListOptions(this.config.id, {
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
elementsPerPage: this.config.pageSize,
|
elementsPerPage: this.config.pageSize,
|
||||||
})]);
|
})]);
|
||||||
@@ -366,7 +372,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
if (eperson != null) {
|
if (eperson != null) {
|
||||||
return this.groupsDataService.findListByHref(eperson._links.groups.href, findListOptions, true, true, followLink('object'));
|
return this.groupsDataService.findListByHref(eperson._links.groups.href, findListOptions, true, true, followLink('object'));
|
||||||
}
|
}
|
||||||
return of(undefined);
|
return observableOf(undefined);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -379,14 +385,14 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
if (hasValue(eperson)) {
|
if (hasValue(eperson)) {
|
||||||
return this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, eperson.self);
|
return this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, eperson.self);
|
||||||
} else {
|
} else {
|
||||||
return of(false);
|
return observableOf(false);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
this.canDelete$ = this.activeEPerson$.pipe(
|
this.canDelete$ = this.activeEPerson$.pipe(
|
||||||
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined)),
|
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined)),
|
||||||
);
|
);
|
||||||
this.canReset$ = of(true);
|
this.canReset$ = observableOf(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -540,16 +546,16 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
take(1),
|
take(1),
|
||||||
switchMap((confirm: boolean) => {
|
switchMap((confirm: boolean) => {
|
||||||
if (confirm && hasValue(eperson.id)) {
|
if (confirm && hasValue(eperson.id)) {
|
||||||
this.canDelete$ = of(false);
|
this.canDelete$ = observableOf(false);
|
||||||
return this.epersonService.deleteEPerson(eperson).pipe(
|
return this.epersonService.deleteEPerson(eperson).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
map((restResponse: RemoteData<NoContent>) => ({ restResponse, eperson })),
|
map((restResponse: RemoteData<NoContent>) => ({ restResponse, eperson })),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return of(null);
|
return observableOf(null);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
finalize(() => this.canDelete$ = of(true)),
|
finalize(() => this.canDelete$ = observableOf(true)),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
).subscribe(({ restResponse, eperson }: { restResponse: RemoteData<NoContent> | null, eperson: EPerson }) => {
|
).subscribe(({ restResponse, eperson }: { restResponse: RemoteData<NoContent> | null, eperson: EPerson }) => {
|
||||||
|
@@ -2,7 +2,13 @@
|
|||||||
<div class="group-form row">
|
<div class="group-form row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|
||||||
@if (activeGroup$ | async) {
|
<div *ngIf="activeGroup$ | async; then editHeader; else createHeader"></div>
|
||||||
|
|
||||||
|
<ng-template #createHeader>
|
||||||
|
<h1 class="border-bottom pb-2">{{messagePrefix + '.head.create' | translate}}</h1>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #editHeader>
|
||||||
<h1 class="border-bottom pb-2">
|
<h1 class="border-bottom pb-2">
|
||||||
<span
|
<span
|
||||||
*dsContextHelp="{
|
*dsContextHelp="{
|
||||||
@@ -11,61 +17,47 @@
|
|||||||
iconPlacement: 'right',
|
iconPlacement: 'right',
|
||||||
tooltipPlacement: ['right', 'bottom']
|
tooltipPlacement: ['right', 'bottom']
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{messagePrefix + '.head.edit' | translate}}
|
{{messagePrefix + '.head.edit' | translate}}
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
} @else {
|
</ng-template>
|
||||||
<h1 class="border-bottom pb-2">{{messagePrefix + '.head.create' | translate}}</h1>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
<ng-container *ngIf="(activeGroup$ | async) as groupBeingEdited">
|
||||||
|
<ds-alert *ngIf="groupBeingEdited?.permanent" [type]="AlertType.Warning"
|
||||||
@if ((activeGroup$ | async); as groupBeingEdited) {
|
[content]="messagePrefix + '.alert.permanent'"></ds-alert>
|
||||||
@if (groupBeingEdited?.permanent) {
|
<ng-container *ngIf="(activeGroupLinkedDSO$ | async) as activeGroupLinkedDSO">
|
||||||
<ds-alert [type]="AlertType.Warning"
|
<ds-alert *ngIf="(canEdit$ | async) !== true" [type]="AlertType.Warning"
|
||||||
[content]="messagePrefix + '.alert.permanent'"></ds-alert>
|
[content]="(messagePrefix + '.alert.workflowGroup' | translate:{ name: dsoNameService.getName(activeGroupLinkedDSO), comcol: activeGroupLinkedDSO.type, comcolEditRolesRoute: (linkedEditRolesRoute$ | async) })">
|
||||||
}
|
</ds-alert>
|
||||||
@if ((activeGroupLinkedDSO$ | async); as activeGroupLinkedDSO) {
|
</ng-container>
|
||||||
@if ((canEdit$ | async) !== true) {
|
</ng-container>
|
||||||
<ds-alert [type]="AlertType.Warning"
|
|
||||||
[content]="(messagePrefix + '.alert.workflowGroup' | translate:{ name: dsoNameService.getName(activeGroupLinkedDSO), comcol: activeGroupLinkedDSO.type, comcolEditRolesRoute: (linkedEditRolesRoute$ | async) })">
|
|
||||||
</ds-alert>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<ds-form [formId]="formId"
|
<ds-form [formId]="formId"
|
||||||
[formModel]="formModel"
|
[formModel]="formModel"
|
||||||
[formGroup]="formGroup"
|
[formGroup]="formGroup"
|
||||||
[formLayout]="formLayout"
|
[formLayout]="formLayout"
|
||||||
[displayCancel]="false"
|
[displayCancel]="false"
|
||||||
(submitForm)="onSubmit()">
|
(submitForm)="onSubmit()">
|
||||||
<div before class="btn-group">
|
<div before class="btn-group">
|
||||||
<button (click)="onCancel()" type="button"
|
<button (click)="onCancel()" type="button"
|
||||||
class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
|
class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
@if ((canEdit$ | async) && !(activeGroup$ | async)?.permanent) {
|
<div after *ngIf="(canEdit$ | async) && !(activeGroup$ | async)?.permanent" class="btn-group">
|
||||||
<div after class="btn-group">
|
<button (click)="delete()" class="btn btn-danger delete-button" type="button">
|
||||||
<button (click)="delete()" class="btn btn-danger delete-button" type="button">
|
<i class="fa fa-trash"></i> {{ messagePrefix + '.actions.delete' | translate}}
|
||||||
<i class="fa fa-trash"></i> {{ messagePrefix + '.actions.delete' | translate}}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</ds-form>
|
||||||
}
|
|
||||||
</ds-form>
|
|
||||||
|
|
||||||
@if ((activeGroup$ | async); as groupBeingEdited) {
|
<ng-container *ngIf="(activeGroup$ | async) as groupBeingEdited">
|
||||||
<div class="mb-5">
|
<div class="mb-5">
|
||||||
@if (groupBeingEdited !== undefined) {
|
<ds-members-list *ngIf="groupBeingEdited !== undefined"
|
||||||
<ds-members-list
|
[messagePrefix]="messagePrefix + '.members-list'"></ds-members-list>
|
||||||
[messagePrefix]="messagePrefix + '.members-list'"></ds-members-list>
|
</div>
|
||||||
}
|
<ds-subgroups-list *ngIf="groupBeingEdited !== undefined"
|
||||||
</div>
|
[messagePrefix]="messagePrefix + '.subgroups-list'"></ds-subgroups-list>
|
||||||
@if (groupBeingEdited !== undefined) {
|
</ng-container>
|
||||||
<ds-subgroups-list
|
|
||||||
[messagePrefix]="messagePrefix + '.subgroups-list'"></ds-subgroups-list>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
@@ -27,7 +27,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { Operation } from 'fast-json-patch';
|
import { Operation } from 'fast-json-patch';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of as observableOf,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||||
@@ -116,7 +116,7 @@ describe('GroupFormComponent', () => {
|
|||||||
activeGroup: null,
|
activeGroup: null,
|
||||||
createdGroup: null,
|
createdGroup: null,
|
||||||
getActiveGroup(): Observable<Group> {
|
getActiveGroup(): Observable<Group> {
|
||||||
return of(this.activeGroup);
|
return observableOf(this.activeGroup);
|
||||||
},
|
},
|
||||||
getGroupRegistryRouterLink(): string {
|
getGroupRegistryRouterLink(): string {
|
||||||
return '/access-control/groups';
|
return '/access-control/groups';
|
||||||
@@ -137,7 +137,7 @@ describe('GroupFormComponent', () => {
|
|||||||
this.activeGroup = null;
|
this.activeGroup = null;
|
||||||
},
|
},
|
||||||
findById(id: string) {
|
findById(id: string) {
|
||||||
return of({ payload: null, hasSucceeded: true });
|
return observableOf({ payload: null, hasSucceeded: true });
|
||||||
},
|
},
|
||||||
findByHref(href: string) {
|
findByHref(href: string) {
|
||||||
return createSuccessfulRemoteDataObject$(this.createdGroup);
|
return createSuccessfulRemoteDataObject$(this.createdGroup);
|
||||||
@@ -164,7 +164,7 @@ describe('GroupFormComponent', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
isAuthorized: of(true),
|
isAuthorized: observableOf(true),
|
||||||
});
|
});
|
||||||
dsoDataServiceStub = {
|
dsoDataServiceStub = {
|
||||||
findByHref(href: string): Observable<RemoteData<DSpaceObject>> {
|
findByHref(href: string): Observable<RemoteData<DSpaceObject>> {
|
||||||
@@ -330,7 +330,7 @@ describe('GroupFormComponent', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(of(expected));
|
spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(observableOf(expected));
|
||||||
spyOn(groupsDataServiceStub, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(expected2));
|
spyOn(groupsDataServiceStub, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(expected2));
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
});
|
});
|
||||||
@@ -417,7 +417,7 @@ describe('GroupFormComponent', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
spyOn(component.submitForm, 'emit');
|
spyOn(component.submitForm, 'emit');
|
||||||
spyOn(dsoDataServiceStub, 'findByHref').and.returnValue(of(expected));
|
spyOn(dsoDataServiceStub, 'findByHref').and.returnValue(observableOf(expected));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.initialisePage();
|
component.initialisePage();
|
||||||
@@ -471,11 +471,11 @@ describe('GroupFormComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
spyOn(groupsDataServiceStub, 'delete').and.callThrough();
|
spyOn(groupsDataServiceStub, 'delete').and.callThrough();
|
||||||
component.activeGroup$ = of({
|
component.activeGroup$ = observableOf({
|
||||||
id: 'active-group',
|
id: 'active-group',
|
||||||
permanent: false,
|
permanent: false,
|
||||||
} as Group);
|
} as Group);
|
||||||
component.canEdit$ = of(true);
|
component.canEdit$ = observableOf(true);
|
||||||
|
|
||||||
component.initialisePage();
|
component.initialisePage();
|
||||||
|
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
import { AsyncPipe } from '@angular/common';
|
import {
|
||||||
|
AsyncPipe,
|
||||||
|
NgIf,
|
||||||
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
@@ -87,13 +90,14 @@ import { ValidateGroupExists } from './validators/group-exists.validator';
|
|||||||
selector: 'ds-group-form',
|
selector: 'ds-group-form',
|
||||||
templateUrl: './group-form.component.html',
|
templateUrl: './group-form.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
AlertComponent,
|
|
||||||
AsyncPipe,
|
|
||||||
ContextHelpDirective,
|
|
||||||
FormComponent,
|
FormComponent,
|
||||||
|
AlertComponent,
|
||||||
|
NgIf,
|
||||||
|
AsyncPipe,
|
||||||
|
TranslateModule,
|
||||||
|
ContextHelpDirective,
|
||||||
MembersListComponent,
|
MembersListComponent,
|
||||||
SubgroupsListComponent,
|
SubgroupsListComponent,
|
||||||
TranslateModule,
|
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
@@ -500,7 +504,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
this.groupDataService.cancelEditGroup();
|
this.groupDataService.cancelEditGroup();
|
||||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
|
||||||
if ( hasValue(this.groupNameValueChangeSubscribe) ) {
|
if (hasValue(this.groupNameValueChangeSubscribe)) {
|
||||||
this.groupNameValueChangeSubscribe.unsubscribe();
|
this.groupNameValueChangeSubscribe.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,70 +3,63 @@
|
|||||||
|
|
||||||
<h3>{{messagePrefix + '.headMembers' | translate}}</h3>
|
<h3>{{messagePrefix + '.headMembers' | translate}}</h3>
|
||||||
|
|
||||||
@if ((ePeopleMembersOfGroup | async)?.totalElements > 0) {
|
<ds-pagination *ngIf="(ePeopleMembersOfGroup | async)?.totalElements > 0"
|
||||||
<ds-pagination
|
[paginationOptions]="config"
|
||||||
[paginationOptions]="config"
|
[collectionSize]="(ePeopleMembersOfGroup | async)?.totalElements"
|
||||||
[collectionSize]="(ePeopleMembersOfGroup | async)?.totalElements"
|
[hideGear]="true"
|
||||||
[hideGear]="true"
|
[hidePagerWhenSinglePage]="true">
|
||||||
[hidePagerWhenSinglePage]="true">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table id="ePeopleMembersOfGroup" class="table table-striped table-hover table-bordered">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.identity' | translate}}</th>
|
|
||||||
<th class="align-middle">{{messagePrefix + '.table.edit' | translate}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (epersonDTO of (ePeopleMembersOfGroup | async)?.page; track epersonDTO) {
|
|
||||||
<tr>
|
|
||||||
<td class="align-middle">{{epersonDTO.eperson.id}}</td>
|
|
||||||
<td class="align-middle">
|
|
||||||
<a [routerLink]="getEPersonEditRoute(epersonDTO.eperson.id)">
|
|
||||||
{{ dsoNameService.getName(epersonDTO.eperson) }}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class="align-middle">
|
|
||||||
{{messagePrefix + '.table.email' | translate}}: {{ epersonDTO.eperson.email ? epersonDTO.eperson.email : '-' }}<br/>
|
|
||||||
{{messagePrefix + '.table.netid' | translate}}: {{ epersonDTO.eperson.netid ? epersonDTO.eperson.netid : '-' }}
|
|
||||||
</td>
|
|
||||||
<td class="align-middle">
|
|
||||||
<div class="btn-group edit-field">
|
|
||||||
@if (epersonDTO.ableToDelete) {
|
|
||||||
<button (click)="deleteMemberFromGroup(epersonDTO.eperson)"
|
|
||||||
[dsBtnDisabled]="actionConfig.remove.disabled"
|
|
||||||
[ngClass]="['btn btn-sm', actionConfig.remove.css]"
|
|
||||||
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(epersonDTO.eperson) } }}">
|
|
||||||
<i [ngClass]="actionConfig.remove.icon"></i>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
@if (!epersonDTO.ableToDelete) {
|
|
||||||
<button
|
|
||||||
(click)="addMemberToGroup(epersonDTO.eperson)"
|
|
||||||
[dsBtnDisabled]="actionConfig.add.disabled"
|
|
||||||
[ngClass]="['btn btn-sm', actionConfig.add.css]"
|
|
||||||
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(epersonDTO.eperson) } }}">
|
|
||||||
<i [ngClass]="actionConfig.add.icon"></i>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</ds-pagination>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if ((ePeopleMembersOfGroup | async) === undefined || (ePeopleMembersOfGroup | async)?.totalElements === 0) {
|
<div class="table-responsive">
|
||||||
<div class="alert alert-info w-100 mb-2"
|
<table id="ePeopleMembersOfGroup" class="table table-striped table-hover table-bordered">
|
||||||
role="alert">
|
<thead>
|
||||||
{{messagePrefix + '.no-members-yet' | translate}}
|
<tr>
|
||||||
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
||||||
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
||||||
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.identity' | translate}}</th>
|
||||||
|
<th class="align-middle">{{messagePrefix + '.table.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let epersonDTO of (ePeopleMembersOfGroup | async)?.page">
|
||||||
|
<td class="align-middle">{{epersonDTO.eperson.id}}</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
<a [routerLink]="getEPersonEditRoute(epersonDTO.eperson.id)">
|
||||||
|
{{ dsoNameService.getName(epersonDTO.eperson) }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
{{messagePrefix + '.table.email' | translate}}: {{ epersonDTO.eperson.email ? epersonDTO.eperson.email : '-' }}<br/>
|
||||||
|
{{messagePrefix + '.table.netid' | translate}}: {{ epersonDTO.eperson.netid ? epersonDTO.eperson.netid : '-' }}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<button (click)="deleteMemberFromGroup(epersonDTO.eperson)"
|
||||||
|
*ngIf="epersonDTO.ableToDelete"
|
||||||
|
[dsBtnDisabled]="actionConfig.remove.disabled"
|
||||||
|
[ngClass]="['btn btn-sm', actionConfig.remove.css]"
|
||||||
|
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(epersonDTO.eperson) } }}">
|
||||||
|
<i [ngClass]="actionConfig.remove.icon"></i>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="!epersonDTO.ableToDelete"
|
||||||
|
(click)="addMemberToGroup(epersonDTO.eperson)"
|
||||||
|
[dsBtnDisabled]="actionConfig.add.disabled"
|
||||||
|
[ngClass]="['btn btn-sm', actionConfig.add.css]"
|
||||||
|
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(epersonDTO.eperson) } }}">
|
||||||
|
<i [ngClass]="actionConfig.add.icon"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(ePeopleMembersOfGroup | async) === undefined || (ePeopleMembersOfGroup | async)?.totalElements === 0" class="alert alert-info w-100 mb-2"
|
||||||
|
role="alert">
|
||||||
|
{{messagePrefix + '.no-members-yet' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3 id="search" class="border-bottom pb-2">
|
<h3 id="search" class="border-bottom pb-2">
|
||||||
<span
|
<span
|
||||||
@@ -76,81 +69,77 @@
|
|||||||
iconPlacement: 'right',
|
iconPlacement: 'right',
|
||||||
tooltipPlacement: ['top', 'right', 'bottom']
|
tooltipPlacement: ['top', 'right', 'bottom']
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{messagePrefix + '.search.head' | translate}}
|
{{messagePrefix + '.search.head' | translate}}
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
|
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
|
||||||
<div class="flex-grow-1 me-3">
|
<div class="flex-grow-1 mr-3">
|
||||||
<div class="form-group input-group me-3">
|
<div class="form-group input-group mr-3">
|
||||||
<input type="text" name="query" id="query" formControlName="query"
|
<input type="text" name="query" id="query" formControlName="query"
|
||||||
class="form-control" aria-label="Search input">
|
class="form-control" aria-label="Search input">
|
||||||
<span class="input-group-append">
|
<span class="input-group-append">
|
||||||
<button type="submit" class="search-button btn btn-primary">
|
<button type="submit" class="search-button btn btn-primary">
|
||||||
<i class="fas fa-search"></i> {{ messagePrefix + '.search.button' | translate }}</button>
|
<i class="fas fa-search"></i> {{ messagePrefix + '.search.button' | translate }}</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<button (click)="clearFormAndResetResult();"
|
<div>
|
||||||
class="btn btn-secondary">{{messagePrefix + '.button.see-all' | translate}}</button>
|
<button (click)="clearFormAndResetResult();"
|
||||||
</div>
|
class="btn btn-secondary">{{messagePrefix + '.button.see-all' | translate}}</button>
|
||||||
</form>
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
@if ((ePeopleSearch | async)?.totalElements > 0) {
|
<ds-pagination *ngIf="(ePeopleSearch | async)?.totalElements > 0"
|
||||||
<ds-pagination
|
[paginationOptions]="configSearch"
|
||||||
[paginationOptions]="configSearch"
|
[collectionSize]="(ePeopleSearch | async)?.totalElements"
|
||||||
[collectionSize]="(ePeopleSearch | async)?.totalElements"
|
[hideGear]="true"
|
||||||
[hideGear]="true"
|
[hidePagerWhenSinglePage]="true">
|
||||||
[hidePagerWhenSinglePage]="true">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table id="epersonsSearch" class="table table-striped table-hover table-bordered">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.identity' | translate}}</th>
|
|
||||||
<th class="align-middle">{{messagePrefix + '.table.edit' | translate}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (eperson of (ePeopleSearch | async)?.page; track eperson) {
|
|
||||||
<tr>
|
|
||||||
<td class="align-middle">{{eperson.id}}</td>
|
|
||||||
<td class="align-middle">
|
|
||||||
<a [routerLink]="getEPersonEditRoute(eperson.id)">
|
|
||||||
{{ dsoNameService.getName(eperson) }}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class="align-middle">
|
|
||||||
{{messagePrefix + '.table.email' | translate}}: {{ eperson.email ? eperson.email : '-' }}<br/>
|
|
||||||
{{messagePrefix + '.table.netid' | translate}}: {{ eperson.netid ? eperson.netid : '-' }}
|
|
||||||
</td>
|
|
||||||
<td class="align-middle">
|
|
||||||
<div class="btn-group edit-field">
|
|
||||||
<button (click)="addMemberToGroup(eperson)"
|
|
||||||
[dsBtnDisabled]="actionConfig.add.disabled"
|
|
||||||
[ngClass]="['btn btn-sm', actionConfig.add.css]"
|
|
||||||
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(eperson) } }}">
|
|
||||||
<i [ngClass]="actionConfig.add.icon"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</ds-pagination>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if ((ePeopleSearch | async)?.totalElements === 0 && searchDone) {
|
<div class="table-responsive">
|
||||||
<div
|
<table id="epersonsSearch" class="table table-striped table-hover table-bordered">
|
||||||
class="alert alert-info w-100 mb-2"
|
<thead>
|
||||||
role="alert">
|
<tr>
|
||||||
{{messagePrefix + '.no-items' | translate}}
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
||||||
</div>
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
||||||
}
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.identity' | translate}}</th>
|
||||||
|
<th class="align-middle">{{messagePrefix + '.table.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let eperson of (ePeopleSearch | async)?.page">
|
||||||
|
<td class="align-middle">{{eperson.id}}</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
<a [routerLink]="getEPersonEditRoute(eperson.id)">
|
||||||
|
{{ dsoNameService.getName(eperson) }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
{{messagePrefix + '.table.email' | translate}}: {{ eperson.email ? eperson.email : '-' }}<br/>
|
||||||
|
{{messagePrefix + '.table.netid' | translate}}: {{ eperson.netid ? eperson.netid : '-' }}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<button (click)="addMemberToGroup(eperson)"
|
||||||
|
[dsBtnDisabled]="actionConfig.add.disabled"
|
||||||
|
[ngClass]="['btn btn-sm', actionConfig.add.css]"
|
||||||
|
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(eperson) } }}">
|
||||||
|
<i [ngClass]="actionConfig.add.icon"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
</ng-container>
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(ePeopleSearch | async)?.totalElements === 0 && searchDone"
|
||||||
|
class="alert alert-info w-100 mb-2"
|
||||||
|
role="alert">
|
||||||
|
{{messagePrefix + '.no-items' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
@@ -32,7 +32,7 @@ import {
|
|||||||
} from '@ngx-translate/core';
|
} from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of as observableOf,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
||||||
@@ -113,7 +113,7 @@ describe('MembersListComponent', () => {
|
|||||||
epersonMembers: epersonMembers,
|
epersonMembers: epersonMembers,
|
||||||
epersonNonMembers: epersonNonMembers,
|
epersonNonMembers: epersonNonMembers,
|
||||||
getActiveGroup(): Observable<Group> {
|
getActiveGroup(): Observable<Group> {
|
||||||
return of(activeGroup);
|
return observableOf(activeGroup);
|
||||||
},
|
},
|
||||||
getEPersonMembers() {
|
getEPersonMembers() {
|
||||||
return this.epersonMembers;
|
return this.epersonMembers;
|
||||||
@@ -127,7 +127,7 @@ describe('MembersListComponent', () => {
|
|||||||
this.epersonNonMembers.splice(index, 1);
|
this.epersonNonMembers.splice(index, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return of(new RestResponse(true, 200, 'Success'));
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
},
|
},
|
||||||
clearGroupsRequests() {
|
clearGroupsRequests() {
|
||||||
// empty
|
// empty
|
||||||
@@ -147,7 +147,7 @@ describe('MembersListComponent', () => {
|
|||||||
});
|
});
|
||||||
// Add eperson to list of non-members
|
// Add eperson to list of non-members
|
||||||
this.epersonNonMembers = [...this.epersonNonMembers, epersonToDelete];
|
this.epersonNonMembers = [...this.epersonNonMembers, epersonToDelete];
|
||||||
return of(new RestResponse(true, 200, 'Success'));
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
builderService = getMockFormBuilderService();
|
builderService = getMockFormBuilderService();
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
NgClass,
|
NgClass,
|
||||||
|
NgForOf,
|
||||||
|
NgIf,
|
||||||
} from '@angular/common';
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
@@ -25,7 +27,7 @@ import {
|
|||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
ObservedValueOf,
|
ObservedValueOf,
|
||||||
of,
|
of as observableOf,
|
||||||
Subscription,
|
Subscription,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
@@ -103,14 +105,16 @@ export interface EPersonListActionConfig {
|
|||||||
selector: 'ds-members-list',
|
selector: 'ds-members-list',
|
||||||
templateUrl: './members-list.component.html',
|
templateUrl: './members-list.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
AsyncPipe,
|
|
||||||
BtnDisabledDirective,
|
|
||||||
ContextHelpDirective,
|
|
||||||
NgClass,
|
|
||||||
PaginationComponent,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
RouterLink,
|
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
|
ContextHelpDirective,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
PaginationComponent,
|
||||||
|
NgIf,
|
||||||
|
AsyncPipe,
|
||||||
|
RouterLink,
|
||||||
|
NgClass,
|
||||||
|
NgForOf,
|
||||||
|
BtnDisabledDirective,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
@@ -260,7 +264,7 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
* @param possibleMember EPerson that is a possible member (being tested) of the group currently being edited
|
* @param possibleMember EPerson that is a possible member (being tested) of the group currently being edited
|
||||||
*/
|
*/
|
||||||
isMemberOfGroup(possibleMember: EPerson): Observable<boolean> {
|
isMemberOfGroup(possibleMember: EPerson): Observable<boolean> {
|
||||||
return of(true);
|
return observableOf(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -3,56 +3,51 @@
|
|||||||
|
|
||||||
<h4>{{messagePrefix + '.headSubgroups' | translate}}</h4>
|
<h4>{{messagePrefix + '.headSubgroups' | translate}}</h4>
|
||||||
|
|
||||||
@if ((subGroups$ | async)?.payload?.totalElements > 0) {
|
<ds-pagination *ngIf="(subGroups$ | async)?.payload?.totalElements > 0"
|
||||||
<ds-pagination
|
[paginationOptions]="config"
|
||||||
[paginationOptions]="config"
|
[collectionSize]="(subGroups$ | async)?.payload?.totalElements"
|
||||||
[collectionSize]="(subGroups$ | async)?.payload?.totalElements"
|
[hideGear]="true"
|
||||||
[hideGear]="true"
|
[hidePagerWhenSinglePage]="true">
|
||||||
[hidePagerWhenSinglePage]="true">
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table id="subgroupsOfGroup" class="table table-striped table-hover table-bordered">
|
<table id="subgroupsOfGroup" class="table table-striped table-hover table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
|
||||||
<th>{{messagePrefix + '.table.edit' | translate}}</th>
|
<th>{{messagePrefix + '.table.edit' | translate}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@for (group of (subGroups$ | async)?.payload?.page; track group) {
|
<tr *ngFor="let group of (subGroups$ | async)?.payload?.page">
|
||||||
<tr>
|
<td class="align-middle">{{group.id}}</td>
|
||||||
<td class="align-middle">{{group.id}}</td>
|
<td class="align-middle">
|
||||||
<td class="align-middle">
|
<a (click)="groupDataService.startEditingNewGroup(group)"
|
||||||
<a (click)="groupDataService.startEditingNewGroup(group)"
|
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">
|
||||||
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">
|
{{ dsoNameService.getName(group) }}
|
||||||
{{ dsoNameService.getName(group) }}
|
</a>
|
||||||
</a>
|
</td>
|
||||||
</td>
|
<td class="align-middle">{{ dsoNameService.getName((group.object | async)?.payload)}}</td>
|
||||||
<td class="align-middle">{{ dsoNameService.getName((group.object | async)?.payload)}}</td>
|
<td class="align-middle">
|
||||||
<td class="align-middle">
|
<div class="btn-group edit-field">
|
||||||
<div class="btn-group edit-field">
|
<button (click)="deleteSubgroupFromGroup(group)"
|
||||||
<button (click)="deleteSubgroupFromGroup(group)"
|
|
||||||
class="btn btn-outline-danger btn-sm deleteButton"
|
class="btn btn-outline-danger btn-sm deleteButton"
|
||||||
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(group) } }}">
|
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(group) } }}">
|
||||||
<i class="fas fa-trash-alt fa-fw"></i>
|
<i class="fas fa-trash-alt fa-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
</tbody>
|
||||||
</tbody>
|
</table>
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</ds-pagination>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if ((subGroups$ | async)?.payload?.totalElements === 0) {
|
|
||||||
<div class="alert alert-info w-100 mb-2"
|
|
||||||
role="alert">
|
|
||||||
{{messagePrefix + '.no-subgroups-yet' | translate}}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(subGroups$ | async)?.payload?.totalElements === 0" class="alert alert-info w-100 mb-2"
|
||||||
|
role="alert">
|
||||||
|
{{messagePrefix + '.no-subgroups-yet' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4 id="search" class="border-bottom pb-2">
|
<h4 id="search" class="border-bottom pb-2">
|
||||||
<span *dsContextHelp="{
|
<span *dsContextHelp="{
|
||||||
@@ -61,80 +56,75 @@
|
|||||||
iconPlacement: 'right',
|
iconPlacement: 'right',
|
||||||
tooltipPlacement: ['top', 'right', 'bottom']
|
tooltipPlacement: ['top', 'right', 'bottom']
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{messagePrefix + '.search.head' | translate}}
|
{{messagePrefix + '.search.head' | translate}}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</h4>
|
</h4>
|
||||||
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
|
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
|
||||||
<div class="flex-grow-1 me-3">
|
<div class="flex-grow-1 mr-3">
|
||||||
<div class="mb-3 input-group me-3">
|
<div class="form-group input-group mr-3">
|
||||||
<input type="text" name="query" id="query" formControlName="query"
|
<input type="text" name="query" id="query" formControlName="query"
|
||||||
class="form-control" aria-label="Search input">
|
class="form-control" aria-label="Search input">
|
||||||
<span class="input-group-append">
|
<span class="input-group-append">
|
||||||
<button type="submit" class="search-button btn btn-primary">
|
<button type="submit" class="search-button btn btn-primary">
|
||||||
<i class="fas fa-search"></i> {{ messagePrefix + '.search.button' | translate }}
|
<i class="fas fa-search"></i> {{ messagePrefix + '.search.button' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button (click)="clearFormAndResetResult();" class="btn btn-secondary float-end">
|
<button (click)="clearFormAndResetResult();" class="btn btn-secondary float-right">
|
||||||
{{messagePrefix + '.button.see-all' | translate}}
|
{{messagePrefix + '.button.see-all' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@if ((searchResults$ | async)?.payload?.totalElements > 0) {
|
<ds-pagination *ngIf="(searchResults$ | async)?.payload?.totalElements > 0"
|
||||||
<ds-pagination
|
[paginationOptions]="configSearch"
|
||||||
[paginationOptions]="configSearch"
|
[collectionSize]="(searchResults$ | async)?.payload?.totalElements"
|
||||||
[collectionSize]="(searchResults$ | async)?.payload?.totalElements"
|
[hideGear]="true"
|
||||||
[hideGear]="true"
|
[hidePagerWhenSinglePage]="true">
|
||||||
[hidePagerWhenSinglePage]="true">
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table id="groupsSearch" class="table table-striped table-hover table-bordered">
|
<table id="groupsSearch" class="table table-striped table-hover table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
|
||||||
<th class="align-middle">{{messagePrefix + '.table.edit' | translate}}</th>
|
<th class="align-middle">{{messagePrefix + '.table.edit' | translate}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@for (group of (searchResults$ | async)?.payload?.page; track group) {
|
<tr *ngFor="let group of (searchResults$ | async)?.payload?.page">
|
||||||
<tr>
|
<td class="align-middle">{{group.id}}</td>
|
||||||
<td class="align-middle">{{group.id}}</td>
|
<td class="align-middle">
|
||||||
<td class="align-middle">
|
<a (click)="groupDataService.startEditingNewGroup(group)"
|
||||||
<a (click)="groupDataService.startEditingNewGroup(group)"
|
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">
|
||||||
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">
|
{{ dsoNameService.getName(group) }}
|
||||||
{{ dsoNameService.getName(group) }}
|
</a>
|
||||||
</a>
|
</td>
|
||||||
</td>
|
<td class="align-middle">{{ dsoNameService.getName((group.object | async)?.payload) }}</td>
|
||||||
<td class="align-middle">{{ dsoNameService.getName((group.object | async)?.payload) }}</td>
|
<td class="align-middle">
|
||||||
<td class="align-middle">
|
<div class="btn-group edit-field">
|
||||||
<div class="btn-group edit-field">
|
<button (click)="addSubgroupToGroup(group)"
|
||||||
<button (click)="addSubgroupToGroup(group)"
|
|
||||||
class="btn btn-outline-primary btn-sm addButton"
|
class="btn btn-outline-primary btn-sm addButton"
|
||||||
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(group) } }}">
|
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(group) } }}">
|
||||||
<i class="fas fa-plus fa-fw"></i>
|
<i class="fas fa-plus fa-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
</tbody>
|
||||||
</tbody>
|
</table>
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</ds-pagination>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if ((searchResults$ | async)?.payload?.totalElements === 0 && searchDone) {
|
|
||||||
<div class="alert alert-info w-100 mb-2"
|
|
||||||
role="alert">
|
|
||||||
{{messagePrefix + '.no-items' | translate}}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(searchResults$ | async)?.payload?.totalElements === 0 && searchDone" class="alert alert-info w-100 mb-2"
|
||||||
|
role="alert">
|
||||||
|
{{messagePrefix + '.no-items' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -31,7 +31,7 @@ import {
|
|||||||
} from '@ngx-translate/core';
|
} from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of as observableOf,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { EPersonMock2 } from 'src/app/shared/testing/eperson.mock';
|
import { EPersonMock2 } from 'src/app/shared/testing/eperson.mock';
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ describe('SubgroupsListComponent', () => {
|
|||||||
subgroups: subgroups,
|
subgroups: subgroups,
|
||||||
groupNonMembers: groupNonMembers,
|
groupNonMembers: groupNonMembers,
|
||||||
getActiveGroup(): Observable<Group> {
|
getActiveGroup(): Observable<Group> {
|
||||||
return of(this.activeGroup);
|
return observableOf(this.activeGroup);
|
||||||
},
|
},
|
||||||
getSubgroups(): Group {
|
getSubgroups(): Group {
|
||||||
return this.subgroups;
|
return this.subgroups;
|
||||||
@@ -138,7 +138,7 @@ describe('SubgroupsListComponent', () => {
|
|||||||
this.groupNonMembers.splice(index, 1);
|
this.groupNonMembers.splice(index, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return of(new RestResponse(true, 200, 'Success'));
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
},
|
},
|
||||||
clearGroupsRequests() {
|
clearGroupsRequests() {
|
||||||
// empty
|
// empty
|
||||||
@@ -155,7 +155,7 @@ describe('SubgroupsListComponent', () => {
|
|||||||
});
|
});
|
||||||
// Add group to list of non-members
|
// Add group to list of non-members
|
||||||
this.groupNonMembers = [...this.groupNonMembers, subgroupToDelete];
|
this.groupNonMembers = [...this.groupNonMembers, subgroupToDelete];
|
||||||
return of(new RestResponse(true, 200, 'Success'));
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
routerStub = new RouterMock();
|
routerStub = new RouterMock();
|
||||||
|
@@ -1,4 +1,8 @@
|
|||||||
import { AsyncPipe } from '@angular/common';
|
import {
|
||||||
|
AsyncPipe,
|
||||||
|
NgForOf,
|
||||||
|
NgIf,
|
||||||
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
Input,
|
Input,
|
||||||
@@ -59,12 +63,14 @@ enum SubKey {
|
|||||||
selector: 'ds-subgroups-list',
|
selector: 'ds-subgroups-list',
|
||||||
templateUrl: './subgroups-list.component.html',
|
templateUrl: './subgroups-list.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
AsyncPipe,
|
|
||||||
ContextHelpDirective,
|
|
||||||
PaginationComponent,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
RouterLink,
|
RouterLink,
|
||||||
|
AsyncPipe,
|
||||||
|
NgForOf,
|
||||||
|
ContextHelpDirective,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
PaginationComponent,
|
||||||
|
NgIf,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
|
@@ -9,7 +9,7 @@ import {
|
|||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of as observableOf,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
@@ -37,7 +37,7 @@ describe('GroupPageGuard', () => {
|
|||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
halEndpointService = jasmine.createSpyObj(['getEndpoint']);
|
halEndpointService = jasmine.createSpyObj(['getEndpoint']);
|
||||||
( halEndpointService as any ).getEndpoint.and.returnValue(of(groupsEndpointUrl));
|
( halEndpointService as any ).getEndpoint.and.returnValue(observableOf(groupsEndpointUrl));
|
||||||
|
|
||||||
authorizationService = jasmine.createSpyObj(['isAuthorized']);
|
authorizationService = jasmine.createSpyObj(['isAuthorized']);
|
||||||
// NOTE: value is set in beforeEach
|
// NOTE: value is set in beforeEach
|
||||||
@@ -46,7 +46,7 @@ describe('GroupPageGuard', () => {
|
|||||||
( router as any ).parseUrl.and.returnValue = {};
|
( router as any ).parseUrl.and.returnValue = {};
|
||||||
|
|
||||||
authService = jasmine.createSpyObj(['isAuthenticated']);
|
authService = jasmine.createSpyObj(['isAuthenticated']);
|
||||||
( authService as any ).isAuthenticated.and.returnValue(of(true));
|
( authService as any ).isAuthenticated.and.returnValue(observableOf(true));
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
@@ -69,7 +69,7 @@ describe('GroupPageGuard', () => {
|
|||||||
describe('canActivate', () => {
|
describe('canActivate', () => {
|
||||||
describe('when the current user can manage the group', () => {
|
describe('when the current user can manage the group', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
( authorizationService as any ).isAuthorized.and.returnValue(of(true));
|
( authorizationService as any ).isAuthorized.and.returnValue(observableOf(true));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true', (done) => {
|
it('should return true', (done) => {
|
||||||
@@ -89,7 +89,7 @@ describe('GroupPageGuard', () => {
|
|||||||
|
|
||||||
describe('when the current user can not manage the group', () => {
|
describe('when the current user can not manage the group', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(authorizationService as any).isAuthorized.and.returnValue(of(false));
|
(authorizationService as any).isAuthorized.and.returnValue(observableOf(false));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return true', (done) => {
|
it('should not return true', (done) => {
|
||||||
|
@@ -6,7 +6,7 @@ import {
|
|||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of as observableOf,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
@@ -33,6 +33,6 @@ export const groupPageGuard = (
|
|||||||
getObjectUrl = defaultGroupPageGetObjectUrl,
|
getObjectUrl = defaultGroupPageGetObjectUrl,
|
||||||
getEPersonUuid?: StringGuardParamFn,
|
getEPersonUuid?: StringGuardParamFn,
|
||||||
): CanActivateFn => someFeatureAuthorizationGuard(
|
): CanActivateFn => someFeatureAuthorizationGuard(
|
||||||
() => of([FeatureID.CanManageGroup]),
|
() => observableOf([FeatureID.CanManageGroup]),
|
||||||
getObjectUrl,
|
getObjectUrl,
|
||||||
getEPersonUuid);
|
getEPersonUuid);
|
||||||
|
@@ -4,21 +4,21 @@
|
|||||||
<div class="d-flex justify-content-between border-bottom mb-3">
|
<div class="d-flex justify-content-between border-bottom mb-3">
|
||||||
<h1 id="header" class="pb-2">{{messagePrefix + 'head' | translate}}</h1>
|
<h1 id="header" class="pb-2">{{messagePrefix + 'head' | translate}}</h1>
|
||||||
<div>
|
<div>
|
||||||
<button class="me-auto btn btn-success"
|
<button class="mr-auto btn btn-success"
|
||||||
[routerLink]="'create'">
|
[routerLink]="'create'">
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus"></i>
|
||||||
<span class="d-none d-sm-inline ms-1">{{messagePrefix + 'button.add' | translate}}</span>
|
<span class="d-none d-sm-inline ml-1">{{messagePrefix + 'button.add' | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 id="search" class="border-bottom pb-2">{{messagePrefix + 'search.head' | translate}}</h2>
|
<h2 id="search" class="border-bottom pb-2">{{messagePrefix + 'search.head' | translate}}</h2>
|
||||||
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
|
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
|
||||||
<div class="flex-grow-1 me-3">
|
<div class="flex-grow-1 mr-3">
|
||||||
<div class="mb-3 input-group">
|
<div class="form-group input-group">
|
||||||
<input type="text" name="query" id="query" formControlName="query"
|
<input type="text" name="query" id="query" formControlName="query"
|
||||||
class="form-control" [attr.aria-label]="messagePrefix + 'search.placeholder' | translate"
|
class="form-control" [attr.aria-label]="messagePrefix + 'search.placeholder' | translate"
|
||||||
[placeholder]="(messagePrefix + 'search.placeholder' | translate)" >
|
[placeholder]="(messagePrefix + 'search.placeholder' | translate)" >
|
||||||
<span class="input-group-append">
|
<span class="input-group-append">
|
||||||
<button type="submit" class="search-button btn btn-primary">
|
<button type="submit" class="search-button btn btn-primary">
|
||||||
<i class="fas fa-search"></i> {{ messagePrefix + 'search.button' | translate }}
|
<i class="fas fa-search"></i> {{ messagePrefix + 'search.button' | translate }}
|
||||||
@@ -33,78 +33,66 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@if (loading$ | async) {
|
<ds-loading *ngIf="loading$ | async"></ds-loading>
|
||||||
<ds-loading></ds-loading>
|
<ds-pagination
|
||||||
}
|
*ngIf="(pageInfoState$ | async)?.totalElements > 0 && (loading$ | async) !== true"
|
||||||
@if ((pageInfoState$ | async)?.totalElements > 0 && (loading$ | async) !== true) {
|
[paginationOptions]="config"
|
||||||
<ds-pagination
|
[collectionSize]="(pageInfoState$ | async)?.totalElements"
|
||||||
[paginationOptions]="config"
|
[hideGear]="true"
|
||||||
[collectionSize]="(pageInfoState$ | async)?.totalElements"
|
[hidePagerWhenSinglePage]="true">
|
||||||
[hideGear]="true"
|
|
||||||
[hidePagerWhenSinglePage]="true">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table id="groups" class="table table-striped table-hover table-bordered">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">{{messagePrefix + 'table.id' | translate}}</th>
|
|
||||||
<th scope="col">{{messagePrefix + 'table.name' | translate}}</th>
|
|
||||||
<th scope="col">{{messagePrefix + 'table.collectionOrCommunity' | translate}}</th>
|
|
||||||
<th scope="col">{{messagePrefix + 'table.members' | translate}}</th>
|
|
||||||
<th>{{messagePrefix + 'table.edit' | translate}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (groupDto of (groupsDto$ | async)?.page; track groupDto) {
|
|
||||||
<tr>
|
|
||||||
<td>{{groupDto.group.id}}</td>
|
|
||||||
<td>{{ dsoNameService.getName(groupDto.group) }}</td>
|
|
||||||
<td>{{ dsoNameService.getName((groupDto.group.object | async)?.payload) }}</td>
|
|
||||||
<td>{{groupDto.epersons?.totalElements + groupDto.subgroups?.totalElements}}</td>
|
|
||||||
<td>
|
|
||||||
<div class="btn-group edit-field">
|
|
||||||
@switch (groupDto.ableToEdit) {
|
|
||||||
@case (true) {
|
|
||||||
<button
|
|
||||||
[routerLink]="groupService.getGroupEditPageRouterLink(groupDto.group)"
|
|
||||||
class="btn btn-outline-primary btn-sm btn-edit"
|
|
||||||
title="{{messagePrefix + 'table.edit.buttons.edit' | translate: {name: dsoNameService.getName(groupDto.group) } }}"
|
|
||||||
>
|
|
||||||
<i class="fas fa-edit fa-fw"></i>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
@case (false) {
|
|
||||||
<button
|
|
||||||
[dsBtnDisabled]="true"
|
|
||||||
class="btn btn-outline-primary btn-sm btn-edit"
|
|
||||||
placement="left"
|
|
||||||
[ngbTooltip]="'admin.access-control.epeople.table.edit.buttons.edit-disabled' | translate"
|
|
||||||
>
|
|
||||||
<i class="fas fa-edit fa-fw"></i>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@if (!groupDto.group?.permanent && groupDto.ableToDelete) {
|
|
||||||
<button
|
|
||||||
(click)="deleteGroup(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>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</ds-pagination>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if ((pageInfoState$ | async)?.totalElements === 0) {
|
<div class="table-responsive">
|
||||||
<div class="alert alert-info w-100 mb-2" role="alert">
|
<table id="groups" class="table table-striped table-hover table-bordered">
|
||||||
{{messagePrefix + 'no-items' | translate}}
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{{messagePrefix + 'table.id' | translate}}</th>
|
||||||
|
<th scope="col">{{messagePrefix + 'table.name' | translate}}</th>
|
||||||
|
<th scope="col">{{messagePrefix + 'table.collectionOrCommunity' | translate}}</th>
|
||||||
|
<th scope="col">{{messagePrefix + 'table.members' | translate}}</th>
|
||||||
|
<th>{{messagePrefix + 'table.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let groupDto of (groupsDto$ | async)?.page">
|
||||||
|
<td>{{groupDto.group.id}}</td>
|
||||||
|
<td>{{ dsoNameService.getName(groupDto.group) }}</td>
|
||||||
|
<td>{{ dsoNameService.getName((groupDto.group.object | async)?.payload) }}</td>
|
||||||
|
<td>{{groupDto.epersons?.totalElements + groupDto.subgroups?.totalElements}}</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<ng-container [ngSwitch]="groupDto.ableToEdit">
|
||||||
|
<button *ngSwitchCase="true"
|
||||||
|
[routerLink]="groupService.getGroupEditPageRouterLink(groupDto.group)"
|
||||||
|
class="btn btn-outline-primary btn-sm btn-edit"
|
||||||
|
title="{{messagePrefix + 'table.edit.buttons.edit' | translate: {name: dsoNameService.getName(groupDto.group) } }}"
|
||||||
|
>
|
||||||
|
<i class="fas fa-edit fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
<button *ngSwitchCase="false"
|
||||||
|
[dsBtnDisabled]="true"
|
||||||
|
class="btn btn-outline-primary btn-sm btn-edit"
|
||||||
|
placement="left"
|
||||||
|
[ngbTooltip]="'admin.access-control.epeople.table.edit.buttons.edit-disabled' | translate"
|
||||||
|
>
|
||||||
|
<i class="fas fa-edit fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
<button *ngIf="!groupDto.group?.permanent && groupDto.ableToDelete"
|
||||||
|
(click)="deleteGroup(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>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
}
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(pageInfoState$ | async)?.totalElements === 0" class="alert alert-info w-100 mb-2" role="alert">
|
||||||
|
{{messagePrefix + 'no-items' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -25,6 +25,7 @@ import { provideMockStore } from '@ngrx/store/testing';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
|
of as observableOf,
|
||||||
of,
|
of,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
@@ -94,11 +95,11 @@ describe('GroupsRegistryComponent', () => {
|
|||||||
(authorizationService as any).isAuthorized.and.callFake((featureId?: FeatureID) => {
|
(authorizationService as any).isAuthorized.and.callFake((featureId?: FeatureID) => {
|
||||||
switch (featureId) {
|
switch (featureId) {
|
||||||
case FeatureID.AdministratorOf:
|
case FeatureID.AdministratorOf:
|
||||||
return of(isAdmin);
|
return observableOf(isAdmin);
|
||||||
case FeatureID.CanManageGroup:
|
case FeatureID.CanManageGroup:
|
||||||
return of(canManageGroup);
|
return observableOf(canManageGroup);
|
||||||
case FeatureID.CanDelete:
|
case FeatureID.CanDelete:
|
||||||
return of(true);
|
return observableOf(true);
|
||||||
default:
|
default:
|
||||||
throw new Error(`setIsAuthorized: this fake implementation does not support ${featureId}.`);
|
throw new Error(`setIsAuthorized: this fake implementation does not support ${featureId}.`);
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,10 @@
|
|||||||
import { AsyncPipe } from '@angular/common';
|
import {
|
||||||
|
AsyncPipe,
|
||||||
|
NgForOf,
|
||||||
|
NgIf,
|
||||||
|
NgSwitch,
|
||||||
|
NgSwitchCase,
|
||||||
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
@@ -19,7 +25,7 @@ import {
|
|||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
EMPTY,
|
EMPTY,
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of as observableOf,
|
||||||
Subscription,
|
Subscription,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
@@ -68,14 +74,18 @@ import { followLink } from '../../shared/utils/follow-link-config.model';
|
|||||||
selector: 'ds-groups-registry',
|
selector: 'ds-groups-registry',
|
||||||
templateUrl: './groups-registry.component.html',
|
templateUrl: './groups-registry.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
AsyncPipe,
|
|
||||||
BtnDisabledDirective,
|
|
||||||
NgbTooltipModule,
|
|
||||||
PaginationComponent,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
RouterLink,
|
|
||||||
ThemedLoadingComponent,
|
ThemedLoadingComponent,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
|
RouterLink,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
AsyncPipe,
|
||||||
|
NgIf,
|
||||||
|
PaginationComponent,
|
||||||
|
NgSwitch,
|
||||||
|
NgSwitchCase,
|
||||||
|
NgbTooltipModule,
|
||||||
|
NgForOf,
|
||||||
|
BtnDisabledDirective,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
@@ -179,7 +189,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
|||||||
getRemoteDataPayload(),
|
getRemoteDataPayload(),
|
||||||
switchMap((groups: PaginatedList<Group>) => {
|
switchMap((groups: PaginatedList<Group>) => {
|
||||||
if (groups.page.length === 0) {
|
if (groups.page.length === 0) {
|
||||||
return of(buildPaginatedList(groups.pageInfo, []));
|
return observableOf(buildPaginatedList(groups.pageInfo, []));
|
||||||
}
|
}
|
||||||
return this.authorizationService.isAuthorized(FeatureID.AdministratorOf).pipe(
|
return this.authorizationService.isAuthorized(FeatureID.AdministratorOf).pipe(
|
||||||
switchMap((isSiteAdmin: boolean) => {
|
switchMap((isSiteAdmin: boolean) => {
|
||||||
@@ -224,7 +234,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
canManageGroup$(isSiteAdmin: boolean, group: Group): Observable<boolean> {
|
canManageGroup$(isSiteAdmin: boolean, group: Group): Observable<boolean> {
|
||||||
if (isSiteAdmin) {
|
if (isSiteAdmin) {
|
||||||
return of(true);
|
return observableOf(true);
|
||||||
} else {
|
} else {
|
||||||
return this.authorizationService.isAuthorized(FeatureID.CanManageGroup, group.self);
|
return this.authorizationService.isAuthorized(FeatureID.CanManageGroup, group.self);
|
||||||
}
|
}
|
||||||
@@ -283,7 +293,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
|||||||
return this.dSpaceObjectDataService.findByHref(group._links.object.href).pipe(
|
return this.dSpaceObjectDataService.findByHref(group._links.object.href).pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteData(),
|
||||||
map((rd: RemoteData<DSpaceObject>) => hasValue(rd) && hasValue(rd.payload)),
|
map((rd: RemoteData<DSpaceObject>) => hasValue(rd) && hasValue(rd.payload)),
|
||||||
catchError(() => of(false)),
|
catchError(() => observableOf(false)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user