Merge branch 'main' into w2p-97184_theme-feedback_contribute-main

This commit is contained in:
LotteHofstede
2023-05-12 10:45:16 +02:00
committed by GitHub
31 changed files with 2870 additions and 168 deletions

View File

@@ -214,6 +214,9 @@ languages:
- code: tr - code: tr
label: Türkçe label: Türkçe
active: true active: true
- code: vi
label: Tiếng Việt
active: true
- code: kk - code: kk
label: Қазақ label: Қазақ
active: true active: true

View File

@@ -8,10 +8,10 @@
<span class="fa fa-chevron-right invisible" aria-hidden="true"></span> <span class="fa fa-chevron-right invisible" aria-hidden="true"></span>
</button> </button>
<div class="align-middle pt-2"> <div class="align-middle pt-2">
<a *ngIf="node!==loadingNode" [routerLink]="[]" (click)="getNextPage(node)" <button *ngIf="node!==loadingNode" (click)="getNextPage(node)"
class="btn btn-outline-primary btn-sm" role="button"> class="btn btn-outline-primary btn-sm" role="button">
<i class="fas fa-angle-down"></i> {{ 'communityList.showMore' | translate }} <i class="fas fa-angle-down"></i> {{ 'communityList.showMore' | translate }}
</a> </button>
<ds-themed-loading *ngIf="node===loadingNode && dataSource.loading$ | async" class="ds-themed-loading"></ds-themed-loading> <ds-themed-loading *ngIf="node===loadingNode && dataSource.loading$ | async" class="ds-themed-loading"></ds-themed-loading>
</div> </div>
</div> </div>

View File

