<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="bulma.min.css"> <title>MeiliSearch</title> <style> em { color: hsl(204, 86%, 25%); font-style: inherit; background-color: hsl(204, 86%, 88%); } #results { max-width: 900px; margin: 20px auto 0 auto; padding: 0; } .notification { display: flex; justify-content: center; } .level-left { margin-right: 50px; } .document { border-radius: 4px; margin-bottom: 20px; display: flex; } .document ol { flex: 0 0 75%; max-width: 75%; padding: 0; margin: 0; list-style-type: none; } .document ol li { list-style: none; } .document .image { max-width: 50%; margin: 0 auto; box-sizing: border-box; } @media screen and (min-width: 770px) { .document .image { max-width: 25%; flex: 0 0 25%; margin: 0; padding-left: 30px; box-sizing: border-box; } } .document .image img { width: 100%; } .attribute { text-align: center; box-sizing: border-box; text-transform: uppercase; font-weight: bold; color: rgba(0,0,0,.7); } @media screen and (min-width: 770px) { .attribute { flex: 0 0 25%; max-width: 25%; text-align: right; padding-right: 10px; font-weight: normal; box-sizing: border-box; } } @media screen and (max-width: 770px) { .attribute { padding-bottom: 0; } } .content { flex: 0 0 75%; box-sizing: border-box; color: rgba(0,0,0,.9); overflow-wrap: anywhere; } .hero-foot { padding-bottom: 3rem; } @media screen and (max-width: 770px) { .align-on-mobile { text-align: center; } } </style> </head> <body> <section class="hero is-light"> <div class="hero-body"> <div class="container"> <div class="content is-medium align-on-mobile"> <h1 class="title is-1 is-spaced"> Welcome to MeiliSearch </h1> <p class="subtitle is-4"> This dashboard will help you check the search results with ease. </p> </div> <div class="columns"> <div class="column is-4"> <div class="field"> <!-- API Key --> <label class="label" for="apiKey">API Key (optionnal)</label> <div class="control"> <input id="apiKey" class="input is-small" type="password" placeholder="Enter your API key"> </div> <p class="help">At least a private API key is required for the dashboard to access the indexes list.</p> </div> </div> </div> <div class="columns"> <div class="column is-8"> <label class="label" for="search">Search something</label> <div class="field has-addons"> <div class="control"> <span class="select"> <select role="listbox" id="index" aria-label="Select the index you want to search on"> <!-- indexes names --> </select> </span> </div> <div class="control is-expanded"> <input id="search" class="input" type="search" autofocus placeholder="e.g. George Clooney" aria-label="Search through your documents"> </div> </div> </div> <div class="column is-4"> <div class="columns"> <div class="column is-6 has-text-centered"> <p class="heading">Documents</p> <p id="count" class="title">0</p> </div> <div class="column is-6 has-text-centered"> <p class="heading">Time Spent</p> <p id="time" class="title">N/A</p> </div> </div> </div> </div> </div> </div> </section> <section> <div class="container"> <ol id="results" class="content"> <!-- documents matching resquests --> </ol> </div> </section> </body> <script> function sanitizeHTMLEntities(str) { if (str && typeof str === 'string') { str = str.replace(/</g,"<"); str = str.replace(/>/g,">"); str = str.replace(/<em>/g,"<em>"); str = str.replace(/<\/em>/g,"<\/em>"); } return str; } function httpGet(theUrl, apiKey) { var xmlHttp = new XMLHttpRequest(); xmlHttp.open("GET", theUrl, false); // false for synchronous request if (apiKey) { xmlHttp.setRequestHeader("x-Meili-API-Key", apiKey); } xmlHttp.send(null); return xmlHttp.responseText; } function refreshIndexList() { // TODO we must not block here let result = JSON.parse(httpGet(`${baseUrl}/indexes`, localStorage.getItem('apiKey'))); if (!Array.isArray(result)) { return } let select = document.getElementById("index"); select.innerHTML = ''; for (index of result) { const option = document.createElement('option'); option.value = index.uid; option.innerHTML = index.name; select.appendChild(option); } } let lastRequest = undefined; function triggerSearch() { var e = document.getElementById("index"); if (e.selectedIndex == -1) { return } var index = e.options[e.selectedIndex].value; let theUrl = `${baseUrl}/indexes/${index}/search?q=${encodeURIComponent(search.value)}&attributesToHighlight=*`; if (lastRequest) { lastRequest.abort() } lastRequest = new XMLHttpRequest(); lastRequest.open("GET", theUrl, true); if (localStorage.getItem('apiKey')) { lastRequest.setRequestHeader("x-Meili-API-Key", localStorage.getItem('apiKey')); } lastRequest.onload = function (e) { if (lastRequest.readyState === 4 && lastRequest.status === 200) { let sanitizedResponseText = sanitizeHTMLEntities(lastRequest.responseText); let httpResults = JSON.parse(sanitizedResponseText); results.innerHTML = ''; let processingTimeMs = httpResults.processingTimeMs; let numberOfDocuments = httpResults.nbHits; time.innerHTML = `${processingTimeMs}ms`; count.innerHTML = `${numberOfDocuments}`; for (result of httpResults.hits) { const element = {...result, ...result._formatted }; delete element._formatted; const elem = document.createElement('li'); elem.classList.add("document","box"); const div = document.createElement('div'); div.classList.add("columns","is-desktop","is-tablet"); const info = document.createElement('div'); info.classList.add("column","align-on-mobile"); let image = undefined; for (const prop in element) { // Check if property is an image url link. if (typeof result[prop] === 'string') { if (image == undefined && result[prop].match(/^(https|http):\/\/.*(jpe?g|png|gif)(\?.*)?$/g)) { image = result[prop]; } } const field = document.createElement('div'); field.classList.add("columns"); const attribute = document.createElement('div'); attribute.classList.add("attribute", "column"); attribute.innerHTML = prop; const content = document.createElement('div'); content.classList.add("content", "column"); if (typeof (element[prop]) === "object") { content.innerHTML = JSON.stringify(element[prop]); } else { content.innerHTML = element[prop]; } field.appendChild(attribute); field.appendChild(content); info.appendChild(field); } div.appendChild(info); elem.appendChild(div); if (image != undefined) { const divImage = document.createElement('div'); divImage.classList.add("image","column","align-on-mobile"); const img = document.createElement('img'); img.src = image; img.setAttribute("alt","Item illustration"); divImage.appendChild(img); div.appendChild(divImage); elem.appendChild(div); } results.appendChild(elem) } } else { console.error(lastRequest.statusText); } }; lastRequest.send(null); } if (!apiKey.value) { apiKey.value = localStorage.getItem('apiKey'); } apiKey.addEventListener('input', function(e) { localStorage.setItem('apiKey', apiKey.value); refreshIndexList(); }, false); let baseUrl = window.location.origin; refreshIndexList(); search.oninput = triggerSearch; let select = document.getElementById("index"); select.onchange = triggerSearch; triggerSearch(); </script> </html>