forked from geolba/tethys.frontend
Merge branch 'feat/opensearch' into develop
This commit is contained in:
commit
9f076daf15
30 changed files with 1821 additions and 997 deletions
|
@ -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">
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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; */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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] + " ➤" }} </a
|
||||
>
|
||||
<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) + ": " }}
|
||||
</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>
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue