UNPKG

@teipublisher/pb-components

Version:
277 lines (265 loc) 6.93 kB
import { LitElement, html, css } from 'lit-element'; import anime from 'animejs'; import { pbMixin } from './pb-mixin.js'; import './pb-paginate.js'; import '@polymer/iron-icons'; import '@polymer/paper-icon-button'; /** * This component talks to the blacklab API of TEI-Publisher to * load KWIC results from a remote Blacklab instance. It displays * a list of documents that match the query given by `pattern`. * * **Note**: There's no demo for this component yet as it would need a blacklab instance. * * For each document a list of hits (matches) is displayed each showing the hit * in context of the text. * * Document Id and hits are links that can be used to open the document * and navigate through the hits with the help of pb-view and pb-blacklab-highlight * components. * * @doc - an optional document Id to limit the search to * @param pattern - a CQL (Common Query Language) conformant query (required) * @perPage - a number to indicate how many result entries to display on one page. Will be passed down to pb-paginate * @csspart paginator - the pb-paginate component * @csspart label - the pb-paginate label */ export class PbBlacklabResults extends pbMixin(LitElement) { static get properties() { return { ...super.properties, /** * results from a kwic search */ data: { type: Object, }, documents: { type: Array, }, /** * document id */ doc: { type: String, }, /** * how many hits per page. will be passed down to pb-paginate */ perPage: { type: Number, attribute: 'per-page', }, /** * must be a valid CQL query as a string */ pattern: { type: String, }, /** * first document number to be displayed */ first: { type: Number, }, /** * sort order of query results */ sort: { type: String, }, /** * target for links */ target: { type: String, attribute: 'target', }, }; } constructor() { super(); this.data = { documents: [] }; this.documents = []; this.first = 1; this.doc = null; this.sort = null; } static get styles() { return css` :host { display: block; max-width: 100%; } table { width: 100%; } .docName { text-align: left; } pb-paginate { justify-content: center; padding-bottom: 3rem; } th, td { padding: 0.3rem; } th:nth-child(1), td:nth-child(1) { width: 35%; } th:nth-child(5), td:nth-child(5) { width: 35%; } .left, .hit-count { text-align: right; } .right { text-align: left; } td.hit { text-align: center; white-space: nowrap; } table { cell-spacing: 0; cell-padding: 0; } tr { cell-spacing: 0; cell-padding: 0; } .t-head th { border-bottom: thin solid #999; } td.hit { position: relative; padding: 0 1rem; } [icon='create'] { display: none; position: absolute; top: -8px; right: -8px; color: blue; } td.hit:hover [icon='create'] { display: inline-block; } `; } connectedCallback() { super.connectedCallback(); this.subscribeTo('pb-load', event => { // ### handle pb-load received from pb-paginate to set number of first displayed document this.first = Number(event.detail.params.start); this.load(); }); this.subscribeTo('force-load', event => { this.load(); this.requestUpdate(); }); this.subscribeTo('pb-results-received', event => { this.data = event.detail.data; this.documents = this.data.documents; this._animate(); }); } render() { return html` <pb-paginate part="paginator" per-page="${this.perPage}" range="5"></pb-paginate> <table> <tr class="t-head"> <th class="docName">Doc Id</th> <th class="left">before</th> <th>hit</th> <th class="right">after</th> <th class="hit-count">hits</th> </tr> ${this.documents.map( document => html` <tr> <td colspan="4" class="docName"> <a href="${this.target}/${document.id}.xml?pattern=${this.pattern}&page=${document .matches[0].page[0]}" target="_blank" >${document.id}</a > </td> <td class="hit-count"> <span class="hit-count">${document.hits}</span> </td> </tr> ${document.matches.map( match => html` <tr> <td class="left" colspan="2">${match.left}</td> <td class="hit"> <a href="${this.target}/${document.id}.xml?pattern=${this.pattern}&match=${match .match.words[0]}&page=${match.page[0]}" target="_blank" >${match.match.display}</a > <!--<paper-icon-button icon="create"></paper-icon-button>--> </td> <td class="right" colspan="2">${match.right}</td> </tr> `, )} `, )} </table> `; } async load() { if (!this.getEndpoint()) return; if (!this.pattern) return; let url = `${this.getEndpoint()}/api/blacklab/search?pattern=${this.pattern}&start=${ this.first }&per-page=${this.perPage}`; if (this.doc) { url += `&doc=${this.doc}`; } if (this.sort) { url += `&sort=${this.sort}`; } await fetch(url, { method: 'GET', mode: 'cors', credentials: 'same-origin', }) .then(response => response.json()) .then(data => { this.data = data; localStorage.setItem('pb-kwic-results', JSON.stringify(this.data)); this.emitTo( 'pb-results-received', { count: data.docs ? parseInt(data.docs, 10) : 0, start: data.start, params: data.params, data, }, [], ); }) .catch(error => { alert(`Error retrieving remote content: ${error}`); }); } _animate() { anime({ targets: this.shadowRoot.querySelector('table'), opacity: [0, 1], duration: 200, delay: 200, easing: 'linear', }); } } customElements.define('pb-blacklab-results', PbBlacklabResults);