diff --git a/src/app/custom1-module/customComponentMappings.ts b/src/app/custom1-module/customComponentMappings.ts index 22d979e..ce30e54 100644 --- a/src/app/custom1-module/customComponentMappings.ts +++ b/src/app/custom1-module/customComponentMappings.ts @@ -1,8 +1,13 @@ import { NdeSearchBarContainerCustomComponent } from "../nde-search-bar-container-custom/nde-search-bar-container-custom.component"; +import { NdeSearchHintCustomComponent } from "../nde-search-hint-custom/nde-search-hint-custom.component"; +import { NdeSearchNoResultsCustomComponent } from "../nde-search-no-results-custom/nde-search-no-results-custom.component"; import { NdeRequestFormCustomComponent } from "../nde-request-form-custom/nde-request-form-custom.component"; + // Define the map export const selectorComponentMap = new Map([ ["nde-formly-general-wrapper-bottom", NdeRequestFormCustomComponent], ["nde-search-bar-container-top", NdeSearchBarContainerCustomComponent], + ["nde-search-no-results-top", NdeSearchNoResultsCustomComponent], + ["nde-search-results-container-top", NdeSearchHintCustomComponent ] ]); diff --git a/src/app/nde-search-hint-custom/nde-search-hint-custom.component.html b/src/app/nde-search-hint-custom/nde-search-hint-custom.component.html new file mode 100644 index 0000000..9843763 --- /dev/null +++ b/src/app/nde-search-hint-custom/nde-search-hint-custom.component.html @@ -0,0 +1,15 @@ +
+ + +
+ info + + You can access ASTM standards using + + ASTM Compass + + +
+
+
+
diff --git a/src/app/nde-search-hint-custom/nde-search-hint-custom.component.scss b/src/app/nde-search-hint-custom/nde-search-hint-custom.component.scss new file mode 100644 index 0000000..9a5c293 --- /dev/null +++ b/src/app/nde-search-hint-custom/nde-search-hint-custom.component.scss @@ -0,0 +1,27 @@ +.hint-container { + display: flex; + justify-content: center; + padding: 16px; + width: 100%; +} + +.astm-hint-card { + max-width: 600px; + width: 100%; +} + +.hint-content { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + + a { + font-weight: 500; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } +} diff --git a/src/app/nde-search-hint-custom/nde-search-hint-custom.component.spec.ts b/src/app/nde-search-hint-custom/nde-search-hint-custom.component.spec.ts new file mode 100644 index 0000000..ada65d2 --- /dev/null +++ b/src/app/nde-search-hint-custom/nde-search-hint-custom.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NdeSearchHintCustomComponent } from './nde-search-hint-custom.component'; + +describe('NdeSearchHintCustomComponent', () => { + let component: NdeSearchHintCustomComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NdeSearchHintCustomComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NdeSearchHintCustomComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/nde-search-hint-custom/nde-search-hint-custom.component.ts b/src/app/nde-search-hint-custom/nde-search-hint-custom.component.ts new file mode 100644 index 0000000..33579c1 --- /dev/null +++ b/src/app/nde-search-hint-custom/nde-search-hint-custom.component.ts @@ -0,0 +1,43 @@ +import { Component, inject, OnInit } from '@angular/core'; +import { createFeatureSelector, Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { AsyncPipe, NgIf } from '@angular/common'; +import { MatCardModule } from '@angular/material/card'; +import { MatIconModule } from '@angular/material/icon'; + +type SearchState = { searchParams: { q: string } }; +export const selectSearchState = createFeatureSelector('Search'); + +@Component({ + selector: 'custom-nde-search-hint-custom', + standalone: true, + imports: [AsyncPipe, NgIf, MatCardModule, MatIconModule], + templateUrl: './nde-search-hint-custom.component.html', + styleUrl: './nde-search-hint-custom.component.scss' +}) +export class NdeSearchHintCustomComponent implements OnInit { + public store = inject(Store); + + searchState$!: Observable; + searchTerm$!: Observable; + isAstmSearch$!: Observable; + + readonly ASTM_COMPASS_URL = "https://databases.library.jhu.edu/databases/proxy/JHU05996"; + + ngOnInit(): void { + this.searchState$ = this.store.select(selectSearchState); + this.searchTerm$ = this.searchState$.pipe( + map((state) => state.searchParams.q) + ); + + this.isAstmSearch$ = this.searchTerm$.pipe( + map((term) => this.isAstmSearch(term)) + ); + } + + isAstmSearch(term: string): boolean { + if (!term) return false; + return term.toLowerCase().includes("astm"); + } +} diff --git a/src/app/nde-search-no-results-custom/nde-search-no-results-custom.component.html b/src/app/nde-search-no-results-custom/nde-search-no-results-custom.component.html new file mode 100644 index 0000000..30e89fd --- /dev/null +++ b/src/app/nde-search-no-results-custom/nde-search-no-results-custom.component.html @@ -0,0 +1,75 @@ +
+ + +
+ + +
+ warning +
+

We detected a DOI in your search, but there were no results found.

+ + open_in_new + Check for access using the Citation Linker + +
+
+
+
+
+ + +
+ + +
+ warning +
+

We detected an ISBN in your search, but there were no results found.

+ + open_in_new + Check for access using the Citation Linker + +
+
+
+
+
+ + +
+ + +
+ search +
+

Try searching other resources:

+ + + +
+ + open_in_new + Search BorrowDirect + + +
+
+ + +
+ + open_in_new + Search WorldCat + + +
+
+
+
+
+
+
+
+ +
diff --git a/src/app/nde-search-no-results-custom/nde-search-no-results-custom.component.scss b/src/app/nde-search-no-results-custom/nde-search-no-results-custom.component.scss new file mode 100644 index 0000000..0b4c4c1 --- /dev/null +++ b/src/app/nde-search-no-results-custom/nde-search-no-results-custom.component.scss @@ -0,0 +1,93 @@ +.search-no-results { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + padding: 16px; + width: 100%; +} + +.hint-container { + display: flex; + justify-content: center; + width: 100%; +} + +.hint-card { + max-width: 600px; + width: 100%; +} + +.hint-content { + display: flex; + align-items: flex-start; + gap: 12px; + + > mat-icon { + margin-top: 4px; + } +} + +.hint-text { + flex: 1; + + p { + margin: 0 0 8px 0; + } + + a { + display: inline-flex; + align-items: center; + gap: 4px; + font-weight: 500; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + + .link-icon { + font-size: 18px; + width: 18px; + height: 18px; + } + } +} + +.section-title { + font-weight: 500; + margin-bottom: 12px !important; +} + +mat-list { + padding: 0; +} + +mat-list-item { + height: auto !important; + padding: 8px 0 !important; +} + +.list-item-content { + display: flex; + flex-direction: column; + gap: 4px; + + a { + display: inline-flex; + align-items: center; + gap: 4px; + font-weight: 500; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + + .link-icon { + font-size: 18px; + width: 18px; + height: 18px; + } + } +} diff --git a/src/app/nde-search-no-results-custom/nde-search-no-results-custom.component.spec.ts b/src/app/nde-search-no-results-custom/nde-search-no-results-custom.component.spec.ts new file mode 100644 index 0000000..d5bcafc --- /dev/null +++ b/src/app/nde-search-no-results-custom/nde-search-no-results-custom.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NdeSearchNoResultsCustomComponent } from './nde-search-no-results-custom.component'; + +describe('NdeSearchNoResultsCustomComponent', () => { + let component: NdeSearchNoResultsCustomComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NdeSearchNoResultsCustomComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NdeSearchNoResultsCustomComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/nde-search-no-results-custom/nde-search-no-results-custom.component.ts b/src/app/nde-search-no-results-custom/nde-search-no-results-custom.component.ts new file mode 100644 index 0000000..a67fdbe --- /dev/null +++ b/src/app/nde-search-no-results-custom/nde-search-no-results-custom.component.ts @@ -0,0 +1,140 @@ +import { Component, inject, OnInit } from '@angular/core'; +import { createFeatureSelector, Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { AsyncPipe, NgIf, NgFor } from '@angular/common'; +import { MatCardModule } from '@angular/material/card'; +import { MatIconModule } from '@angular/material/icon'; +import { MatListModule } from '@angular/material/list'; + +type SearchState = { searchParams: { q: string } }; +export const selectSearchState = createFeatureSelector('Search'); + +@Component({ + selector: 'custom-nde-search-no-results-custom-component', + standalone: true, + imports: [AsyncPipe, NgIf, NgFor, MatCardModule, MatIconModule, MatListModule], + templateUrl: './nde-search-no-results-custom.component.html', + styleUrl: './nde-search-no-results-custom.component.scss' +}) +export class NdeSearchNoResultsCustomComponent implements OnInit { + public store = inject(Store); + + searchState$!: Observable; + searchTerm$!: Observable; + isDoi$!: Observable; + isIsbn$!: Observable; + doiLink$!: Observable; + isbnLink$!: Observable; + worldCatLink$!: Observable; + borrowDirectLink$!: Observable; + + private readonly BASE_URL = 'https://catalyst.library.jhu.edu/discovery/openurl'; + private readonly WORLDCAT_BASE_URL = 'http://worldcat.org/search'; + private readonly BORROWDIRECT_BASE_URL = 'https://borrowdirect.reshare.indexdata.com/Search/Results'; + + private readonly BASE_PARAMS = { + institution: '01JHU_INST', + vid: '01JHU_INST:nde', + ctx_ver: 'Z39.88-2004', + ctx_enc: 'info:ofi/enc:UTF-8', + url_ver: 'Z39.88-2004', + url_ctx_fmt: 'infofi/fmt:kev:mtx:ctx', + rfr_id: 'info:sid/primo.exlibrisgroup.com:primo4-article-cLinker', + isCitationLinker: 'Y', + lang: 'en' + }; + + ngOnInit(): void { + this.searchState$ = this.store.select(selectSearchState); + this.searchTerm$ = this.searchState$.pipe( + map((state) => state.searchParams.q) + ); + + this.isDoi$ = this.searchTerm$.pipe( + map((term) => this.isDoi(term)) + ); + + this.isIsbn$ = this.searchTerm$.pipe( + map((term) => this.isIsbn(term)) + ); + + this.doiLink$ = this.searchTerm$.pipe( + map((term) => this.isDoi(term) ? this.generateDoiLink(term) : null) + ); + + this.isbnLink$ = this.searchTerm$.pipe( + map((term) => this.isIsbn(term) ? this.generateIsbnLink(term) : null) + ); + + this.worldCatLink$ = this.searchTerm$.pipe( + map((term) => this.generateWorldCatLink(term)) + ); + + this.borrowDirectLink$ = this.searchTerm$.pipe( + map((term) => this.generateBorrowDirectLink(term)) + ); + } + + isDoi(term: string): boolean { + if (!term) return false; + const doiPattern = /^10\.\d{4,}(\.\d+)*\/[^\s]+$/i; + const doiUrlPattern = /^(https?:\/\/)?(dx\.)?doi\.org\/10\.\d{4,}(\.\d+)*\/[^\s]+$/i; + return doiPattern.test(term.trim()) || doiUrlPattern.test(term.trim()); + } + + isIsbn(term: string): boolean { + if (!term) return false; + const cleanedTerm = term.replace(/[-\s]/g, ''); + const isbn10Pattern = /^[0-9]{9}[0-9Xx]$/; + const isbn13Pattern = /^(978|979)[0-9]{10}$/; + return isbn10Pattern.test(cleanedTerm) || isbn13Pattern.test(cleanedTerm); + } + + extractDoi(term: string): string { + const urlPattern = /^(https?:\/\/)?(dx\.)?doi\.org\//i; + return term.trim().replace(urlPattern, ''); + } + + generateDoiLink(doi: string): string { + const cleanDoi = this.extractDoi(doi); + const params = new URLSearchParams({ + ...this.BASE_PARAMS, + 'rft.genre': 'article', + 'rft_val_fmt': 'info:ofi/fmt:kev:mtx:article', + 'rft_id': `info:doi/${cleanDoi}`, + 'rft.doi': cleanDoi + }); + + return `${this.BASE_URL}?${params.toString()}`; + } + + generateIsbnLink(isbn: string): string { + const cleanIsbn = isbn.replace(/[-\s]/g, ''); + const params = new URLSearchParams({ + ...this.BASE_PARAMS, + 'rft.genre': 'book', + 'rft_val_fmt': 'info:ofi/fmt:kev:mtx:book', + 'rft.isbn': cleanIsbn + }); + + return `${this.BASE_URL}?${params.toString()}`; + } + + generateWorldCatLink(term: string): string { + if (!term) return this.WORLDCAT_BASE_URL; + const params = new URLSearchParams({ + q: term.trim() + }); + return `${this.WORLDCAT_BASE_URL}?${params.toString()}`; + } + + generateBorrowDirectLink(term: string): string { + if (!term) return this.BORROWDIRECT_BASE_URL; + const params = new URLSearchParams({ + type: 'AllFields', + lookfor: term.trim() + }); + return `${this.BORROWDIRECT_BASE_URL}?${params.toString()}`; + } +} diff --git a/src/assets/css/custom.css b/src/assets/css/custom.css index 086ed5b..944e165 100755 --- a/src/assets/css/custom.css +++ b/src/assets/css/custom.css @@ -1,3 +1,5 @@ +/* This color is the one that can be changed in the Alma interface */ + :root { --sys-primary: #002d72; } @@ -71,10 +73,8 @@ /* Mobile header links */ @media (max-width: 480px) { .jh-header-menu ul { - display: flex; - flex-direction: column; - align-items: center; - padding: 30px; + display: none; + } } diff --git a/src/assets/homepage/homepage.css b/src/assets/homepage/homepage.css index 3834874..e038a39 100755 --- a/src/assets/homepage/homepage.css +++ b/src/assets/homepage/homepage.css @@ -71,18 +71,6 @@ text-decoration: underline; } -/* Responsive design */ -@media (max-width: 768px) { - .content-container { - flex-direction: column; - } - - .flex-60, - .flex-40 { - flex: 1; - } -} - /* Utility classes */ .display-flex { display: flex;