MeiliSearch/meilisearch-http/public/interface.html

365 lines
11 KiB
HTML
Raw Normal View History

2019-12-08 15:31:42 +01:00
<!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">
2019-12-08 15:31:42 +01:00
<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;
2019-12-08 15:31:42 +01:00
}
.document ol li {
list-style: none;
2019-12-08 15:31:42 +01:00
}
.document .image {
max-width: 50%;
margin: 0 auto;
box-sizing: border-box;
2019-12-08 15:31:42 +01:00
}
@media screen and (min-width: 770px) {
.document .image {
max-width: 25%;
flex: 0 0 25%;
margin: 0;
padding-left: 30px;
box-sizing: border-box;
}
2019-12-08 15:31:42 +01:00
}
.document .image img {
width: 100%;
2019-12-08 15:31:42 +01:00
}
.attribute {
text-align: center;
2019-12-08 15:31:42 +01:00
box-sizing: border-box;
text-transform: uppercase;
font-weight: bold;
2019-12-08 15:31:42 +01:00
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;
}
}
2019-12-08 15:31:42 +01:00
.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;
}
2019-12-08 15:31:42 +01:00
}
</style>
</head>
<body>
<section class="hero is-light">
2019-12-08 15:31:42 +01:00
<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 id="apiKeyContainer" class="columns">
<input type="hidden" id="apiKey">
</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">
2019-12-08 15:31:42 +01:00
<span class="select">
<select role="listbox" id="index" aria-label="Select the index you want to search on">
2019-12-08 15:31:42 +01:00
<!-- 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>
2019-12-08 15:31:42 +01:00
</div>
</div>
<div class="column is-4">
<div class="columns">
<div class="column is-6 has-text-centered">
2019-12-08 15:31:42 +01:00
<p class="heading">Documents</p>
2020-07-21 16:27:03 +02:00
<p id="count" class="title">0</p>
2019-12-08 15:31:42 +01:00
</div>
<div class="column is-6 has-text-centered">
2019-12-08 15:31:42 +01:00
<p class="heading">Time Spent</p>
2020-07-21 16:27:03 +02:00
<p id="time" class="title">N/A</p>
2019-12-08 15:31:42 +01:00
</div>
</div>
</div>
</div>
2019-12-08 15:31:42 +01:00
</div>
</div>
2019-12-08 15:31:42 +01:00
</section>
<section>
<div class="container">
<ol id="results" class="content">
<!-- documents matching resquests -->
</ol>
</div>
2019-12-08 15:31:42 +01:00
</section>
</body>
<script>
function setApiKeyField () {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", `${baseUrl}/version`, false);
xmlHttp.onload = function () {
let apiKeyContainer = document.getElementById('apiKeyContainer');
if (xmlHttp.status !== 200) {
document.getElementById('apiKey').remove();
let inputNode = document.createElement('input');
inputNode.setAttribute('id', 'apiKey');
inputNode.setAttribute('type', 'password');
inputNode.setAttribute('placeholder', 'Enter your API key');
inputNode.classList.add('input', 'is-small');
let controlNode = document.createElement('div');
controlNode.classList.add('control');
controlNode.appendChild(inputNode);
let labelNode = document.createElement('label');
labelNode.classList.add('label')
labelNode.setAttribute('for', 'apiKey');
let textNode = document.createTextNode('API Key');
labelNode.appendChild(textNode);
let fieldNode = document.createElement('div');
fieldNode.classList.add('field');
fieldNode.appendChild(labelNode);
fieldNode.append(controlNode);
let columnNode = document.createElement('div');
columnNode.classList.add('column', 'is-4');
columnNode.appendChild(fieldNode);
apiKeyContainer.appendChild(columnNode);
}
}
xmlHttp.send(null);
}
2020-03-24 19:03:16 +01:00
function sanitizeHTMLEntities(str) {
if (str && typeof str === 'string') {
str = str.replace(/</g,"&lt;");
str = str.replace(/>/g,"&gt;");
str = str.replace(/&lt;em&gt;/g,"<em>");
str = str.replace(/&lt;\/em&gt;/g,"<\/em>");
}
return str;
}
function httpGet(theUrl, apiKey) {
2019-12-08 15:31:42 +01:00
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", theUrl, false); // false for synchronous request
if (apiKey) {
xmlHttp.setRequestHeader("x-Meili-API-Key", apiKey);
}
2019-12-08 15:31:42 +01:00
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);
}
}
2019-12-08 15:31:42 +01:00
let lastRequest = undefined;
function triggerSearch() {
var e = document.getElementById("index");
if (e.selectedIndex == -1) { return }
var index = e.options[e.selectedIndex].value;
2020-10-05 20:57:52 +02:00
let theUrl = `${baseUrl}/indexes/${index}/search?q=${encodeURIComponent(search.value)}&attributesToHighlight=*`;
2019-12-08 15:31:42 +01:00
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'));
}
2019-12-08 15:31:42 +01:00
lastRequest.onload = function (e) {
if (lastRequest.readyState === 4 && lastRequest.status === 200) {
2020-03-24 19:03:16 +01:00
let sanitizedResponseText = sanitizeHTMLEntities(lastRequest.responseText);
let httpResults = JSON.parse(sanitizedResponseText);
2019-12-08 15:31:42 +01:00
results.innerHTML = '';
let processingTimeMs = httpResults.processingTimeMs;
2020-07-21 16:27:03 +02:00
let numberOfDocuments = httpResults.nbHits;
2019-12-08 15:31:42 +01:00
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");
2019-12-08 15:31:42 +01:00
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");
2019-12-08 15:31:42 +01:00
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");
2019-12-08 15:31:42 +01:00
const attribute = document.createElement('div');
attribute.classList.add("attribute", "column");
2019-12-08 15:31:42 +01:00
attribute.innerHTML = prop;
const content = document.createElement('div');
content.classList.add("content", "column");
2020-03-27 02:45:57 +01:00
if (typeof (element[prop]) === "object") {
content.innerHTML = JSON.stringify(element[prop]);
} else {
content.innerHTML = element[prop];
}
2019-12-08 15:31:42 +01:00
field.appendChild(attribute);
field.appendChild(content);
info.appendChild(field);
2019-12-08 15:31:42 +01:00
}
div.appendChild(info);
elem.appendChild(div);
2019-12-08 15:31:42 +01:00
if (image != undefined) {
const divImage = document.createElement('div');
divImage.classList.add("image","column","align-on-mobile");
2019-12-08 15:31:42 +01:00
const img = document.createElement('img');
img.src = image;
img.setAttribute("alt","Item illustration");
2019-12-08 15:31:42 +01:00
divImage.appendChild(img);
div.appendChild(divImage);
2019-12-08 15:31:42 +01:00
elem.appendChild(div);
}
results.appendChild(elem)
}
} else {
console.error(lastRequest.statusText);
}
};
lastRequest.send(null);
}
if (!apiKey.value) {
apiKey.value = localStorage.getItem('apiKey');
2019-12-08 15:31:42 +01:00
}
apiKey.addEventListener('input', function(e) {
localStorage.setItem('apiKey', apiKey.value);
refreshIndexList();
}, false);
let baseUrl = window.location.origin;
setApiKeyField();
refreshIndexList();
2019-12-08 15:31:42 +01:00
search.oninput = triggerSearch;
let select = document.getElementById("index");
2019-12-08 15:31:42 +01:00
select.onchange = triggerSearch;
triggerSearch();
</script>
</html>