Working Search

This commit is contained in:
Jai A
2020-05-10 22:30:28 -07:00
parent da19743ba5
commit 6be22c474d
11 changed files with 503 additions and 774 deletions

View File

@@ -0,0 +1,21 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="7">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
<item index="6" class="java.lang.String" itemvalue="style" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
</profile>
</component>

754
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,6 @@ actix-rt = "1.1.1"
handlebars = { version = "3.0.0", features = ["dir_source"] }
serde_json = "1.0"
serde_derive = "1.0.107"
serde = "1.0.107"
tantivy = "0.12.0"
tempdir = "0.3.7"
serde = {version="1.0", features=["derive"]}
meilisearch-sdk = "0.1.1"
actix-files = "0.2.1"

View File

@@ -1,48 +1,48 @@
#[macro_use]
extern crate serde_json;
#[macro_use]
extern crate tantivy;
use actix_web::{web, web::Data, web::Query, App, HttpRequest, HttpResponse, HttpServer, Responder, get, post};
use actix_web::{web, web::Data, App, HttpRequest, HttpResponse, HttpServer, Responder, get, post};
use handlebars::Handlebars;
use serde_derive::Deserialize;
use meilisearch_sdk::{document::*, indexes::*, client::*, search::*};
use serde::{Serialize, Deserialize};
use actix_files as fs;
#[derive(Serialize, Deserialize, Debug)]
struct Mod {
mod_id: usize,
title: String,
description: String,
}
impl Document for Mod {
type UIDType = usize;
fn get_uid(&self) -> &Self::UIDType {
&self.mod_id
}
}
use tantivy::collector::TopDocs;
use tantivy::query::{QueryParser};
use tantivy::schema::*;
use tantivy::{Index, IndexReader};
use tantivy::ReloadPolicy;
use tempdir::TempDir;
#[derive(Deserialize)]
pub struct SearchRequest {
q: Option<String>,
f: Option<String>,
}
#[post("search")]
async fn search_post(Query(info): Query<SearchRequest>, reader: Data<IndexReader>, parser: Data<QueryParser<>>, schema: Data<Schema<>>) -> HttpResponse {
let results = handle_search(Query(info), reader, parser, schema);
async fn search_post(web::Query(info): web::Query<SearchRequest>) -> HttpResponse {
let results = search(web::Query(info));
let mut data = "{ \"results\": [".to_owned();
for result in &results {
data.push_str(&result);
data.push_str(",");
}
if &results.len() > &(0 as usize) {
data.pop();
}
data.push_str("] }");
let data = json!({
"results": results,
});
HttpResponse::Ok().body(data)
}
#[get("search")]
async fn search(Query(info): Query<SearchRequest>, hb: Data<Handlebars<'_>>, reader: Data<IndexReader>, parser: Data<QueryParser<>>, schema: Data<Schema<>>) -> HttpResponse {
let results = handle_search(Query(info), reader, parser, schema);
async fn search_get(web::Query(info): web::Query<SearchRequest>, hb: Data<Handlebars<'_>>) -> HttpResponse {
let results = search(web::Query(info));
let data = json!({
"results": results,
@@ -53,28 +53,27 @@ async fn search(Query(info): Query<SearchRequest>, hb: Data<Handlebars<'_>>, rea
HttpResponse::Ok().body(body)
}
fn handle_search(Query(info): Query<SearchRequest>, reader: Data<IndexReader>, parser: Data<QueryParser<>>, schema: Data<Schema<>>) -> Vec<String>{
let mut search_query : String = "".to_string();
fn search(web::Query(info): web::Query<SearchRequest>) -> Vec<Mod> {
let client = Client::new("http://localhost:7700", "");
let mut search_query = "".to_string();
let mut filters = "".to_string();
if let Some(q) = info.q {
search_query = q;
}
let searcher = reader.searcher();
let mut results = vec![];
if let Ok(query) = parser.parse_query(&search_query) {
if let Ok(top_docs) = searcher.search(&query, &TopDocs::with_limit(10)) {
for (_score, doc_address) in top_docs {
if let Ok(retrieved_doc) = searcher.doc(doc_address) {
results.push(schema.to_json(&retrieved_doc));
}
}
}
if let Some(f) = info.f {
filters = f;
}
return results;
let mut query = Query::new(&search_query).with_limit(10);
if !filters.is_empty() {
query = Query::new(&search_query).with_limit(10).with_filters(&filters);
}
client.get_index("mods").unwrap().search::<Mod>(&query).unwrap().hits
}
#[get("/")]
@@ -88,72 +87,53 @@ async fn index(hb: web::Data<Handlebars<'_>>) -> HttpResponse {
}
#[actix_rt::main]
async fn main() -> tantivy::Result<()> {
async fn main() -> std::io::Result<()> {
//Handlebars
let mut handlebars = Handlebars::new();
handlebars
.register_templates_directory(".html", "./static/templates")
.register_templates_directory(".hbs", "./templates")
.unwrap();
let handlebars_ref = web::Data::new(handlebars);
//Search
let index_path = TempDir::new("search_index")?;
let mut schema_builder = Schema::builder();
let client = Client::new("http://localhost:7700", "");
let mut mods = client.get_or_create("mods").unwrap();
schema_builder.add_text_field("title", TEXT | STORED);
schema_builder.add_text_field("keywords", TEXT | STORED);
schema_builder.add_text_field("description", TEXT | STORED);
schema_builder.add_text_field("body", TEXT);
let schema = schema_builder.build();
let schema_ref = web::Data::new(schema.clone());
let title = schema.get_field("title").unwrap();
let keywords = schema.get_field("keywords").unwrap();
let description = schema.get_field("description").unwrap();
let body = schema.get_field("body").unwrap();
let site_index = Index::create_in_dir(&index_path, schema.clone())?;
let mut index_writer = site_index.writer(50_000_000)?;
index_writer.add_document(doc!(
title => "Magic",
keywords => "Magic Fun Adventure",
description => "A magic mod for magical purposes!",
body => "A cool magic mod made by your mom :)",
));
index_writer.add_document(doc!(
title => "Technology",
keywords => "Technology Fun Adventure",
description => "A tech mod for tech purposes!",
body => "A tech mod made by your mom :)",
));
index_writer.commit()?;
let reader = site_index.reader_builder().reload_policy(ReloadPolicy::OnCommit).try_into()?;
let reader_ref = web::Data::new(reader);
let query_parser = QueryParser::for_index(&site_index, vec![title, body, keywords, description]);
let query_parser_ref = web::Data::new(query_parser);
mods.add_documents(vec![
Mod {
mod_id: 0,
title: String::from("Magic Mod"),
description: String::from("An illustrious magic mod for magical wizards"),
},
Mod {
mod_id: 1,
title: String::from("Tech Mod"),
description: String::from("An technological mod for complete NERDS"),
},
Mod {
mod_id: 2,
title: String::from("Hood Mod"),
description: String::from("A hood mod to roleplay as if you were a real street person. Some adventure stuff too"),
},
Mod {
mod_id: 3,
title: String::from("Adventure Mod"),
description: String::from("An epic gamer adventure mod for epic adventure gamers"),
},
], Some("mod_id")).unwrap();
//Init App
HttpServer::new(move || {
App::new()
.app_data(handlebars_ref.clone())
.app_data(reader_ref.clone())
.app_data(query_parser_ref.clone())
.app_data(schema_ref.clone())
.service(fs::Files::new("/static", "./static").show_files_listing())
.service(index)
.service(search)
.service(search_get)
.service(search_post)
})
.bind("127.0.0.1:8000")?
.run()
.await?;
Ok(())
.await
}

122
static/css/multiselect.css Normal file
View File

@@ -0,0 +1,122 @@
.multiselect-wrapper {
width: 180px;
display: inline-block;
white-space: nowrap;
font-size: 12px;
font-family: "Segoe UI", Verdana, Helvetica, Sans-Serif;
}
.multiselect-wrapper .multiselect-input {
width: 100%;
padding-right: 50px;
}
.multiselect-wrapper label {
display: block;
font-size: 12px;
font-weight : 600;
}
.multiselect-wrapper .multiselect-list {
z-index: 1;
position: absolute;
display: none;
background-color: white;
border: 1px solid grey;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
margin-top: -2px;
}
.multiselect-wrapper .multiselect-list.active {
display: block;
}
.multiselect-wrapper .multiselect-list > span {
font-weight: bold;
}
.multiselect-wrapper .multiselect-list .multiselect-checkbox {
margin-right: 2px;
}
.multiselect-wrapper .multiselect-list > span,
.multiselect-wrapper .multiselect-list li {
cursor: default;
}
.multiselect-wrapper .multiselect-list {
padding: 5px;
min-width: 200px;
}
.multiselect-wrapper ul {
list-style: none;
display: block;
position: relative;
padding: 0px;
margin: 0px;
max-height: 200px;
overflow-y: auto;
overflow-x: hidden;
}
.multiselect-wrapper ul li {
padding-right: 20px;
display: block;
}
.multiselect-wrapper ul li.active {
background-color: rgb(0, 102, 255);
color: white;
}
.multiselect-wrapper ul li:hover {
background-color: rgb(0, 102, 255);
color: white;
}
.multiselect-input-div {
height: 34px;
}
.multiselect-input-div input{
border: 1px solid #ababab;
background : #fff;
margin: 5px 0 6px 0;
padding: 5px;
vertical-align:middle;
}
.multiselect-count {
position: relative;
text-align: center;
border-radius: 2px;
background-color: lightblue;
display: inline-block !important;
padding: 2px 7px;
left: -45px;
}
.multiselect-wrapper.disabled .multiselect-dropdown-arrow {
border-top: 5px solid lightgray;
}
.multiselect-wrapper.disabled .multiselect-count {
background-color: lightgray;
}
.multiselect-dropdown-arrow {
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid black;
position: absolute;
line-height: 20px;
text-align: center;
display: inline-block !important;
margin-top: 17px;
margin-left: -42px;
}

48
static/css/style.css Normal file
View File

@@ -0,0 +1,48 @@
@import url('https://fonts.googleapis.com/css?family=Montserrat:400,600');
body {
margin: 0;
font-family: 'Montserrat', sans-serif;
}
#categories_multiSelect {
width: 200px;
}
.results {
width: 75%;
margin: auto;
}
.result {
display: flex;
height: 100px;
margin: 30px auto;
padding: 5px;
width: 75%;
flex-direction: row;
align-items: center;
}
.result img {
padding: 0 10px 0 0;
}
.result-info {
display: flex;
flex-direction: column;
}
.result-info * {
padding: 0 5px 0 0;
margin: 0;
}
.rounded-border {
border-radius: 10px;
border: 1px;
}
.gray-border {
border: 1px solid darkgray;
}

44
static/js/multiselect.min.js vendored Normal file
View File

@@ -0,0 +1,44 @@
if(!m_helper)
{var m_helper={removeNode:function(id){var el=document.getElementById(id);if(el){el.parentNode.removeChild(el)}},insertAfter:function(item,target){var parent=target.parentNode;if(target.nextElementSibling){parent.insertBefore(item,target.nextElementSibling)}else{parent.appendChild(item)}},hide:function(element){element.style.display='none'},hideAll:function(array){for(var i=0;i<array.length;i++){this.hide(array[i])}},show:function(element){element.style.display='block'},showAll:function(array){for(var i=0;i<array.length;i++){this.show(array[i])}},parent:function(element,id){var parent=element.parentElement;while(parent&&parent.tagName!='BODY'){if(parent.id==id){return parent}
parent=parent.parentElement}
return null},create:function(data){var result=document.createElement(data.tag);if(data.id){result.id=data.id}
if(data.class){result.className=data.class}
if(data.attributes){for(var prop in data.attributes){result.setAttribute(prop,data.attributes[prop])}}
if(data.data){for(var prop in data.data){result.dataset[prop]=data.data[prop]}}
return result},div:function(data){if(!data){data=new Object()}
data.tag='div';return this.create(data)},label:function(data){if(!data){data=new Object()}
data.tag='label';return this.create(data)},textField:function(data){if(!data){data=new Object()}
data.tag='input';if(!data.attributes)
data.attributes=new Object();data.attributes.type='text';return this.create(data)},checkbox:function(data){if(!data){data=new Object()}
data.tag='input';if(!data.attributes)
data.attributes=new Object();data.attributes.type='checkbox';return this.create(data)},each:function(array,handler){for(var i=0;i<array.length;i++){handler(array[i])}},setActive:function(element){element.classList.add('active')},setUnactive:function(element){element.classList.remove('active')},select:function(element){element.selected=!0;element.setAttribute('selected','selected')},deselect:function(element){element.selected=!1;element.removeAttribute('selected')},check:function(element){element.checked=!0},uncheck:function(element){element.checked=!1},click:function(element){if(element.fireEvent){el.fireEvent('onclick')}else{var evObj=document.createEvent('Events');evObj.initEvent('click',!0,!1);element.dispatchEvent(evObj)}},setDisabled:function(element,value){element.disabled=value},}}
function Multiselect(item,opts){if((typeof($)!='undefined'&&!$(item).is('select'))||(typeof($)=='undefined'&&item.tagName!='SELECT')){throw "Multiselect: passed object must be a select"}
if((typeof($)!='undefined'&&!$(item).attr('multiple'))||(typeof($)=='undefined'&&!item.hasAttribute('multiple'))){throw "Multiselect: passed object should contain 'multiple' attribute"}
this._item=item;this._createUI();this._appendEvents();this._initSelectedFields();this._initIsEnabled()}
Multiselect.prototype={_createUI:function(){m_helper.removeNode(this._getIdentifier());var wrapper=this._createWrapper();m_helper.insertAfter(wrapper,this._item);wrapper.appendChild(this._createInputField());wrapper.appendChild(this._createItemList());m_helper.hide(this._item)},_createWrapper:function(){var result=document.createElement('div');result.className='multiselect-wrapper';result.id=this._getIdentifier();return result},_createInputField:function(){var input=m_helper.textField({id:this._getInputFieldIdentifier(),class:'multiselect-input',attributes:{autocomplete:'off'}}),label=m_helper.label({id:this._getInputBadgeIdentifier(),class:'multiselect-count',attributes:{for:this._getInputFieldIdentifier()}}),dropDownArrow=m_helper.label({class:'multiselect-dropdown-arrow',attributes:{for:this._getInputFieldIdentifier()}}),result=m_helper.div({class:'multiselect-input-div'});label.style.visibility='hidden';label.innerHTML=0;result.appendChild(input);result.appendChild(label);result.appendChild(dropDownArrow);return result},_createItemList:function(){var list=m_helper.create({tag:'ul'});var self=this;m_helper.each(this._getItems(this._item),function(e){var insertItem=self._createItem('li',e.id,e.text,e.selected);list.appendChild(insertItem);var checkBox=insertItem.querySelector('input[type=checkbox]');e.multiselectElement=checkBox;checkBox.dataset.multiselectElement=JSON.stringify(e)});var selectAll=this._createItem('span',-1,'Select all');var result=m_helper.div({id:this._getItemListIdentifier(),class:'multiselect-list'});result.appendChild(selectAll);result.appendChild(m_helper.create({tag:'hr'}));result.appendChild(list);return result},_createItem:function(wrapper,value,text,selected){var checkBox=m_helper.checkbox({class:'multiselect-checkbox',data:{val:value}}),textBox=m_helper.create({tag:'span',class:'multiselect-text'}),result=m_helper.create({tag:wrapper}),label=m_helper.label();textBox.className='multiselect-text';textBox.innerHTML=text;label.appendChild(checkBox);label.appendChild(textBox);result.appendChild(label);return result},_initSelectedFields:function(){var itemResult=this._getItems().filter(function(obj){return obj.selected});if(itemResult.length!=0){var self=this;m_helper.each(itemResult,function(e){self.select(e.id)})}
this._hideList(this)},_initIsEnabled:function(){this.setIsEnabled(!this._item.disabled)},destroy(){m_helper.removeNode(this._getIdentifier());m_helper.show(this._item);var index=window.multiselects._items.indexOf(this._item);if(index>-1){window.multiselects._items.splice(index,1);window.multiselects.splice(index,1)}},select:function(val){this._toggle(val,!0)},deselect:function(val){this._toggle(val,!1)},setIsEnabled(isEnabled){if(this._isEnabled===isEnabled)return;var wrapperItem=document.getElementById(this._getIdentifier());if(isEnabled){wrapperItem.classList.remove('disabled')}else{wrapperItem.classList.add('disabled')}
m_helper.setDisabled(this._item,!isEnabled);m_helper.setDisabled(document.getElementById(this._getInputFieldIdentifier()),!isEnabled);this._isEnabled=isEnabled},_toggle:function(val,setCheck){var self=this;if(val){m_helper.each(document.getElementById(this._getIdentifier()).querySelectorAll('.multiselect-checkbox'),function(e){if(e.dataset.val==val){if(setCheck&&!e.checked){m_helper.check(e);self._onCheckBoxChange(e,self)}else if(!setCheck&&e.checked){m_helper.uncheck(e);self._onCheckBoxChange(e,self)}}});self._updateText(self)}},selectAll:function(val){var selectAllChkBox=document.querySelector('#'+this._getIdentifier()+' .multiselect-checkbox');m_helper.check(selectAllChkBox);this._onCheckBoxChange(selectAllChkBox,this);this._updateText(this)},deselectAll:function(){var selectAllChkBox=document.querySelector('#'+this._getIdentifier()+' .multiselect-checkbox');m_helper.uncheck(selectAllChkBox);this._onCheckBoxChange(selectAllChkBox,this);this._updateText(this)},_checkboxClickEvents:{},setCheckBoxClick(id,handler){if(typeof handler==="function"){this._checkboxClickEvents[id]=handler}else{console.error("Checkbox click handler for checkbox value="+id+" is not a function")}
return this},_appendEvents:function(){var self=this;document.getElementById(self._getInputFieldIdentifier()).addEventListener('focus',function(event){self._showList(self);document.getElementById(self._getInputFieldIdentifier()).value='';m_helper.each(window.multiselects,function(e){if(document.getElementById(e._getItemListIdentifier()).offsetParent&&m_helper.parent(event.target,e._getIdentifier())){e._hideList(self)}})});document.getElementById(self._getInputFieldIdentifier()).addEventListener('click',function(){self._showList(self);document.getElementById(self._getInputFieldIdentifier()).value=''});document.getElementById(self._getIdentifier()).addEventListener('click',function(event){event=event||window.event;var target=event.target||event.srcElement;if(m_helper.parent(target,self._getIdentifier())){event.stopPropagation()}});document.getElementById(self._getItemListIdentifier()).addEventListener('mouseover',function(){self._showList(self)});m_helper.each(document.getElementById(self._getIdentifier()).querySelectorAll('.multiselect-checkbox'),function(e){e.addEventListener('change',function(event){self._onCheckBoxChange(e,self,event)})});var onInput=function(){var text=this.value.toLowerCase();if(!text||text==''){m_helper.show(document.querySelector('#'+self._getItemListIdentifier()+' > span'));m_helper.show(document.querySelector('#'+self._getItemListIdentifier()+' > hr'));m_helper.showAll(document.querySelectorAll('#'+self._getItemListIdentifier()+' li'))}else{m_helper.hide(document.querySelector('#'+self._getItemListIdentifier()+' > span'));m_helper.hide(document.querySelector('#'+self._getItemListIdentifier()+' > hr'));var array=Array.prototype.filter.call(document.querySelectorAll('#'+self._getItemListIdentifier()+' li span'),function(obj){return obj.innerHTML.toLowerCase().indexOf(text)>-1});m_helper.hideAll(document.querySelectorAll('#'+self._getItemListIdentifier()+' li'));m_helper.each(array,function(e){m_helper.show(e.parentElement.parentElement)})}}
document.getElementById(self._getInputFieldIdentifier()).addEventListener('propertychange',onInput);document.getElementById(self._getInputFieldIdentifier()).addEventListener('input',onInput)},_onCheckBoxChange:function(checkbox,self,event){if(!checkbox.dataset.multiselectElement){var checkedState=self._performSelectAll(checkbox,self);if(typeof self._checkboxClickEvents.checkboxAll==="function"){self._checkboxClickEvents.checkboxAll(checkbox,{checked:checkedState})}}
else{var checkedState=self._performSelectItem(checkbox,self);if(typeof self._checkboxClickEvents[checkedState.id]==="function"){self._checkboxClickEvents[checkedState.id](checkbox,checkedState)}
self._updateSelectAll(self)}
self._forceUpdate()},_performSelectItem:function(checkbox,self){var item=JSON.parse(checkbox.dataset.multiselectElement);if(checkbox.checked){self._itemCounter++;m_helper.select(this._item.options[item.index]);m_helper.setActive(checkbox.parentElement.parentElement);return{id:item.id,checked:!0}}
self._itemCounter--;m_helper.deselect(this._item.options[item.index]);m_helper.setUnactive(checkbox.parentElement.parentElement);return{id:item.id,checked:!1}},_performSelectAll:function(checkbox,self){var items=self._getItems();if(checkbox.checked){self._itemCounter=items.length;m_helper.each(items,function(e){m_helper.setActive(e.multiselectElement.parentElement.parentElement);m_helper.select(self._item.options[e.index]);m_helper.check(e.multiselectElement)});return!0}
self._itemCounter=0;m_helper.each(items,function(e){e.multiselectElement.parentElement.parentElement.classList.remove('active');m_helper.deselect(self._item.options[e.index]);m_helper.uncheck(e.multiselectElement)});return!1},_updateSelectAll:function(self){var allChkBox=document.getElementById(self._getItemListIdentifier()).querySelector('input[type=checkbox]');if(self._itemCounter==self._getItems().length){allChkBox.checked=!0}
else if(allChkBox.checked){allChkBox.checked=!1}},_hideList:function(context,event){m_helper.setUnactive(document.getElementById(context._getItemListIdentifier()));m_helper.show(document.getElementById(context._getItemListIdentifier()).querySelector('span'));m_helper.show(document.getElementById(context._getItemListIdentifier()).querySelector('hr'));m_helper.showAll(document.getElementById(context._getItemListIdentifier()).querySelectorAll('li'));context._updateText(context);if(event)
event.stopPropagation()},_updateText:function(context){var activeItems=document.getElementById(context._getItemListIdentifier()).querySelectorAll('ul .active');if(activeItems.length>0){var val='';for(var i=0;i<(activeItems.length<5?activeItems.length:5);i++){val+=activeItems[i].innerText+", "}
val=val.substr(0,val.length-2);if(val.length>20){val=val.substr(0,17)+'...'}}
if(activeItems.length==document.getElementById(context._getItemListIdentifier()).querySelectorAll('ul li').length){val='All selected'}
document.getElementById(context._getInputFieldIdentifier()).value=val?val:''},_showList:function(context){m_helper.setActive(document.getElementById(context._getItemListIdentifier()))},_forceUpdate:function(){var badge=document.getElementById(this._getInputBadgeIdentifier());badge.style.visibility='hidden';if(this._itemCounter!=0){badge.innerHTML=this._itemCounter;badge.style.visibility='visible';var ddArrow=badge.nextElementSibling;if(this._itemCounter<10){badge.style.left='-45px';ddArrow.style.marginLeft='-42px'}
else if(this._itemCounter<100){badge.style.left='-50px';ddArrow.style.marginLeft='-47px'}
else if(this._itemCounter<1000){badge.style.left='-55px';ddArrow.style.marginLeft='-52px'}
else if(this._itemCounter<10000){badge.style.left='-60px';ddArrow.style.marginLeft='-57px'}}},_items:undefined,_itemCounter:0,_isEnabled:!0,_getItems:function(){if(this._items==undefined){var result=[];var opts=this._item.options;for(var i=0;i<opts.length;i++){var insertItem={id:opts[i].value,index:i,text:opts[i].innerHTML,selected:!!opts[i].selected,selectElement:opts[i]};result.push(insertItem)}
this._items=result}
return this._items},_getItemUniqueIdentifier:function(){var id=this._item.getAttribute('id'),name=this._item.getAttribute('name');if(!(id||name)){throw "Multiselect: object does not contain any identifier (id or name)"}
return id?id:name},_getIdentifier:function(){return this._getItemUniqueIdentifier()+'_multiSelect'},_getInputFieldIdentifier:function(){return this._getItemUniqueIdentifier()+'_input'},_getItemListIdentifier:function(){return this._getItemUniqueIdentifier()+'_itemList'},_getInputBadgeIdentifier:function(){return this._getItemUniqueIdentifier()+'_inputCount'}}
window.multiselects=[];if(typeof($)!='undefined'){$.fn.multiselect=function(){var res=[];if(!window.multiselects._items){window.multiselects._items=[]}
if(this.length!=0){$(this).each(function(i,e){var index=window.multiselects._items.indexOf(e);if(index==-1){var inputItem=new Multiselect(e);window.multiselects.push(inputItem);window.multiselects._items.push(e);res.push(inputItem)}else{res.push(window.multiselects[index])}})}
return res.length==1?res[0]:$(res)};$(document).click(function(event){hideMultiselects(event)})}else{document.multiselect=function(selector){var res=[];if(!window.multiselects._items){window.multiselects._items=[]}
m_helper.each(document.querySelectorAll(selector),function(e){var index=window.multiselects._items.indexOf(e);if(index==-1){var inputItem=new Multiselect(e);window.multiselects.push(inputItem);window.multiselects._items.push(e);res.push(inputItem)}else{res.push(window.multiselects[index])}});return res.length==1?res[0]:res}
window.onclick=function(event){hideMultiselects(event)}}
function hideMultiselects(event){m_helper.each(window.multiselects,function(e){if(document.getElementById(e._getItemListIdentifier()).offsetParent&&!m_helper.parent(event.target,e._getIdentifier())){e._hideList(e,event)}})}

32
static/js/search.js Normal file
View File

@@ -0,0 +1,32 @@
let input = document.getElementById("search-input");
let resultContainer = document.getElementById("results");
function handleSearch() {
let safeName = encodeURIComponent(input.value).replace(/%20/g,'+');
let xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
let parsedResponse = JSON.parse(xmlHttp.responseText);
let contentToSet = "";
for (let result of parsedResponse.results) {
contentToSet += `
<div class="result gray-border rounded-border">
<img src="..." width="75px" height="75px">
<div class="result-info">
<h2>${result.title}</h2>
<p>${result.description}</p>
</div>
</div>
`
}
resultContainer.innerHTML = contentToSet;
}
}
xmlHttp.open("POST", "search?q=" + safeName, true);
xmlHttp.send(null);
window.history.pushState('Search', 'Search', '/search?q=' + safeName);
}

View File

@@ -1,38 +0,0 @@
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Search</title>
</head>
<body>
<h1>Test Mod Search</h1>
<form>
<label for="search-input">Search for Mods:</label>
<input type="text" id="search-input" oninput="handleSearch()">
</form>
<p>{{results}}</p>
<script>
let input = document.getElementById("search-input");
function handleSearch() {
let safeName = encodeURIComponent(input.value).replace(/%20/g,'+');
let xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
console.log(xmlHttp.responseText);
console.log(JSON.parse(xmlHttp.responseText));
}
}
xmlHttp.open("POST", "search?q=" + safeName, true);
xmlHttp.send(null);
window.history.pushState('Search', 'Search', '/search?q=' + safeName);
}
</script>
</body>
</html>

51
templates/search.hbs Normal file
View File

@@ -0,0 +1,51 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="static/css/style.css" rel="stylesheet" type="text/css">
<link href="static/css/multiselect.css" rel="stylesheet" type="text/css">
<script src="static/js/multiselect.min.js"></script>
<title>Search</title>
</head>
<body>
<h1>Test Mod Search</h1>
<form>
<label for="search-input">Search for Mods:</label>
<input type="text" id="search-input" oninput="handleSearch()">
<label for="categories">Categories:</label>
<select id="categories" multiple>
<option value="Adventure">Adventure</option>
<option value="Technology">Technology</option>
<option value="Magic">Magic</option>
<option value="Utility">Utility</option>
</select>
</form>
<div id="results" class="results">
{{#each results}}
<div class="result gray-border rounded-border">
<img src="..." width="75px" height="75px">
<div class="result-info">
<h2>{{this.title}}</h2>
<p>{{this.description}}</p>
</div>
</div>
{{/each}}
</div>
<script src="static/js/search.js"></script>
<script>
document.multiselect('#categories')
</script>
</body>
</html>