Add tag_autocomplete.js.

Instead of embedding the entire tag list in the search.html template
every single time, this script loads the tags from the new,
cache-enabled endpoint /all_tags.json. Then we can use html5
datalists to create autocomplete forms on the search and photo pages.
This commit is contained in:
voussoir 2018-07-22 18:17:39 -07:00
parent bbf07f4401
commit 8a12a24e8e
7 changed files with 121 additions and 15 deletions

View file

@ -792,6 +792,15 @@ class PDBTagMixin:
names = [row[0] for row in rows]
return names
def get_all_synonyms(self):
'''
Return a dict mapping {synonym: mastertag} as strings.
'''
query = 'SELECT name, mastername FROM tag_synonyms'
rows = self.sql_select(query)
synonyms = {syn: tag for (syn, tag) in rows}
return synonyms
def get_root_tags(self):
'''
Yield Tags that have no parent.

View file

@ -371,11 +371,9 @@ def get_search_core():
def get_search_html():
search_results = get_search_core()
search_kwargs = search_results['search_kwargs']
all_tags = common.P.get_all_tag_names()
session = session_manager.get(request)
response = flask.render_template(
'search.html',
all_tags=json.dumps(all_tags),
next_page_url=search_results['next_page_url'],
prev_page_url=search_results['prev_page_url'],
photos=search_results['photos'],

View file

@ -1,7 +1,9 @@
import flask; from flask import request
import json
import etiquette
from .. import caching
from .. import common
from .. import decorators
from .. import jsonify
@ -48,6 +50,14 @@ def post_tag_edit(specific_tag):
# Tag listings #####################################################################################
@site.route('/all_tags.json')
@caching.cached_endpoint(max_age=0)
def get_all_tag_names():
all_tags = common.P.get_all_tag_names()
all_synonyms = common.P.get_all_synonyms()
response = {'tags': all_tags, 'synonyms': all_synonyms}
return json.dumps(response)
@site.route('/tag/<specific_tag_name>')
@site.route('/tags')
@session_manager.give_token

View file

@ -121,3 +121,15 @@ function html_to_element(html)
template.innerHTML = html;
return template.content.firstChild;
}
function normalize_tagname(tagname)
{
tagname = tagname.trim();
tagname = tagname.toLocaleLowerCase();
tagname = tagname.split(".");
tagname = tagname[tagname.length-1];
tagname = tagname.split("+")[0];
tagname = tagname.replace(new RegExp(" ", 'g'), "_");
tagname = tagname.replace(new RegExp("-", 'g'), "_");
return tagname;
}

View file

@ -0,0 +1,79 @@
var tag_autocomplete = {};
tag_autocomplete.tagset = {"tags": [], "synonyms": {}};
tag_autocomplete.DATALIST_ID = "tag_autocomplete_datalist"
tag_autocomplete.init_datalist =
function init_datalist()
{
var datalist;
datalist = document.getElementById(tag_autocomplete.DATALIST_ID);
if (!datalist)
{
var datalist = document.createElement("datalist");
datalist.id = tag_autocomplete.DATALIST_ID;
document.body.appendChild(datalist);
}
delete_all_children(datalist);
for (var index = 0; index < tag_autocomplete.tagset["tags"].length; index += 1)
{
var option = document.createElement("option");
option.value = tag_autocomplete.tagset["tags"][index];
datalist.appendChild(option);
}
for (var synonym in tag_autocomplete.tagset["synonyms"])
{
var option = document.createElement("option");
option.value = tag_autocomplete.tagset["synonyms"][synonym] + "+" + synonym;
datalist.appendChild(option);
}
}
tag_autocomplete.resolve =
function resolve(tagname)
{
tagname = normalize_tagname(tagname);
if (tag_autocomplete.tagset["tags"].indexOf(tagname) != -1)
{
return tagname;
}
if (tagname in tag_autocomplete.tagset["synonyms"])
{
return tag_autocomplete.tagset["synonyms"][tagname];
}
return null;
}
tag_autocomplete.update_tagset_callback =
function update_tagset_callback(response)
{
if (response["meta"]["status"] == 304)
{
return;
}
if (response["meta"]["status"] == 200)
{
tag_autocomplete.tagset = response["data"];
if (document.getElementById(tag_autocomplete.DATALIST_ID))
{
tag_autocomplete.init_datalist();
}
return tag_autocomplete.tagset;
}
console.error(response);
}
tag_autocomplete.update_tagset =
function update_tagset()
{
console.log("Updating known tagset.");
var url = "/all_tags.json";
get(url, tag_autocomplete.update_tagset_callback);
}
function on_pageload()
{
tag_autocomplete.update_tagset();
}
document.addEventListener("DOMContentLoaded", on_pageload);

View file

@ -10,6 +10,7 @@
<script src="/static/js/common.js"></script>
<script src="/static/js/hotkeys.js"></script>
<script src="/static/js/photoclipboard.js"></script>
<script src="/static/js/tag_autocomplete.js"></script>
<style>
#content_body
@ -162,7 +163,7 @@
<h4>Tags</h4>
<ul id="this_tags">
<li>
<input id="add_tag_textbox" type="text" autofocus>
<input id="add_tag_textbox" type="text" list="tag_autocomplete_datalist" autofocus>
<button id="add_tag_button" class="green_button" onclick="submit_tag(receive_callback);">add</button>
</li>
{% set tags = photo.get_tags()|sort_tags %}
@ -434,6 +435,8 @@ function move_hoverzoom(event)
photo_img_holder.style.backgroundPosition=(-x)+"px "+(-y)+"px";
}
tag_autocomplete.init_datalist();
setTimeout(
/*
When the screen is in column mode, the autofocusing of the tag box snaps the

View file

@ -14,6 +14,7 @@
<script src="/static/js/common.js"></script>
<script src="/static/js/hotkeys.js"></script>
<script src="/static/js/photoclipboard.js"></script>
<script src="/static/js/tag_autocomplete.js"></script>
<style>
form
@ -171,7 +172,7 @@ form
</li>
{% endfor %}
{% endif %}
<li><input id="search_builder_{{tagtype}}_input" type="text"></li>
<li><input id="search_builder_{{tagtype}}_input" type="text" list="tag_autocomplete_datalist"></li>
</ul>
</div>
{% endfor %}
@ -525,10 +526,9 @@ function tags_on_this_page_hook()
*/
var tagname = this.innerHTML.split(/\./);
tagname = tagname[tagname.length-1];
var qualname = ALL_TAGS[tagname];
add_searchtag(
input_musts,
qualname,
tagname,
inputted_musts,
"search_builder_musts_inputted"
);
@ -546,13 +546,9 @@ function tag_input_hook(box, inputted_list, li_class)
if (!box.value)
{return;}
var value = box.value.toLocaleLowerCase();
value = value.split(".");
value = value[value.length-1];
value = value.split("+")[0];
value = value.replace(new RegExp(" ", 'g'), "_");
value = value.replace(new RegExp("-", 'g'), "_");
if (ALL_TAGS.indexOf(value) == -1)
var value = box.value;
value = tag_autocomplete.resolve(value);
if (value === null)
{
return;
}
@ -561,8 +557,7 @@ function tag_input_hook(box, inputted_list, li_class)
box.value = "";
}
ALL_TAGS = {{all_tags|safe}};
tag_autocomplete.init_datalist();
var input_musts = document.getElementById("search_builder_musts_input");
var input_mays = document.getElementById("search_builder_mays_input");
var input_forbids = document.getElementById("search_builder_forbids_input");