diff --git a/package.json b/package.json index c4194bf6f1..e3e1d294d1 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "@ngx-translate/core": "9.1.1", "@ngx-translate/http-loader": "2.0.1", "angular-idle-preload": "2.0.4", + "angular2-moment": "^1.9.0", "body-parser": "1.18.2", "bootstrap": "^4.0.0", "cerialize": "0.1.18", @@ -101,6 +102,7 @@ "js.clone": "0.0.3", "jsonschema": "1.2.2", "methods": "1.1.2", + "moment": "^2.22.1", "morgan": "1.9.0", "ng2-nouislider": "^1.7.7", "ngx-pagination": "3.0.3", diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 53ae9015f6..2cea867af6 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -123,7 +123,12 @@ "head": "Subject" }, "dateIssued": { - "placeholder": "Date", + "max": { + "placeholder": "Minimum Date" + }, + "min": { + "placeholder": "Maximum Date" + }, "head": "Date" }, "has_content_in_original_bundle": { diff --git a/src/app/+search-page/search-filters/search-filter/search-boolean-filter/search-boolean-filter.component.spec.ts b/src/app/+search-page/search-filters/search-filter/search-boolean-filter/search-boolean-filter.component.spec.ts index 03b760318f..e1e34b47e6 100644 --- a/src/app/+search-page/search-filters/search-filter/search-boolean-filter/search-boolean-filter.component.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-boolean-filter/search-boolean-filter.component.spec.ts @@ -1,9 +1,7 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { TranslateModule } from '@ngx-translate/core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { SearchFacetFilterComponent } from './search-facet-filter.component'; import { SearchFilterService } from '../search-filter.service'; import { SearchFilterConfig } from '../../../search-service/search-filter-config.model'; import { FilterType } from '../../../search-service/filter-type.model'; @@ -19,6 +17,7 @@ import { RouterStub } from '../../../../shared/testing/router-stub'; import { Router } from '@angular/router'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { PageInfo } from '../../../../core/shared/page-info.model'; +import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component'; describe('SearchFacetFilterComponent', () => { let comp: SearchFacetFilterComponent; diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts index a83312006c..e93a48330b 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts @@ -71,10 +71,31 @@ export class SearchFilterService { ); } - getCurrentFilters() { - return this.routeService.getQueryParamsWithPrefix('f.'); + getCurrentFilters(): Observable { + return this.routeService.getQueryParamsWithPrefix('f.').map((filterParams) => { + if (isNotEmpty(filterParams)) { + const params = {}; + Object.keys(filterParams).forEach((key) => { + if (key.endsWith('.min') || key.endsWith('.max')) { + const realKey = key.slice(0, -4); + if (isEmpty(params[realKey])) { + const min = filterParams[realKey + '.min'][0] || '*'; + const max = filterParams[realKey + '.max'][0] || '*'; + params[realKey] = ['[' + min + ' TO ' + max + ']']; + } + } else { + params[key] = filterParams[key]; + } + }); + return params; + } + return filterParams; + }); } + getCurrentFrontendFilters(): Observable { + return this.routeService.getQueryParamsWithPrefix('f.'); + } getCurrentView() { return this.routeService.getQueryParameterValue('view'); } diff --git a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.scss b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.scss index 595b2aefb8..1297f1ae3a 100644 --- a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.scss +++ b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.scss @@ -7,7 +7,7 @@ a { color: $body-color; &:hover { - text-decoration: none; + text-decoration: none; } } .toggle-more-filters a { diff --git a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts index 03b760318f..e1e34b47e6 100644 --- a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts @@ -1,9 +1,7 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { TranslateModule } from '@ngx-translate/core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { SearchFacetFilterComponent } from './search-facet-filter.component'; import { SearchFilterService } from '../search-filter.service'; import { SearchFilterConfig } from '../../../search-service/search-filter-config.model'; import { FilterType } from '../../../search-service/filter-type.model'; @@ -19,6 +17,7 @@ import { RouterStub } from '../../../../shared/testing/router-stub'; import { Router } from '@angular/router'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { PageInfo } from '../../../../core/shared/page-info.model'; +import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component'; describe('SearchFacetFilterComponent', () => { let comp: SearchFacetFilterComponent; diff --git a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.html index ae51f3bcb1..06d084adf5 100644 --- a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.html @@ -3,28 +3,27 @@
-
-
- + - {{value.value}} {{value.count}} diff --git a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.scss b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.scss index 595b2aefb8..ad36c7b8d3 100644 --- a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.scss +++ b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.scss @@ -2,17 +2,13 @@ @import '../../../../../styles/mixins.scss'; .filters { - margin-top: $spacer/2; - margin-bottom: $spacer/2; + a { - color: $body-color; + color: $link-color; &:hover { - text-decoration: none; + text-decoration: underline; + color: $link-hover-color; + } } - .toggle-more-filters a { - color: $link-color; - text-decoration: underline; - cursor: pointer; - } } \ No newline at end of file diff --git a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts index 9cf763099c..1f0e16600f 100644 --- a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts @@ -1,8 +1,13 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Inject, OnInit } from '@angular/core'; import { FilterType } from '../../../search-service/filter-type.model'; import { renderFacetFor } from '../search-filter-type-decorator'; import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component'; import { isNotEmpty } from '../../../../shared/empty.util'; +import { SearchFilterConfig } from '../../../search-service/search-filter-config.model'; +import { FILTER_CONFIG, SearchFilterService, SELECTED_VALUES } from '../search-filter.service'; +import { SearchService } from '../../../search-service/search.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import * as moment from 'moment'; /** * This component renders a simple item page. @@ -20,21 +25,28 @@ import { isNotEmpty } from '../../../../shared/empty.util'; export class SearchRangeFilterComponent extends SearchFacetFilterComponent implements OnInit { rangeDelimiter = '-'; min = 1950; - max = 1960; - rangeMin = 1900; // calculate using available values - rangeMax = 2000; + max = 2018; + range; + dateFormats = ['YYYY', 'YYYY-MM', 'YYYY-MM-DD'] + + constructor(protected searchService: SearchService, + protected filterService: SearchFilterService, + protected router: Router, + @Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig, + @Inject(SELECTED_VALUES) public selectedValues: string[], + private route: ActivatedRoute) { + super(searchService, filterService, router, filterConfig, selectedValues); + } ngOnInit(): void { + super.ngOnInit(); + this.min = moment(this.filterConfig.minValue, this.dateFormats).year() || this.min; + this.max = moment(this.filterConfig.maxValue, this.dateFormats).year() || this.max; + const iniMin = this.route.snapshot.queryParams[this.filterConfig.paramName + '.min'] || this.min; + const iniMax = this.route.snapshot.queryParams[this.filterConfig.paramName + '.max'] || this.max; + this.range = [iniMin, iniMax]; } - get range() { - return [this.min, this.max]; - } - - set range(value: number[]) { - this.min = value[0]; - this.max = value[1]; - } getAddParams(value: string) { const parts = value.split(this.rangeDelimiter); @@ -59,8 +71,10 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple if (isNotEmpty(data)) { this.router.navigate([this.getSearchLink()], { queryParams: - { [this.filterConfig.paramName + '.min']: [data[this.filterConfig.paramName + '.min']], - [this.filterConfig.paramName + '.max']: [data[this.filterConfig.paramName + '.max']]}, + { + [this.filterConfig.paramName + '.min']: [data[this.filterConfig.paramName + '.min']], + [this.filterConfig.paramName + '.max']: [data[this.filterConfig.paramName + '.max']] + }, queryParamsHandling: 'merge' }); this.filter = ''; diff --git a/src/app/+search-page/search-filters/search-filter/search-text-filter/search-text-filter.component.spec.ts b/src/app/+search-page/search-filters/search-filter/search-text-filter/search-text-filter.component.spec.ts index 03b760318f..c44519743e 100644 --- a/src/app/+search-page/search-filters/search-filter/search-text-filter/search-text-filter.component.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-text-filter/search-text-filter.component.spec.ts @@ -3,7 +3,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { TranslateModule } from '@ngx-translate/core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { SearchFacetFilterComponent } from './search-facet-filter.component'; import { SearchFilterService } from '../search-filter.service'; import { SearchFilterConfig } from '../../../search-service/search-filter-config.model'; import { FilterType } from '../../../search-service/filter-type.model'; @@ -19,6 +18,7 @@ import { RouterStub } from '../../../../shared/testing/router-stub'; import { Router } from '@angular/router'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { PageInfo } from '../../../../core/shared/page-info.model'; +import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component'; describe('SearchFacetFilterComponent', () => { let comp: SearchFacetFilterComponent; diff --git a/src/app/+search-page/search-filters/search-filters.component.ts b/src/app/+search-page/search-filters/search-filters.component.ts index 517b2e1e59..b0c69ee5ce 100644 --- a/src/app/+search-page/search-filters/search-filters.component.ts +++ b/src/app/+search-page/search-filters/search-filters.component.ts @@ -22,7 +22,7 @@ export class SearchFiltersComponent { clearParams; constructor(private searchService: SearchService, private filterService: SearchFilterService) { this.filters = searchService.getConfig(); - this.clearParams = filterService.getCurrentFilters().map((filters) => {Object.keys(filters).forEach((f) => filters[f] = null); return filters;}); + this.clearParams = filterService.getCurrentFrontendFilters().map((filters) => {Object.keys(filters).forEach((f) => filters[f] = null); return filters;}); } getSearchLink() { diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index 4f50723ced..1a71a70807 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { Observable } from 'rxjs/Observable'; -import { flatMap, } from 'rxjs/operators'; +import { flatMap, tap, } from 'rxjs/operators'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { CommunityDataService } from '../core/data/community-data.service'; import { PaginatedList } from '../core/data/paginated-list'; diff --git a/src/app/+search-page/search-service/search-filter-config.model.ts b/src/app/+search-page/search-service/search-filter-config.model.ts index 2b77ef6768..000e06e1a8 100644 --- a/src/app/+search-page/search-service/search-filter-config.model.ts +++ b/src/app/+search-page/search-service/search-filter-config.model.ts @@ -17,6 +17,12 @@ @autoserialize isOpenByDefault: boolean; + + @autoserialize + maxValue: string; + + @autoserialize + minValue: string; /** * Name of this configuration that can be used in a url * @returns Parameter name diff --git a/src/app/core/data/search-response-parsing.service.ts b/src/app/core/data/search-response-parsing.service.ts index c7456aa2f9..efecccf54a 100644 --- a/src/app/core/data/search-response-parsing.service.ts +++ b/src/app/core/data/search-response-parsing.service.ts @@ -17,6 +17,7 @@ export class SearchResponseParsingService implements ResponseParsingService { parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { const payload = data.payload; + console.log(payload); const hitHighlights = payload._embedded.objects .map((object) => object.hitHighlights) .map((hhObject) => { diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 39fb454ac5..cf597195e9 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -26,7 +26,7 @@ import { Metadatum } from '../shared/metadatum.model'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import { BitstreamFormat } from '../shared/bitstream-format.model'; -import { hasValue } from '../../shared/empty.util'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; @Injectable() export class MetadataService { @@ -269,11 +269,9 @@ export class MetadataService { private setCitationPdfUrlTag(): void { if (this.currentObject.value instanceof Item) { const item = this.currentObject.value as Item; - // NOTE: Observable resolves many times with same data - // taking only two, fist one is empty array - item.getFiles().take(2).subscribe((bitstreams: Bitstream[]) => { + item.getFiles().filter((files) => isNotEmpty(files)).first().subscribe((bitstreams: Bitstream[]) => { for (const bitstream of bitstreams) { - bitstream.format.take(1) + bitstream.format.first() .map((rd: RemoteData) => rd.payload) .filter((format: BitstreamFormat) => hasValue(format)) .subscribe((format: BitstreamFormat) => { diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index ba877fcdd7..c26c0d03e8 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -45,6 +45,7 @@ import { TruncatePipe } from './utils/truncate.pipe'; import { TruncatableComponent } from './truncatable/truncatable.component'; import { TruncatableService } from './truncatable/truncatable.service'; import { TruncatablePartComponent } from './truncatable/truncatable-part/truncatable-part.component'; +import { MomentModule } from 'angular2-moment'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -55,7 +56,8 @@ const MODULES = [ ReactiveFormsModule, RouterModule, TranslateModule, - NouisliderModule + NouisliderModule, + MomentModule ]; const PIPES = [ diff --git a/yarn.lock b/yarn.lock index 44d4dd54d4..c67f761b40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -421,6 +421,12 @@ angular-idle-preload@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/angular-idle-preload/-/angular-idle-preload-2.0.4.tgz#7b177c0f52918c090e5c345480b922297cd59a0d" +angular2-moment@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/angular2-moment/-/angular2-moment-1.9.0.tgz#d198a4d9bc825f61de19106ac7ea07a78569f5a1" + dependencies: + moment "^2.19.3" + angular2-template-loader@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/angular2-template-loader/-/angular2-template-loader-0.6.2.tgz#c0d44e90fff0fac95e8b23f043acda7fd1c51d7c" @@ -5485,6 +5491,10 @@ module-deps@^4.0.8: through2 "^2.0.0" xtend "^4.0.0" +moment@^2.19.3, moment@^2.22.1: + version "2.22.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad" + morgan@1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.0.tgz#d01fa6c65859b76fcf31b3cb53a3821a311d8051"