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:
		
							parent
							
								
									bbf07f4401
								
							
						
					
					
						commit
						8a12a24e8e
					
				
					 7 changed files with 121 additions and 15 deletions
				
			
		|  | @ -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. | ||||||
|  |  | ||||||
|  | @ -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'], | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										79
									
								
								frontends/etiquette_flask/static/js/tag_autocomplete.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								frontends/etiquette_flask/static/js/tag_autocomplete.js
									
									
									
									
									
										Normal 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); | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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"); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue