Merge branch 'feat/opensearch' into develop

This commit is contained in:
Porras-Bernardez 2024-09-16 14:44:05 +02:00
commit 9f076daf15
30 changed files with 1821 additions and 997 deletions

View file

@ -86,7 +86,7 @@ const prevClick = () => {
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div id="frontText">
<div id="frontText" class="w-full text-right mr-4">
<p class="text-sm text-gray-700 leading-5">
Showing
<span class="font-medium">{{ fromPage }}</span>
@ -104,7 +104,7 @@ const prevClick = () => {
<span v-if="props.data.currentPage <= 1" aria-disabled="true" aria-label="Previous">
<span
aria-disabled="true"
class="bg-gray-300 cursor-not-allowed opacity-50 relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-l-md leading-5"
class="bg-gray-300 cursor-not-allowed opacity-30 relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-l-md leading-5"
aria-hidden="true"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
@ -147,7 +147,8 @@ const prevClick = () => {
</template>
</template> -->
<!-- next button -->
<!-- next button ----------------------------------------------------------------------------- -->
<!-- v-bind:href="nextPageLink" -->
<a
v-if="props.data.currentPage < props.data.lastPage"
@ -168,7 +169,7 @@ const prevClick = () => {
<span v-else aria-disabled="true" aria-label="Next">
<span
aria-disabled="true"
class="bg-gray-300 cursor-not-allowed relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-r-md leading-5"
class="opacity-30 bg-gray-300 cursor-not-allowed relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-r-md leading-5"
aria-hidden="true"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">

View file

@ -5,7 +5,6 @@ import { Component, Vue, Prop, Emit } from "vue-facing-decorator";
name: "ActiveFacetCategory",
})
export default class ActiveFacetCategory extends Vue {
bar = "";
@Prop({
type: Array<string>,
@ -18,11 +17,65 @@ export default class ActiveFacetCategory extends Vue {
})
categoryName!: string;
replacements = new Map<string, string>([
["gis", "GIS"],
["analysisdata", "Analysis Data"],
["models", "Models"],
["monitoring", "Monitoring"],
["measurementdata", "Measurement Data"],
["mixedtype", "Mixed Type"],
["de", "Deutsch"],
["en", "English"]
]);
// @Prop([String])
// alias;
get alias(): string {
return this.categoryName == "doctype" ? "datatype" : this.categoryName;
/**
* The alias for the Active facet box will be set depending on the name of the category.
* This will allow to display the customised terms "creator" and "keyword" instead of the values currently used in the OpenSearch index: "author" and "subjects"
* TODO: This should be corrected directly in the index
*/
get categoryAlias(): string {
// return this.categoryName == "doctype" ? "datatype" : this.categoryName;
// console.log("getAlias!");
switch (this.categoryName) {
case "author":
return "creator";
case "subjects":
return "keyword";
case "doctype":
return "data type";
default:
return this.categoryName;
}
}
/**
* The alias for the items inside the "doctype / Datatype" category will be set manually in order to show user-friendly terms instead of the predefined doctypes in the DB
* If the category alias is Data Type, the name of the items is set
* NOTE: This could be corrected directly in the index
*/
getFilterItemsAlias(categoryAlias: string): string {
console.log(categoryAlias);
if (categoryAlias === ("data type") || categoryAlias === ("language")) {
/**
* Iterate over the filterItems array using the map method to create a new array (updatedItems).
* For each item in the array, check if the item exists as a key in the replacements map.
* - If the item exists in the replacements map, replace it with the corresponding value from the map.
* - If the item does not exist in the replacements map, keep the original item unchanged.
* The map method returns a new array where each element is either the original item or its replacement.
* */
const updatedItems = this.filterItems.map((item) =>
this.replacements.get(item) || item
);
return updatedItems.join(" | ");
}
// console.log("other categories");
return this.filterItems.join(" | ");
}
// get filterItems(): Array<string> {

View file

@ -1,9 +1,10 @@
<template>
<div>
<input v-bind:id="alias" v-bind:name="alias" type="checkbox" checked="checked" class="css-checkbox" @click.prevent="deactivateFacetCategory()" />
<label v-bind:for="alias" class="css-label">
<span>{{ alias + ": " }}</span>
<a v-if="filterItems && filterItems.length > 0" class="gbaterm">{{ filterItems.join(", ") }}</a>
<input v-bind:id="categoryAlias" v-bind:name="categoryAlias" type="checkbox" checked="checked" class="css-checkbox" @click.prevent="deactivateFacetCategory()" />
<label v-bind:for="categoryAlias" class="css-label">
<span>{{ categoryAlias + ": " }}</span>
<!-- <a v-if="filterItems && filterItems.length > 0" class="gsaterm">{{ filterItems.join(" | ") }}</a> -->
<a v-if="filterItems && filterItems.length > 0" class="gsaterm">{{ getFilterItemsAlias(categoryAlias) }}</a>
</label>
</div>
</template>
@ -18,7 +19,7 @@ th,
td {
border-bottom: 0px solid #e1e1e1;
}
.gbaterm {
.gsaterm {
color: #0099cc;
border: 1px solid rgb(200, 210, 255);
padding: 4px;
@ -42,6 +43,7 @@ input[type="checkbox"].css-checkbox {
}
input[type="checkbox"].css-checkbox + label.css-label {
text-transform: capitalize;
padding-left: 25px;
/* height: 24px;
display: inline-block; */

View file

@ -19,8 +19,44 @@ export default class FacetCategory extends Vue {
})
filterName!: string;
get alias(): string {
return this.filterName == "datatype" ? "doctype" : this.filterName;
replacements = new Map<string, string>([
["gis", "GIS"],
["analysisdata", "Analysis Data"],
["models", "Models"],
["monitoring", "Monitoring"],
["measurementdata", "Measurement Data"],
["mixedtype", "Mixed Type"],
["de", "Deutsch"],
["en", "English"]
]);
/**
* The alias for the Active facet box will be set depending on the name of the category.
* This will allow to display the customised terms "creator" and "keyword" instead of the values currently used in the OpenSearch index: "author" and "subjects"
* NOTE: This should be corrected directly in the index
*/
get categoryAlias(): string {
// console.log("filterName:", this.filterName);
// return this.filterName == "datatype" ? "doctype" : this.filterName;
switch (this.filterName) {
case "author":
return "creator";
case "subjects":
return "keyword";
case "doctype":
return "Data Type";
default:
return this.filterName;
}
}
/**
* The alias for the items inside the "doctype / Datatype" category will be set manually in order to show user-friendly terms instead of the predefined doctypes in the DB
* NOTE: This could be corrected directly in the index
*/
itemAlias(val: string): string {
return this.replacements.get(val) || val;
}
// get filterItems(): Array<FilterItem> {
@ -53,7 +89,9 @@ export default class FacetCategory extends Vue {
@Emit("filter")
activateItem(filterItem: FacetItem): FacetItem {
filterItem.category = this.alias;
console.log("Emit: ActivateItem");
// filterItem.category = this.alias;
filterItem.category = this.filterName;
filterItem.active = true;
// this.$emit("filter", filterItem);
return filterItem;

View file

@ -2,14 +2,16 @@
<div class="card panel-default">
<!-- <h3 class="panel-title filterViewModelName">{{ filterName }}</h3> -->
<div class="panel-heading">
<h3 class="panel-title titlecase filterViewModelName">{{ filterName }}</h3>
<h3 class="panel-title titlecase filterViewModelName">{{ categoryAlias }}</h3>
<!-- <h3 class="panel-title titlecase filterViewModelName">{{ filterName }}</h3> -->
</div>
<div class="panel-body">
<!-- e.g.language -->
<ul class="filter-items list-unstyled" v-bind:class="{ limited: facetItems.length > 1 && collapsed }">
<li v-for="(item, index) in facetItems" v-bind:key="index" class="list-group-item titlecase">
<!-- <span :class="item.Active ? 'disabled' : ''" @click.prevent="activateItem(item)">{{ item.val }} ({{ item.count }}) </span> -->
<span v-bind:class="item.active ? 'disabled' : ''" @click.prevent="activateItem(item)">{{ item.val }} ({{ item.count }}) </span>
<!-- <span v-bind:class="item.active ? 'disabled' : ''" @click.prevent="activateItem(item)">{{ item.val }} ({{ item.count }}) </span> -->
<span v-bind:class="item.active ? 'disabled' : ''" @click.prevent="activateItem(item)">{{ itemAlias(item.val) }} ({{ item.count }}) </span>
</li>
</ul>
<!-- <ul class="overflowing" v-if="overflowing == true">
@ -74,7 +76,10 @@ export default FacetCategory;
flex-grow: 1;
flex-shrink: 0;
/* padding: 0.75rem; */
padding: 0.75em 2em;
padding-top: 0em;
padding-right: 2em;
padding-bottom: 0.75em;
padding-left: 2em;
justify-content: left;
}
@ -87,6 +92,7 @@ export default FacetCategory;
}
.panel-body {
padding: 0 2em;
padding-bottom: 0.75em; /* Increase padding at the bottom */
}
.disabled {

View file

@ -2,12 +2,17 @@
// import debounce from 'lodash/debounce';
// import { DatasetService } from "../../services/dataset.service";
import DatasetService from "../../services/dataset.service";
import { SolrSettings } from "@/models/solr";
// import { ref } from "vue";
// import { SolrSettings } from "@/models/solr"; // PENDING USE
import { OpenSettings } from "@/models/solr";
import { Component, Vue, Prop, Emit } from "vue-facing-decorator";
// import { Prop, Emit } from "vue-property-decorator";
import { Dataset, Suggestion, SearchType } from "@/models/dataset";
import { SOLR_HOST, SOLR_CORE } from "@/constants";
// import { SOLR_HOST, SOLR_CORE } from "@/constants";
import { OPEN_HOST, OPEN_CORE } from "@/constants"; // PENDING USE
import { HitHighlight } from "@/models/headers";
import DOMPurify from 'dompurify'; // To sanitize the HTML content to prevent XSS attacks!
@Component({
name: "VsInput",
@ -16,30 +21,40 @@ export default class VsInput extends Vue {
// @Prop()
// private title!: string;
// Define the placeholder text for the input field
@Prop({ default: "Search" })
readonly placeholder!: string;
private display = "";
private display = ""; // Input display value
@Prop()
private propDisplay = "";
private value!: Suggestion | string;
private error = "";
private results: Array<Dataset> = [];
private loading = false;
private selectedIndex = -1;
// private selectedDisplay = "";
private solr: SolrSettings = {
core: SOLR_CORE, //"rdr_data", // SOLR.core;
host: SOLR_HOST, //"tethys.at",
private results: Array<Dataset> = []; // Array to store search results
private highlights: Array<HitHighlight> = [];
private loading = false; // Loading state indicator
private selectedIndex = -1; // Index of the currently selected suggestion
// private solr: SolrSettings = {
// core: SOLR_CORE, //"rdr_data", // SOLR.core;
// host: SOLR_HOST, //"tethys.at",
// };
private openSearch: OpenSettings = {
core: OPEN_CORE, //"rdr_data", // SOLR.core;
host: OPEN_HOST, //"tethys.at",
// core: "test_data", // SOLR.core;
// host: "repository.geologie.ac.at",
};
// private rdrAPI!: DatasetService;
itemRefs!: Array<Element>;
emits = ["filter"];
// private rdrAPI!: DatasetService;
itemRefs!: Array<Element>; // Array to store references to suggestion items
emits = ["filter"]; // Emits filter event
// Set reference for each item
setItemRef(el: Element): void {
this.itemRefs.push(el);
}
@ -68,53 +83,143 @@ export default class VsInput extends Vue {
return this.error !== null;
}
// Computed property to generate suggestions based on search results
get suggestions(): Suggestion[] {
// const suggestion = {
// titles: new Array<string>(),
// authors: new Array<string>(),
// subjects: new Array<string>(),
// };
const suggestions = new Array<Suggestion>();
this.results.forEach((dataset) => {
// const del = dataset.title_output?.toLowerCase();
if (dataset.title_output.toLowerCase().includes(this.display.toLowerCase())) {
const title = dataset.title_output;
// if (!suggestion["titles"].find((value) => value === title)) {
// suggestion.titles.push(title);
// }
const hasTitleSuggestion = suggestions.some((suggestion) => suggestion.value === title && suggestion.type == SearchType.Title);
if (!hasTitleSuggestion) {
const suggestion = new Suggestion(title, SearchType.Title);
suggestions.push(suggestion);
}
}
if (this.find(dataset.author, this.display.toLowerCase()) !== "") {
const author = this.find(dataset.author, this.display.toLowerCase());
// console.log("getSuggestions > Display:", this.display);
// console.log("results:", this.results );
// console.log("highlights:", this.highlights);
//The method checks if there are any highlighted titles in the highlight object. If found, it joins the highlighted fragments into a single string
// Generate suggestions based on search results
this.results.forEach((dataset, index) => {
const hasAuthorSuggestion = suggestions.some((suggestion) => suggestion.value === author && suggestion.type == SearchType.Author);
const highlight = this.highlights[index];
// console.log("get suggestions:id", dataset.id);
// console.log("get suggestions:title_output", dataset.title_output);
// console.log("get suggestions:author", dataset.author);
// console.log("get suggestions:subjects", dataset.subjects);
// Checks if a suggestion with the same title and type already exists in the suggestions array. If not, it creates a new Suggestion object and adds it to the suggestions array.
if (highlight.title && highlight.title.length > 0) {
/** This line checks if the highlight object has a title property and if that property is an array with at least one element.
* The highlight object contains highlighted fragments of the search term in various fields (e.g., title, author, subjects) as returned by the OpenSearch API.
* This check ensures that we only process results that have highlighted titles. */
const highlightedTitle = highlight.title.join(" ");
/**
* The highlight.title property is an array of strings, where each string is a highlighted fragment of the title. join(" ") combines these fragments into a single string with spaces between them.
* This step constructs a full highlighted title from the individual fragments.
* OpenSearch can return multiple fragments of a field (like the title) in its response, especially when the field contains multiple terms that match the search query.
* This can happen because OpenSearch's highlighting feature is designed to provide context around each match within the field, which can result in multiple highlighted fragments.
*/
const hasTitleSuggestion = suggestions.some((suggestion) => suggestion.highlight.toLowerCase() === highlightedTitle.toLowerCase() && suggestion.type == SearchType.Title);
if (!hasTitleSuggestion) {
const suggestion = new Suggestion(dataset.title_output, highlightedTitle, SearchType.Title);
suggestions.push(suggestion);
}
}
if (highlight.author && highlight.author.length > 0) {
const highlightedAuthor = highlight.author.join(" ");
const datasetAuthor = this.find(dataset.author, this.display.toLowerCase());
const hasAuthorSuggestion = suggestions.some((suggestion) => suggestion.highlight.toLowerCase() === highlightedAuthor.toLowerCase() && suggestion.type == SearchType.Author);
if (!hasAuthorSuggestion) {
const suggestion = new Suggestion(author, SearchType.Author);
const suggestion = new Suggestion(datasetAuthor, highlightedAuthor, SearchType.Author);
suggestions.push(suggestion);
}
}
if (this.find(dataset.subject, this.display.toLowerCase()) != "") {
const subject = this.find(dataset.subject, this.display.toLowerCase());
const hasSubjectSuggestion = suggestions.some((suggestion) => suggestion.value === subject && suggestion.type == SearchType.Subject);
if (highlight.subjects && highlight.subjects.length > 0) {
const highlightedSubject = highlight.subjects.join(" ");
const datasetSubject = this.find(dataset.subjects, this.display.toLowerCase());
const hasSubjectSuggestion = suggestions.some((suggestion) => suggestion.highlight.toLowerCase() === highlightedSubject.toLowerCase() && suggestion.type == SearchType.Subject);
if (!hasSubjectSuggestion) {
const suggestion = new Suggestion(subject, SearchType.Subject);
const suggestion = new Suggestion(datasetSubject, highlightedSubject, SearchType.Subject);
suggestions.push(suggestion);
}
}
// To allow search by doctype
if (highlight.doctype && highlight.doctype.length > 0) {
const highlightedDoctype = highlight.doctype.join(" ");
const hasDoctypeSuggestion = suggestions.some((suggestion) => suggestion.highlight.toLowerCase() === highlightedDoctype.toLowerCase() && suggestion.type == SearchType.Doctype);
if (!hasDoctypeSuggestion) {
const suggestion = new Suggestion(dataset.doctype, highlightedDoctype, SearchType.Doctype);
suggestions.push(suggestion);
}
}
// ORIGINAL SOLR ===================================================================================================
// if (dataset.title_output.toLowerCase().includes(this.display.toLowerCase())) {
// const title = dataset.title_output;
// // Check if there is already a suggestion with this title and type
// const hasTitleSuggestion = suggestions.some((suggestion) => suggestion.value === title && suggestion.type == SearchType.Title);
// if (!hasTitleSuggestion) {
// // If there is no such suggestion, create a new one and add it to the suggestions array
// const suggestion = new Suggestion(title, SearchType.Title);
// suggestions.push(suggestion);
// }
// }
// if (this.find(dataset.author, this.display.toLowerCase()) !== "") {
// const author = this.find(dataset.author, this.display.toLowerCase());
// // Check if there is already a suggestion with this author and type
// const hasAuthorSuggestion = suggestions.some((suggestion) => suggestion.value === author && suggestion.type == SearchType.Author);
// if (!hasAuthorSuggestion) {
// const suggestion = new Suggestion(author, SearchType.Author);
// suggestions.push(suggestion);
// }
// }
// if (this.find(dataset.subjects, this.display.toLowerCase()) != "") {
// const subject = this.find(dataset.subjects, this.display.toLowerCase());
// const hasSubjectSuggestion = suggestions.some((suggestion) => suggestion.value === subject && suggestion.type == SearchType.Subject);
// if (!hasSubjectSuggestion) {
// const suggestion = new Suggestion(subject, SearchType.Subject);
// suggestions.push(suggestion);
// }
// }
});
return suggestions;
}
/**
* This method combines the suggestion value and type into a single HTML string. It also sanitizes the HTML content using DOMPurify to prevent XSS attacks.
* The vue file uses the v-html directive to bind the combined HTML string to the label element. This ensures that the HTML content (e.g., <em>Wien</em>) is rendered correctly in the browser.
*/
formatSuggestion(result: Suggestion): string {
const sanitizedValue = DOMPurify.sanitize(result.highlight);
// Replacing the predefined format for highlights given by OpenSearch from <em> emphasys to <b> bold
const replacedValue = sanitizedValue.replace(/<em>/g, '<b>').replace(/<\/em>/g, '</b>');
// return `${replacedValue} <em>| ${result.type}</em>`;
return `${replacedValue} <em>| ${this.getTypeAlias(result.type)}</em>`;
}
/**
* The alias for the result type will be set depending on the name of the type.
* This will allow to display the customised terms instead of the values currently used in the OpenSearch index.
* TODO: This should be corrected directly in the index
*/
getTypeAlias(type: string): string {
switch (type) {
case "author":
return "creator";
case "subjects":
return "keyword";
case "doctype":
return "data type";
default:
return type;
}
}
/**
* Clear all values, results and errors
**/
clear(): void {
console.log("clear");
this.display = "";
// this.value = null;
this.results = [];
@ -122,15 +227,20 @@ export default class VsInput extends Vue {
// this.$emit("clear");
}
/* When the search button is clicked or the search input is changed, it updates the value property of the component with the current value of display,
and emits a search-change event with the current value of display as the argument. */
@Emit("search-change")
search(): string {
console.log("search");
this.results = [];
// this.$emit("search", this.display)
this.value = this.display; //(obj["title_output"]) ? obj["title_output"] : obj.id
return this.display;
}
// Handler for search input change
searchChanged(): void {
// console.log("Search changed!");
this.selectedIndex = -1;
// Let's warn the parent that a change was made
// this.$emit("input", this.display);
@ -142,7 +252,9 @@ export default class VsInput extends Vue {
}
}
// Perform the search request
private resourceSearch() {
// console.log("resourceSearch");
if (!this.display) {
this.results = [];
return;
@ -152,23 +264,29 @@ export default class VsInput extends Vue {
this.request();
}
// Make the API request to search for datasets
private request(): void {
DatasetService.searchTerm(this.display, this.solr.core, this.solr.host).subscribe({
next: (res: Dataset[]) => this.dataHandler(res),
console.log("request()");
// DatasetService.searchTerm(this.display, this.solr.core, this.solr.host).subscribe({
DatasetService.searchTerm(this.display, this.openSearch.core, this.openSearch.host).subscribe({
// next: (res: Dataset[]) => this.dataHandler(res),
next: (res: { datasets: Dataset[], highlights: HitHighlight[] }) => this.dataHandler(res.datasets, res.highlights),
error: (error: string) => this.errorHandler(error),
complete: () => (this.loading = false),
});
}
private dataHandler(datasets: Dataset[]): void {
// Handle the search results
private dataHandler(datasets: Dataset[], highlights: HitHighlight[]): void {
this.results = datasets;
// this.$emit("search", this.display);
// this.loading = false;
this.highlights = highlights; // Store highlights
// console.log(datasets);
}
// Handle errors from the search request
private errorHandler(err: string): void {
this.error = err;
// this.loading = false;
}
/**
@ -180,7 +298,9 @@ export default class VsInput extends Vue {
return key === this.selectedIndex;
}
// Handle arrow down key press to navigate suggestions
onArrowDown(ev: Event): void {
console.log("onArrowDown");
ev.preventDefault();
if (this.selectedIndex === -1) {
this.selectedIndex = 0;
@ -190,6 +310,7 @@ export default class VsInput extends Vue {
this.fixScrolling();
}
// Scroll the selected suggestion into view
private fixScrolling() {
const currentElement = this.itemRefs[this.selectedIndex];
currentElement.scrollIntoView({
@ -199,7 +320,9 @@ export default class VsInput extends Vue {
});
}
// Handle arrow up key press to navigate suggestions
onArrowUp(ev: Event): void {
console.log("onArrowUp");
ev.preventDefault();
if (this.selectedIndex === -1) {
this.selectedIndex = this.suggestions.length - 1;
@ -209,7 +332,10 @@ export default class VsInput extends Vue {
this.fixScrolling();
}
// Handle enter key press to select a suggestion
onEnter(): void {
console.log("onEnter");
if (this.selectedIndex === -1) {
// this.$emit("nothingSelected", this.display);
this.display && this.search();
@ -222,18 +348,18 @@ export default class VsInput extends Vue {
@Emit("search-change")
private select(obj: Suggestion): Suggestion {
// if (!obj) {
// return;
// }
this.value = obj; //(obj["title_output"]) ? obj["title_output"] : obj.id
this.display = obj.value; // this.formatDisplay(obj)
// this.selectedDisplay = this.display;
console.log("select:");
this.value = obj;
console.log(obj);
this.display = obj.value;
this.close();
// this.$emit("update", this.value);
return this.value;
}
// Find a search term in an array
private find(myarray: Array<string>, searchterm: string): string {
for (let i = 0, len = myarray.length; i < len; i += 1) {
if (typeof myarray[i] === "string" && myarray[i].toLowerCase().indexOf(searchterm) !== -1) {
@ -248,15 +374,11 @@ export default class VsInput extends Vue {
* Close the results list. If nothing was selected clear the search
*/
close(): void {
console.log("close");
if (!this.value) {
this.clear();
}
// if (this.selectedDisplay !== this.display && this.value) {
// this.display = this.selectedDisplay;
// }
this.results = [];
this.error = "";
//this.removeEventListener()
// this.$emit("close");
}
}

View file

@ -1,10 +1,15 @@
<template>
<!-- Parent container with multiple rows -->
<div class="is-multiline">
<!-- <div class="content column is-half is-offset-one-quarter" style="margin-top: 30px; padding-bottom: 0; margin-bottom: 0px"> -->
<!-- Search input wrapper -->
<div class="column is-two-thirds-tablet is-half-desktop is-one-third-widescreen mx-auto">
<!-- Search box -->
<div class="search-box mx-auto">
<!-- Search field -->
<div class="field has-addons main-search-from-bg">
<div class="control is-expanded">
<!-- Input field for search query -->
<input
id="search_query"
v-model="display"
@ -20,24 +25,26 @@
@keydown.tab="close"
@focus="focus"
/>
<!-- <p>Display is: {{ display }}</p> -->
<!-- v-on:input="searchChanged" -->
</div>
<!-- Search button -->
<div class="control">
<button class="button input is-medium search-button-icon" @click="search()">
<!-- <img src="../../assets/fa/search.svg" style="height: 22px; width: 22px" /> -->
<!-- Search icon -->
<i class="fas fa-search text-white"></i>
</button>
</div>
</div>
</div>
</div>
<!-- <div class="column is-half is-offset-one-quarter"> -->
<!-- Suggestions list -->
<div class="column is-two-thirds-tablet is-half-desktop is-one-third-widescreen mx-auto">
<ul v-show="showResults" class="autocomplete-results pure-u-23-24">
<!-- Loading indicator -->
<li v-if="isLoading" class="loading">Loading results...</li>
<!-- Iterating over suggestions -->
<li
v-for="(result, i) in suggestions"
v-else
@ -47,8 +54,10 @@
v-bind:class="{ 'is-active': isSelected(i) }"
@click.prevent="select(result)"
>
<!-- Displaying suggestion result -->
<div class="small-label">
<label>{{ result.value }} ({{ result.type }})</label>
<!-- <label>{{ result.value }} ({{ result.type }})</label> -->
<label v-html="formatSuggestion(result)"></label>
</div>
</li>
</ul>
@ -127,7 +136,7 @@ input {
list-style-type: none;
z-index: 1000;
position: absolute;
max-height: 200px;
max-height: 192px;
overflow-y: auto;
overflow: hidden;
background: white;
@ -140,15 +149,17 @@ input {
.autocomplete-result-item {
list-style: none;
text-align: left;
/* padding: 7px 10px; */
padding: 0px 0px 0px 5px; // top,right,bottom,left
cursor: pointer;
}
.autocomplete-result-item.is-active {
background: rgba(0, 180, 255, 0.15);
// background: #3cc;
}
.autocomplete-result-item:hover {
background: rgba(0, 180, 255, 0.075);
// background: rgba(0, 180, 255, 0.075);
background: #baedf1;
}
</style>

View file

@ -14,6 +14,16 @@ export default class VsResult extends Vue {
return this.datasets;
}
public simplifyAuthor(author:string): string {
if (author.endsWith(" ")) {
return author.substring(0, author.indexOf(","));
} else {
let firstNameInitial:string = author.charAt(author.indexOf(",") + 2);
return author.substring(0, author.indexOf(",") + 2) + firstNameInitial;
}
}
public getDomainWithoutSubdomain(): string {
const urlParts = new URL(window.location.href).hostname.split(".");
@ -23,13 +33,15 @@ export default class VsResult extends Vue {
.join(".");
}
private convert(unixtimestamp: number): string {
// private convert(unixtimestamp: number): string { // SOLR
private convert(unixtimestamp: string): string { // OpenSearch
// Unixtimestamp
// var unixtimestamp = document.getElementById('timestamp').value;
// Months array
const months_arr = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
// Convert timestamp to milliseconds
const date = new Date(unixtimestamp * 1000);
// const date = new Date(unixtimestamp * 1000); // SOLR
const date = new Date(Number(unixtimestamp) * 1000); // OpenSearch
// Year
const year = date.getFullYear();
// Month

View file

@ -3,49 +3,46 @@
<div class="card result-list-container">
<div class="card-content record-elem">
<p v-if="document.identifier && document.identifier.length > 0">
<!-- <span>Author: {{ document.identifier.join(', ') }}</span> -->
<!-- <span v-for="(author,index) in document.author" :key="index">{{ author }}; </span> -->
<!-- <span>'https://doi.org/' + {{ document.identifier[0] }}</span> -->
<a target="_blank" v-bind:href="'https://doi.org/' + document.identifier[0]">
{{ "https://doi.org/" + document.identifier[0] + " &#10148;" }} </a
>&nbsp;
<span v-if="document.author && document.author.length > 0" class="disabled">{{ document.author[0] }}</span>
<!-- Display authors conditionally -->
<span v-if="document.author && document.author.length > 0">
<!-- For one author, just display the author's name -->
<span v-if="document.author.length === 1" class="disabled">
{{ simplifyAuthor(document.author[0]) }}
</span>
<!-- For 2-3 authors, display them all -->
<span v-if="document.author.length > 1 && document.author.length < 3">
<span v-for="(author, index) in document.author" :key="index" class="disabled">
{{ simplifyAuthor(author) }}<span v-if="index < document.author.length - 1" class="disabled">; </span>
</span>
</span>
<!-- For 4 or more authors, display the first three and add "et al." -->
<span v-if="document.author.length >= 3" class="disabled">
<span v-for="(author, index) in document.author.slice(0, 2)" :key="index" class="disabled">
{{ simplifyAuthor(author) }}<span v-if="index < 1" class="disabled">; </span>
</span>
et al.
</span>
</span>
</p>
<!-- <span class="label label-info" data-container="div" data-title="Publication date">
{{ convert(document.server_date_published) }}
</span>
<span class="label label-default ng-binding">{{ document.doctype }}</span>
<span v-if="openAccessLicences.includes(document.licence)" class="label label-success titlecase">Open Access</span> -->
<h4>
<!-- <a
v-if="document.identifier && document.identifier.length > 0"
target="_self"
v-bind:href="'https://doi.' + getDomainWithoutSubdomain() + '/' + document.identifier[0]"
class="ng-binding"
>
<router-link class="ng-binding" v-bind:to="{ name: 'dataset', params: { datasetId: document.id } }">
{{ document.title_output }}
</a> -->
<!-- <a target="_self" v-bind:href="'dataset/' + document.id" class="ng-binding">
{{ document.title_output }}
</a> -->
<router-link class="ng-binding" v-bind:to="{ name: 'dataset', params: { datasetId: document.id } }">{{
document.title_output
}}</router-link>
</router-link>
</h4>
<!-- <p v-if="document.author && document.author.length > 0">
<span>Author: {{ document.author.join(', ') }}</span>
<span v-for="(author, index) in document.author" :key="index">{{ author }}; </span>
</p> -->
<p class="clamped clamped-2">
<span class="disabled" data-container="div" data-title="Publication date">
{{ convert(document.server_date_published) + ":&nbsp;" }}
</span>
<span class="text">
{{ document.abstract_output }}
<!-- {{ document.abstract_output }} -->
{{ document.abstract[0] }}
<span class="ellipsis">...</span>
<span class="fill"></span>
</span>
@ -53,11 +50,9 @@
<p>
<span class="label"><i class="fas fa-file"></i> {{ document.doctype }}</span>
<!-- <span>Licence: {{ document.licence }}</span> -->
<span v-if="openAccessLicences.includes(document.licence)" class="label titlecase"><i class="fas fa-lock-open"></i> Open Access</span>
</p>
<!-- <span class="label label-keyword titlecase" v-for="(item, index) in document.subject" :key="index"> #{{ item }} </span> -->
</div>
</div>
</div>