@@ -16,6 +16,7 @@ import { of as observableOf } from 'rxjs';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { isEmpty, isNotEmpty } from '../../shared/empty.util'; import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { FlatNode } from '../flat-node.model'; import { FlatNode } from '../flat-node.model';
import { RouterLinkWithHref } from '@angular/router';
describe('CommunityListComponent', () => { describe('CommunityListComponent', () => {
let component: CommunityListComponent; let component: CommunityListComponent;
@@ -194,7 +195,7 @@ describe('CommunityListComponent', () => {
}), }),
CdkTreeModule, CdkTreeModule,
RouterTestingModule], RouterTestingModule],
declarations: [CommunityListComponent], declarations: [CommunityListComponent, RouterLinkWithHref],
providers: [CommunityListComponent, providers: [CommunityListComponent,
{ provide: CommunityListService, useValue: communityListServiceStub },], { provide: CommunityListService, useValue: communityListServiceStub },],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -230,9 +231,14 @@ describe('CommunityListComponent', () => {
expect(showMoreEl).toBeTruthy(); expect(showMoreEl).toBeTruthy();
}); });
it('should not render the show more button as an empty link', () => {
const debugElements = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
expect(debugElements).toBeTruthy();
});
describe('when show more of top communities is clicked', () => { describe('when show more of top communities is clicked', () => {
beforeEach(fakeAsync(() => { beforeEach(fakeAsync(() => {
const showMoreLink = fixture.debugElement.query(By.css('.show-more-node a')); const showMoreLink = fixture.debugElement.query(By.css('.show-more-node .btn-outline-primary'));
showMoreLink.triggerEventHandler('click', { showMoreLink.triggerEventHandler('click', {
preventDefault: () => {/**/ preventDefault: () => {/**/
} }
@@ -240,6 +246,7 @@ describe('CommunityListComponent', () => {
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
})); }));
it('tree contains maximum of currentPage (2) * (2) elementsPerPage of first top communities, or less if there are less communities (3)', () => { it('tree contains maximum of currentPage (2) * (2) elementsPerPage of first top communities, or less if there are less communities (3)', () => {
const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a')); const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a'));
const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a')); const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a'));

View File

@@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { ScriptDataService } from '../core/data/processes/script-data.service'; import { ScriptDataService } from '../core/data/processes/script-data.service';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { getFirstCompletedRemoteData } from '../core/shared/operators'; import { getFirstCompletedRemoteData } from '../core/shared/operators';
@@ -40,7 +40,8 @@ export class CurationFormComponent implements OnInit {
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private translateService: TranslateService, private translateService: TranslateService,
private handleService: HandleService, private handleService: HandleService,
private router: Router private router: Router,
private cdr: ChangeDetectorRef
) { ) {
} }
@@ -59,6 +60,7 @@ export class CurationFormComponent implements OnInit {
.filter((value) => isNotEmpty(value) && value.includes('=')) .filter((value) => isNotEmpty(value) && value.includes('='))
.map((value) => value.split('=')[1].trim()); .map((value) => value.split('=')[1].trim());
this.form.get('task').patchValue(this.tasks[0]); this.form.get('task').patchValue(this.tasks[0]);
this.cdr.detectChanges();
}); });
} }

View File

@@ -1,7 +1,4 @@
@media screen and (max-width: map-get($grid-breakpoints, md)) { :host {
:host.open { position: relative;
background-color: var(--bs-white); z-index: var(--ds-nav-z-index);
top: 0;
position: sticky;
}
} }

View File

@@ -1,3 +0,0 @@
:host {
z-index: var(--ds-nav-z-index);
}

View File

@@ -11,13 +11,12 @@
line-height: 1.5; line-height: 1.5;
} }
.navbar ::ng-deep { .navbar-toggler {
a { border: none;
color: var(--ds-header-icon-color); color: var(--ds-header-icon-color);
&:hover, &:focus { &:hover, &:focus {
color: var(--ds-header-icon-color-hover); color: var(--ds-header-icon-color-hover);
}
} }
} }

View File

@@ -38,7 +38,7 @@ import { IdentifierDataService } from '../../core/data/identifier-data.service';
import { IdentifierDataComponent } from '../../shared/object-list/identifier-data/identifier-data.component'; import { IdentifierDataComponent } from '../../shared/object-list/identifier-data/identifier-data.component';
import { ItemRegisterDoiComponent } from './item-register-doi/item-register-doi.component'; import { ItemRegisterDoiComponent } from './item-register-doi/item-register-doi.component';
import { DsoSharedModule } from '../../dso-shared/dso-shared.module'; import { DsoSharedModule } from '../../dso-shared/dso-shared.module';
import { ItemCurateComponent } from './item-curate/item-curate.component';
/** /**
* Module that contains all components related to the Edit Item page administrator functionality * Module that contains all components related to the Edit Item page administrator functionality
@@ -81,7 +81,8 @@ import { DsoSharedModule } from '../../dso-shared/dso-shared.module';
VirtualMetadataComponent, VirtualMetadataComponent,
ItemAuthorizationsComponent, ItemAuthorizationsComponent,
IdentifierDataComponent, IdentifierDataComponent,
ItemRegisterDoiComponent ItemRegisterDoiComponent,
ItemCurateComponent
], ],
providers: [ providers: [
BundleDataService, BundleDataService,

View File

@@ -41,6 +41,7 @@ import { ItemPageVersionHistoryGuard } from './item-page-version-history.guard';
import { ItemPageCollectionMapperGuard } from './item-page-collection-mapper.guard'; import { ItemPageCollectionMapperGuard } from './item-page-collection-mapper.guard';
import { ThemedDsoEditMetadataComponent } from '../../dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component'; import { ThemedDsoEditMetadataComponent } from '../../dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component';
import { ItemPageRegisterDoiGuard } from './item-page-register-doi.guard'; import { ItemPageRegisterDoiGuard } from './item-page-register-doi.guard';
import { ItemCurateComponent } from './item-curate/item-curate.component';
/** /**
* Routing module that handles the routing for the Edit Item page administrator functionality * Routing module that handles the routing for the Edit Item page administrator functionality
@@ -82,6 +83,11 @@ import { ItemPageRegisterDoiGuard } from './item-page-register-doi.guard';
data: { title: 'item.edit.tabs.metadata.title', showBreadcrumbs: true }, data: { title: 'item.edit.tabs.metadata.title', showBreadcrumbs: true },
canActivate: [ItemPageMetadataGuard] canActivate: [ItemPageMetadataGuard]
}, },
{
path: 'curate',
component: ItemCurateComponent,
data: { title: 'item.edit.tabs.curate.title', showBreadcrumbs: true }
},
{ {
path: 'relationships', path: 'relationships',
component: ItemRelationshipsComponent, component: ItemRelationshipsComponent,

View File

@@ -0,0 +1,7 @@
<div class="container mt-3">
<h3>{{'item.edit.curate.title' |translate:{item: (itemName$ |async)} }}</h3>
<ds-curation-form
*ngIf="dsoRD$ | async as dsoRD"
[dsoHandle]="dsoRD?.payload.handle"
></ds-curation-form>
</div>

View File

@@ -0,0 +1,75 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
import { ItemCurateComponent } from './item-curate.component';
import { of as observableOf } from 'rxjs';
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
import { ActivatedRoute } from '@angular/router';
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
import { Item } from '../../../core/shared/item.model';
describe('ItemCurateComponent', () => {
let comp: ItemCurateComponent;
let fixture: ComponentFixture<ItemCurateComponent>;
let debugEl: DebugElement;
let routeStub;
let dsoNameService;
const item = Object.assign(new Item(), {
handle: '123456789/1',
metadata: {'dc.title': ['Item Name']}
});
beforeEach(waitForAsync(() => {
routeStub = {
parent: {
data: observableOf({
dso: createSuccessfulRemoteDataObject(item)
})
}
};
dsoNameService = jasmine.createSpyObj('dsoNameService', {
getName: 'Item Name'
});
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [ItemCurateComponent],
providers: [
{provide: ActivatedRoute, useValue: routeStub},
{provide: DSONameService, useValue: dsoNameService}
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ItemCurateComponent);
comp = fixture.componentInstance;
debugEl = fixture.debugElement;
fixture.detectChanges();
});
describe('init', () => {
it('should initialise the comp', () => {
expect(comp).toBeDefined();
expect(debugEl.nativeElement.innerHTML).toContain('ds-curation-form');
});
it('should contain the item information provided in the route', (done) => {
comp.dsoRD$.subscribe((value) => {
expect(value.payload.handle).toEqual('123456789/1');
done();
});
});
it('should contain the item name', (done) => {
comp.itemName$.subscribe((value) => {
expect(value).toEqual('Item Name');
done();
});
});
});
});

View File

@@ -0,0 +1,39 @@
import { Component, OnInit } from '@angular/core';
import { filter, map, take } from 'rxjs/operators';
import { RemoteData } from '../../../core/data/remote-data';
import { Observable } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
import { hasValue } from '../../../shared/empty.util';
import { Item } from '../../../core/shared/item.model';
/**
* Component for managing a collection's curation tasks
*/
@Component({
selector: 'ds-item-curate',
templateUrl: './item-curate.component.html',
})
export class ItemCurateComponent implements OnInit {
dsoRD$: Observable<RemoteData<Item>>;
itemName$: Observable<string>;
constructor(
private route: ActivatedRoute,
private dsoNameService: DSONameService,
) {}
ngOnInit(): void {
this.dsoRD$ = this.route.parent.data.pipe(
take(1),
map((data) => data.dso),
);
this.itemName$ = this.dsoRD$.pipe(
filter((rd: RemoteData<Item>) => hasValue(rd)),
map((rd: RemoteData<Item>) => {
return this.dsoNameService.getName(rd.payload);
})
);
}
}

View File

@@ -8,10 +8,10 @@
</ds-themed-file-download-link> </ds-themed-file-download-link>
<ds-themed-loading *ngIf="isLoading" message="{{'loading.default' | translate}}" [showMessage]="false"></ds-themed-loading> <ds-themed-loading *ngIf="isLoading" message="{{'loading.default' | translate}}" [showMessage]="false"></ds-themed-loading>
<div *ngIf="!isLastPage" class="mt-1" id="view-more"> <div *ngIf="!isLastPage" class="mt-1" id="view-more">
<a class="bitstream-view-more btn btn-outline-secondary btn-sm" [routerLink]="[]" (click)="getNextPage()">{{'item.page.bitstreams.view-more' | translate}}</a> <button class="bitstream-view-more btn btn-outline-secondary btn-sm" (click)="getNextPage()">{{'item.page.bitstreams.view-more' | translate}}</button>
</div> </div>
<div *ngIf="isLastPage && currentPage != 1" class="mt-1" id="collapse"> <div *ngIf="isLastPage && currentPage != 1" class="mt-1" id="collapse">
<a class="bitstream-collapse btn btn-outline-secondary btn-sm" [routerLink]="[]" (click)="currentPage = undefined; getNextPage();">{{'item.page.bitstreams.collapse' | translate}}</a> <button class="bitstream-collapse btn btn-outline-secondary btn-sm" (click)="currentPage = undefined; getNextPage();">{{'item.page.bitstreams.collapse' | translate}}</button>
</div> </div>
</div> </div>
</ds-metadata-field-wrapper> </ds-metadata-field-wrapper>

View File

@@ -7,12 +7,12 @@
<ds-themed-loading *ngIf="(i + 1) === objects.length && (i > 0) && (!representations || representations?.length === 0)" message="{{'loading.default' | translate}}"></ds-themed-loading> <ds-themed-loading *ngIf="(i + 1) === objects.length && (i > 0) && (!representations || representations?.length === 0)" message="{{'loading.default' | translate}}"></ds-themed-loading>
<div class="d-inline-block w-100 mt-2" *ngIf="(i + 1) === objects.length && representations?.length > 0"> <div class="d-inline-block w-100 mt-2" *ngIf="(i + 1) === objects.length && representations?.length > 0">
<div *ngIf="(objects.length * incrementBy) < total" class="float-left"> <div *ngIf="(objects.length * incrementBy) < total" class="float-left">
<a [routerLink]="[]" (click)="increase()">{{'item.page.related-items.view-more' | <button class="btn btn-link btn-link-inline" (click)="increase()">{{'item.page.related-items.view-more' |
translate:{ amount: (total - (objects.length * incrementBy) < incrementBy) ? total - (objects.length * incrementBy) : incrementBy } }}</a> translate:{ amount: (total - (objects.length * incrementBy) < incrementBy) ? total - (objects.length * incrementBy) : incrementBy } }}</button>
</div> </div>
<div *ngIf="objects.length > 1" class="float-right"> <div *ngIf="objects.length > 1" class="float-right">
<a [routerLink]="[]" (click)="decrease()">{{'item.page.related-items.view-less' | <button class="btn btn-link btn-link-inline" (click)="decrease()">{{'item.page.related-items.view-less' |
translate:{ amount: representations?.length } }}</a> translate:{ amount: representations?.length } }}</button>
</div> </div>
</div> </div>
</ng-container> </ng-container>

View File

@@ -7,12 +7,12 @@
<ds-themed-loading *ngIf="(i + 1) === objects.length && (itemsRD || i > 0) && !(itemsRD?.hasSucceeded && itemsRD?.payload && itemsRD?.payload?.page?.length > 0)" message="{{'loading.default' | translate}}"></ds-themed-loading> <ds-themed-loading *ngIf="(i + 1) === objects.length && (itemsRD || i > 0) && !(itemsRD?.hasSucceeded && itemsRD?.payload && itemsRD?.payload?.page?.length > 0)" message="{{'loading.default' | translate}}"></ds-themed-loading>
<div class="d-inline-block w-100 mt-2" *ngIf="(i + 1) === objects.length && itemsRD?.payload?.page?.length > 0"> <div class="d-inline-block w-100 mt-2" *ngIf="(i + 1) === objects.length && itemsRD?.payload?.page?.length > 0">
<div *ngIf="itemsRD?.payload?.totalPages > objects.length" class="float-left" id="view-more"> <div *ngIf="itemsRD?.payload?.totalPages > objects.length" class="float-left" id="view-more">
<a [routerLink]="[]" (click)="increase()">{{'item.page.related-items.view-more' | <button class="btn btn-link btn-link-inline" (click)="increase()">{{'item.page.related-items.view-more' |
translate:{ amount: (itemsRD?.payload?.totalElements - (incrementBy * objects.length) < incrementBy) ? itemsRD?.payload?.totalElements - (incrementBy * objects.length) : incrementBy } }}</a> translate:{ amount: (itemsRD?.payload?.totalElements - (incrementBy * objects.length) < incrementBy) ? itemsRD?.payload?.totalElements - (incrementBy * objects.length) : incrementBy } }}</button>
</div> </div>
<div *ngIf="objects.length > 1" class="float-right" id="view-less"> <div *ngIf="objects.length > 1" class="float-right" id="view-less">
<a [routerLink]="[]" (click)="decrease()">{{'item.page.related-items.view-less' | <button class="btn btn-link btn-link-inline" (click)="decrease()">{{'item.page.related-items.view-less' |
translate:{ amount: itemsRD?.payload?.page?.length } }}</a> translate:{ amount: itemsRD?.payload?.page?.length } }}</button>
</div> </div>
</div> </div>
</ng-container> </ng-container>

View File

@@ -4,9 +4,9 @@
<input #searchInput [@toggleAnimation]="isExpanded" [attr.aria-label]="('nav.search' | translate)" name="query" <input #searchInput [@toggleAnimation]="isExpanded" [attr.aria-label]="('nav.search' | translate)" name="query"
formControlName="query" type="text" placeholder="{{searchExpanded ? ('nav.search' | translate) : ''}}" formControlName="query" type="text" placeholder="{{searchExpanded ? ('nav.search' | translate) : ''}}"
class="d-inline-block bg-transparent position-absolute form-control dropdown-menu-right p-1" [attr.data-test]="'header-search-box' | dsBrowserOnly"> class="d-inline-block bg-transparent position-absolute form-control dropdown-menu-right p-1" [attr.data-test]="'header-search-box' | dsBrowserOnly">
<a class="submit-icon" [routerLink]="" (click)="searchExpanded ? onSubmit(searchForm.value) : expand()" [attr.data-test]="'header-search-icon' | dsBrowserOnly"> <button class="submit-icon btn btn-link btn-link-inline" [attr.aria-label]="'nav.search.button' | translate" type="button" (click)="searchExpanded ? onSubmit(searchForm.value) : expand()" [attr.data-test]="'header-search-icon' | dsBrowserOnly">
<em class="fas fa-search fa-lg fa-fw"></em> <em class="fas fa-search fa-lg fa-fw"></em>
</a> </button>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -1,13 +1,14 @@
input[type="text"] { input[type="text"] {
margin-top: calc(-0.5 * var(--bs-font-size-base)); margin-top: calc(-0.5 * var(--bs-font-size-base));
background-color: #fff !important; background-color: #fff !important;
border-color: var(--ds-header-icon-color);
&.collapsed { &.collapsed {
opacity: 0; opacity: 0;
} }
} }
a.submit-icon { .submit-icon {
cursor: pointer; cursor: pointer;
position: sticky; position: sticky;
top: 0; top: 0;

View File

@@ -19,7 +19,7 @@
</li> </li>
<li *ngIf="(isAuthenticated | async) && !(isXsOrSm$ | async) && (showAuth | async)" class="nav-item"> <li *ngIf="(isAuthenticated | async) && !(isXsOrSm$ | async) && (showAuth | async)" class="nav-item">
<div ngbDropdown display="dynamic" placement="bottom-right" class="d-inline-block" @fadeInOut> <div ngbDropdown display="dynamic" placement="bottom-right" class="d-inline-block" @fadeInOut>
<a href="javascript:void(0);" role="button" [attr.aria-label]="'nav.user-profile-menu-and-logout' |translate" (click)="$event.preventDefault()" [title]="'nav.user-profile-menu-and-logout' | translate" class="px-1" [attr.data-test]="'user-menu' | dsBrowserOnly" ngbDropdownToggle> <a href="javascript:void(0);" role="button" [attr.aria-label]="'nav.user-profile-menu-and-logout' |translate" (click)="$event.preventDefault()" [title]="'nav.user-profile-menu-and-logout' | translate" class="dropdownLogout px-1" [attr.data-test]="'user-menu' | dsBrowserOnly" ngbDropdownToggle>
<i class="fas fa-user-circle fa-lg fa-fw"></i></a> <i class="fas fa-user-circle fa-lg fa-fw"></i></a>
<div class="logoutDropdownMenu" ngbDropdownMenu [attr.aria-label]="'nav.user-profile-menu-and-logout' |translate"> <div class="logoutDropdownMenu" ngbDropdownMenu [attr.aria-label]="'nav.user-profile-menu-and-logout' |translate">
<ds-user-menu></ds-user-menu> <ds-user-menu></ds-user-menu>
@@ -27,7 +27,7 @@
</div> </div>
</li> </li>
<li *ngIf="(isAuthenticated | async) && (isXsOrSm$ | async)" class="nav-item"> <li *ngIf="(isAuthenticated | async) && (isXsOrSm$ | async)" class="nav-item">
<a id="logoutLink" role="button" [attr.aria-label]="'nav.logout' |translate" [title]="'nav.logout' | translate" routerLink="/logout" routerLinkActive="active" class="px-1"> <a role="button" [attr.aria-label]="'nav.logout' |translate" [title]="'nav.logout' | translate" routerLink="/logout" routerLinkActive="active" class="logoutLink px-1">
<i class="fas fa-sign-out-alt fa-lg fa-fw"></i> <i class="fas fa-sign-out-alt fa-lg fa-fw"></i>
<span class="sr-only">(current)</span> <span class="sr-only">(current)</span>
</a> </a>

View File

@@ -12,7 +12,7 @@
background-color: transparent !important; background-color: transparent !important;
} }
.dropdown-toggle { .loginLink, .dropdownLogin, .logoutLink, .dropdownLogout {
color: var(--ds-header-icon-color); color: var(--ds-header-icon-color);
&:hover, &:focus { &:hover, &:focus {

View File

@@ -358,7 +358,7 @@ describe('AuthNavMenuComponent', () => {
}); });
it('should render logout link', inject([Store], (store: Store<AppState>) => { it('should render logout link', inject([Store], (store: Store<AppState>) => {
const logoutDropdownMenu = deNavMenuItem.query(By.css('a[id=logoutLink]')); const logoutDropdownMenu = deNavMenuItem.query(By.css('a.logoutLink'));
expect(logoutDropdownMenu.nativeElement).toBeDefined(); expect(logoutDropdownMenu.nativeElement).toBeDefined();
})); }));
}); });

