Adding LIVE IMAGE SEARCH

While having a short break, I coded the functionality to search my art galleries in a live, or instant fashion - like all my other searches on this website. If you navigate to collections, from your account page, or simply to my art section, you can click the search icon after it finished scanning, and start typing. Should your query match the name, or description of any of the images in my art section, they will load into the gallery below. The way this is done is by fetching my art section, getting all the links to my art collections while omitting a predefined set of other links which are there. After this we fetch all the images, or rather their “src=” and “alt=” attributes, which are effectively the link to the image itself and the name of the image, or the description given to it. This is stored to be filtered through upon search. There are a few more safety checks included so that no non-sense images show up and that there will be no duplicates. Currently you can like and unlike the found images, just like with any of my art collections. But beware, it is still not possible to add images to an existing collection, apart from overriding it completely, that functionality will come later. As of the last code update, the initial fetch returns the correct number of images and galleries to be searched.

HTML:

<input type="text" id="imageSearchInput" class="image-search-input" placeholder="">
JAVASCRIPT:

<script>
document.addEventListener('DOMContentLoaded', function() {
const subString = 'usergallery';
const subStringTwo = 'artland';
const getURL = window.location.href;
const imageSearchInput = document.querySelector('#imageSearchInput');  
if (getURL.includes(subString) || getURL.includes(subStringTwo)) {
const gallerySection = document.querySelector('.gallery-masonry');
const gallery = gallerySection.querySelector('.gallery-masonry-wrapper');
if (getURL.includes(subString)) {
//embed into user gallery
const searchPlaceBlock = document.getElementById('block-yui_3_17_2_1_1724836764613_4889'); 
const searchPlaceText = searchPlaceBlock.querySelector('p');  
searchPlaceText.textContent = '';
searchPlaceBlock.appendChild(imageSearchInput);
} else { 
//embed into art section
const searchPlaceBlock = document.getElementById('block-yui_3_17_2_1_1724869769291_7417'); 
const searchPlaceText = searchPlaceBlock.querySelector('p');  
searchPlaceText.textContent = '';
searchPlaceBlock.appendChild(imageSearchInput); 
imageSearchInput.classList.add('artsection');
gallerySection.style.display = 'none';
}
//check if has text for cosmetic reasons
var hasText = false;
function checkInput() {
hasText = imageSearchInput.value.trim() !== '';
}
document.addEventListener('click', function(event) {
checkInput();
if (event.target !== imageSearchInput && hasText == false) {
imageSearchInput.classList.remove('texted');
if (getURL.includes(subStringTwo)) {
gallerySection.style.display = 'none';
}
} else {
imageSearchInput.classList.add('texted');
gallerySection.style.display = '';
}
});
//fetch the links to galleries from art page
imageSearchInput.style.textAlign = 'left'; 
imageSearchInput.value = 'Fetching...';
const artPageURL = '/artland';
const omitURLs = ['/contact', '/legal', '/history', '/artland', '/shop', '/useraccount', '/cart', '/']; 
const keywordsToExclude = ['#'];
fetch(artPageURL)
.then(response => response.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const pageLinks = Array.from(doc.querySelectorAll('a'));   
const imagePromises = pageLinks
.filter(link => !omitURLs.includes(link.pathname))
.filter(link => !keywordsToExclude.some(keyword => link.href.includes(keyword)))
.map(link => {
const pageURL = link.href;
return fetch(pageURL).then(res => res.text());
});     
imageSearchInput.value = imagePromises.length + ' Galleries';
//get the images metadata
const forbiddenImageNames = ['Polivantage', 'icon'];
Promise.all(imagePromises).then(pagesHTML => {
const imagesData = [];
const totalImages = pagesHTML.reduce((count, pageHTML) => {
const pageDoc = parser.parseFromString(pageHTML, 'text/html');
const images = pageDoc.querySelectorAll('img');
return count + images.length;
}, 0);
let loadedImages = 0;
pagesHTML.forEach(pageHTML => {
const pageDoc = parser.parseFromString(pageHTML, 'text/html');
const imageGalleries = pageDoc.querySelectorAll('.gallery-masonry-wrapper');
let images = [];  
imageGalleries.forEach(gallery => {
const galleryImages = gallery.querySelectorAll('img'); 
images = images.concat(Array.from(galleryImages));
});
setTimeout(() => {
images.forEach(img => {
if (!imagesData.some(image => image.src === img.src) && !forbiddenImageNames.some(name => img.alt.includes(name))) {
imagesData.push({
src: img.src,
alt: img.alt
});
}
});
//show load progress
loadedImages += images.length;
const progress = Math.floor((loadedImages / totalImages) * 100);
imageSearchInput.value = progress + '%';
});
}, 0); //wrap img function to display % due to load timing
setTimeout(() => {
imageSearchInput.value = loadedImages + ' sigils';
imageSearchInput.disabled = false;
}, 500); //delay before final nn images displayed
setTimeout(() => {
imageSearchInput.style.textAlign = ''; 
imageSearchInput.value = '';
}, 2000); //delay before input field is cleared of preloader info
//search function
let debounceTimeout;
const forbiddenWords = ['previ', '.', 'scale', 'png', 'jpg', '_'];
imageSearchInput.addEventListener('input', function() {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(function() {
const searchTerms = imageSearchInput.value.toLowerCase().split(/\s+/).filter(Boolean);
if (searchTerms.length === 0 || searchTerms.some(term => forbiddenWords.some(word => term.includes(word))) || searchTerms.some(term => term.length < 3)) { //3 char minimum
gallery.innerHTML = '';
return;
}
gallery.innerHTML = '';
const filteredImages = imagesData
.filter(image => searchTerms.every(term => image.alt.toLowerCase().includes(term)))
.map(image => {
const matchCount = searchTerms.reduce((count, term) => {
return count + (image.alt.toLowerCase().includes(term) ? 1 : 0);
}, 0);
return { ...image, matchCount };
})
.sort((a, b) => b.matchCount - a.matchCount);           
filteredImages.forEach(image => {
//create the image structure and embed into gallery
const imgElement = document.createElement('img');
imgElement.src = image.src;
imgElement.alt = image.alt;
imgElement.setAttribute('data-src', image.src);
imgElement.setAttribute('data-image', image.src);
imgElement.setAttribute('srcset', image.src);
imgElement.classList.add('loaded');
const figureElement = document.createElement('figure');
figureElement.classList.add('gallery-masonry-item');
const divElement = document.createElement('div');
divElement.classList.add('gallery-masonry-item-wrapper', 'preFade', 'fadeIn');
divElement.setAttribute('data-animation-role', "image");
divElement.style.overflow = 'hidden';
const lightboxLink = document.createElement('a');
lightboxLink.classList.add('gallery-masonry-lightbox-link');
lightboxLink.style.height = "100%";
figureElement.appendChild(divElement);
divElement.appendChild(lightboxLink);
lightboxLink.appendChild(imgElement);
gallery.appendChild(figureElement);
});
}, 1000); // debounce search delay
});
}); 
});  
} else {
imageSearchInput.remove();
}
});
</script>
CSS:

