/*Singleton object*/ var tag_suggestion_list = { input_el: null, list_element: document.createElement('ol'), suggestion_elements: [], hover_last: -1, } tag_suggestion_list.list_element.setAttribute("class","tag-suggestion-list"); tag_suggestion_list.list_element.setAttribute("style","position:absolute;"); function appendTag(name){ return function(event){ var ie = tag_suggestion_list.input_el; var prev = ie.value.split(";"); prev.pop(); prev.push(name); ie.value = prev.join(";"); ie.value += ";"; ie.focus(); tag_suggestion_list.list_element.style = "display:none;"; } } function hoverTag(name, root){ return function(event){ var ie = tag_suggestion_list.input_el; if(ie.value.slice(-1) == ";"){//comming from another tab completion var prev = ie.value.slice(hover_last); }else{ var prev = ie.value.split(";"); prev.pop() prev.push(name) ie.value = prev.join(";"); } } } /** * Stolen from medium.com/@jh3y * returns x, y coordinates for absolute positioning of a span within a given text input * at a given selection point * @param {object} input - the input element to obtain coordinates for * @param {number} selectionPoint - the selection point for the input */ function getCursorXY(input, selectionPoint){ const { offsetLeft: inputX, offsetTop: inputY, } = input const div = document.createElement('div') const copyStyle = getComputedStyle(input) for (const prop of copyStyle) { div.style[prop] = copyStyle[prop] } const swap = '.' const inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value const textContent = inputValue.substr(0, selectionPoint) div.textContent = textContent if (input.tagName === 'TEXTAREA') div.style.height = 'auto' if (input.tagName === 'INPUT') div.style.width = 'auto' const span = document.createElement('span') span.textContent = inputValue.substr(selectionPoint) || '.' div.appendChild(span) document.body.appendChild(div) const { offsetLeft: spanX, offsetTop: spanY } = span document.body.removeChild(div) return { x: inputX + spanX, y: inputY + spanY, } } function display_suggestions(elem, sugg, event){ //Check that the value hasn't change since we fired //off the request var tags_so_far = elem.value.split(";"); recent = elem.value.split(";").pop().trim(); if(recent == sugg[0]){ var v = getCursorXY(elem,elem.value.length); var sugx = v.x; var sugy = v.y; var sty = `position:absolute; margin-left:${sugx}px;`; tag_suggestion_list.list_element.style = sty; for(var i in tag_suggestion_list.suggestion_elements){ tag_suggestion_list.list_element.removeChild(tag_suggestion_list.suggestion_elements[i]); } tag_suggestion_list.suggestion_elements = []; var hover_last = 0; for(var i in tags_so_far){ hover_last += tags_so_far[i].length + 1; } tag_suggestion_list.hover_last = hover_last; for(var i in sugg){ if(i == 0){ continue; } var suggestion_el = document.createElement("li"); var suggestion_but = document.createElement("input") suggestion_el.appendChild(suggestion_but); suggestion_but.setAttribute("type","button"); suggestion_but.setAttribute("value",sugg[i]); suggestion_el.setAttribute("class"," button-clear tag-suggestion"); tag_suggestion_list.list_element.appendChild(suggestion_el); tag_suggestion_list.suggestion_elements.push(suggestion_el); suggestion_but.onkeyup = function(event){ if(event.key == "Tab"){ hoverTag(event.target.value)(event); }else if(event.key == ";"){ appendTag(event.target.value)(event); } } suggestion_but.onclick = function(event){ appendTag(event.target.value)(event); } suggestion_but.onblur = function(event){ var other_input = false; for(var i in tag_suggestion_list.suggestion_elements){ if(tag_suggestion_list.suggestion_elements[i].firstChild == event.relatedTarget){ other_input = true; break; } } if(!other_input){ tag_suggestion_list.list_element.style = "display:none;"; } } } if(tag_suggestion_list.suggestion_elements.length > 0){ var last_element = tag_suggestion_list.suggestion_elements[tag_suggestion_list.suggestion_elements.length - 1]; //last_element.firstChild.last_element = true; last_element.firstChild.onblur = function(event){ tag_suggestion_list.suggestion_elements[0].firstChild.focus(); } } } } function hint_tags(elem, event){ //Get the most recent tag var recent = elem.value.split(";").pop().trim(); if(recent.length > 0){ //Ask the server for tags that look like this var xhr = new XMLHttpRequest(); xhr.open("GET", "/_api?call=suggest&data=" + recent); xhr.onreadystatechange = function(e){ if(xhr.readyState === 4){ suggestions = xhr.response.split(";"); display_suggestions(elem,suggestions, event); } } xhr.send() } } function init(){ var head_el = document.head; var extra_css_el = document.createElement("link"); document.head.appendChild(extra_css_el); extra_css_el.setAttribute("rel","stylesheet"); extra_css_el.setAttribute("href","/_css/suggest_tags.css"); var tag_el_list = document.getElementsByName("tags"); console.assert(tag_el_list.length == 1); var tag_el = tag_el_list[0]; tag_suggestion_list.input_el = tag_el; tag_el.onkeyup = function(event){ hint_tags(tag_el, event); } tag_el.onblur = function(event){ var not_suggestion = true; var ies = tag_suggestion_list.suggestion_elements; for(var i in ies){ if(event.relatedTarget == ies[i].firstChild){ not_suggestion = false; break; } } if(not_suggestion){ tag_suggestion_list.list_element.style = "display:none;"; } } var fieldset = tag_el.parentNode; fieldset.appendChild(tag_suggestion_list.list_element); var paste_el = document.getElementsByName("tags"); } document.addEventListener("DOMContentLoaded",init,false);