View File

@@ -1,5 +1,5 @@
<div class="btn-group" data-toggle="buttons"> <div class="btn-group" data-toggle="buttons">
<a *ngIf="isToShow(viewModeEnum.ListElement)" <button *ngIf="isToShow(viewModeEnum.ListElement)"
routerLink="." routerLink="."
[queryParams]="{view: 'list'}" [queryParams]="{view: 'list'}"
queryParamsHandling="merge" queryParamsHandling="merge"
@@ -8,9 +8,9 @@
[class.active]="currentMode === viewModeEnum.ListElement" [class.active]="currentMode === viewModeEnum.ListElement"
class="btn btn-secondary" class="btn btn-secondary"
[attr.data-test]="'list-view' | dsBrowserOnly"> [attr.data-test]="'list-view' | dsBrowserOnly">
<i class="fas fa-list" title="{{'search.view-switch.show-list' | translate}}"></i> <span class="fas fa-list"></span><span class="sr-only">{{'search.view-switch.show-list' | translate}}</span>
</a> </button>
<a *ngIf="isToShow(viewModeEnum.GridElement)" <button *ngIf="isToShow(viewModeEnum.GridElement)"
routerLink="." routerLink="."
[queryParams]="{view: 'grid'}" [queryParams]="{view: 'grid'}"
queryParamsHandling="merge" queryParamsHandling="merge"
@@ -19,9 +19,9 @@
[class.active]="currentMode === viewModeEnum.GridElement" [class.active]="currentMode === viewModeEnum.GridElement"
class="btn btn-secondary" class="btn btn-secondary"
[attr.data-test]="'grid-view' | dsBrowserOnly"> [attr.data-test]="'grid-view' | dsBrowserOnly">
<i class="fas fa-th-large" title="{{'search.view-switch.show-grid' | translate}}"></i> <span class="fas fa-th-large"></span><span class="sr-only">{{'search.view-switch.show-grid' | translate}}</span>
</a> </button>
<a *ngIf="isToShow(viewModeEnum.DetailedListElement)" <button *ngIf="isToShow(viewModeEnum.DetailedListElement)"
routerLink="." routerLink="."
[queryParams]="{view: 'detailed'}" [queryParams]="{view: 'detailed'}"
queryParamsHandling="merge" queryParamsHandling="merge"
@@ -30,6 +30,6 @@
[class.active]="currentMode === viewModeEnum.DetailedListElement" [class.active]="currentMode === viewModeEnum.DetailedListElement"
class="btn btn-secondary" class="btn btn-secondary"
[attr.data-test]="'detail-view' | dsBrowserOnly"> [attr.data-test]="'detail-view' | dsBrowserOnly">
<i class="far fa-square" title="{{'search.view-switch.show-detail' | translate}}"></i> <span class="far fa-square"></span><span class="sr-only">{{'search.view-switch.show-detail' | translate}}</span>
</a> </button>
</div> </div>

View File

@@ -61,7 +61,7 @@ describe('ViewModeSwitchComponent', () => {
searchService.setViewMode(ViewMode.ListElement); searchService.setViewMode(ViewMode.ListElement);
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
const debugElements = fixture.debugElement.queryAll(By.css('a')); const debugElements = fixture.debugElement.queryAll(By.css('button'));
listButton = debugElements[0].nativeElement; listButton = debugElements[0].nativeElement;
gridButton = debugElements[1].nativeElement; gridButton = debugElements[1].nativeElement;
})); }));
@@ -96,7 +96,7 @@ describe('ViewModeSwitchComponent', () => {
searchService.setViewMode(ViewMode.ListElement); searchService.setViewMode(ViewMode.ListElement);
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
const debugElements = fixture.debugElement.queryAll(By.css('a')); const debugElements = fixture.debugElement.queryAll(By.css('button'));
listButton = debugElements[0].nativeElement; listButton = debugElements[0].nativeElement;
detailButton = debugElements[1].nativeElement; detailButton = debugElements[1].nativeElement;
})); }));

View File

@@ -1,4 +1,4 @@
<span class="mb-5" [innerHTML]="licenseText$ | async"></span> <span class="mb-5 preserve-line-breaks" [innerHTML]="licenseText$ | async"></span>
<br> <br> <br> <br>
<ds-form *ngIf="formModel" #formRef="formComponent" <ds-form *ngIf="formModel" #formRef="formComponent"
[formId]="formId" [formId]="formId"

