mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-09 19:13:08 +00:00
Merge remote-tracking branch 'upstream/main' into add-json5-eslint-support
# Conflicts: # src/assets/i18n/fi.json5 # src/assets/i18n/fr.json5
This commit is contained in:
@@ -22,6 +22,7 @@ import { PaginationService } from '../../core/pagination/pagination.service';
|
||||
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
||||
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { SortDirection } from '../../core/cache/models/sort-options.model';
|
||||
|
||||
describe('BrowseByDatePageComponent', () => {
|
||||
let comp: BrowseByDatePageComponent;
|
||||
@@ -49,12 +50,22 @@ describe('BrowseByDatePageComponent', () => {
|
||||
]
|
||||
}
|
||||
});
|
||||
const lastItem = Object.assign(new Item(), {
|
||||
id: 'last-item-id',
|
||||
metadata: {
|
||||
'dc.date.issued': [
|
||||
{
|
||||
value: '1960-01-01'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const mockBrowseService = {
|
||||
getBrowseEntriesFor: (options: BrowseEntrySearchOptions) => toRemoteData([]),
|
||||
getBrowseItemsFor: (value: string, options: BrowseEntrySearchOptions) => toRemoteData([firstItem]),
|
||||
getFirstItemFor: () => createSuccessfulRemoteDataObject$(firstItem)
|
||||
};
|
||||
const mockBrowseService = {
|
||||
getBrowseEntriesFor: (options: BrowseEntrySearchOptions) => toRemoteData([]),
|
||||
getBrowseItemsFor: (value: string, options: BrowseEntrySearchOptions) => toRemoteData([firstItem]),
|
||||
getFirstItemFor: (definition: string, scope?: string, sortDirection?: SortDirection) => null
|
||||
};
|
||||
|
||||
const mockDsoService = {
|
||||
findById: () => createSuccessfulRemoteDataObject$(mockCommunity)
|
||||
@@ -91,9 +102,14 @@ describe('BrowseByDatePageComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BrowseByDatePageComponent);
|
||||
const browseService = fixture.debugElement.injector.get(BrowseService);
|
||||
spyOn(browseService, 'getFirstItemFor')
|
||||
// ok to expect the default browse as first param since we just need the mock items obtained via sort direction.
|
||||
.withArgs('author', undefined, SortDirection.ASC).and.returnValue(createSuccessfulRemoteDataObject$(firstItem))
|
||||
.withArgs('author', undefined, SortDirection.DESC).and.returnValue(createSuccessfulRemoteDataObject$(lastItem));
|
||||
comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
route = (comp as any).route;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should initialize the list of items', () => {
|
||||
@@ -107,6 +123,7 @@ describe('BrowseByDatePageComponent', () => {
|
||||
});
|
||||
|
||||
it('should create a list of startsWith options with the current year first', () => {
|
||||
expect(comp.startsWithOptions[0]).toEqual(new Date().getUTCFullYear());
|
||||
//expect(comp.startsWithOptions[0]).toEqual(new Date().getUTCFullYear());
|
||||
expect(comp.startsWithOptions[0]).toEqual(1960);
|
||||
});
|
||||
});
|
||||
|
@@ -1,11 +1,10 @@
|
||||
import { ChangeDetectorRef, Component, Inject } from '@angular/core';
|
||||
import {
|
||||
BrowseByMetadataPageComponent,
|
||||
browseParamsToOptions, getBrowseSearchOptions
|
||||
browseParamsToOptions,
|
||||
getBrowseSearchOptions
|
||||
} from '../browse-by-metadata-page/browse-by-metadata-page.component';
|
||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { BrowseService } from '../../core/browse/browse.service';
|
||||
@@ -16,7 +15,9 @@ import { map } from 'rxjs/operators';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { isValidDate } from '../../shared/date.util';
|
||||
import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface';
|
||||
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-browse-by-date-page',
|
||||
@@ -72,30 +73,24 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
||||
|
||||
/**
|
||||
* Update the StartsWith options
|
||||
* In this implementation, it creates a list of years starting from now, going all the way back to the earliest
|
||||
* date found on an item within this scope. The further back in time, the bigger the change in years become to avoid
|
||||
* extremely long lists with a one-year difference.
|
||||
* In this implementation, it creates a list of years starting from the most recent item or the current year, going
|
||||
* all the way back to the earliest date found on an item within this scope. The further back in time, the bigger
|
||||
* the change in years become to avoid extremely long lists with a one-year difference.
|
||||
* To determine the change in years, the config found under GlobalConfig.BrowseBy is used for this.
|
||||
* @param definition The metadata definition to fetch the first item for
|
||||
* @param metadataKeys The metadata fields to fetch the earliest date from (expects a date field)
|
||||
* @param scope The scope under which to fetch the earliest item for
|
||||
*/
|
||||
updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) {
|
||||
const firstItemRD = this.browseService.getFirstItemFor(definition, scope, SortDirection.ASC);
|
||||
const lastItemRD = this.browseService.getFirstItemFor(definition, scope, SortDirection.DESC);
|
||||
this.subs.push(
|
||||
this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData<Item>) => {
|
||||
let lowerLimit = this.appConfig.browseBy.defaultLowerLimit;
|
||||
if (hasValue(firstItemRD.payload)) {
|
||||
const date = firstItemRD.payload.firstMetadataValue(metadataKeys);
|
||||
if (isNotEmpty(date) && isValidDate(date)) {
|
||||
const dateObj = new Date(date);
|
||||
// TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC.
|
||||
lowerLimit = isNaN(dateObj.getUTCFullYear()) ? lowerLimit : dateObj.getUTCFullYear();
|
||||
}
|
||||
}
|
||||
observableCombineLatest([firstItemRD, lastItemRD]).subscribe(([firstItem, lastItem]) => {
|
||||
let lowerLimit = this.getLimit(firstItem, metadataKeys, this.appConfig.browseBy.defaultLowerLimit);
|
||||
let upperLimit = this.getLimit(lastItem, metadataKeys, new Date().getUTCFullYear());
|
||||
const options = [];
|
||||
const currentYear = new Date().getUTCFullYear();
|
||||
const oneYearBreak = Math.floor((currentYear - this.appConfig.browseBy.oneYearLimit) / 5) * 5;
|
||||
const fiveYearBreak = Math.floor((currentYear - this.appConfig.browseBy.fiveYearLimit) / 10) * 10;
|
||||
const oneYearBreak = Math.floor((upperLimit - this.appConfig.browseBy.oneYearLimit) / 5) * 5;
|
||||
const fiveYearBreak = Math.floor((upperLimit - this.appConfig.browseBy.fiveYearLimit) / 10) * 10;
|
||||
if (lowerLimit <= fiveYearBreak) {
|
||||
lowerLimit -= 10;
|
||||
} else if (lowerLimit <= oneYearBreak) {
|
||||
@@ -103,7 +98,7 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
||||
} else {
|
||||
lowerLimit -= 1;
|
||||
}
|
||||
let i = currentYear;
|
||||
let i = upperLimit;
|
||||
while (i > lowerLimit) {
|
||||
options.push(i);
|
||||
if (i <= fiveYearBreak) {
|
||||
@@ -121,4 +116,24 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the year from the item metadata field or the limit.
|
||||
* @param itemRD the item remote data
|
||||
* @param metadataKeys The metadata fields to fetch the earliest date from (expects a date field)
|
||||
* @param limit the limit to use if the year can't be found in metadata
|
||||
* @private
|
||||
*/
|
||||
private getLimit(itemRD: RemoteData<Item>, metadataKeys: string[], limit: number): number {
|
||||
if (hasValue(itemRD.payload)) {
|
||||
const date = itemRD.payload.firstMetadataValue(metadataKeys);
|
||||
if (isNotEmpty(date) && isValidDate(date)) {
|
||||
const dateObj = new Date(date);
|
||||
// TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC.
|
||||
return isNaN(dateObj.getUTCFullYear()) ? limit : dateObj.getUTCFullYear();
|
||||
} else {
|
||||
return new Date().getUTCFullYear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -154,6 +154,8 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
||||
|
||||
if (typeof params.value === 'string'){
|
||||
this.value = params.value.trim();
|
||||
} else {
|
||||
this.value = '';
|
||||
}
|
||||
|
||||
if (typeof params.startsWith === 'string'){
|
||||
|
@@ -28,7 +28,8 @@
|
||||
[title]="'toggle ' + node.name"
|
||||
[attr.aria-label]="'toggle ' + node.name"
|
||||
(click)="toggleExpanded(node)"
|
||||
[ngClass]="(hasChild(null, node)| async) ? 'visible' : 'invisible'">
|
||||
[ngClass]="(hasChild(null, node)| async) ? 'visible' : 'invisible'"
|
||||
[attr.data-test]="(hasChild(null, node)| async) ? 'expand-button' : ''">
|
||||
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
||||
aria-hidden="true"></span>
|
||||
</button>
|
||||
|
@@ -8,7 +8,7 @@ import { PostRequest } from '../data/request.models';
|
||||
import {
|
||||
XSRF_REQUEST_HEADER,
|
||||
XSRF_RESPONSE_HEADER
|
||||
} from '../xsrf/xsrf.interceptor';
|
||||
} from '../xsrf/xsrf.constants';
|
||||
|
||||
describe(`ServerAuthRequestService`, () => {
|
||||
let href: string;
|
||||
|
@@ -13,7 +13,7 @@ import {
|
||||
XSRF_REQUEST_HEADER,
|
||||
XSRF_RESPONSE_HEADER,
|
||||
DSPACE_XSRF_COOKIE
|
||||
} from '../xsrf/xsrf.interceptor';
|
||||
} from '../xsrf/xsrf.constants';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
|
@@ -22,6 +22,7 @@ import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
|
||||
import { HrefOnlyDataService } from '../data/href-only-data.service';
|
||||
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { BrowseDefinitionDataService } from './browse-definition-data.service';
|
||||
import { SortDirection } from '../cache/models/sort-options.model';
|
||||
|
||||
|
||||
export const BROWSE_LINKS_TO_FOLLOW: FollowLinkConfig<BrowseEntry | Item>[] = [
|
||||
@@ -160,8 +161,9 @@ export class BrowseService {
|
||||
* Get the first item for a metadata definition in an optional scope
|
||||
* @param definition
|
||||
* @param scope
|
||||
* @param sortDirection optional sort parameter
|
||||
*/
|
||||
getFirstItemFor(definition: string, scope?: string): Observable<RemoteData<Item>> {
|
||||
getFirstItemFor(definition: string, scope?: string, sortDirection?: SortDirection): Observable<RemoteData<Item>> {
|
||||
const href$ = this.getBrowseDefinitions().pipe(
|
||||
getBrowseDefinitionLinks(definition),
|
||||
hasValueOperator(),
|
||||
@@ -177,6 +179,9 @@ export class BrowseService {
|
||||
}
|
||||
args.push('page=0');
|
||||
args.push('size=1');
|
||||
if (sortDirection) {
|
||||
args.push('sort=default,' + sortDirection);
|
||||
}
|
||||
if (isNotEmpty(args)) {
|
||||
href = new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||
}
|
||||
|
@@ -62,25 +62,33 @@ describe('LocaleService test suite', () => {
|
||||
});
|
||||
|
||||
describe('getCurrentLanguageCode', () => {
|
||||
it('should return language saved on cookie', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(translateService, 'getLangs').and.returnValue(langList);
|
||||
});
|
||||
|
||||
it('should return the language saved on cookie if it\'s a valid & active language', () => {
|
||||
spyOnGet.and.returnValue('de');
|
||||
expect(service.getCurrentLanguageCode()).toBe('de');
|
||||
});
|
||||
|
||||
describe('', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(translateService, 'getLangs').and.returnValue(langList);
|
||||
});
|
||||
it('should return the default language if the cookie language is disabled', () => {
|
||||
spyOnGet.and.returnValue('disabled');
|
||||
expect(service.getCurrentLanguageCode()).toBe('en');
|
||||
});
|
||||
|
||||
it('should return language from browser setting', () => {
|
||||
spyOn(translateService, 'getBrowserLang').and.returnValue('it');
|
||||
expect(service.getCurrentLanguageCode()).toBe('it');
|
||||
});
|
||||
it('should return the default language if the cookie language does not exist', () => {
|
||||
spyOnGet.and.returnValue('does-not-exist');
|
||||
expect(service.getCurrentLanguageCode()).toBe('en');
|
||||
});
|
||||
|
||||
it('should return default language from config', () => {
|
||||
spyOn(translateService, 'getBrowserLang').and.returnValue('fr');
|
||||
expect(service.getCurrentLanguageCode()).toBe('en');
|
||||
});
|
||||
it('should return language from browser setting', () => {
|
||||
spyOn(translateService, 'getBrowserLang').and.returnValue('it');
|
||||
expect(service.getCurrentLanguageCode()).toBe('it');
|
||||
});
|
||||
|
||||
it('should return default language from config', () => {
|
||||
spyOn(translateService, 'getBrowserLang').and.returnValue('fr');
|
||||
expect(service.getCurrentLanguageCode()).toBe('en');
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -11,6 +11,7 @@ import { map, mergeMap, take } from 'rxjs/operators';
|
||||
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
|
||||
import { RouteService } from '../services/route.service';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { LangConfig } from '../../../config/lang-config.interface';
|
||||
|
||||
export const LANG_COOKIE = 'dsLanguage';
|
||||
|
||||
@@ -52,8 +53,7 @@ export class LocaleService {
|
||||
getCurrentLanguageCode(): string {
|
||||
// Attempt to get the language from a cookie
|
||||
let lang = this.getLanguageCodeFromCookie();
|
||||
if (isEmpty(lang)) {
|
||||
// Cookie not found
|
||||
if (isEmpty(lang) || environment.languages.find((langConfig: LangConfig) => langConfig.code === lang && langConfig.active) === undefined) {
|
||||
// Attempt to get the browser language from the user
|
||||
if (this.translate.getLangs().includes(this.translate.getBrowserLang())) {
|
||||
lang = this.translate.getBrowserLang();
|
||||
|
33
src/app/core/xsrf/xsrf.constants.ts
Normal file
33
src/app/core/xsrf/xsrf.constants.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* XSRF / CSRF related constants
|
||||
*/
|
||||
|
||||
/**
|
||||
* Name of CSRF/XSRF header we (client) may SEND in requests to backend.
|
||||
* (This is a standard header name for XSRF/CSRF defined by Angular)
|
||||
*/
|
||||
export const XSRF_REQUEST_HEADER = 'X-XSRF-TOKEN';
|
||||
|
||||
/**
|
||||
* Name of CSRF/XSRF header we (client) may RECEIVE in responses from backend
|
||||
* This header is defined by DSpace backend, see https://github.com/DSpace/RestContract/blob/main/csrf-tokens.md
|
||||
*/
|
||||
export const XSRF_RESPONSE_HEADER = 'DSPACE-XSRF-TOKEN';
|
||||
|
||||
/**
|
||||
* Name of client-side Cookie where we store the CSRF/XSRF token between requests.
|
||||
* This cookie is only available to client, and should be updated whenever a new XSRF_RESPONSE_HEADER
|
||||
* is found in a response from the backend.
|
||||
*/
|
||||
export const XSRF_COOKIE = 'XSRF-TOKEN';
|
||||
|
||||
/**
|
||||
* Name of server-side cookie the backend expects the XSRF token to be in.
|
||||
* When the backend receives a modifying request, it will validate the CSRF/XSRF token by looking
|
||||
* for a match between the XSRF_REQUEST_HEADER and this Cookie. For more details see
|
||||
* https://github.com/DSpace/RestContract/blob/main/csrf-tokens.md
|
||||
*
|
||||
* NOTE: This Cookie is NOT readable to the client/UI. It is only readable to the backend and will
|
||||
* be sent along automatically by the user's browser.
|
||||
*/
|
||||
export const DSPACE_XSRF_COOKIE = 'DSPACE-XSRF-COOKIE';
|
@@ -12,15 +12,7 @@ import { Observable, throwError } from 'rxjs';
|
||||
import { tap, catchError } from 'rxjs/operators';
|
||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||
import { CookieService } from '../services/cookie.service';
|
||||
|
||||
// Name of XSRF header we may send in requests to backend (this is a standard name defined by Angular)
|
||||
export const XSRF_REQUEST_HEADER = 'X-XSRF-TOKEN';
|
||||
// Name of XSRF header we may receive in responses from backend
|
||||
export const XSRF_RESPONSE_HEADER = 'DSPACE-XSRF-TOKEN';
|
||||
// Name of cookie where we store the XSRF token
|
||||
export const XSRF_COOKIE = 'XSRF-TOKEN';
|
||||
// Name of cookie the backend expects the XSRF token to be in
|
||||
export const DSPACE_XSRF_COOKIE = 'DSPACE-XSRF-COOKIE';
|
||||
import { XSRF_COOKIE, XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER } from './xsrf.constants';
|
||||
|
||||
/**
|
||||
* Custom Http Interceptor intercepting Http Requests & Responses to
|
||||
|
@@ -1,6 +1,20 @@
|
||||
.ngx-gallery {
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
width: 340px !important;
|
||||
height: 279px !important;
|
||||
:host ::ng-deep {
|
||||
.ngx-gallery {
|
||||
width: unset !important;
|
||||
height: unset !important;
|
||||
}
|
||||
|
||||
ngx-gallery-image {
|
||||
max-width: 340px !important;
|
||||
|
||||
.ngx-gallery-image {
|
||||
background-position: left;
|
||||
}
|
||||
}
|
||||
|
||||
ngx-gallery-image:after {
|
||||
padding-top: 75%;
|
||||
display: block;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,10 @@
|
||||
video {
|
||||
width: 340px;
|
||||
height: 279px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: 340px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: .25rem;
|
||||
}
|
||||
|
@@ -20,9 +20,9 @@
|
||||
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="mediaViewer.image">
|
||||
<div *ngIf="mediaViewer.image" class="mb-2">
|
||||
<ds-themed-media-viewer [item]="object" [videoOptions]="mediaViewer.video"></ds-themed-media-viewer>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ds-themed-item-page-file-section [item]="object"></ds-themed-item-page-file-section>
|
||||
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||
<ds-themed-metadata-representation-list class="ds-item-page-mixed-author-field"
|
||||
|
@@ -21,9 +21,9 @@
|
||||
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
|
||||
</ds-metadata-field-wrapper>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="mediaViewer.image">
|
||||
<div *ngIf="mediaViewer.image" class="mb-2">
|
||||
<ds-themed-media-viewer [item]="object" [videoOptions]="mediaViewer.video"></ds-themed-media-viewer>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ds-themed-item-page-file-section [item]="object"></ds-themed-item-page-file-section>
|
||||
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||
<ds-themed-metadata-representation-list class="ds-item-page-mixed-author-field"
|
||||
|
@@ -16,7 +16,7 @@
|
||||
<ng-container #componentViewContainer></ng-container>
|
||||
</div>
|
||||
|
||||
<small *ngIf="hasHint && ((!model.repeatable && (isRelationship === false || value?.value === null)) || (model.repeatable === true && context?.index === context?.context?.groups?.length - 1)) && (!showErrorMessages || errorMessages.length === 0)"
|
||||
<small *ngIf="hasHint && (formBuilderService.hasArrayGroupValue(model) || (!model.repeatable && (isRelationship === false || value?.value === null)) || (model.repeatable === true && context?.index === context?.context?.groups?.length - 1)) && (!showErrorMessages || errorMessages.length === 0)"
|
||||
class="text-muted ds-hint" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
|
||||
<!-- In case of repeatable fields show empty space for all elements except the first -->
|
||||
<div *ngIf="context?.index !== null
|
||||
|
@@ -147,12 +147,14 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
|
||||
new DynamicListCheckboxGroupModel({
|
||||
id: 'checkboxList',
|
||||
vocabularyOptions: vocabularyOptions,
|
||||
repeatable: true
|
||||
repeatable: true,
|
||||
required: false,
|
||||
}),
|
||||
new DynamicListRadioGroupModel({
|
||||
id: 'radioList',
|
||||
vocabularyOptions: vocabularyOptions,
|
||||
repeatable: false
|
||||
repeatable: false,
|
||||
required: false,
|
||||
}),
|
||||
new DynamicRelationGroupModel({
|
||||
submissionId: '1234',
|
||||
|
@@ -259,7 +259,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
||||
private submissionObjectService: SubmissionObjectDataService,
|
||||
private ref: ChangeDetectorRef,
|
||||
private formService: FormService,
|
||||
private formBuilderService: FormBuilderService,
|
||||
public formBuilderService: FormBuilderService,
|
||||
private submissionService: SubmissionService,
|
||||
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||
) {
|
||||
|
@@ -15,8 +15,10 @@ export interface DynamicListCheckboxGroupModelConfig extends DynamicFormGroupMod
|
||||
vocabularyOptions: VocabularyOptions;
|
||||
groupLength?: number;
|
||||
repeatable: boolean;
|
||||
value?: any;
|
||||
value?: VocabularyEntry[];
|
||||
typeBindRelations?: DynamicFormControlRelation[];
|
||||
required: boolean;
|
||||
hint?: string;
|
||||
}
|
||||
|
||||
export class DynamicListCheckboxGroupModel extends DynamicCheckboxGroupModel {
|
||||
@@ -26,6 +28,8 @@ export class DynamicListCheckboxGroupModel extends DynamicCheckboxGroupModel {
|
||||
@serializable() groupLength: number;
|
||||
@serializable() _value: VocabularyEntry[];
|
||||
@serializable() typeBindRelations: DynamicFormControlRelation[];
|
||||
@serializable() required: boolean;
|
||||
@serializable() hint: string;
|
||||
isListGroup = true;
|
||||
valueUpdates: Subject<any>;
|
||||
|
||||
@@ -36,6 +40,8 @@ export class DynamicListCheckboxGroupModel extends DynamicCheckboxGroupModel {
|
||||
this.groupLength = config.groupLength || 5;
|
||||
this._value = [];
|
||||
this.repeatable = config.repeatable;
|
||||
this.required = config.required;
|
||||
this.hint = config.hint;
|
||||
|
||||
this.valueUpdates = new Subject<any>();
|
||||
this.valueUpdates.subscribe((value: VocabularyEntry | VocabularyEntry[]) => this.value = value);
|
||||
@@ -56,9 +62,8 @@ export class DynamicListCheckboxGroupModel extends DynamicCheckboxGroupModel {
|
||||
if (Array.isArray(value)) {
|
||||
this._value = value;
|
||||
} else {
|
||||
// _value is non extendible so assign it a new array
|
||||
const newValue = (this.value as VocabularyEntry[]).concat([value]);
|
||||
this._value = newValue;
|
||||
// _value is non-extendable so assign it a new array
|
||||
this._value = (this.value as VocabularyEntry[]).concat([value]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,12 +6,15 @@ import {
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||
import { hasValue } from '../../../../../empty.util';
|
||||
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||
|
||||
export interface DynamicListModelConfig extends DynamicRadioGroupModelConfig<any> {
|
||||
vocabularyOptions: VocabularyOptions;
|
||||
groupLength?: number;
|
||||
repeatable: boolean;
|
||||
value?: any;
|
||||
value?: VocabularyEntry[];
|
||||
required: boolean;
|
||||
hint?: string;
|
||||
}
|
||||
|
||||
export class DynamicListRadioGroupModel extends DynamicRadioGroupModel<any> {
|
||||
@@ -19,6 +22,8 @@ export class DynamicListRadioGroupModel extends DynamicRadioGroupModel<any> {
|
||||
@serializable() vocabularyOptions: VocabularyOptions;
|
||||
@serializable() repeatable: boolean;
|
||||
@serializable() groupLength: number;
|
||||
@serializable() required: boolean;
|
||||
@serializable() hint: string;
|
||||
isListGroup = true;
|
||||
|
||||
constructor(config: DynamicListModelConfig, layout?: DynamicFormControlLayout) {
|
||||
@@ -27,6 +32,8 @@ export class DynamicListRadioGroupModel extends DynamicRadioGroupModel<any> {
|
||||
this.vocabularyOptions = config.vocabularyOptions;
|
||||
this.groupLength = config.groupLength || 5;
|
||||
this.repeatable = config.repeatable;
|
||||
this.required = config.required;
|
||||
this.hint = config.hint;
|
||||
this.value = config.value;
|
||||
}
|
||||
|
||||
|
@@ -17,7 +17,6 @@
|
||||
[id]="item.id"
|
||||
[formControlName]="item.id"
|
||||
[name]="model.name"
|
||||
[required]="model.required"
|
||||
[value]="item.value"
|
||||
(blur)="onBlur($event)"
|
||||
(change)="onChange($event)"
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
import { FormGroup, ValidatorFn, ValidationErrors, AbstractControl } from '@angular/forms';
|
||||
import {
|
||||
DynamicCheckboxModel,
|
||||
DynamicFormControlComponent,
|
||||
@@ -110,6 +109,9 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
||||
protected setOptionsFromVocabulary() {
|
||||
if (this.model.vocabularyOptions.name && this.model.vocabularyOptions.name.length > 0) {
|
||||
const listGroup = this.group.controls[this.model.id] as FormGroup;
|
||||
if (this.model.repeatable && this.model.required) {
|
||||
listGroup.addValidators(this.hasAtLeastOneVocabularyEntry());
|
||||
}
|
||||
const pageInfo: PageInfo = new PageInfo({
|
||||
elementsPerPage: 9999, currentPage: 1
|
||||
} as PageInfo);
|
||||
@@ -121,7 +123,7 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
||||
let tempList: ListItem[] = [];
|
||||
this.optionsList = entries.page;
|
||||
// Make a list of available options (checkbox/radio) and split in groups of 'model.groupLength'
|
||||
entries.page.forEach((option, key) => {
|
||||
entries.page.forEach((option: VocabularyEntry, key: number) => {
|
||||
const value = option.authority || option.value;
|
||||
const checked: boolean = isNotEmpty(findKey(
|
||||
this.model.value,
|
||||
@@ -156,4 +158,13 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if at least one {@link VocabularyEntry} has been selected.
|
||||
*/
|
||||
hasAtLeastOneVocabularyEntry(): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
return control && control.value && Object.values(control.value).find((checked: boolean) => checked === true) ? null : this.model.errorMessages;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -235,10 +235,16 @@ describe('FormBuilderService test suite', () => {
|
||||
new DynamicListCheckboxGroupModel({
|
||||
id: 'testCheckboxList',
|
||||
vocabularyOptions: vocabularyOptions,
|
||||
repeatable: true
|
||||
repeatable: true,
|
||||
required: false,
|
||||
}),
|
||||
|
||||
new DynamicListRadioGroupModel({ id: 'testRadioList', vocabularyOptions: vocabularyOptions, repeatable: false }),
|
||||
new DynamicListRadioGroupModel({
|
||||
id: 'testRadioList',
|
||||
vocabularyOptions: vocabularyOptions,
|
||||
repeatable: false,
|
||||
required: false,
|
||||
}),
|
||||
|
||||
new DynamicRelationGroupModel({
|
||||
submissionId,
|
||||
|
@@ -9,7 +9,7 @@ import { UploaderOptions } from './uploader-options.model';
|
||||
import { hasValue, isNotEmpty, isUndefined } from '../../empty.util';
|
||||
import { UploaderProperties } from './uploader-properties.model';
|
||||
import { HttpXsrfTokenExtractor } from '@angular/common/http';
|
||||
import { XSRF_COOKIE, XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER } from '../../../core/xsrf/xsrf.interceptor';
|
||||
import { XSRF_COOKIE, XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER } from '../../../core/xsrf/xsrf.constants';
|
||||
import { CookieService } from '../../../core/services/cookie.service';
|
||||
import { DragService } from '../../../core/drag.service';
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
{{ 'statistics.table.title.' + report.reportType | translate }}
|
||||
</h3>
|
||||
|
||||
<table class="table table-striped">
|
||||
<table class="table table-striped" [attr.data-test]="report.reportType">
|
||||
|
||||
<tbody>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<tr *ngFor="let point of report.points"
|
||||
class="{{point.id}}-data">
|
||||
<th scope="row">
|
||||
<th scope="row" data-test="statistics-label">
|
||||
{{ getLabel(point) | async }}
|
||||
</th>
|
||||
<td *ngFor="let header of headers"
|
||||
|
@@ -3,8 +3,10 @@ import { Point, UsageReport } from '../../core/statistics/models/usage-report.mo
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../core/shared/operators';
|
||||
import { getRemoteDataPayload, getFinishedRemoteData } from '../../core/shared/operators';
|
||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { isEmpty } from '../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* Component representing a statistics table for a given usage report.
|
||||
@@ -35,6 +37,7 @@ export class StatisticsTableComponent implements OnInit {
|
||||
constructor(
|
||||
protected dsoService: DSpaceObjectDataService,
|
||||
protected nameService: DSONameService,
|
||||
private translateService: TranslateService,
|
||||
) {
|
||||
|
||||
}
|
||||
@@ -54,9 +57,9 @@ export class StatisticsTableComponent implements OnInit {
|
||||
switch (this.report.reportType) {
|
||||
case 'TotalVisits':
|
||||
return this.dsoService.findById(point.id).pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getFinishedRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
map((item) => this.nameService.getName(item)),
|
||||
map((item) => !isEmpty(item) ? this.nameService.getName(item) : this.translateService.instant('statistics.table.no-name')),
|
||||
);
|
||||
case 'TopCities':
|
||||
case 'topCountries':
|
||||
|
@@ -5,5 +5,6 @@
|
||||
[submissionDefinition]="submissionDefinition"
|
||||
[submissionErrors]="submissionErrors"
|
||||
[item]="item"
|
||||
[collectionModifiable]="collectionModifiable"
|
||||
[submissionId]="submissionId"></ds-submission-form>
|
||||
</div>
|
||||
|
@@ -36,6 +36,13 @@ export class SubmissionEditComponent implements OnDestroy, OnInit {
|
||||
*/
|
||||
public collectionId: string;
|
||||
|
||||
/**
|
||||
* Checks if the collection can be modifiable by the user
|
||||
* @type {booelan}
|
||||
*/
|
||||
public collectionModifiable: boolean | null = null;
|
||||
|
||||
|
||||
/**
|
||||
* The list of submission's sections
|
||||
* @type {WorkspaceitemSectionsObject}
|
||||
@@ -109,6 +116,9 @@ export class SubmissionEditComponent implements OnDestroy, OnInit {
|
||||
* Retrieve workspaceitem/workflowitem from server and initialize all instance variables
|
||||
*/
|
||||
ngOnInit() {
|
||||
|
||||
this.collectionModifiable = this.route.snapshot.data?.collectionModifiable ?? null;
|
||||
|
||||
this.subs.push(
|
||||
this.route.paramMap.pipe(
|
||||
switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))),
|
||||
|
@@ -25,7 +25,7 @@
|
||||
class="btn btn-outline-primary"
|
||||
(blur)="onClose()"
|
||||
(click)="onClose()"
|
||||
[disabled]="(processingChange$ | async)"
|
||||
[disabled]="(processingChange$ | async) || collectionModifiable == false"
|
||||
ngbDropdownToggle>
|
||||
<span *ngIf="(processingChange$ | async)"><i class='fas fa-circle-notch fa-spin'></i></span>
|
||||
<span *ngIf="!(processingChange$ | async)">{{ selectedCollectionName$ | async }}</span>
|
||||
|
@@ -52,6 +52,12 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit {
|
||||
*/
|
||||
@Input() currentDefinition: string;
|
||||
|
||||
/**
|
||||
* Checks if the collection can be modifiable by the user
|
||||
* @type {booelan}
|
||||
*/
|
||||
@Input() collectionModifiable: boolean | null = null;
|
||||
|
||||
/**
|
||||
* The submission id
|
||||
* @type {string}
|
||||
|
@@ -11,6 +11,7 @@
|
||||
<ds-submission-form-collection [currentCollectionId]="collectionId"
|
||||
[currentDefinition]="definitionId"
|
||||
[submissionId]="submissionId"
|
||||
[collectionModifiable]="collectionModifiable"
|
||||
(collectionChange)="onCollectionChange($event)">
|
||||
</ds-submission-form-collection>
|
||||
</div>
|
||||
|
@@ -34,8 +34,16 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
|
||||
* @type {string}
|
||||
*/
|
||||
@Input() collectionId: string;
|
||||
|
||||
@Input() item: Item;
|
||||
|
||||
/**
|
||||
* Checks if the collection can be modifiable by the user
|
||||
* @type {booelan}
|
||||
*/
|
||||
@Input() collectionModifiable: boolean | null = null;
|
||||
|
||||
|
||||
/**
|
||||
* The list of submission's sections
|
||||
* @type {WorkspaceitemSectionsObject}
|
||||
|
@@ -34,7 +34,11 @@ import {
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver
|
||||
},
|
||||
data: { title: 'workflow-item.edit.title', breadcrumbKey: 'workflow-item.edit' }
|
||||
data: {
|
||||
title: 'workflow-item.edit.title',
|
||||
breadcrumbKey: 'workflow-item.edit',
|
||||
collectionModifiable: false
|
||||
}
|
||||
},
|
||||
{
|
||||
canActivate: [AuthenticatedGuard],
|
||||
|
@@ -2680,7 +2680,7 @@
|
||||
|
||||
"itemtemplate.edit.metadata.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button",
|
||||
|
||||
"itemtemplate.edit.metadata.notifications.discarded.title": "Changed discarded",
|
||||
"itemtemplate.edit.metadata.notifications.discarded.title": "Changes discarded",
|
||||
|
||||
"itemtemplate.edit.metadata.notifications.error.title": "An error occurred",
|
||||
|
||||
@@ -2690,7 +2690,7 @@
|
||||
|
||||
"itemtemplate.edit.metadata.notifications.outdated.content": "The item template you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts",
|
||||
|
||||
"itemtemplate.edit.metadata.notifications.outdated.title": "Changed outdated",
|
||||
"itemtemplate.edit.metadata.notifications.outdated.title": "Changes outdated",
|
||||
|
||||
"itemtemplate.edit.metadata.notifications.saved.content": "Your changes to this item template's metadata were saved.",
|
||||
|
||||
@@ -4033,6 +4033,8 @@
|
||||
|
||||
"statistics.table.header.views": "Views",
|
||||
|
||||
"statistics.table.no-name": "(object name could not be loaded)",
|
||||
|
||||
|
||||
|
||||
"submission.edit.breadcrumbs": "Edit Submission",
|
||||
|
@@ -156,7 +156,8 @@
|
||||
|
||||
// "admin.registries.bitstream-formats.table.name": "Name",
|
||||
"admin.registries.bitstream-formats.table.name": "Nimi",
|
||||
// TODO New key - Add a translation
|
||||
|
||||
// "admin.registries.bitstream-formats.table.id" : "ID",
|
||||
"admin.registries.bitstream-formats.table.id": "ID",
|
||||
|
||||
// "admin.registries.bitstream-formats.table.return": "Return",
|
||||
@@ -207,7 +208,7 @@
|
||||
"admin.registries.metadata.schemas.table.delete": "Poista valittu",
|
||||
|
||||
// "admin.registries.metadata.schemas.table.id": "ID",
|
||||
"admin.registries.metadata.schemas.table.id": "ID-tunnus",
|
||||
"admin.registries.metadata.schemas.table.id": "ID",
|
||||
|
||||
// "admin.registries.metadata.schemas.table.name": "Name",
|
||||
"admin.registries.metadata.schemas.table.name": "Nimi",
|
||||
@@ -237,7 +238,8 @@
|
||||
|
||||
// "admin.registries.schema.fields.table.field": "Field",
|
||||
"admin.registries.schema.fields.table.field": "Kenttä",
|
||||
// TODO New key - Add a translation
|
||||
|
||||
// "admin.registries.schema.fields.table.id": "ID",
|
||||
"admin.registries.schema.fields.table.id": "ID",
|
||||
|
||||
// "admin.registries.schema.fields.table.scopenote": "Scope Note",
|
||||
@@ -408,7 +410,7 @@
|
||||
"admin.access-control.epeople.form.groupsEPersonIsMemberOf": "Jäsenenä näissä ryhmissä:",
|
||||
|
||||
// "admin.access-control.epeople.form.table.id": "ID",
|
||||
"admin.access-control.epeople.form.table.id": "ID-tunnus",
|
||||
"admin.access-control.epeople.form.table.id": "ID",
|
||||
|
||||
// "admin.access-control.epeople.form.table.name": "Name",
|
||||
"admin.access-control.epeople.form.table.name": "Nimi",
|
||||
@@ -2495,7 +2497,7 @@
|
||||
"item.edit.tabs.status.buttons.reinstate.label": "Palauta tietue arkistoon",
|
||||
|
||||
// "item.edit.tabs.status.buttons.withdraw.button": "Withdraw this item",
|
||||
"item.edit.tabs.status.buttons.withdraw.button": "poista tämä kohde",
|
||||
"item.edit.tabs.status.buttons.withdraw.button": "Poista tämä kohde",
|
||||
|
||||
// "item.edit.tabs.status.buttons.withdraw.label": "Withdraw item from the repository",
|
||||
"item.edit.tabs.status.buttons.withdraw.label": "Poista tietue käytöstä",
|
||||
@@ -2510,7 +2512,7 @@
|
||||
"item.edit.tabs.status.labels.handle": "Handle-tunnus",
|
||||
|
||||
// "item.edit.tabs.status.labels.id": "Item Internal ID",
|
||||
"item.edit.tabs.status.labels.id": "Tietueen sisäinen ID-tunnus",
|
||||
"item.edit.tabs.status.labels.id": "Tietueen sisäinen ID",
|
||||
|
||||
// "item.edit.tabs.status.labels.itemPage": "Item Page",
|
||||
"item.edit.tabs.status.labels.itemPage": "Tietueen tiedot",
|
||||
@@ -3348,7 +3350,7 @@
|
||||
"orgunit.page.edit": "Muokkaa tietuetta",
|
||||
|
||||
// "orgunit.page.id": "ID",
|
||||
"orgunit.page.id": "ID-tunnus",
|
||||
"orgunit.page.id": "ID",
|
||||
|
||||
// "orgunit.page.titleprefix": "Organizational Unit: ",
|
||||
"orgunit.page.titleprefix": "Organisaatioyksikkö: ",
|
||||
@@ -3400,7 +3402,7 @@
|
||||
"person.page.orcid": "ORCID-tunniste",
|
||||
|
||||
// "person.page.staffid": "Staff ID",
|
||||
"person.page.staffid": "Henkilökunnan ID-tunnus",
|
||||
"person.page.staffid": "Henkilökunnan ID",
|
||||
|
||||
// "person.page.titleprefix": "Person: ",
|
||||
"person.page.titleprefix": "Käyttäjä: ",
|
||||
@@ -3652,7 +3654,7 @@
|
||||
"project.page.funder": "Rahoittajat",
|
||||
|
||||
// "project.page.id": "ID",
|
||||
"project.page.id": "ID-tunnus",
|
||||
"project.page.id": "ID",
|
||||
|
||||
// "project.page.keyword": "Keywords",
|
||||
"project.page.keyword": "Asiasanat",
|
||||
@@ -3936,7 +3938,7 @@
|
||||
"resource-policies.form.eperson-group-list.table.headers.action": "Toimenpide",
|
||||
|
||||
// "resource-policies.form.eperson-group-list.table.headers.id": "ID",
|
||||
"resource-policies.form.eperson-group-list.table.headers.id": "ID-tunnus",
|
||||
"resource-policies.form.eperson-group-list.table.headers.id": "ID",
|
||||
|
||||
// "resource-policies.form.eperson-group-list.table.headers.name": "Name",
|
||||
"resource-policies.form.eperson-group-list.table.headers.name": "Nimi",
|
||||
@@ -3984,7 +3986,7 @@
|
||||
"resource-policies.table.headers.group": "Ryhmä",
|
||||
|
||||
// "resource-policies.table.headers.id": "ID",
|
||||
"resource-policies.table.headers.id": "ID-tunnus",
|
||||
"resource-policies.table.headers.id": "ID",
|
||||
|
||||
// "resource-policies.table.headers.name": "Name",
|
||||
"resource-policies.table.headers.name": "Nimi",
|
||||
@@ -4911,8 +4913,8 @@
|
||||
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
|
||||
"submission.sections.upload.header.policy.default.withlist": "Yksittäisten tiedostojen pääsyrajoitusten lisäksi {{collectionName}}-kokoelmaan ladatut tiedostot ovat seuraavien ryhmien saatavilla:",
|
||||
|
||||
// "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or <strong>upload additional files by dragging & dropping them anywhere on the page.</strong>",
|
||||
"submission.sections.upload.info": "Tietueen kaikki tiedostot on lueteltu tässä. Voit päivittää tiedoston metadataa ja pääsyehtoja tai <strong>ladata lisää tiedostoja raahaamalla ne mihin hyvänsä sivun kohtaan</strong>",
|
||||
// "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
"submission.sections.upload.info": "Tietueen kaikki tiedostot on lueteltu tässä. Voit päivittää tiedoston metadataa ja pääsyehtoja tai <strong>ladata lisää tiedostoja raahaamalla ne mihin hyvänsä sivun kohtaan.</strong>",
|
||||
|
||||
// "submission.sections.upload.no-entry": "No",
|
||||
"submission.sections.upload.no-entry": "Ei",
|
||||
@@ -5063,8 +5065,7 @@
|
||||
"uploader.or": " tai",
|
||||
|
||||
// "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)",
|
||||
// TODO Source message changed - Revise the translation
|
||||
"uploader.processing": "Käsitellään",
|
||||
"uploader.processing": "Käsitellään ladattuja tiedostoja... (voit sulkea tämän sivun)",
|
||||
|
||||
// "uploader.queue-length": "Queue length",
|
||||
"uploader.queue-length": "Jonon pituus",
|
||||
|
@@ -952,6 +952,9 @@
|
||||
// "bitstream-request-a-copy.submit.error": "Something went wrong with submitting the item request.",
|
||||
"bitstream-request-a-copy.submit.error": "Un problème est survenu lors de la soumission de la demande d'Item",
|
||||
|
||||
//"browse.back.all-results": "All browse results",
|
||||
"browse.back.all-results": "Tous les résultats",
|
||||
|
||||
// "browse.comcol.by.author": "By Author",
|
||||
"browse.comcol.by.author": "Auteur",
|
||||
|
||||
@@ -1069,9 +1072,18 @@
|
||||
// "browse.title": "Browsing {{ collection }} by {{ field }} {{ value }}",
|
||||
"browse.title": "Parcourir la collection {{ collection }} par {{ field }} {{ value }}",
|
||||
|
||||
//"browse.title.page": "Browsing {{ collection }} by {{ field }} {{ value }}",
|
||||
"browse.title.page": "Parcourir la collection {{ collection }} par {{ field }} {{ value }}",
|
||||
|
||||
//"search.browse.item-back": "Back to Results",
|
||||
"search.browse.item-back": "Retour aux résultats",
|
||||
|
||||
// "chips.remove": "Remove chip",
|
||||
"chips.remove": "Supprimer fragment",
|
||||
|
||||
//"claimed-approved-search-result-list-element.title": "Approved",
|
||||
"claimed-approved-search-result-list-element.title": "Approuvé",
|
||||
|
||||
// "collection.create.head": "Create a Collection",
|
||||
"collection.create.head": "Créer une collection",
|
||||
|
||||
@@ -1719,6 +1731,9 @@
|
||||
// "cookies.consent.decline": "Decline",
|
||||
"cookies.consent.decline": "Refuser",
|
||||
|
||||
//"cookies.consent.ok": "That's ok",
|
||||
"cookies.consent.ok": "Accepter",
|
||||
|
||||
// "cookies.consent.content-notice.description": "We collect and process your personal information for the following purposes: <strong>Authentication, Preferences, Acknowledgement and Statistics</strong>. <br/> To learn more, please read our {privacyPolicy}.",
|
||||
"cookies.consent.content-notice.description": "Vos données personnelles sont récupérées et utilisées dans les contextes suivants : <strong>authentification, préférences, consentement et statistiques</strong>. <br/> Pour plus d'informations, veuillez vous référer à la {privacyPolicy}.",
|
||||
|
||||
@@ -1884,6 +1899,9 @@
|
||||
// "dso-selector.set-scope.community.button": "Search all of DSpace",
|
||||
"dso-selector.set-scope.community.button": "Chercher dans toutes les collections",
|
||||
|
||||
//"dso-selector.set-scope.community.or-divider": "or",
|
||||
"dso-selector.set-scope.community.or-divider": "ou",
|
||||
|
||||
// "dso-selector.set-scope.community.input-header": "Search for a community or collection",
|
||||
"dso-selector.set-scope.community.input-header": "Chercher une communauté ou une collection",
|
||||
|
||||
@@ -2496,6 +2514,71 @@
|
||||
// "item.edit.tabs.item-mapper.title": "Item Edit - Collection Mapper",
|
||||
"item.edit.tabs.item-mapper.title": "Édition d'Item - Association de collection",
|
||||
|
||||
// "item.edit.identifiers.doi.status.UNKNOWN": "Unknown",
|
||||
"item.edit.identifiers.doi.status.UNKNOWN": "Inconnu",
|
||||
|
||||
// "item.edit.identifiers.doi.status.TO_BE_REGISTERED": "Queued for registration",
|
||||
"item.edit.identifiers.doi.status.TO_BE_REGISTERED": "En attente d'inscription",
|
||||
|
||||
// "item.edit.identifiers.doi.status.TO_BE_RESERVED": "Queued for reservation",
|
||||
"item.edit.identifiers.doi.status.TO_BE_RESERVED": "En attente de réservation",
|
||||
|
||||
// "item.edit.identifiers.doi.status.IS_REGISTERED": "Registered",
|
||||
"item.edit.identifiers.doi.status.IS_REGISTERED": "Inscrit",
|
||||
|
||||
// "item.edit.identifiers.doi.status.IS_RESERVED": "Reserved",
|
||||
"item.edit.identifiers.doi.status.IS_RESERVED": "Réseré",
|
||||
|
||||
// "item.edit.identifiers.doi.status.UPDATE_RESERVED": "Reserved (update queued)",
|
||||
"item.edit.identifiers.doi.status.UPDATE_RESERVED": "Réservé (en attente)",
|
||||
|
||||
// "item.edit.identifiers.doi.status.UPDATE_REGISTERED": "Registered (update queued)",
|
||||
"item.edit.identifiers.doi.status.UPDATE_REGISTERED": "Inscrit (en attente)",
|
||||
|
||||
// "item.edit.identifiers.doi.status.UPDATE_BEFORE_REGISTRATION": "Queued for update and registration",
|
||||
"item.edit.identifiers.doi.status.UPDATE_BEFORE_REGISTRATION": "En attente pour la mise à jour et l'inscription",
|
||||
|
||||
// "item.edit.identifiers.doi.status.TO_BE_DELETED": "Queued for deletion",
|
||||
"item.edit.identifiers.doi.status.TO_BE_DELETED": "En attente pour la suppression",
|
||||
|
||||
// "item.edit.identifiers.doi.status.DELETED": "Deleted",
|
||||
"item.edit.identifiers.doi.status.DELETED": "Supprimé",
|
||||
|
||||
// "item.edit.identifiers.doi.status.PENDING": "Pending (not registered)",
|
||||
"item.edit.identifiers.doi.status.PENDING": "En attente (non inscrit)",
|
||||
|
||||
// "item.edit.identifiers.doi.status.MINTED": "Minted (not registered)",
|
||||
"item.edit.identifiers.doi.status.MINTED": "Émis (non inscrit)",
|
||||
|
||||
// "item.edit.tabs.status.buttons.register-doi.label": "Register a new or pending DOI",
|
||||
"item.edit.tabs.status.buttons.register-doi.label": "Inscrire un nouveau DOI ou un DOI en attente",
|
||||
|
||||
// "item.edit.tabs.status.buttons.register-doi.button": "Register DOI...",
|
||||
"item.edit.tabs.status.buttons.register-doi.button": "Inscrire le DOI...",
|
||||
|
||||
// "item.edit.register-doi.header": "Register a new or pending DOI",
|
||||
"item.edit.register-doi.header": "Inscrire un nouveau DOI ou un DOI en attente",
|
||||
|
||||
// "item.edit.register-doi.description": "Review any pending identifiers and item metadata below and click Confirm to proceed with DOI registration, or Cancel to back out",
|
||||
"item.edit.register-doi.description": "Réviser les identifiants en attente and les métadonnées ci-dessous item metadata below puis cliquer sur Confirmer afin de lancer l'inscription du DOI ou sur Annuler pour interrompre l'inscription",
|
||||
|
||||
// "item.edit.register-doi.confirm": "Confirm",
|
||||
"item.edit.register-doi.confirm": "Confirmer",
|
||||
|
||||
// "item.edit.register-doi.cancel": "Cancel",
|
||||
"item.edit.register-doi.cancel": "Annuler",
|
||||
|
||||
// "item.edit.register-doi.success": "DOI queued for registration successfully.",
|
||||
"item.edit.register-doi.success": "DOI mis en attente pour l'inscription.",
|
||||
|
||||
// "item.edit.register-doi.error": "Error registering DOI",
|
||||
"item.edit.register-doi.error": "Erreur lors de l'inscription du DOI",
|
||||
|
||||
// "item.edit.register-doi.to-update": "The following DOI has already been minted and will be queued for registration online",
|
||||
"item.edit.register-doi.to-update": "Le DOI suivant a déjà été généré et sera mis en attente pour être inscrit",
|
||||
|
||||
|
||||
|
||||
// "item.edit.item-mapper.buttons.add": "Map item to selected collections",
|
||||
"item.edit.item-mapper.buttons.add": "Associer l'Item aux collections sélectionnées",
|
||||
|
||||
@@ -3739,6 +3822,9 @@
|
||||
// "mydspace.show.workspace": "Your Submissions",
|
||||
"mydspace.show.workspace": "Vos dépôts",
|
||||
|
||||
//"mydspace.show.supervisedWorkspace": "Supervised items",
|
||||
"mydspace.show.supervisedWorkspace": "Items supervisés",
|
||||
|
||||
// "mydspace.status.archived": "Archived",
|
||||
"mydspace.status.archived": "Archivés",
|
||||
|
||||
@@ -3805,6 +3891,9 @@
|
||||
// "nav.stop-impersonating": "Stop impersonating EPerson",
|
||||
"nav.stop-impersonating": "Retour à votre propre EPerson",
|
||||
|
||||
//"nav.subscriptions" : "Subscriptions",
|
||||
"nav.subscriptions": "Abonnements",
|
||||
|
||||
// "nav.toggle": "Toggle navigation",
|
||||
"nav.toggle": "Basculer la navigation",
|
||||
|
||||
@@ -5061,6 +5150,9 @@
|
||||
// "submission.import-external.source.crossref": "CrossRef",
|
||||
"submission.import-external.source.crossref": "CrossRef (DOI)",
|
||||
|
||||
//"submission.import-external.source.datacite": "DataCite",
|
||||
"submission.import-external.source.datacite": "DataCite (DOI)",
|
||||
|
||||
// "submission.import-external.source.scielo": "SciELO",
|
||||
"submission.import-external.source.scielo": "SciELO",
|
||||
|
||||
@@ -5076,6 +5168,9 @@
|
||||
// "submission.import-external.source.orcidWorks": "ORCID",
|
||||
"submission.import-external.source.orcidWorks": "ORCID",
|
||||
|
||||
//"submission.import-external.source.epo": "European Patent Office (EPO)",
|
||||
"submission.import-external.source.epo": "Office Européen des brevets (OEB)",
|
||||
|
||||
// "submission.import-external.source.loading": "Loading ...",
|
||||
"submission.import-external.source.loading": "En cours de chargement ...",
|
||||
|
||||
@@ -5991,8 +6086,15 @@
|
||||
// "virtual-metadata.delete-relationship.modal-head": "Select the items for which you want to save the virtual metadata as real metadata",
|
||||
"virtual-metadata.delete-relationship.modal-head": "Sélectionner les Items pour lesquels vous souhaitez sauvegarder les métadonnées virtuelles en tant que métadonnées réelles",
|
||||
|
||||
|
||||
//"supervisedWorkspace.search.results.head": "Supervised Items",
|
||||
"supervisedWorkspace.search.results.head": "Items supervisés",
|
||||
|
||||
//"workspace.search.results.head": "Your submissions",
|
||||
"workspace.search.results.head": "Vos dépôts",
|
||||
|
||||
// "workspace.search.results.head": "Your submissions",
|
||||
"workspace.search.results.head": "Vos soumissions",
|
||||
"workspace.search.results.head": "Vos dépôts",
|
||||
|
||||
// "workflowAdmin.search.results.head": "Administer Workflow",
|
||||
"workflowAdmin.search.results.head": "Workflow Administrateur",
|
||||
@@ -6000,6 +6102,9 @@
|
||||
// "workflow.search.results.head": "Workflow tasks",
|
||||
"workflow.search.results.head": "Tableau de suivi",
|
||||
|
||||
//"supervision.search.results.head": "Workflow and Workspace tasks",
|
||||
"supervision.search.results.head": "Tableau de suivi et dépôts en cours",
|
||||
|
||||
// "workflow-item.edit.breadcrumbs": "Edit workflowitem",
|
||||
"workflow-item.edit.breadcrumbs": "Éditer l'Item du Workflow",
|
||||
|
||||
@@ -6072,7 +6177,83 @@
|
||||
// "idle-modal.log-out": "Log out",
|
||||
"idle-modal.log-out": "Déconnexion",
|
||||
|
||||
// "idle-modal.extend-session": "Extend session"
|
||||
// "idle-modal.extend-session": "Extend session",
|
||||
"idle-modal.extend-session": "Prolonger la session",
|
||||
|
||||
|
||||
|
||||
// "system-wide-alert-banner.retrieval.error": "Something went wrong retrieving the system-wide alert banner",
|
||||
"system-wide-alert-banner.retrieval.error": "Une erreur s'est produite lors de la récupération de la bannière du message d'avertissement",
|
||||
|
||||
//"system-wide-alert-banner.countdown.prefix": "In",
|
||||
"system-wide-alert-banner.countdown.prefix": "Dans",
|
||||
|
||||
// "system-wide-alert-banner.countdown.days": "{{days}} day(s),",
|
||||
"system-wide-alert-banner.countdown.days": "{{days}} jour(s),",
|
||||
|
||||
// "system-wide-alert-banner.countdown.hours": "{{hours}} hour(s) and",
|
||||
"system-wide-alert-banner.countdown.hours": "{{hours}} heure(s) et",
|
||||
|
||||
// "system-wide-alert-banner.countdown.minutes": "{{minutes}} minute(s):",
|
||||
"system-wide-alert-banner.countdown.minutes": "{{minutes}} minute(s):",
|
||||
|
||||
|
||||
|
||||
// "menu.section.system-wide-alert": "System-wide Alert",
|
||||
"menu.section.system-wide-alert": "Messages d'avertissement",
|
||||
|
||||
// "system-wide-alert.form.header": "System-wide Alert",
|
||||
"system-wide-alert.form.header": "Messages d'avertissement",
|
||||
|
||||
// "system-wide-alert-form.retrieval.error": "Something went wrong retrieving the system-wide alert",
|
||||
"system-wide-alert-form.retrieval.error": "Une erreur s'est produite lors de la récupération de la bannière du message d'avertissement",
|
||||
|
||||
//"system-wide-alert.form.cancel": "Cancel",
|
||||
"system-wide-alert.form.cancel": "Annuler",
|
||||
|
||||
|
||||
//"system-wide-alert.form.save": "Save",
|
||||
"system-wide-alert.form.save": "Sauvegarder",
|
||||
|
||||
//"system-wide-alert.form.label.active": "ACTIVE",
|
||||
"system-wide-alert.form.label.active": "ACTIF",
|
||||
|
||||
//"system-wide-alert.form.label.inactive": "INACTIVE",
|
||||
"system-wide-alert.form.label.inactive": "INACTIF",
|
||||
|
||||
//"system-wide-alert.form.error.message": "The system wide alert must have a message",
|
||||
"system-wide-alert.form.error.message": "Le message d'avertissement ne peut pas être vide",
|
||||
|
||||
//"system-wide-alert.form.label.message": "Alert message",
|
||||
"system-wide-alert.form.label.message": "Message d'avertissement",
|
||||
|
||||
//"system-wide-alert.form.label.countdownTo.enable": "Enable a countdown timer",
|
||||
"system-wide-alert.form.label.countdownTo.enable": "Activer un compte à rebours",
|
||||
|
||||
//"system-wide-alert.form.label.countdownTo.hint": "Hint: Set a countdown timer. When enabled, a date can be set in the future and the system-wide alert banner will perform a countdown to the set date. When this timer ends, it will disappear from the alert. The server will NOT be automatically stopped.",
|
||||
"system-wide-alert.form.label.countdownTo.hint": "Lorsque cette option est activée, il est possible de fixer une date dans le futur et la bannière du message d'avertissement effectuera un compte à rebours jusqu'à la date fixée. À la fin du compte à rebours, le message d'avertissement disparaîtra mais le serveur ne sera pas arrêté automatiquement.",
|
||||
|
||||
//"system-wide-alert.form.label.preview": "System-wide alert preview",
|
||||
"system-wide-alert.form.label.preview": "Aperçu du message d'avertissement",
|
||||
|
||||
//"system-wide-alert.form.update.success": "The system-wide alert was successfully updated",
|
||||
"system-wide-alert.form.update.success": "Le message d'avertissement a été mis à jour",
|
||||
|
||||
//"system-wide-alert.form.update.error": "Something went wrong when updating the system-wide alert",
|
||||
"system-wide-alert.form.update.error": "Un erreur s'est produite lors de la mise à jour du message d'avertissement",
|
||||
|
||||
//"system-wide-alert.form.create.success": "The system-wide alert was successfully created",
|
||||
"system-wide-alert.form.create.success": "Le message d'avertissement a été crée",
|
||||
|
||||
//"system-wide-alert.form.create.error": "Something went wrong when creating the system-wide alert",
|
||||
"system-wide-alert.form.create.error": "Un erreur s'est produite lors de la création du message d'avertissement",
|
||||
|
||||
//"admin.system-wide-alert.breadcrumbs": "System-wide Alerts",
|
||||
"admin.system-wide-alert.breadcrumbs": "Messages d'avertissement",
|
||||
|
||||
//"admin.system-wide-alert.title": "System-wide Alerts",
|
||||
"admin.system-wide-alert.title": "Messages d'avertissement",
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@@ -209,6 +209,10 @@ export const environment: BuildConfig = {
|
||||
code: 'el',
|
||||
label: 'Ελληνικά',
|
||||
active: true,
|
||||
}, {
|
||||
code: 'disabled',
|
||||
label: 'Disabled',
|
||||
active: false,
|
||||
}],
|
||||
|
||||
// Browse-By Pages
|
||||
|
Reference in New Issue
Block a user