#imageSearchInput {
  height: 50px !important;
  display: block;
  background: transparent;
  border: 0 !important;
  font-size: 1.3rem;
  letter-spacing: 1.8px;
  outline: none;
  padding: 0;
  margin: 0;
  margin-bottom: 28px;
  margin-left: -50px;
  text-align: left;
  position: relative;
  z-index: 999;
  transition: all .7s ease !important;
  background-image: url(https://images.squarespace-cdn.com/content/v1/6654b2fee26f292de13f1a9d/2b60bbec-4d1c-4d73-9d6a-009b9bf1d26a/SearchCrosshairIcon.png);
  background-size: 50px;
  background-position: bottom 0px right 0px;
  background-repeat: no-repeat;
  padding-left: 50px !important; 
}
@media screen and (max-width:790px) {
#imageSearchInput {
  position: relative;
  text-align: center;
  width: 90%;
  background-position: bottom 0px left 58%;
  margin-left: -15px;
  top: -5px;
  margin-bottom: 0;
  }
}
#imageSearchInput:focus {
  outline: none; 
  transition: all .7s ease;
  background-position: bottom 0px left 0px;
} 
#imageSearchInput.texted {
  outline: none; 
  transition: all .7s ease;
  background-position: bottom 0px left -6px;
} 

Updated Javascript to search for multiple keywords and filter the results by the number of keywords. Also added a progress loader which should show the percentage of images ready for search as they become available. It is a quick operation and this is not necessary even with over a 1000 images, so it appears briefly and almost instantly hits 100%. The 0ms timeout ensures that the numbers are displayed, even though they are calculated quicker than a non-timeout function would allow to update the screen with.

Previous
Previous

Gallery Layout Controls

Next
Next

Adding Chat with firebase