213 lines
9.9 KiB
JavaScript
213 lines
9.9 KiB
JavaScript
document.addEventListener('DOMContentLoaded', function() {
|
|
// Define product type keywords and their standardized forms
|
|
const productTypes = {
|
|
'mug': ['mug', 'cup'],
|
|
'sticker': ['sticker'],
|
|
'shirt': ['shirt', 't-shirt', 'tee'],
|
|
'hoodie': ['hoodie', 'jacket'],
|
|
'blanket': ['blanket'],
|
|
// Add more product types and synonyms as needed
|
|
};
|
|
|
|
const MAX_RESULTS = 20; // Limit the number of search results displayed
|
|
let searchTimeout; // Timeout for delayed search
|
|
|
|
// Function to convert plurals to singular
|
|
function normalizeKeyword(keyword) {
|
|
if (keyword.endsWith('s')) {
|
|
return keyword.slice(0, -1);
|
|
}
|
|
return keyword;
|
|
}
|
|
|
|
// Function to standardize synonyms to a common keyword
|
|
function standardizeKeyword(keyword) {
|
|
const normalizedKeyword = normalizeKeyword(keyword);
|
|
for (let standard in productTypes) {
|
|
if (productTypes[standard].includes(normalizedKeyword)) {
|
|
return standard;
|
|
}
|
|
}
|
|
return normalizedKeyword;
|
|
}
|
|
|
|
// Function to extract keywords from the description
|
|
function extractKeywords(description) {
|
|
const keywordPrefix = "keywords: ";
|
|
const keywordStart = description.toLowerCase().indexOf(keywordPrefix);
|
|
if (keywordStart !== -1) {
|
|
return description.slice(keywordStart + keywordPrefix.length).trim().toLowerCase();
|
|
}
|
|
return '';
|
|
}
|
|
|
|
// Fetch and parse the RSS feed
|
|
fetch('https://merch.ookamikun.tv/.well-known/merchant-center/rss.xml')
|
|
.then(response => response.text())
|
|
.then(data => {
|
|
const parser = new DOMParser();
|
|
const xml = parser.parseFromString(data, "application/xml");
|
|
const items = xml.getElementsByTagName("item");
|
|
const products = {};
|
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
const item = items[i];
|
|
|
|
const idElement = item.getElementsByTagName("g:id")[0];
|
|
const titleElement = item.getElementsByTagName("g:title")[0];
|
|
const descriptionElement = item.getElementsByTagName("g:description")[0];
|
|
const linkElement = item.getElementsByTagName("g:link")[0];
|
|
const imageElement = item.getElementsByTagName("g:image_link")[0];
|
|
const priceElement = item.getElementsByTagName("g:price")[0];
|
|
const sizeElement = item.getElementsByTagName("g:size")[0];
|
|
const colorElement = item.getElementsByTagName("g:color")[0];
|
|
|
|
if (idElement && titleElement && descriptionElement && linkElement && imageElement && priceElement) {
|
|
const id = idElement.textContent || '';
|
|
const title = titleElement.textContent.trim() || 'Untitled Product';
|
|
const description = descriptionElement.textContent.trim() || 'No description available.';
|
|
const link = linkElement.textContent || '#';
|
|
const image = imageElement.textContent || 'default-image-url.jpg';
|
|
const price = parseFloat(priceElement.textContent.replace(/[^\d.]/g, '')) || 0;
|
|
const size = sizeElement ? sizeElement.textContent : 'One Size';
|
|
const color = colorElement ? colorElement.textContent.trim().toLowerCase() : '';
|
|
const keywords = extractKeywords(description); // Extracting keywords using the new function
|
|
|
|
if (!products[title]) {
|
|
products[title] = {
|
|
title,
|
|
description,
|
|
link,
|
|
image,
|
|
prices: [],
|
|
sizes: new Set(),
|
|
color,
|
|
keywords,
|
|
searchText: title.toLowerCase() + ' ' + description.toLowerCase() + ' ' + color + ' ' + keywords,
|
|
};
|
|
}
|
|
products[title].prices.push(price);
|
|
if (size) products[title].sizes.add(size);
|
|
}
|
|
}
|
|
|
|
function calculateRelevanceScore(product, keywords, query) {
|
|
let score = 0;
|
|
let matchesAnyKeyword = false;
|
|
|
|
// Standardize and normalize keywords
|
|
const standardizedKeywords = keywords.map(standardizeKeyword);
|
|
|
|
// Keywords match - highest weight (75%)
|
|
const keywordMatches = standardizedKeywords.filter(keyword => product.keywords.includes(keyword));
|
|
if (keywordMatches.length > 0) {
|
|
score += keywordMatches.length * 75; // 75% weight for keywords
|
|
matchesAnyKeyword = true;
|
|
}
|
|
|
|
// Exact match - significant weight
|
|
if (standardizedKeywords.includes(standardizeKeyword(product.title.toLowerCase())) ||
|
|
standardizedKeywords.includes(standardizeKeyword(product.description.toLowerCase()))) {
|
|
score += 30;
|
|
matchesAnyKeyword = true;
|
|
}
|
|
|
|
// Product type match - significant weight
|
|
let productTypeMatched = false;
|
|
for (let type in productTypes) {
|
|
const standardType = standardizeKeyword(type);
|
|
if (standardizedKeywords.includes(standardType)) {
|
|
score += 20;
|
|
matchesAnyKeyword = true;
|
|
productTypeMatched = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!productTypeMatched && standardizedKeywords.some(keyword => productTypes.hasOwnProperty(keyword))) {
|
|
score -= 20; // Stronger penalty for missing product type match if the user provided a product type keyword
|
|
}
|
|
|
|
// Color match - medium weight
|
|
const colorMatches = standardizedKeywords.filter(keyword => product.color.includes(keyword));
|
|
if (colorMatches.length > 0) {
|
|
score += colorMatches.length * 10;
|
|
matchesAnyKeyword = true;
|
|
}
|
|
|
|
// Title match - lower weight
|
|
const titleMatches = standardizedKeywords.filter(keyword => product.title.toLowerCase().includes(keyword));
|
|
if (titleMatches.length > 0) {
|
|
score += titleMatches.length * 5;
|
|
matchesAnyKeyword = true;
|
|
}
|
|
|
|
// Description match - lower weight
|
|
const descriptionMatches = standardizedKeywords.filter(keyword => product.description.toLowerCase().includes(keyword));
|
|
if (descriptionMatches.length > 0) {
|
|
score += descriptionMatches.length * 3;
|
|
matchesAnyKeyword = true;
|
|
}
|
|
|
|
// Ensure the product is only considered if it contains at least one of the keywords
|
|
if (!matchesAnyKeyword) {
|
|
score = 0;
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
const searchInput = document.querySelector('#search-input');
|
|
const resultsContainer = document.querySelector('#search-results');
|
|
|
|
searchInput.addEventListener('input', function() {
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(() => {
|
|
const query = this.value.toLowerCase().trim();
|
|
const keywords = query.split(/\s+/);
|
|
const results = [];
|
|
|
|
for (let key in products) {
|
|
const product = products[key];
|
|
const relevanceScore = calculateRelevanceScore(product, keywords, query);
|
|
if (relevanceScore > 0) {
|
|
results.push({ product, relevanceScore });
|
|
}
|
|
}
|
|
|
|
results.sort((a, b) => b.relevanceScore - a.relevanceScore);
|
|
|
|
// Render the top X results
|
|
resultsContainer.innerHTML = '';
|
|
results.slice(0, MAX_RESULTS).forEach(({ product }) => {
|
|
const minPrice = Math.min(...product.prices).toFixed(2);
|
|
const maxPrice = Math.max(...product.prices).toFixed(2);
|
|
const priceRange = minPrice === maxPrice ? `$${minPrice}` : `$${minPrice} - $${maxPrice}`;
|
|
|
|
const productElement = document.createElement('div');
|
|
productElement.className = 'search-result-item';
|
|
productElement.innerHTML = `
|
|
<img src="${product.image}" alt="${product.title}" class="search-result-image">
|
|
<div class="search-result-info">
|
|
<h3>${product.title}</h3>
|
|
<p>${product.description.substring(0, 100)}...</p>
|
|
<div class="search-result-meta">
|
|
<span>Sizes: ${[...product.sizes].join(', ')}</span>
|
|
<span>Price: ${priceRange}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
resultsContainer.appendChild(productElement);
|
|
});
|
|
}, 500); // 0.5-second delay before performing the search
|
|
});
|
|
})
|
|
.catch(error => console.error('Error fetching or parsing the RSS feed:', error));
|
|
|
|
const searchIcon = document.querySelector('.header__icon--search');
|
|
const searchBar = document.querySelector('#search-bar');
|
|
searchIcon.addEventListener('click', function() {
|
|
searchBar.classList.toggle('active');
|
|
searchBar.querySelector('input').focus();
|
|
});
|
|
});
|