OpenSearch: almost implemented, fixing case sensitive issues

This commit is contained in:
Porras-Bernardez 2024-06-11 09:33:03 +02:00
parent 4f53411d07
commit 9b8b2bd5ac
8 changed files with 184 additions and 60 deletions

View file

@ -10,6 +10,9 @@ import { Dataset, Suggestion, SearchType } from "@/models/dataset";
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",
@ -30,6 +33,8 @@ export default class VsInput extends Vue {
private value!: Suggestion | string;
private error = "";
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 selectedDisplay = "";
@ -82,64 +87,123 @@ export default class VsInput extends Vue {
// 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>();
console.log("Display:", this.display);
console.log("results:", this.results );
console.log("Suggestions > 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) => {
this.results.forEach((dataset, index) => {
let foundAny = false;
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);
// console.log("get suggestions:title_output", dataset.title_output);
// console.log("get suggestions:author", dataset.author);
// console.log("get suggestions:subjects", dataset.subjects);
// 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);
// }
// Check if there is already a suggestion with this title and type
const hasTitleSuggestion = suggestions.some((suggestion) => suggestion.value === title && suggestion.type == SearchType.Title);
// 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) {
// If there is no such suggestion, create a new one and add it to the suggestions array
const suggestion = new Suggestion(title, SearchType.Title);
const suggestion = new Suggestion(dataset.title_output, highlightedTitle, SearchType.Title);
suggestions.push(suggestion);
foundAny = true;
}
}
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 (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 === highlightedAuthor && suggestion.type == SearchType.Author);
if (!hasAuthorSuggestion) {
const suggestion = new Suggestion(author, SearchType.Author);
const suggestion = new Suggestion(datasetAuthor, highlightedAuthor, SearchType.Author);
suggestions.push(suggestion);
foundAny = true;
}
}
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 (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 === highlightedSubject && suggestion.type == SearchType.Subject);
if (!hasSubjectSuggestion) {
const suggestion = new Suggestion(subject, SearchType.Subject);
const suggestion = new Suggestion(datasetSubject, highlightedSubject, SearchType.Subject);
suggestions.push(suggestion);
foundAny = true;
}
}
// if (!foundAny) {
// const suggestion = new Suggestion(dataset.title_output, SearchType.Fuzzy);
// suggestions.push(suggestion);
// // 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 title = 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.value === title && suggestion.type == SearchType.Title);
// if (!hasTitleSuggestion) {
// const suggestion = new Suggestion(title, SearchType.Title);
// suggestions.push(suggestion);
// }
// }
// if (highlight.author && highlight.author.length > 0) {
// const author = highlight.author.join(" ");
// 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 (highlight.subjects && highlight.subjects.length > 0) {
// const subject = highlight.subjects.join(" ");
// const hasSubjectSuggestion = suggestions.some((suggestion) => suggestion.value === subject && suggestion.type == SearchType.Subject);
// if (!hasSubjectSuggestion) {
// const suggestion = new Suggestion(subject, SearchType.Subject);
// 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);
// }
// }
});
@ -147,10 +211,22 @@ export default class VsInput extends Vue {
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>`;
}
/**
* Clear all values, results and errors
**/
clear(): void {
console.log("clear");
this.display = "";
// this.value = null;
this.results = [];
@ -198,15 +274,17 @@ export default class VsInput extends Vue {
console.log("request()");
// DatasetService.searchTerm(this.display, this.solr.core, this.solr.host).subscribe({
DatasetService.searchTerm(this.display).subscribe({
next: (res: Dataset[]) => this.dataHandler(res),
// 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),
});
}
// Handle the search results
private dataHandler(datasets: Dataset[]): void {
private dataHandler(datasets: Dataset[], highlights: HitHighlight[]): void {
this.results = datasets;
this.highlights = highlights; // Store highlights
// console.log(datasets);
// this.$emit("search", this.display);
@ -230,6 +308,7 @@ export default class VsInput extends Vue {
// Handle arrow down key press to navigate suggestions
onArrowDown(ev: Event): void {
console.log("onArrowDown");
ev.preventDefault();
if (this.selectedIndex === -1) {
this.selectedIndex = 0;
@ -251,6 +330,7 @@ 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;
@ -262,6 +342,8 @@ export default class VsInput extends Vue {
// 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();
@ -277,7 +359,10 @@ export default class VsInput extends Vue {
// if (!obj) {
// return;
// }
console.log("select");
this.value = obj; //(obj["title_output"]) ? obj["title_output"] : obj.id
console.log(obj);
this.display = obj.value; // this.formatDisplay(obj)
// this.selectedDisplay = this.display;
@ -301,6 +386,7 @@ 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();
}