View File

@@ -2291,6 +2291,7 @@
"item.edit.tabs.curate.head": "Curate", "item.edit.tabs.curate.head": "Curate",
"item.edit.tabs.curate.title": "Item Edit - Curate", "item.edit.tabs.curate.title": "Item Edit - Curate",
"item.edit.curate.title": "Curate Item: {{item}}",
"item.edit.tabs.metadata.head": "Metadata", "item.edit.tabs.metadata.head": "Metadata",
@@ -3146,6 +3147,9 @@
"nav.search": "Search", "nav.search": "Search",
"nav.search.button": "Submit search",
"nav.statistics.header": "Statistics", "nav.statistics.header": "Statistics",
"nav.stop-impersonating": "Stop impersonating EPerson", "nav.stop-impersonating": "Stop impersonating EPerson",

2541
src/assets/i18n/vi.json5 Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -229,6 +229,7 @@ export class DefaultAppConfig implements AppConfig {
{ code: 'fi', label: 'Suomi', active: true }, { code: 'fi', label: 'Suomi', active: true },
{ code: 'sv', label: 'Svenska', active: true }, { code: 'sv', label: 'Svenska', active: true },
{ code: 'tr', label: 'Türkçe', active: true }, { code: 'tr', label: 'Türkçe', active: true },
{ code: 'vi', label: 'Tiếng Việt', active: true },
{ code: 'kk', label: 'Қазақ', active: true }, { code: 'kk', label: 'Қазақ', active: true },
{ code: 'bn', label: 'বাংলা', active: true }, { code: 'bn', label: 'বাংলা', active: true },
{ code: 'hi', label: 'हिंदी', active: true}, { code: 'hi', label: 'हिंदी', active: true},

View File

@@ -1,209 +1,225 @@
html { html {
position: relative; position: relative;
min-height: 100%; min-height: 100%;
} }
body { body {
overflow-x: hidden; overflow-x: hidden;
} }
// Sticky Footer // Sticky Footer
.outer-wrapper { .outer-wrapper {
display: flex; display: flex;
margin: 0; margin: 0;
} }
.inner-wrapper { .inner-wrapper {
flex: 1 1 auto; flex: 1 1 auto;
flex-flow: column nowrap; flex-flow: column nowrap;
display: flex; display: flex;
min-height: 100vh; min-height: 100vh;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
position: relative; position: relative;
} }
.main-content { .main-content {
z-index: var(--ds-main-z-index); z-index: var(--ds-main-z-index);
flex: 1 1 100%; flex: 1 1 100%;
margin-top: var(--ds-content-spacing); margin-top: var(--ds-content-spacing);
margin-bottom: var(--ds-content-spacing); margin-bottom: var(--ds-content-spacing);
} }
.alert.hide { .alert.hide {
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
ds-admin-sidebar { ds-admin-sidebar {
position: fixed; position: fixed;
z-index: var(--ds-sidebar-z-index); z-index: var(--ds-sidebar-z-index);
} }
.sticky-top { .sticky-top {
z-index: 0; z-index: 0;
} }
.media-viewer .media-viewer
.change-gallery .change-gallery
.ngx-gallery .ngx-gallery
ngx-gallery-preview.ngx-gallery-active { ngx-gallery-preview.ngx-gallery-active {
right: 0; right: 0;
left: auto; left: auto;
width: calc(100% - 53px); width: calc(100% - 53px);
} }
.ds-submission-reorder-dragging { .ds-submission-reorder-dragging {
.ds-hint, .ds-hint,
button { button {
display: none; display: none;
} }
} }
ngb-modal-backdrop { ngb-modal-backdrop {
// ng-bootsrap animates opacity, causing the fully opaque background to flash briefly before the transition starts // ng-bootsrap animates opacity, causing the fully opaque background to flash briefly before the transition starts
// animating background-color between transparent & a RGBA color instead looks smoother // animating background-color between transparent & a RGBA color instead looks smoother
&.fade { &.fade {
opacity: 1 !important; opacity: 1 !important;
background-color: transparent; background-color: transparent;
transition: background-color 0.15s linear; transition: background-color 0.15s linear;
&.show { &.show {
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);
}
} }
}
} }
.dont-break-out { .dont-break-out {
/* These are technically the same, but use both */ /* These are technically the same, but use both */
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
-ms-word-break: break-all; -ms-word-break: break-all;
/* This is the dangerous one in WebKit, as it breaks things wherever */ /* This is the dangerous one in WebKit, as it breaks things wherever */
word-break: break-all; word-break: break-all;
/* Instead use this non-standard one: */ /* Instead use this non-standard one: */
word-break: break-word; word-break: break-word;
/* Adds a hyphen where the word breaks, if supported (No Blink) */ /* Adds a hyphen where the word breaks, if supported (No Blink) */
-ms-hyphens: auto; -ms-hyphens: auto;
-moz-hyphens: auto; -moz-hyphens: auto;
-webkit-hyphens: auto; -webkit-hyphens: auto;
hyphens: auto; hyphens: auto;
} }
.researcher-profile-switch button:focus{ .researcher-profile-switch button:focus {
outline: none !important; outline: none !important;
} }
.researcher-profile-switch .switch.checked{
color: #fff; .researcher-profile-switch .switch.checked {
color: #fff;
} }
/* Replicate default spacing look ~ preserveWhitespace=true /* Replicate default spacing look ~ preserveWhitespace=true
To be used e.g. on a div containing buttons that should have a bit of spacing in between To be used e.g. on a div containing buttons that should have a bit of spacing in between
*/ */
.space-children-mr > :not(:last-child) { .space-children-mr > :not(:last-child) {
margin-right: var(--ds-gap); margin-right: var(--ds-gap);
} }
/* Complement .space-children-mr when spaced elements are not on the same level */ /* Complement .space-children-mr when spaced elements are not on the same level */
.mr-gap { .mr-gap {
margin-right: var(--ds-gap); margin-right: var(--ds-gap);
} }
.ml-gap { .ml-gap {
margin-left: var(--ds-gap); margin-left: var(--ds-gap);
} }
.custom-accordion .card-header button { .custom-accordion .card-header button {
-webkit-box-shadow: none!important; -webkit-box-shadow: none !important;
box-shadow: none!important; box-shadow: none !important;
width: 100%; width: 100%;
} }
.custom-accordion .card:first-of-type { .custom-accordion .card:first-of-type {
border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color) !important; border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color) !important;
border-bottom-left-radius: var(--bs-card-border-radius) !important; border-bottom-left-radius: var(--bs-card-border-radius) !important;
border-bottom-right-radius: var(--bs-card-border-radius) !important; border-bottom-right-radius: var(--bs-card-border-radius) !important;
} }
ds-dynamic-form-control-container.d-none { ds-dynamic-form-control-container.d-none {
/* Ensures that form-control containers hidden and disabled by type binding collapse and let other fields in /* Ensures that form-control containers hidden and disabled by type binding collapse and let other fields in
the same row expand accordingly the same row expand accordingly
*/ */
visibility: collapse; visibility: collapse;
} }
/* Used for dso administrative functionality */ /* Used for dso administrative functionality */
.btn-dark { .btn-dark {
background-color: var(--ds-admin-sidebar-bg); background-color: var(--ds-admin-sidebar-bg);
} }
.preserve-line-breaks { .preserve-line-breaks {
white-space: pre-line; white-space: pre-line;
} }
/* Thumbnail styles */ /* Thumbnail styles */
.hide-placeholder-text { .hide-placeholder-text {
.thumbnail-placeholder { .thumbnail-placeholder {
color: transparent !important; color: transparent !important;
} }
} }
/* Used to hide the thumbnail column in modals. */ /* Used to hide the thumbnail column in modals. */
.hide-modal-thumbnail-column { .hide-modal-thumbnail-column {
.modal-body ds-listable-object-component-loader div.row > div:first-child { .modal-body ds-listable-object-component-loader div.row > div:first-child {
display: none; display: none;
} }
.modal-body ds-listable-object-component-loader div.row > div:nth-child(2) {
flex: 0 0 100%; .modal-body ds-listable-object-component-loader div.row > div:nth-child(2) {
max-width: 100%; flex: 0 0 100%;
} max-width: 100%;
}
} }
/* The font sizes used in "no thumbnail" placeholder */ /* The font sizes used in "no thumbnail" placeholder */
.thumb-font-0 { .thumb-font-0 {
.thumbnail-placeholder { .thumbnail-placeholder {
@media screen and (max-width: map-get($grid-breakpoints, lg)) { @media screen and (max-width: map-get($grid-breakpoints, lg)) {
font-size: 0.7rem; font-size: 0.7rem;
padding: 0.2rem; padding: 0.2rem;
}
@media screen and (max-width: map-get($grid-breakpoints, sm)) {
font-size: 0.6rem;
padding: 0.1rem;
}
font-size: 0.4rem;
padding: 0.1rem;
} }
@media screen and (max-width: map-get($grid-breakpoints, sm)) {
font-size: 0.6rem;
padding: 0.1rem;
}
font-size: 0.4rem;
padding: 0.1rem;
}
} }
.thumb-font-1 { .thumb-font-1 {
.thumbnail-placeholder { .thumbnail-placeholder {
@media screen and (max-width: map-get($grid-breakpoints, sm)) { @media screen and (max-width: map-get($grid-breakpoints, sm)) {
font-size: 0.9rem; font-size: 0.9rem;
padding: 0.1rem; padding: 0.1rem;
}
@media screen and (max-width: 950px) {
font-size: 0.4rem;
padding: 0.1rem;
}
font-size: 0.5rem;
padding: 0.125rem;
} }
@media screen and (max-width: 950px) {
font-size: 0.4rem;
padding: 0.1rem;
}
font-size: 0.5rem;
padding: 0.125rem;
}
} }
.thumb-font-2 { .thumb-font-2 {
.thumbnail-placeholder { .thumbnail-placeholder {
font-size: 0.9rem; font-size: 0.9rem;
padding: 0.125rem; padding: 0.125rem;
} }
} }
.thumb-font-3 { .thumb-font-3 {
.thumbnail-placeholder { .thumbnail-placeholder {
font-size: 1.25rem; font-size: 1.25rem;
padding: 0.5rem; padding: 0.5rem;
} }
} }
.btn.btn-link.btn-link-inline {
display: inline;
padding: 0;
&:not(:disabled){
&:hover, &:focus {
box-shadow: none;
}
}
}
.badge-validation { .badge-validation {
background-color: #{map-get($theme-colors, warning)}; background-color: #{map-get($theme-colors, warning)};

View File

@@ -6,7 +6,7 @@ import { HeaderNavbarWrapperComponent as BaseComponent } from '../../../../app/h
*/ */
@Component({ @Component({
selector: 'ds-header-navbar-wrapper', selector: 'ds-header-navbar-wrapper',
styleUrls: ['header-navbar-wrapper.component.scss'], styleUrls: ['../../../../app/header-nav-wrapper/header-navbar-wrapper.component.scss'],
templateUrl: 'header-navbar-wrapper.component.html', templateUrl: 'header-navbar-wrapper.component.html',
}) })
export class HeaderNavbarWrapperComponent extends BaseComponent { export class HeaderNavbarWrapperComponent extends BaseComponent {

View File

@@ -6,7 +6,7 @@
</a> </a>
</div> </div>
<div class="d-flex flex-grow-1 ml-auto justify-content-end align-items-center"> <div class="d-flex flex-grow-1 ml-auto justify-content-end align-items-center">
<ds-search-navbar class="navbar-search"></ds-search-navbar> <ds-themed-search-navbar></ds-themed-search-navbar>
<ds-lang-switch></ds-lang-switch> <ds-lang-switch></ds-lang-switch>
<ds-context-help-toggle></ds-context-help-toggle> <ds-context-help-toggle></ds-context-help-toggle>
<ds-themed-auth-nav-menu></ds-themed-auth-nav-menu> <ds-themed-auth-nav-menu></ds-themed-auth-nav-menu>

View File

@@ -15,5 +15,12 @@
.navbar-toggler .navbar-toggler-icon { .navbar-toggler .navbar-toggler-icon {
background-image: none !important; background-image: none !important;
line-height: 1.5; line-height: 1.5;
color: var(--bs-link-color); }
.navbar-toggler {
color: var(--ds-header-icon-color);
&:hover, &:focus {
color: var(--ds-header-icon-color-hover);
}
} }

View File

@@ -2,7 +2,6 @@ nav.navbar {
border-top: 1px var(--ds-header-navbar-border-top-color) solid; border-top: 1px var(--ds-header-navbar-border-top-color) solid;
border-bottom: 5px var(--ds-header-navbar-border-bottom-color) solid; border-bottom: 5px var(--ds-header-navbar-border-bottom-color) solid;
align-items: baseline; align-items: baseline;
color: var(--ds-header-icon-color);
} }
/** Mobile menu styling **/ /** Mobile menu styling **/