LPXXX Ang catalog search highlighting
authorBill Erickson <berickxx@gmail.com>
Thu, 16 Jan 2020 18:23:15 +0000 (13:23 -0500)
committerBill Erickson <berickxx@gmail.com>
Thu, 16 Jan 2020 18:23:48 +0000 (13:23 -0500)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/share/catalog/bib-record.service.ts
Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts
Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.css
Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html
Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts
Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts

index b2058e7..465a0af 100644 (file)
@@ -31,6 +31,7 @@ export class BibRecordSummary {
     holdCount: number;
     bibCallNumber: string;
     net: NetService;
+    displayHighlights: {[name: string]: string} = {};
 
     constructor(record: IdlObject, orgId: number, orgDepth: number) {
         this.id = Number(record.id());
index 2aaaf1f..cbc89c9 100644 (file)
@@ -215,6 +215,40 @@ export class CatalogService {
         })).toPromise();
     }
 
+    fetchFieldHighlights(ctx: CatalogSearchContext): Promise<any> {
+
+        let hlMap;
+
+        // Extract the highlight map.  Not all searches have them.
+        if ((hlMap = ctx.result) &&
+            (hlMap = hlMap.global_summary) &&
+            (hlMap = hlMap.query_struct) &&
+            (hlMap = hlMap.additional_data) &&
+            (hlMap = hlMap.highlight_map) &&
+            (Object.keys(hlMap).length > 0)) {
+        } else { return Promise.resolve(); }
+
+        return this.net.requestWithParamList(
+            'open-ils.search',
+            'open-ils.search.fetch.metabib.display_field.highlight',
+            [hlMap].concat(ctx.currentResultIds())
+        ).pipe(map(fields => {
+            if (fields.length === 0) { return; }
+
+            // Each 'fields' collection is an array of highlighted
+            // fields for a given bib record.
+            const summary =
+                ctx.result.records.filter(r => r.id === fields[0].source)[0];
+
+            fields.forEach(field => {
+                const dfMap = this.cmfMap[field.field].display_field_map();
+                if (!dfMap) { return; }
+                summary.displayHighlights[dfMap.name()] = field.highlight;
+            });
+        })).toPromise();
+    }
+
+
     fetchFacets(ctx: CatalogSearchContext): Promise<void> {
 
         if (!ctx.result) {
@@ -312,14 +346,15 @@ export class CatalogService {
     }
 
     fetchCmfs(): Promise<void> {
-        // At the moment, we only need facet CMFs.
         if (Object.keys(this.cmfMap).length) {
             return Promise.resolve();
         }
 
         return new Promise((resolve, reject) => {
             this.pcrud.search('cmf',
-                {facet_field : 't'}, {}, {atomic: true, anonymous: true}
+                {'-or': [{facet_field : 't'}, {display_field: 't'}]},
+                {flesh: 1, flesh_fields: {cmf: ['display_field_map']}},
+                {atomic: true, anonymous: true}
             ).subscribe(
                 cmfs => {
                     cmfs.forEach(c => this.cmfMap[c.id()] = c);
index 3d753f4..c688a6d 100644 (file)
@@ -1,7 +1,7 @@
 
 /**
- * Force the jacket image column to consume a consistent amount of 
- * horizontal space, while allowing some room for the browser to 
+ * Force the jacket image column to consume a consistent amount of
+ * horizontal space, while allowing some room for the browser to
  * render the correct aspect ratio.
  */
 .record-jacket-div {
     max-height: 158px;
     max-width: 100px;
 }
+
+.oils_SH {
+  font-weight: bolder;
+  background-color: #99ff99;
+}
+
+.oils_SH.identifier {
+  font-weight: bolder;
+  background-color: #42b0f4;
+}
+
index a27c1bd..ee93fb0 100644 (file)
@@ -4,6 +4,19 @@
   egDateFilter's
 -->
 
+<ng-template #displayField let-field="field" let-summary="summary">
+  <ng-template #noHl>{{summary.display[field]}}</ng-template>
+  <ng-container *ngIf="summary.displayHighlights[field]; else noHl">
+    <span [innerHTML]="summary.displayHighlights[field]">
+    </span>
+  </ng-container>
+  <!--
+  <ng-container *ngIf="!summary.displayHighlights[field]">
+    {{summary.display[field]}}
+  </ng-container>
+  -->
+</ng-template>
+
 <div class="col-lg-12 card tight-card mb-2 bg-light">
   <div class="card-body">
     <div class="row">
               <ng-container *ngIf="!hasMrConstituentRecords(summary)">
                 <a routerLink="/staff/catalog/record/{{summary.id}}"
                   [queryParams]="currentParams()">
-                  {{summary.display.title || '&nbsp;'}}
+                  <ng-container 
+                    *ngTemplateOutlet="displayField; 
+                      context: {summary: summary, field: 'title'}">
+                  </ng-container>
                 </a>
               </ng-container>
             </div>
             <div class="col-lg-12">
               <!-- nbsp allows the column to take shape when no value exists -->
               <a routerLink="/staff/catalog/search"
-                  [queryParams]="getAuthorSearchParams(summary)">
-                {{summary.display.author || '&nbsp;'}}
+                [queryParams]="getAuthorSearchParams(summary)">
+                <ng-container 
+                  *ngTemplateOutlet="displayField; 
+                    context: {summary: summary, field: 'author'}">
+                </ng-container>
               </a>
             </div>
           </div>
                 </div>
               </ng-container>
               <ng-container *ngIf="summary.display.edition">
-                <div class="pb-1" i18n>Edition: {{summary.display.edition}}</div>
+                <div class="pb-1" i18n>Edition: 
+                  <ng-container 
+                    *ngTemplateOutlet="displayField; 
+                      context: {summary: summary, field: 'edition'}">
+                  </ng-container>
+                </div>
               </ng-container>
               <ng-container *ngIf="summary.display.publisher || summary.display.pubdate">
                 <!-- note publisher typically includes pubdate -->
index b46e4ca..69f4dd0 100644 (file)
@@ -1,4 +1,4 @@
-import {Component, OnInit, OnDestroy, Input} from '@angular/core';
+import {Component, OnInit, OnDestroy, Input, ViewEncapsulation} from '@angular/core';
 import {Subscription} from 'rxjs';
 import {Router, ParamMap} from '@angular/router';
 import {OrgService} from '@eg/core/org.service';
@@ -14,7 +14,8 @@ import {BasketService} from '@eg/share/catalog/basket.service';
 @Component({
   selector: 'eg-catalog-result-record',
   templateUrl: 'record.component.html',
-  styleUrls: ['record.component.css']
+  styleUrls: ['record.component.css'],
+  encapsulation: ViewEncapsulation.None // required for search highlighting
 })
 export class ResultRecordComponent implements OnInit, OnDestroy {
 
index 869eff2..cc6581e 100644 (file)
@@ -99,7 +99,9 @@ export class ResultsComponent implements OnInit, OnDestroy {
             .then(ok => {
                 this.cat.fetchFacets(this.searchContext);
                 this.cat.fetchBibSummaries(this.searchContext)
-                .then(ok2 => this.fleshSearchResults());
+                .then(_ =>
+                    this.cat.fetchFieldHighlights(this.searchContext))
+                .then(_ => this.fleshSearchResults());
             });
         }
     }