diff --git a/src/app/shared/rss-feed/rss.component.html b/src/app/shared/rss-feed/rss.component.html
new file mode 100644
index 0000000000..91140c50c5
--- /dev/null
+++ b/src/app/shared/rss-feed/rss.component.html
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/app/shared/rss-feed/rss.component.scss b/src/app/shared/rss-feed/rss.component.scss
new file mode 100644
index 0000000000..91310eddcb
--- /dev/null
+++ b/src/app/shared/rss-feed/rss.component.scss
@@ -0,0 +1,12 @@
+:host {
+ .dropdown-toggle::after {
+ display: none;
+ }
+ .dropdown-item {
+ padding-left: 20px;
+ }
+}
+
+.margin-right {
+ margin-right: .5em;
+}
diff --git a/src/app/shared/rss-feed/rss.component.spec.ts b/src/app/shared/rss-feed/rss.component.spec.ts
new file mode 100644
index 0000000000..fc19c65e60
--- /dev/null
+++ b/src/app/shared/rss-feed/rss.component.spec.ts
@@ -0,0 +1,113 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
+import { ConfigurationDataService } from '../../core/data/configuration-data.service';
+import { RemoteData } from '../../core/data/remote-data';
+import { GroupDataService } from '../../core/eperson/group-data.service';
+import { PaginationService } from '../../core/pagination/pagination.service';
+import { LinkHeadService } from '../../core/services/link-head.service';
+import { Collection } from '../../core/shared/collection.model';
+import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
+import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
+import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
+import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
+import { PaginationServiceStub } from '../testing/pagination-service.stub';
+import { createPaginatedList } from '../testing/utils.test';
+import { RSSComponent } from './rss.component';
+import { of as observableOf } from 'rxjs';
+import { SearchConfigurationServiceStub } from '../testing/search-configuration-service.stub';
+import { PaginatedSearchOptions } from '../search/models/paginated-search-options.model';
+import { Router } from '@angular/router';
+import { RouterMock } from '../mocks/router.mock';
+
+
+
+describe('RssComponent', () => {
+ let comp: RSSComponent;
+ let options: SortOptions;
+ let fixture: ComponentFixture ;
+ let uuid: string;
+ let query: string;
+ let groupDataService: GroupDataService;
+ let linkHeadService: LinkHeadService;
+ let configurationDataService: ConfigurationDataService;
+ let paginationService;
+
+ beforeEach(waitForAsync(() => {
+ const mockCollection: Collection = Object.assign(new Collection(), {
+ id: 'ce41d451-97ed-4a9c-94a1-7de34f16a9f4',
+ name: 'test-collection',
+ _links: {
+ mappedItems: {
+ href: 'https://rest.api/collections/ce41d451-97ed-4a9c-94a1-7de34f16a9f4/mappedItems'
+ },
+ self: {
+ href: 'https://rest.api/collections/ce41d451-97ed-4a9c-94a1-7de34f16a9f4'
+ }
+ }
+ });
+ configurationDataService = jasmine.createSpyObj('configurationDataService', {
+ findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), {
+ name: 'test',
+ values: [
+ 'org.dspace.ctask.general.ProfileFormats = test'
+ ]
+ }))
+ });
+ linkHeadService = jasmine.createSpyObj('linkHeadService', {
+ addTag: ''
+ });
+ const mockCollectionRD: RemoteData = createSuccessfulRemoteDataObject(mockCollection);
+ const mockSearchOptions = observableOf(new PaginatedSearchOptions({
+ pagination: Object.assign(new PaginationComponentOptions(), {
+ id: 'search-page-configuration',
+ pageSize: 10,
+ currentPage: 1
+ }),
+ sort: new SortOptions('dc.title', SortDirection.ASC),
+ }));
+ groupDataService = jasmine.createSpyObj('groupsDataService', {
+ findAllByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])),
+ getGroupRegistryRouterLink: '',
+ getUUIDFromString: '',
+ });
+ paginationService = new PaginationServiceStub();
+ const searchConfigService = {
+ paginatedSearchOptions: mockSearchOptions
+ };
+ TestBed.configureTestingModule({
+ providers: [
+ { provide: GroupDataService, useValue: groupDataService },
+ { provide: LinkHeadService, useValue: linkHeadService },
+ { provide: ConfigurationDataService, useValue: configurationDataService },
+ { provide: SearchConfigurationService, useValue: new SearchConfigurationServiceStub() },
+ { provide: PaginationService, useValue: paginationService },
+ { provide: Router, useValue: new RouterMock() }
+ ],
+ declarations: [RSSComponent]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ options = new SortOptions('dc.title', SortDirection.DESC);
+ uuid = '2cfcf65e-0a51-4bcb-8592-b8db7b064790';
+ query = 'test';
+ fixture = TestBed.createComponent(RSSComponent);
+ comp = fixture.componentInstance;
+ });
+
+ it('should formulate the correct url given params in url', () => {
+ const route = comp.formulateRoute(uuid, 'opensearch', options, query);
+ expect(route).toBe('/opensearch/search?format=atom&scope=2cfcf65e-0a51-4bcb-8592-b8db7b064790&sort=dc.title&sort_direction=DESC&query=test');
+ });
+
+ it('should skip uuid if its null', () => {
+ const route = comp.formulateRoute(null, 'opensearch', options, query);
+ expect(route).toBe('/opensearch/search?format=atom&sort=dc.title&sort_direction=DESC&query=test');
+ });
+
+ it('should default to query * if none provided', () => {
+ const route = comp.formulateRoute(null, 'opensearch', options, null);
+ expect(route).toBe('/opensearch/search?format=atom&sort=dc.title&sort_direction=DESC&query=*');
+ });
+});
+
diff --git a/src/app/shared/rss-feed/rss.component.ts b/src/app/shared/rss-feed/rss.component.ts
new file mode 100644
index 0000000000..3fdb859bdc
--- /dev/null
+++ b/src/app/shared/rss-feed/rss.component.ts
@@ -0,0 +1,163 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ OnDestroy,
+ OnInit,
+ ViewEncapsulation
+} from '@angular/core';
+import { BehaviorSubject, Observable, Subscription } from 'rxjs';
+import { GroupDataService } from '../../core/eperson/group-data.service';
+import { LinkHeadService } from '../../core/services/link-head.service';
+import { ConfigurationDataService } from '../../core/data/configuration-data.service';
+import { getFirstCompletedRemoteData } from '../../core/shared/operators';
+import { environment } from '../../../../src/environments/environment';
+import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
+import { SortOptions } from '../../core/cache/models/sort-options.model';
+import { PaginationService } from '../../core/pagination/pagination.service';
+import { Router } from '@angular/router';
+import { map, switchMap } from 'rxjs/operators';
+import { PaginatedSearchOptions } from '../search/models/paginated-search-options.model';
+import { RemoteData } from '../../core/data/remote-data';
+
+
+/**
+ * The Rss feed button componenet.
+ */
+@Component({
+ exportAs: 'rssComponent',
+ selector: 'ds-rss',
+ styleUrls: ['rss.component.scss'],
+ templateUrl: 'rss.component.html',
+ changeDetection: ChangeDetectionStrategy.Default,
+ encapsulation: ViewEncapsulation.Emulated
+})
+export class RSSComponent implements OnInit, OnDestroy {
+
+ route$: BehaviorSubject;
+
+ isEnabled$: BehaviorSubject = new BehaviorSubject(null);
+
+ uuid: string;
+ configuration$: Observable;
+ sortOption$: Observable;
+
+ subs: Subscription[] = [];
+
+ constructor(private groupDataService: GroupDataService,
+ private linkHeadService: LinkHeadService,
+ private configurationService: ConfigurationDataService,
+ private searchConfigurationService: SearchConfigurationService,
+ private router: Router,
+ protected paginationService: PaginationService) {
+ }
+ /**
+ * Removes the linktag created when the component gets removed from the page.
+ */
+ ngOnDestroy(): void {
+ this.linkHeadService.removeTag("rel='alternate'");
+ this.subs.forEach(sub => {
+ sub.unsubscribe();
+ });
+ }
+
+
+ /**
+ * Generates the link tags and the url to opensearch when the component is loaded.
+ */
+ ngOnInit(): void {
+ this.configuration$ = this.searchConfigurationService.getCurrentConfiguration('default');
+
+ this.subs.push(this.configurationService.findByPropertyName('websvc.opensearch.enable').pipe(
+ getFirstCompletedRemoteData(),
+ ).subscribe((result) => {
+ if (result.hasSucceeded) {
+ const enabled = (result.payload.values[0] === 'true');
+ this.isEnabled$.next(enabled);
+ }
+ }));
+ this.subs.push(this.configurationService.findByPropertyName('websvc.opensearch.svccontext').pipe(
+ getFirstCompletedRemoteData(),
+ map((result: RemoteData) => {
+ if (result.hasSucceeded) {
+ return result.payload.values[0];
+ }
+ return null;
+ }),
+ switchMap((openSearchUri: string) =>
+ this.searchConfigurationService.paginatedSearchOptions.pipe(
+ map((searchOptions: PaginatedSearchOptions) => ({ openSearchUri, searchOptions }))
+ )
+ ),
+ ).subscribe(({ openSearchUri, searchOptions }) => {
+ if (!openSearchUri) {
+ return null;
+ }
+ this.uuid = this.groupDataService.getUUIDFromString(this.router.url);
+ const route = environment.rest.baseUrl + this.formulateRoute(this.uuid, openSearchUri, searchOptions.sort, searchOptions.query);
+ this.addLinks(route);
+ this.linkHeadService.addTag({
+ href: environment.rest.baseUrl + '/' + openSearchUri + '/service',
+ type: 'application/atom+xml',
+ rel: 'search',
+ title: 'Dspace'
+ });
+ this.route$ = new BehaviorSubject(route);
+ }));
+ }
+
+ /**
+ * Function created a route given the different params available to opensearch
+ * @param uuid The uuid if a scope is present
+ * @param opensearch openSearch uri
+ * @param sort The sort options for the opensearch request
+ * @param query The query string that was provided in the search
+ * @returns The combine URL to opensearch
+ */
+ formulateRoute(uuid: string, opensearch: string, sort: SortOptions, query: string): string {
+ let route = 'search?format=atom';
+ if (uuid) {
+ route += `&scope=${uuid}`;
+ }
+ if (sort && sort.direction && sort.field && sort.field !== 'id') {
+ route += `&sort=${sort.field}&sort_direction=${sort.direction}`;
+ }
+ if (query) {
+ route += `&query=${query}`;
+ } else {
+ route += `&query=*`;
+ }
+ route = '/' + opensearch + '/' + route;
+ return route;
+ }
+
+ /**
+ * Check if the router url contains the specified route
+ *
+ * @param {string} route
+ * @returns
+ * @memberof MyComponent
+ */
+ hasRoute(route: string) {
+ return this.router.url.includes(route);
+ }
+
+ /**
+ * Creates tags in the header of the page
+ * @param route The composed url to opensearch
+ */
+ addLinks(route: string): void {
+ this.linkHeadService.addTag({
+ href: route,
+ type: 'application/atom+xml',
+ rel: 'alternate',
+ title: 'Sitewide Atom feed'
+ });
+ route = route.replace('format=atom', 'format=rss');
+ this.linkHeadService.addTag({
+ href: route,
+ type: 'application/rss+xml',
+ rel: 'alternate',
+ title: 'Sitewide RSS feed'
+ });
+ }
+}
diff --git a/src/app/shared/search-form/search-form.component.spec.ts b/src/app/shared/search-form/search-form.component.spec.ts
index 934f00b10c..6f485ec77e 100644
--- a/src/app/shared/search-form/search-form.component.spec.ts
+++ b/src/app/shared/search-form/search-form.component.spec.ts
@@ -13,7 +13,9 @@ import { SearchConfigurationService } from '../../core/shared/search/search-conf
import { PaginationServiceStub } from '../testing/pagination-service.stub';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
-import { FindListOptions } from '../../core/data/find-list-options.model';
+import { SearchServiceStub } from '../testing/search-service.stub';
+import { Router } from '@angular/router';
+import { RouterStub } from '../testing/router.stub';
describe('SearchFormComponent', () => {
let comp: SearchFormComponent;
@@ -21,21 +23,23 @@ describe('SearchFormComponent', () => {
let de: DebugElement;
let el: HTMLElement;
+ const router = new RouterStub();
+ const searchService = new SearchServiceStub();
const paginationService = new PaginationServiceStub();
-
- const searchConfigService = {paginationID: 'test-id'};
+ const searchConfigService = { paginationID: 'test-id' };
+ const dspaceObjectService = {
+ findById: () => createSuccessfulRemoteDataObject$(undefined),
+ };
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [FormsModule, RouterTestingModule, TranslateModule.forRoot()],
providers: [
- {
- provide: SearchService,
- useValue: {}
- },
+ { provide: Router, useValue: router },
+ { provide: SearchService, useValue: searchService },
{ provide: PaginationService, useValue: paginationService },
{ provide: SearchConfigurationService, useValue: searchConfigService },
- { provide: DSpaceObjectDataService, useValue: { findById: () => createSuccessfulRemoteDataObject$(undefined)} }
+ { provide: DSpaceObjectDataService, useValue: dspaceObjectService },
],
declarations: [SearchFormComponent]
}).compileComponents();
@@ -90,6 +94,81 @@ describe('SearchFormComponent', () => {
expect(scopeSelect.textContent).toBe(testCommunity.name);
}));
+
+ describe('updateSearch', () => {
+ const query = 'THOR';
+ const scope = 'MCU';
+ let searchQuery = {};
+
+ it('should navigate to the search page even when no parameters are provided', () => {
+ comp.updateSearch(searchQuery);
+
+ expect(router.navigate).toHaveBeenCalledWith(comp.getSearchLinkParts(), {
+ queryParams: searchQuery,
+ queryParamsHandling: 'merge'
+ });
+ });
+
+ it('should navigate to the search page with parameters only query if only query is provided', () => {
+ searchQuery = {
+ query: query
+ };
+
+ comp.updateSearch(searchQuery);
+
+ expect(router.navigate).toHaveBeenCalledWith(comp.getSearchLinkParts(), {
+ queryParams: searchQuery,
+ queryParamsHandling: 'merge'
+ });
+ });
+
+ it('should navigate to the search page with parameters only query if only scope is provided', () => {
+ searchQuery = {
+ scope: scope
+ };
+
+ comp.updateSearch(searchQuery);
+
+ expect(router.navigate).toHaveBeenCalledWith(comp.getSearchLinkParts(), {
+ queryParams: searchQuery,
+ queryParamsHandling: 'merge'
+ });
+ });
+ });
+
+ describe('when the scope variable is used', () => {
+ const query = 'THOR';
+ const scope = 'MCU';
+ let searchQuery = {};
+
+ beforeEach(() => {
+ spyOn(comp, 'updateSearch');
+ });
+
+ it('should only search in the provided scope', () => {
+ searchQuery = {
+ query: query,
+ scope: scope
+ };
+
+ comp.scope = scope;
+ comp.onSubmit(searchQuery);
+
+ expect(comp.updateSearch).toHaveBeenCalledWith(searchQuery);
+ });
+
+ it('should not create searchQuery with the scope if an empty scope is provided', () => {
+ searchQuery = {
+ query: query
+ };
+
+ comp.scope = '';
+ comp.onSubmit(searchQuery);
+
+ expect(comp.updateSearch).toHaveBeenCalledWith(searchQuery);
+ });
+ });
+
// it('should call updateSearch when clicking the submit button with correct parameters', fakeAsync(() => {
// comp.query = 'Test String'
// fixture.detectChanges();
@@ -112,7 +191,7 @@ describe('SearchFormComponent', () => {
//
// expect(comp.updateSearch).toHaveBeenCalledWith({ scope: scope, query: query });
// }));
- });
+});
export const objects: DSpaceObject[] = [
Object.assign(new Community(), {
diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts
index caf6a91046..7ea51e4c1e 100644
--- a/src/app/shared/search-form/search-form.component.ts
+++ b/src/app/shared/search-form/search-form.component.ts
@@ -98,6 +98,9 @@ export class SearchFormComponent implements OnInit {
* @param data Values submitted using the form
*/
onSubmit(data: any) {
+ if (isNotEmpty(this.scope)) {
+ data = Object.assign(data, { scope: this.scope });
+ }
this.updateSearch(data);
this.submitSearch.emit(data);
}
diff --git a/src/app/shared/search/search-filters/search-filters.component.spec.ts b/src/app/shared/search/search-filters/search-filters.component.spec.ts
index f84de65fb0..ec1a51a1c4 100644
--- a/src/app/shared/search/search-filters/search-filters.component.spec.ts
+++ b/src/app/shared/search/search-filters/search-filters.component.spec.ts
@@ -80,7 +80,7 @@ describe('SearchFiltersComponent', () => {
expect(comp.initFilters).toHaveBeenCalledTimes(1);
- refreshFiltersEmitter.next();
+ refreshFiltersEmitter.next(null);
expect(comp.initFilters).toHaveBeenCalledTimes(2);
});
diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts
index 37806aab60..f40947ad19 100644
--- a/src/app/shared/search/search.component.ts
+++ b/src/app/shared/search/search.component.ts
@@ -31,6 +31,7 @@ import { ViewMode } from '../../core/shared/view-mode.model';
import { SelectionConfig } from './search-results/search-results.component';
import { ListableObject } from '../object-collection/shared/listable-object.model';
import { CollectionElementLinkType } from '../object-collection/collection-element-link.type';
+import { environment } from 'src/environments/environment';
@Component({
selector: 'ds-search',
@@ -355,7 +356,8 @@ export class SearchComponent implements OnInit {
undefined,
this.useCachedVersionIfAvailable,
true,
- followLink- ('thumbnail', { isOptional: true })
+ followLink
- ('thumbnail', { isOptional: true }),
+ followLink
- ('accessStatus', { isOptional: true, shouldEmbed: environment.item.showAccessStatuses })
).pipe(getFirstCompletedRemoteData())
.subscribe((results: RemoteData>) => {
if (results.hasSucceeded && results.payload?.page?.length > 0) {
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index 37f4fafb1e..7c6fe6657a 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -111,6 +111,7 @@ import { FilterInputSuggestionsComponent } from './input-suggestions/filter-sugg
import { DsoInputSuggestionsComponent } from './input-suggestions/dso-input-suggestions/dso-input-suggestions.component';
import { ItemGridElementComponent } from './object-grid/item-grid-element/item-types/item/item-grid-element.component';
import { TypeBadgeComponent } from './object-list/type-badge/type-badge.component';
+import { AccessStatusBadgeComponent } from './object-list/access-status-badge/access-status-badge.component';
import { MetadataRepresentationLoaderComponent } from './metadata-representation/metadata-representation-loader.component';
import { MetadataRepresentationDirective } from './metadata-representation/metadata-representation.directive';
import { ListableObjectComponentLoaderComponent } from './object-collection/shared/listable-object/listable-object-component-loader.component';
@@ -172,10 +173,10 @@ import { BitstreamRequestACopyPageComponent } from './bitstream-request-a-copy-p
import { DsSelectComponent } from './ds-select/ds-select.component';
import { LogInOidcComponent } from './log-in/methods/oidc/log-in-oidc.component';
import { ThemedItemListPreviewComponent } from './object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component';
+import { RSSComponent } from './rss-feed/rss.component';
import { ExternalLinkMenuItemComponent } from './menu/menu-item/external-link-menu-item.component';
const MODULES = [
- // Do NOT include UniversalModule, HttpModule, or JsonpModule here
CommonModule,
SortablejsModule,
FileUploadModule,
@@ -238,6 +239,7 @@ const COMPONENTS = [
AbstractListableElementComponent,
ObjectCollectionComponent,
PaginationComponent,
+ RSSComponent,
SearchFormComponent,
PageWithSidebarComponent,
SidebarDropdownComponent,
@@ -293,6 +295,7 @@ const COMPONENTS = [
AbstractTrackableComponent,
ComcolMetadataComponent,
TypeBadgeComponent,
+ AccessStatusBadgeComponent,
BrowseByComponent,
AbstractTrackableComponent,
diff --git a/src/app/shared/testing/hal-endpoint-service.stub.ts b/src/app/shared/testing/hal-endpoint-service.stub.ts
index 19f95d577c..753efcdb5d 100644
--- a/src/app/shared/testing/hal-endpoint-service.stub.ts
+++ b/src/app/shared/testing/hal-endpoint-service.stub.ts
@@ -1,9 +1,13 @@
import { of as observableOf } from 'rxjs';
+import { hasValue } from '../empty.util';
export class HALEndpointServiceStub {
constructor(private url: string) {}
- getEndpoint(path: string) {
+ getEndpoint(path: string, startHref?: string) {
+ if (hasValue(startHref)) {
+ return observableOf(startHref + '/' + path);
+ }
return observableOf(this.url + '/' + path);
}
}
diff --git a/src/app/shared/testing/search-configuration-service.stub.ts b/src/app/shared/testing/search-configuration-service.stub.ts
index 80744ba59a..78b358f0d4 100644
--- a/src/app/shared/testing/search-configuration-service.stub.ts
+++ b/src/app/shared/testing/search-configuration-service.stub.ts
@@ -17,6 +17,10 @@ export class SearchConfigurationServiceStub {
return observableOf('test-id');
}
+ getCurrentQuery(a) {
+ return observableOf(a);
+ }
+
getCurrentConfiguration(a) {
return observableOf(a);
}
diff --git a/src/app/shared/vocabulary-treeview/vocabulary-tree-flat-data-source.ts b/src/app/shared/vocabulary-treeview/vocabulary-tree-flat-data-source.ts
index 9d093874b8..23ac6fcf8f 100644
--- a/src/app/shared/vocabulary-treeview/vocabulary-tree-flat-data-source.ts
+++ b/src/app/shared/vocabulary-treeview/vocabulary-tree-flat-data-source.ts
@@ -38,7 +38,7 @@ export class VocabularyTreeFlatDataSource extends DataSource {
this._treeControl.expansionModel.changed,
this._flattenedData
];
- return merge(...changes).pipe(map(() => {
+ return merge(...changes).pipe(map((): F[] => {
this._expandedData.next(
this._treeFlattener.expandFlattenedNodes(this._flattenedData.value, this._treeControl));
return this._expandedData.value;
diff --git a/src/app/shared/vocabulary-treeview/vocabulary-treeview.service.spec.ts b/src/app/shared/vocabulary-treeview/vocabulary-treeview.service.spec.ts
index ef84a290dd..c1c64c80bd 100644
--- a/src/app/shared/vocabulary-treeview/vocabulary-treeview.service.spec.ts
+++ b/src/app/shared/vocabulary-treeview/vocabulary-treeview.service.spec.ts
@@ -2,7 +2,7 @@ import { TestBed, waitForAsync } from '@angular/core/testing';
import { TestScheduler } from 'rxjs/testing';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
-import { getTestScheduler, hot } from 'jasmine-marbles';
+import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { VocabularyTreeviewService } from './vocabulary-treeview.service';
import { VocabularyService } from '../../core/submission/vocabularies/vocabulary.service';
@@ -14,6 +14,8 @@ import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models
import { buildPaginatedList } from '../../core/data/paginated-list.model';
import { createSuccessfulRemoteDataObject } from '../remote-data.utils';
import { VocabularyEntry } from '../../core/submission/vocabularies/models/vocabulary-entry.model';
+import { expand, map, switchMap } from 'rxjs/operators';
+import { from as observableFrom } from 'rxjs';
describe('VocabularyTreeviewService test suite', () => {
@@ -320,10 +322,25 @@ describe('VocabularyTreeviewService test suite', () => {
scheduler.schedule(() => service.searchByQuery(vocabularyOptions));
scheduler.flush();
- searchChildNode.childrenChange.next([searchChildNode3]);
- searchItemNode.childrenChange.next([searchChildNode]);
- expect(serviceAsAny.dataChange.value.length).toEqual(1);
- expect(serviceAsAny.dataChange.value).toEqual([searchItemNode]);
+ // We can't check the tree by comparing root TreeviewNodes directly in this particular test;
+ // Since RxJs 7, BehaviorSubjects can no longer be reliably compared because of the new currentObservers property
+ // (see https://github.com/ReactiveX/rxjs/pull/6842)
+ const levels$ = serviceAsAny.dataChange.pipe(
+ expand((nodes: TreeviewNode[]) => { // recursively apply:
+ return observableFrom(nodes).pipe( // for each node in the array...
+ switchMap(node => node.childrenChange) // ...map it to the array its child nodes.
+ ); // because we only have one child per node in this case,
+ }), // this results in an array of nodes for each level of the tree.
+ map((nodes: TreeviewNode[]) => nodes.map(node => node.item)), // finally, replace nodes with their vocab entries
+ );
+
+ // Confirm that this corresponds to the hierarchy we set up above
+ expect(levels$).toBeObservable(cold('-(abcd)', {
+ a: [item],
+ b: [child],
+ c: [child3],
+ d: [] // ensure that grandchild has no children & the recursion stopped there
+ }));
});
});
diff --git a/src/app/statistics/google-analytics.service.spec.ts b/src/app/statistics/google-analytics.service.spec.ts
index c9a267a76f..0c6bc2bc51 100644
--- a/src/app/statistics/google-analytics.service.spec.ts
+++ b/src/app/statistics/google-analytics.service.spec.ts
@@ -1,5 +1,5 @@
import { GoogleAnalyticsService } from './google-analytics.service';
-import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
+import { Angulartics2GoogleAnalytics } from 'angulartics2';
import { ConfigurationDataService } from '../core/data/configuration-data.service';
import {
createFailedRemoteDataObject$,
diff --git a/src/app/statistics/google-analytics.service.ts b/src/app/statistics/google-analytics.service.ts
index 94e5ad20af..0b52f54c4f 100644
--- a/src/app/statistics/google-analytics.service.ts
+++ b/src/app/statistics/google-analytics.service.ts
@@ -1,5 +1,5 @@
import { Inject, Injectable } from '@angular/core';
-import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
+import { Angulartics2GoogleAnalytics } from 'angulartics2';
import { ConfigurationDataService } from '../core/data/configuration-data.service';
import { getFirstCompletedRemoteData } from '../core/shared/operators';
import { isEmpty } from '../shared/empty.util';
diff --git a/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.spec.ts b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.spec.ts
index cd7fa86b0a..4f3c54b642 100644
--- a/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.spec.ts
+++ b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.spec.ts
@@ -64,14 +64,24 @@ describe('SubmissionImportExternalCollectionComponent test suite', () => {
compAsAny = null;
});
- it('The variable \'selectedEvent\' should be assigned', () => {
- const event = new EventEmitter();
- comp.selectObject(event);
+ it('should emit from selectedEvent on selectObject', () => {
+ spyOn(comp.selectedEvent, 'emit').and.callThrough();
- expect(comp.selectedEvent).toEqual(event);
+ const entry = {
+ communities: [
+ { id: 'community1' },
+ { id: 'community2' }
+ ],
+ collection: {
+ id: 'collection'
+ }
+ } as CollectionListEntry;
+ comp.selectObject(entry);
+
+ expect(comp.selectedEvent.emit).toHaveBeenCalledWith(entry);
});
- it('The variable \'selectedEvent\' should be assigned', () => {
+ it('should dismiss modal on closeCollectionModal', () => {
spyOn(compAsAny.activeModal, 'dismiss');
comp.closeCollectionModal();
diff --git a/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.ts b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.ts
index e35bde03cf..5fb4e5d406 100644
--- a/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.ts
+++ b/src/app/submission/import-external/import-external-collection/submission-import-external-collection.component.ts
@@ -35,10 +35,10 @@ export class SubmissionImportExternalCollectionComponent {
) { }
/**
- * This method populates the 'selectedEvent' variable.
+ * This method emits the selected Collection from the 'selectedEvent' variable.
*/
- public selectObject(event): void {
- this.selectedEvent.emit(event);
+ public selectObject(object: CollectionListEntry): void {
+ this.selectedEvent.emit(object);
}
/**
diff --git a/src/app/submission/sections/form/section-form-operations.service.spec.ts b/src/app/submission/sections/form/section-form-operations.service.spec.ts
index d5798b82c8..65ddbe0cb0 100644
--- a/src/app/submission/sections/form/section-form-operations.service.spec.ts
+++ b/src/app/submission/sections/form/section-form-operations.service.spec.ts
@@ -814,7 +814,9 @@ describe('SectionFormOperationsService test suite', () => {
required: false,
metadataKey: 'dc.contributor.author',
metadataFields: ['dc.contributor.author'],
- hasSelectableMetadata: true
+ hasSelectableMetadata: true,
+ showButtons: true,
+ typeBindRelations: []
}
);
spyOn(serviceAsAny, 'getFieldPathSegmentedFromChangeEvent').and.returnValue('path');
diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts
index 939d1bff29..05aa765054 100644
--- a/src/app/submission/submission.module.ts
+++ b/src/app/submission/submission.module.ts
@@ -97,7 +97,7 @@ const DECLARATIONS = [
SectionsService,
SubmissionUploadsConfigService,
SubmissionAccessesConfigService,
- SectionAccessesService
+ SectionAccessesService,
]
})
diff --git a/src/app/workflowitems-edit-page/item-from-workflow.resolver.ts b/src/app/workflowitems-edit-page/item-from-workflow.resolver.ts
index 2aaa762b2a..bacf515656 100644
--- a/src/app/workflowitems-edit-page/item-from-workflow.resolver.ts
+++ b/src/app/workflowitems-edit-page/item-from-workflow.resolver.ts
@@ -1,43 +1,21 @@
import { Injectable } from '@angular/core';
-import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
-import { Observable } from 'rxjs';
+import { Resolve } from '@angular/router';
import { RemoteData } from '../core/data/remote-data';
import { Item } from '../core/shared/item.model';
-import { followLink } from '../shared/utils/follow-link-config.model';
-import { getFirstCompletedRemoteData } from '../core/shared/operators';
import { Store } from '@ngrx/store';
import { WorkflowItemDataService } from '../core/submission/workflowitem-data.service';
-import { WorkflowItem } from '../core/submission/models/workflowitem.model';
-import { switchMap } from 'rxjs/operators';
+import { SubmissionObjectResolver } from '../core/submission/resolver/submission-object.resolver';
/**
* This class represents a resolver that requests a specific item before the route is activated
*/
@Injectable()
-export class ItemFromWorkflowResolver implements Resolve> {
+export class ItemFromWorkflowResolver extends SubmissionObjectResolver
- implements Resolve> {
constructor(
private workflowItemService: WorkflowItemDataService,
protected store: Store
) {
+ super(workflowItemService, store);
}
- /**
- * Method for resolving an item based on the parameters in the current route
- * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
- * @param {RouterStateSnapshot} state The current RouterStateSnapshot
- * @returns Observable<> Emits the found item based on the parameters in the current route,
- * or an error if something went wrong
- */
- resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> {
- const itemRD$ = this.workflowItemService.findById(route.params.id,
- true,
- false,
- followLink('item'),
- ).pipe(
- getFirstCompletedRemoteData(),
- switchMap((wfiRD: RemoteData) => wfiRD.payload.item as Observable>),
- getFirstCompletedRemoteData()
- );
- return itemRD$;
- }
}
diff --git a/src/app/workspaceitems-edit-page/item-from-workspace.resolver.spec.ts b/src/app/workspaceitems-edit-page/item-from-workspace.resolver.spec.ts
new file mode 100644
index 0000000000..c14344d70d
--- /dev/null
+++ b/src/app/workspaceitems-edit-page/item-from-workspace.resolver.spec.ts
@@ -0,0 +1,36 @@
+import { first } from 'rxjs/operators';
+import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
+import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
+import { ItemFromWorkspaceResolver } from './item-from-workspace.resolver';
+
+describe('ItemFromWorkspaceResolver', () => {
+ describe('resolve', () => {
+ let resolver: ItemFromWorkspaceResolver;
+ let wfiService: WorkspaceitemDataService;
+ const uuid = '1234-65487-12354-1235';
+ const itemUuid = '8888-8888-8888-8888';
+ const wfi = {
+ id: uuid,
+ item: createSuccessfulRemoteDataObject$({ id: itemUuid })
+ };
+
+
+ beforeEach(() => {
+ wfiService = {
+ findById: (id: string) => createSuccessfulRemoteDataObject$(wfi)
+ } as any;
+ resolver = new ItemFromWorkspaceResolver(wfiService, null);
+ });
+
+ it('should resolve a an item from from the workflow item with the correct id', (done) => {
+ resolver.resolve({ params: { id: uuid } } as any, undefined)
+ .pipe(first())
+ .subscribe(
+ (resolved) => {
+ expect(resolved.payload.id).toEqual(itemUuid);
+ done();
+ }
+ );
+ });
+ });
+});
diff --git a/src/app/workspaceitems-edit-page/item-from-workspace.resolver.ts b/src/app/workspaceitems-edit-page/item-from-workspace.resolver.ts
new file mode 100644
index 0000000000..60e1fe6a87
--- /dev/null
+++ b/src/app/workspaceitems-edit-page/item-from-workspace.resolver.ts
@@ -0,0 +1,21 @@
+import { Injectable } from '@angular/core';
+import { Resolve } from '@angular/router';
+import { RemoteData } from '../core/data/remote-data';
+import { Item } from '../core/shared/item.model';
+import { Store } from '@ngrx/store';
+import { SubmissionObjectResolver } from '../core/submission/resolver/submission-object.resolver';
+import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
+
+/**
+ * This class represents a resolver that requests a specific item before the route is activated
+ */
+@Injectable()
+export class ItemFromWorkspaceResolver extends SubmissionObjectResolver
- implements Resolve> {
+ constructor(
+ private workspaceItemService: WorkspaceitemDataService,
+ protected store: Store
+ ) {
+ super(workspaceItemService, store);
+ }
+
+}
diff --git a/src/app/workspaceitems-edit-page/workspace-item-page.resolver.spec.ts b/src/app/workspaceitems-edit-page/workspace-item-page.resolver.spec.ts
new file mode 100644
index 0000000000..bbd3360db4
--- /dev/null
+++ b/src/app/workspaceitems-edit-page/workspace-item-page.resolver.spec.ts
@@ -0,0 +1,30 @@
+import { first } from 'rxjs/operators';
+import { WorkspaceItemPageResolver } from './workspace-item-page.resolver';
+import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
+import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
+
+describe('WorkflowItemPageResolver', () => {
+ describe('resolve', () => {
+ let resolver: WorkspaceItemPageResolver;
+ let wsiService: WorkspaceitemDataService;
+ const uuid = '1234-65487-12354-1235';
+
+ beforeEach(() => {
+ wsiService = {
+ findById: (id: string) => createSuccessfulRemoteDataObject$({ id })
+ } as any;
+ resolver = new WorkspaceItemPageResolver(wsiService);
+ });
+
+ it('should resolve a workspace item with the correct id', (done) => {
+ resolver.resolve({ params: { id: uuid } } as any, undefined)
+ .pipe(first())
+ .subscribe(
+ (resolved) => {
+ expect(resolved.payload.id).toEqual(uuid);
+ done();
+ }
+ );
+ });
+ });
+});
diff --git a/src/app/workspaceitems-edit-page/workspace-item-page.resolver.ts b/src/app/workspaceitems-edit-page/workspace-item-page.resolver.ts
new file mode 100644
index 0000000000..1b1aa25492
--- /dev/null
+++ b/src/app/workspaceitems-edit-page/workspace-item-page.resolver.ts
@@ -0,0 +1,34 @@
+import { Injectable } from '@angular/core';
+import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
+import { Observable } from 'rxjs';
+import { RemoteData } from '../core/data/remote-data';
+import { followLink } from '../shared/utils/follow-link-config.model';
+import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
+import { WorkflowItem } from '../core/submission/models/workflowitem.model';
+import { getFirstCompletedRemoteData } from '../core/shared/operators';
+
+/**
+ * This class represents a resolver that requests a specific workflow item before the route is activated
+ */
+@Injectable()
+export class WorkspaceItemPageResolver implements Resolve> {
+ constructor(private workspaceItemService: WorkspaceitemDataService) {
+ }
+
+ /**
+ * Method for resolving a workflow item based on the parameters in the current route
+ * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
+ * @param {RouterStateSnapshot} state The current RouterStateSnapshot
+ * @returns Observable<> Emits the found workflow item based on the parameters in the current route,
+ * or an error if something went wrong
+ */
+ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> {
+ return this.workspaceItemService.findById(route.params.id,
+ true,
+ false,
+ followLink('item'),
+ ).pipe(
+ getFirstCompletedRemoteData(),
+ );
+ }
+}
diff --git a/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing-paths.ts b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing-paths.ts
new file mode 100644
index 0000000000..74917b4392
--- /dev/null
+++ b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing-paths.ts
@@ -0,0 +1,8 @@
+import { getWorkspaceItemModuleRoute } from '../app-routing-paths';
+import { URLCombiner } from '../core/url-combiner/url-combiner';
+
+export function getWorkspaceItemViewRoute(wfiId: string) {
+ return new URLCombiner(getWorkspaceItemModuleRoute(), wfiId, WORKSPACE_ITEM_VIEW_PATH).toString();
+}
+
+export const WORKSPACE_ITEM_VIEW_PATH = 'view';
diff --git a/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts
index 1a58417d0c..cc76634c03 100644
--- a/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts
+++ b/src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts
@@ -4,22 +4,42 @@ import { RouterModule } from '@angular/router';
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.component';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
+import { ThemedFullItemPageComponent } from '../item-page/full/themed-full-item-page.component';
+import { ItemFromWorkspaceResolver } from './item-from-workspace.resolver';
+import { WorkspaceItemPageResolver } from './workspace-item-page.resolver';
@NgModule({
imports: [
RouterModule.forChild([
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{
- canActivate: [AuthenticatedGuard],
- path: ':id/edit',
- component: ThemedSubmissionEditComponent,
- resolve: {
- breadcrumb: I18nBreadcrumbResolver
- },
- data: { title: 'submission.edit.title', breadcrumbKey: 'submission.edit' }
+ path: ':id',
+ resolve: { wsi: WorkspaceItemPageResolver },
+ children: [
+ {
+ canActivate: [AuthenticatedGuard],
+ path: 'edit',
+ component: ThemedSubmissionEditComponent,
+ resolve: {
+ breadcrumb: I18nBreadcrumbResolver
+ },
+ data: { title: 'submission.edit.title', breadcrumbKey: 'submission.edit' }
+ },
+ {
+ canActivate: [AuthenticatedGuard],
+ path: 'view',
+ component: ThemedFullItemPageComponent,
+ resolve: {
+ dso: ItemFromWorkspaceResolver,
+ breadcrumb: I18nBreadcrumbResolver
+ },
+ data: { title: 'workspace-item.view.title', breadcrumbKey: 'workspace-item.view' }
+ }
+ ]
}
])
- ]
+ ],
+ providers: [WorkspaceItemPageResolver, ItemFromWorkspaceResolver]
})
/**
* This module defines the default component to load when navigating to the workspaceitems edit page path
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5
index c22ec1c3ee..4fcdbff335 100644
--- a/src/assets/i18n/en.json5
+++ b/src/assets/i18n/en.json5
@@ -27,6 +27,16 @@
"404.page-not-found": "page not found",
+ "access-status.embargo.listelement.badge": "Embargo",
+
+ "access-status.metadata.only.listelement.badge": "Metadata only",
+
+ "access-status.open.access.listelement.badge": "Open Access",
+
+ "access-status.restricted.listelement.badge": "Restricted",
+
+ "access-status.unknown.listelement.badge": "Unknown",
+
"admin.curation-tasks.breadcrumbs": "System curation tasks",
"admin.curation-tasks.title": "System curation tasks",
@@ -538,6 +548,10 @@
"admin.metadata-import.page.error.addFile": "Select file first!",
+ "admin.metadata-import.page.validateOnly": "Validate Only",
+
+ "admin.metadata-import.page.validateOnly.hint": "When selected, the uploaded CSV will be validated. You will receive a report of detected changes, but no changes will be saved.",
+
@@ -1387,6 +1401,9 @@
"error.validation.groupExists": "This group already exists",
+ "feed.description": "Syndication feed",
+
+
"file-section.error.header": "Error obtaining files for this item",
@@ -4095,6 +4112,10 @@
"submission.workflow.tasks.pool.show-detail": "Show detail",
+ "submission.workspace.generic.view": "View",
+
+ "submission.workspace.generic.view-help": "Select this option to view the item's metadata.",
+
"thumbnail.default.alt": "Thumbnail Image",
@@ -4201,6 +4222,9 @@
"workflow-item.view.breadcrumbs": "Workflow View",
+ "workspace-item.view.breadcrumbs": "Workspace View",
+
+ "workspace-item.view.title": "Workspace View",
"idle-modal.header": "Session will expire soon",
diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5
index 0f14b78341..3b9d7498be 100644
--- a/src/assets/i18n/fr.json5
+++ b/src/assets/i18n/fr.json5
@@ -36,6 +36,21 @@
// "404.page-not-found": "page not found",
"404.page-not-found": "Page introuvable",
+ // "access-status.embargo.listelement.badge": "Embargo",
+ "access-status.embargo.listelement.badge": "Restriction temporaire",
+
+ // "access-status.metadata.only.listelement.badge": "Metadata only",
+ "access-status.metadata.only.listelement.badge": "Métadonnées seulement",
+
+ // "access-status.open.access.listelement.badge": "Open Access",
+ "access-status.open.access.listelement.badge": "Accès libre",
+
+ // "access-status.restricted.listelement.badge": "Restricted",
+ "access-status.restricted.listelement.badge": "Restreint",
+
+ // "access-status.unknown.listelement.badge": "Unknown",
+ "access-status.unknown.listelement.badge": "Inconnu",
+
// "admin.curation-tasks.breadcrumbs": "System curation tasks",
"admin.curation-tasks.breadcrumbs": "Tâches de conservation système",
diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts
index e8bda53373..649efacb7b 100644
--- a/src/config/app-config.interface.ts
+++ b/src/config/app-config.interface.ts
@@ -7,7 +7,7 @@ import { INotificationBoardOptions } from './notifications-config.interfaces';
import { SubmissionConfig } from './submission-config.interface';
import { FormConfig } from './form-config.interfaces';
import { LangConfig } from './lang-config.interface';
-import { ItemPageConfig } from './item-page-config.interface';
+import { ItemConfig } from './item-config.interface';
import { CollectionPageConfig } from './collection-page-config.interface';
import { ThemeConfig } from './theme.model';
import { AuthConfig } from './auth-config.interfaces';
@@ -30,7 +30,7 @@ interface AppConfig extends Config {
defaultLanguage: string;
languages: LangConfig[];
browseBy: BrowseByConfig;
- item: ItemPageConfig;
+ item: ItemConfig;
collection: CollectionPageConfig;
themes: ThemeConfig[];
mediaViewer: MediaViewerConfig;
diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts
index 27950f5269..383b92cf73 100644
--- a/src/config/default-app-config.ts
+++ b/src/config/default-app-config.ts
@@ -6,7 +6,7 @@ import { BrowseByConfig } from './browse-by-config.interface';
import { CacheConfig } from './cache-config.interface';
import { CollectionPageConfig } from './collection-page-config.interface';
import { FormConfig } from './form-config.interfaces';
-import { ItemPageConfig } from './item-page-config.interface';
+import { ItemConfig } from './item-config.interface';
import { LangConfig } from './lang-config.interface';
import { MediaViewerConfig } from './media-viewer-config.interface';
import { INotificationBoardOptions } from './notifications-config.interfaces';
@@ -116,6 +116,9 @@ export class DefaultAppConfig implements AppConfig {
*/
timer: 0
},
+ typeBind: {
+ field: 'dc.type'
+ },
icons: {
metadata: [
/**
@@ -203,11 +206,13 @@ export class DefaultAppConfig implements AppConfig {
defaultLowerLimit: 1900
};
- // Item Page Config
- item: ItemPageConfig = {
+ // Item Config
+ item: ItemConfig = {
edit: {
undoTimeout: 10000 // 10 seconds
- }
+ },
+ // Show the item access status label in items lists
+ showAccessStatuses: false
};
// Collection Page Config
diff --git a/src/config/item-config.interface.ts b/src/config/item-config.interface.ts
new file mode 100644
index 0000000000..f842c37c05
--- /dev/null
+++ b/src/config/item-config.interface.ts
@@ -0,0 +1,9 @@
+import { Config } from './config.interface';
+
+export interface ItemConfig extends Config {
+ edit: {
+ undoTimeout: number;
+ };
+ // This is used to show the access status label of items in results lists
+ showAccessStatuses: boolean;
+}
diff --git a/src/config/item-page-config.interface.ts b/src/config/item-page-config.interface.ts
deleted file mode 100644
index 2b05e28715..0000000000
--- a/src/config/item-page-config.interface.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Config } from './config.interface';
-
-export interface ItemPageConfig extends Config {
- edit: {
- undoTimeout: number;
- };
-}
diff --git a/src/config/submission-config.interface.ts b/src/config/submission-config.interface.ts
index ce275b9bf8..a63af45e38 100644
--- a/src/config/submission-config.interface.ts
+++ b/src/config/submission-config.interface.ts
@@ -5,6 +5,10 @@ interface AutosaveConfig extends Config {
timer: number;
}
+interface TypeBindConfig extends Config {
+ field: string;
+}
+
interface IconsConfig extends Config {
metadata: MetadataIconConfig[];
authority: {
@@ -24,5 +28,6 @@ export interface ConfidenceIconConfig extends Config {
export interface SubmissionConfig extends Config {
autosave: AutosaveConfig;
+ typeBind: TypeBindConfig;
icons: IconsConfig;
}
diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts
index d2f2ad7426..8ad842c33e 100644
--- a/src/environments/environment.test.ts
+++ b/src/environments/environment.test.ts
@@ -104,6 +104,9 @@ export const environment: BuildConfig = {
// NOTE: every how many minutes submission is saved automatically
timer: 5
},
+ typeBind: {
+ field: 'dc.type'
+ },
icons: {
metadata: [
{
@@ -200,7 +203,9 @@ export const environment: BuildConfig = {
item: {
edit: {
undoTimeout: 10000 // 10 seconds
- }
+ },
+ // Show the item access status label in items lists
+ showAccessStatuses: false
},
collection: {
edit: {
diff --git a/src/mirador-viewer/mirador.html b/src/mirador-viewer/mirador.html
index 6a9547133c..3cd1e16501 100644
--- a/src/mirador-viewer/mirador.html
+++ b/src/mirador-viewer/mirador.html
@@ -6,5 +6,6 @@
+
diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts
index 88a59eb157..252227b056 100644
--- a/src/modules/app/browser-app.module.ts
+++ b/src/modules/app/browser-app.module.ts
@@ -18,7 +18,7 @@ import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.ser
import { ClientCookieService } from '../../app/core/services/client-cookie.service';
import { CookieService } from '../../app/core/services/cookie.service';
import { AuthService } from '../../app/core/auth/auth.service';
-import { Angulartics2RouterlessModule } from 'angulartics2/routerlessmodule';
+import { Angulartics2RouterlessModule } from 'angulartics2';
import { SubmissionService } from '../../app/submission/submission.service';
import { StatisticsModule } from '../../app/statistics/statistics.module';
import { BrowserKlaroService } from '../../app/shared/cookies/browser-klaro.service';
diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts
index f5b2c4e27b..52f0048f4d 100644
--- a/src/modules/app/server-app.module.ts
+++ b/src/modules/app/server-app.module.ts
@@ -8,7 +8,7 @@ import { RouterModule } from '@angular/router';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { Angulartics2 } from 'angulartics2';
-import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
+import { Angulartics2GoogleAnalytics } from 'angulartics2';
import { AppComponent } from '../../app/app.component';
diff --git a/src/styles/_global-styles.scss b/src/styles/_global-styles.scss
index 4afd2eb05e..4a855a635e 100644
--- a/src/styles/_global-styles.scss
+++ b/src/styles/_global-styles.scss
@@ -108,3 +108,10 @@ ngb-modal-backdrop {
.ml-gap {
margin-left: var(--ds-gap);
}
+
+ds-dynamic-form-control-container.d-none {
+ /* Ensures that form-control containers hidden and disabled by type binding collapse and let other fields in
+ the same row expand accordingly
+ */
+ visibility: collapse;
+}
diff --git a/webpack/webpack.mirador.config.ts b/webpack/webpack.mirador.config.ts
index 3e04ad6b79..c0083ded6e 100644
--- a/webpack/webpack.mirador.config.ts
+++ b/webpack/webpack.mirador.config.ts
@@ -1,4 +1,4 @@
-const HtmlWebpackPlugin = require('html-webpack-plugin');
+const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = {
@@ -10,19 +10,16 @@ module.exports = {
path: path.resolve(__dirname, '..' , 'dist/iiif/mirador'),
filename: '[name].js'
},
- module: {
- rules: [
- {
- test: /\.html$/i,
- loader: 'html-loader',
- },
- ],
- },
devServer: {
contentBase: '../dist/iiif/mirador',
},
- plugins: [new HtmlWebpackPlugin({
- filename: 'index.html',
- template: './src/mirador-viewer/mirador.html'
+ resolve: {
+ fallback: {
+ url: false
+ }},
+ plugins: [new CopyWebpackPlugin({
+ patterns: [
+ {from: './src/mirador-viewer/mirador.html', to: './index.html'}
+ ]
})]
};
diff --git a/yarn.lock b/yarn.lock
index a612a985d8..c95c35c433 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1670,17 +1670,17 @@
dependencies:
tslib "^2.3.0"
-"@ng-dynamic-forms/core@^14.0.1":
- version "14.0.1"
- resolved "https://registry.yarnpkg.com/@ng-dynamic-forms/core/-/core-14.0.1.tgz#e5815a7f67b4e23a5c726afd137b3e27afe09ab9"
- integrity sha512-Pys4H0lSk2Ae8y80mRD4yZMTu+80DIOmf4B2L9fK2q/zYyxVSexu0DynDR8XApArXYU78EPsWnEwgNSWwX6RKw==
+"@ng-dynamic-forms/core@^15.0.0":
+ version "15.0.0"
+ resolved "https://registry.yarnpkg.com/@ng-dynamic-forms/core/-/core-15.0.0.tgz#674a88c253aa100b30144bf7ebf518e24b72f553"
+ integrity sha512-JJ0w8WdOA+wsHyt/hwitGhv/e1j95/TlRS82vvZetP/Ip3kjvD/Ge8jbg4bEssIAXZjfBqS/Gy00Hxo4h57DgQ==
dependencies:
tslib "^2.0.0"
-"@ng-dynamic-forms/ui-ng-bootstrap@^14.0.1":
- version "14.0.1"
- resolved "https://registry.yarnpkg.com/@ng-dynamic-forms/ui-ng-bootstrap/-/ui-ng-bootstrap-14.0.1.tgz#10f271b85eceadad02f616f752cf9806eb085106"
- integrity sha512-Xf56kZBwM0vsRgEKcZvh8SsypCWcVTKeyq9id68+jQzH9/bQ+qriLBF35zDHrS9vJWmSufa5xqqRx/ycxhfpLw==
+"@ng-dynamic-forms/ui-ng-bootstrap@^15.0.0":
+ version "15.0.0"
+ resolved "https://registry.yarnpkg.com/@ng-dynamic-forms/ui-ng-bootstrap/-/ui-ng-bootstrap-15.0.0.tgz#0ab5614bc2efccc4cddbb384865b66d4740bcd3d"
+ integrity sha512-b/+tOJxtDRMzoFA7KLA8JRxbAnXd8d8072/P6C+2xOMaG0Ttc1UUiNQOZ5w82y78nr0bZ63oFHSR0xzSVtMXnA==
dependencies:
tslib "^2.0.0"
@@ -2920,12 +2920,12 @@ angular-idle-preload@3.0.0:
resolved "https://registry.yarnpkg.com/angular-idle-preload/-/angular-idle-preload-3.0.0.tgz#decace34d9fac1cb00000727a6dc5caafdb84e4d"
integrity sha512-W3P2m2B6MHdt1DVunH6H3VWkAZrG3ZwxGcPjedVvIyRhg/LmMtILoizHSxTXw3fsKIEdAPwGObXGpML9WD1jJA==
-angulartics2@^10.0.0:
- version "10.1.0"
- resolved "https://registry.yarnpkg.com/angulartics2/-/angulartics2-10.1.0.tgz#2988f95f25cf6a8dd630d63ea604eb6643e076c3"
- integrity sha512-MnwQxRXJkfbBF7417Cs7L/SIuTRNWHCOBnGolZXHFz5ogw1e51KdCKUaUkfgBogR7JpXP279FU9UDkzerIS3xw==
+angulartics2@^12.0.0:
+ version "12.0.0"
+ resolved "https://registry.yarnpkg.com/angulartics2/-/angulartics2-12.0.0.tgz#d9440ff98d133ae02d97b991a32a711a5b88559f"
+ integrity sha512-hNjvOp/IvKD00Ix3zRGfGJUwwOhSM5RFhvM/iSBH7dvJKavCBWbI464PWshjXfRBbruangPUbJGhSLnoENNtmg==
dependencies:
- tslib "^2.0.0"
+ tslib "^2.3.0"
ansi-align@^3.0.0:
version "3.0.1"
@@ -4621,10 +4621,10 @@ custom-event@~1.0.0:
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=
-cypress-axe@^0.13.0:
- version "0.13.0"
- resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-0.13.0.tgz#3234e1a79a27701f2451fcf2f333eb74204c7966"
- integrity sha512-fCIy7RiDCm7t30U3C99gGwQrUO307EYE1QqXNaf9ToK4DVqW8y5on+0a/kUHMrHdlls2rENF6TN9ZPpPpwLrnw==
+cypress-axe@^0.14.0:
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-0.14.0.tgz#5f5e70fb36b8cb3ba73a8ba01e9262ff1268d5e2"
+ integrity sha512-7Rdjnko0MjggCmndc1wECAkvQBIhuy+DRtjF7bd5YPZRFvubfMNvrxfqD8PWQmxm7MZE0ffS4Xr43V6ZmvLopg==
cypress@9.5.1:
version "9.5.1"
@@ -7544,12 +7544,12 @@ jasmine-core@~2.8.0:
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e"
integrity sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=
-jasmine-marbles@0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/jasmine-marbles/-/jasmine-marbles-0.6.0.tgz#f78dc1a3bc452976de10ee8b47c73d616532a954"
- integrity sha512-1uzgjEesEeCb+r+v46qn5x326TiGqk5SUZa+A3O+XnMCjG/pGcUOhL9Xsg5L7gLC6RFHyWGTkB5fei4rcvIOiQ==
+jasmine-marbles@0.9.2:
+ version "0.9.2"
+ resolved "https://registry.yarnpkg.com/jasmine-marbles/-/jasmine-marbles-0.9.2.tgz#5adfee5f72c7f24270687fa64a6e8a8613ffa841"
+ integrity sha512-T7RjG4fRsdiGGzbQZ6Kj39qYt6O1/KIcR4FkUNsD3DUGkd/AzpwzN+xtk0DXlLWEz5BaVdK1SzMgQDVw879c4Q==
dependencies:
- lodash "^4.5.0"
+ lodash "^4.17.20"
jasmine-spec-reporter@~5.0.0:
version "5.0.2"
@@ -8219,7 +8219,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
-lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.5.0, lodash@^4.7.0:
+lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -8861,12 +8861,12 @@ ngx-infinite-scroll@^10.0.1:
"@scarf/scarf" "^1.1.0"
opencollective-postinstall "^2.0.2"
-ngx-mask@^12.0.0:
- version "12.0.0"
- resolved "https://registry.yarnpkg.com/ngx-mask/-/ngx-mask-12.0.0.tgz#8eb363cc609ab71b687bbe6f87497c461ca120b1"
- integrity sha512-q4vUjhjJfg4faRud/tUdCTOs3JA6B+rBB2OPZ2xBZy4LNTRKGfUK683LrDCitMVBezjEAVrkQdUT1I4C7LXBZQ==
+ngx-mask@^13.1.7:
+ version "13.1.7"
+ resolved "https://registry.yarnpkg.com/ngx-mask/-/ngx-mask-13.1.7.tgz#9ef40354a83484aaf77aff74742cd0f43b4a65cd"
+ integrity sha512-zwGSEGt+WRlb31qMd92K25MCNUhfI2XKOMv+m5NypkZ+stONdBxAXjp8wA/1MJ46uYF5UYLmKPdkXloZBtOXQQ==
dependencies:
- tslib "^2.1.0"
+ tslib "^2.3.0"
ngx-moment@^5.0.0:
version "5.0.0"
@@ -11133,10 +11133,10 @@ rxjs-report-usage@^1.0.4:
glob "~7.2.0"
prompts "~2.4.2"
-rxjs-spy@^7.5.3:
- version "7.5.3"
- resolved "https://registry.yarnpkg.com/rxjs-spy/-/rxjs-spy-7.5.3.tgz#0194bc23ed0c30fb6a61f8bccbc8090e545b91b9"
- integrity sha512-8QsSL6Ma51dTeaJ5Q9zWqhqnCSEkDf56Evs1gUsI9N22oB7bYrPMMx4UnoifNGc+Pko2sGX/xydzinLwGO+2pw==
+rxjs-spy@^8.0.2:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/rxjs-spy/-/rxjs-spy-8.0.2.tgz#dd510bdb58d798e0bc23121ab67714dd6fd95f88"
+ integrity sha512-w2yc+EiwYA8J97hxqMD+pxGZkNbRCQwxR660r4nw4Soa8kCvatsdSRc0THndYk9uk6SvZy2RNyiVcxfX39pWpw==
dependencies:
"@types/circular-json" "^0.4.0"
"@types/stacktrace-js" "^0.0.33"
@@ -11145,7 +11145,7 @@ rxjs-spy@^7.5.3:
rxjs-report-usage "^1.0.4"
stacktrace-gps "^3.0.2"
-rxjs@6.6.7, rxjs@^6.5.4, rxjs@^6.5.5, rxjs@^6.6.3, rxjs@~6.6.0:
+rxjs@6.6.7, rxjs@^6.5.4, rxjs@^6.5.5, rxjs@~6.6.0:
version "6.6.7"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
@@ -11166,6 +11166,13 @@ rxjs@^7.2.0, rxjs@^7.5.1:
dependencies:
tslib "^2.1.0"
+rxjs@^7.5.5:
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f"
+ integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==
+ dependencies:
+ tslib "^2.1.0"
+
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|