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] names = [row[0] for row in rows]
return names 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): def get_root_tags(self):
''' '''
Yield Tags that have no parent. Yield Tags that have no parent.

View file

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

View file

@ -1,7 +1,9 @@
import flask; from flask import request import flask; from flask import request
import json
import etiquette import etiquette
from .. import caching
from .. import common from .. import common
from .. import decorators from .. import decorators
from .. import jsonify from .. import jsonify
@ -48,6 +50,14 @@ def post_tag_edit(specific_tag):
# Tag listings ##################################################################################### # 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('/tag/<specific_tag_name>')
@site.route('/tags') @site.route('/tags')
@session_manager.give_token @session_manager.give_token

View file

@ -121,3 +121,15 @@ function html_to_element(html)
template.innerHTML = html; template.innerHTML = html;
return template.content.firstChild; 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/common.js"></script>
<script src="/static/js/hotkeys.js"></script> <script src="/static/js/hotkeys.js"></script>
<script src="/static/js/photoclipboard.js"></script> <script src="/static/js/photoclipboard.js"></script>
<script src="/static/js/tag_autocomplete.js"></script>
<style> <style>
#content_body #content_body
@ -162,7 +163,7 @@
<h4>Tags</h4> <h4>Tags</h4>
<ul id="this_tags"> <ul id="this_tags">
<li> <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> <button id="add_tag_button" class="green_button" onclick="submit_tag(receive_callback);">add</button>
</li> </li>
{% set tags = photo.get_tags()|sort_tags %} {% set tags = photo.get_tags()|sort_tags %}
@ -434,6 +435,8 @@ function move_hoverzoom(event)
photo_img_holder.style.backgroundPosition=(-x)+"px "+(-y)+"px"; photo_img_holder.style.backgroundPosition=(-x)+"px "+(-y)+"px";
} }
tag_autocomplete.init_datalist();
setTimeout( setTimeout(
/* /*
When the screen is in column mode, the autofocusing of the tag box snaps the 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/common.js"></script>
<script src="/static/js/hotkeys.js"></script> <script src="/static/js/hotkeys.js"></script>
<script src="/static/js/photoclipboard.js"></script> <script src="/static/js/photoclipboard.js"></script>
<script src="/static/js/tag_autocomplete.js"></script>
<style> <style>
form form
@ -171,7 +172,7 @@ form
</li> </li>
{% endfor %} {% endfor %}
{% endif %} {% 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> </ul>
</div> </div>
{% endfor %} {% endfor %}
@ -525,10 +526,9 @@ function tags_on_this_page_hook()
*/ */
var tagname = this.innerHTML.split(/\./); var tagname = this.innerHTML.split(/\./);
tagname = tagname[tagname.length-1]; tagname = tagname[tagname.length-1];
var qualname = ALL_TAGS[tagname];
add_searchtag( add_searchtag(
input_musts, input_musts,
qualname, tagname,
inputted_musts, inputted_musts,
"search_builder_musts_inputted" "search_builder_musts_inputted"
); );
@ -546,13 +546,9 @@ function tag_input_hook(box, inputted_list, li_class)
if (!box.value) if (!box.value)
{return;} {return;}
var value = box.value.toLocaleLowerCase(); var value = box.value;
value = value.split("."); value = tag_autocomplete.resolve(value);
value = value[value.length-1]; if (value === null)
value = value.split("+")[0];
value = value.replace(new RegExp(" ", 'g'), "_");
value = value.replace(new RegExp("-", 'g'), "_");
if (ALL_TAGS.indexOf(value) == -1)
{ {
return; return;
} }
@ -561,8 +557,7 @@ function tag_input_hook(box, inputted_list, li_class)
box.value = ""; box.value = "";
} }
tag_autocomplete.init_datalist();
ALL_TAGS = {{all_tags|safe}};
var input_musts = document.getElementById("search_builder_musts_input"); var input_musts = document.getElementById("search_builder_musts_input");
var input_mays = document.getElementById("search_builder_mays_input"); var input_mays = document.getElementById("search_builder_mays_input");
var input_forbids = document.getElementById("search_builder_forbids_input"); var input_forbids = document.getElementById("search_builder_forbids_input");