mirror of
				https://github.com/meilisearch/meilisearch.git
				synced 2025-10-31 07:56:28 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			334 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			334 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <!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>
 |