From 701800cfe2ed86ad3240e8e09c9122bf588ada6b Mon Sep 17 00:00:00 2001 From: Robin Malley Date: Mon, 22 Feb 2021 06:59:51 +0000 Subject: [PATCH] Finish adding tag suggestions Lots of changes, did an inital wack at adding tag suggestions. --- assets/suggest_tags.css | 29 +++++ assets/suggest_tags.js | 204 ++++++++++++++++++++++++---------- conf/smr.conf.in | 1 + src/lua/endpoints/api_get.lua | 31 +----- src/pages/edit.etlua.in | 4 +- src/pages/paste.etlua.in | 4 +- src/smr.c | 12 -- 7 files changed, 187 insertions(+), 98 deletions(-) create mode 100644 assets/suggest_tags.css diff --git a/assets/suggest_tags.css b/assets/suggest_tags.css new file mode 100644 index 0000000..49d7eb2 --- /dev/null +++ b/assets/suggest_tags.css @@ -0,0 +1,29 @@ +.tag-suggestion{ + font-size:0.8rem !important; + height: 1rem !important; + margin: 0px; + display:block !important; +} +.tag-suggestion>input{ + line-height:1rem !important; + height:1rem !important; + width:100% !important; + text-align:left; + background-color:transparent; + color:black; + border:none; +} +.tag-suggestion-list{ + list-style: none; + margin-top:3.8rem; + background: white; + border: 1px solid black; + border-top: 0px; +} +@media (prefers-color-scheme: dark){ + body, input, select, textarea, pre, code{ + background: #1c1428; + color: #d0d4d8 !important; + } + .spoiler, .spoiler2{color:#444;} +} diff --git a/assets/suggest_tags.js b/assets/suggest_tags.js index 5ffd823..1164945 100644 --- a/assets/suggest_tags.js +++ b/assets/suggest_tags.js @@ -1,11 +1,41 @@ -console.log("Hello, world!"); -tag_suggestion_list = { - list_element: null, +/*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;"; + } } -var lel = document.createElement('div'); +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 @@ -15,75 +45,114 @@ var lel = document.createElement('div'); * @param {number} selectionPoint - the selection point for the input */ function getCursorXY(input, selectionPoint){ - const { - offsetLeft: inputX, - offsetTop: inputY, - } = input - // create a dummy element that will be a clone of our input - const div = document.createElement('div') - // get the computed style of the input and clone it onto the dummy element - const copyStyle = getComputedStyle(input) - for (const prop of copyStyle) { - div.style[prop] = copyStyle[prop] - } - // we need a character that will replace whitespace when filling our dummy element if it's a single line - const swap = '.' - const inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value - // set the div content to that of the textarea up until selection - const textContent = inputValue.substr(0, selectionPoint) - // set the text content of the dummy element div - div.textContent = textContent - if (input.tagName === 'TEXTAREA') div.style.height = 'auto' - // if a single line input then the div needs to be single line and not break out like a text area - if (input.tagName === 'INPUT') div.style.width = 'auto' - // create a marker element to obtain caret position - const span = document.createElement('span') - // give the span the textContent of remaining content so that the recreated dummy element is as close as possible - span.textContent = inputValue.substr(selectionPoint) || '.' - // append the span marker to the div - div.appendChild(span) - // append the dummy element to the body - document.body.appendChild(div) - // get the marker position, this is the caret position top and left relative to the input - const { offsetLeft: spanX, offsetTop: spanY } = span - // lastly, remove that dummy element - // NOTE:: can comment this out for debugging purposes if you want to see where that span is rendered - document.body.removeChild(div) - // return an object with the x and y of the caret. account for input positioning so that you don't need to wrap the input - return { - x: inputX + spanX, - y: inputY + spanY, - } + 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){ - console.log("sugg:",sugg); //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 sugx, sugy = getCursorXY(elem,elem.value.length); - console.log("Looking at position to display suggestions:",sugx, sugy); + 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){ - console.log("Displaying suggestion:",sugg[i]); - lel.setAttribute('style',`left: $(sugx)px; top: $(sugy)px;`); + 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 - recent = elem.value.split(";").pop().trim(); + var recent = elem.value.split(";").pop().trim(); if(recent.length > 0){ - console.log("Most recent tag:",recent); //Ask the server for tags that look like this - xhr = new XMLHttpRequest(); + var xhr = new XMLHttpRequest(); xhr.open("GET", "/_api?call=suggest&data=" + recent); xhr.onreadystatechange = function(e){ if(xhr.readyState === 4){ - console.log("Event:",e); suggestions = xhr.response.split(";"); - console.log("suggestions:",suggestions); display_suggestions(elem,suggestions, event); } @@ -93,13 +162,34 @@ function hint_tags(elem, event){ } function init(){ - tag_el_list = document.getElementsByName("tags"); + 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); - tag_el = tag_el_list[0]; + var tag_el = tag_el_list[0]; + tag_suggestion_list.input_el = tag_el; tag_el.onkeyup = function(event){ - console.log("Looking at tag:", event); - console.log("And element:",tag_el); 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); diff --git a/conf/smr.conf.in b/conf/smr.conf.in index a896510..3f36dab 100644 --- a/conf/smr.conf.in +++ b/conf/smr.conf.in @@ -40,6 +40,7 @@ domain * { route / home route /_css/style.css asset_serve_style_css + route /_css/suggest_tags.css asset_serve_suggest_tags_css route /_css/milligram.css asset_serve_milligram_css route /_css/milligram.min.css.map asset_serve_milligram_min_css_map route /_faq asset_serve_faq_html diff --git a/src/lua/endpoints/api_get.lua b/src/lua/endpoints/api_get.lua index bc156b8..99dd228 100644 --- a/src/lua/endpoints/api_get.lua +++ b/src/lua/endpoints/api_get.lua @@ -13,35 +13,16 @@ function configure(...) end local function suggest_tags(req,data) - print("Suggesting tags!") stmnt_tags_get:bind_names{ match = data .. "%" } - local err = util.do_sql(stmnt_tags_get) - if err == sql.ROW or err == sql.DONE then - local tags = {data} - for tag in stmnt_tags_get:rows() do - print("Found tag:",tag[1]) - table.insert(tags,tag[1]) - end - stmnt_tags_get:reset() - http_response_header(req,"Content-Type","text/plain") - http_response(req,200,table.concat(tags,";")) - else - log(LOG_ALERT,"Failed to get tag suggestions in an unusual way:" .. err .. ":" .. db.conn:errmsg()) - --This is bad though - local page = pages.error({ - errcode = 500, - errcodemsg = "Server error", - explanation = string.format( - "Failed to retreive tags from database:%d:%q", - err, - db.conn:errmsg() - ), - }) - stmnt_tags_get:reset() - http_response(req,500,page) + local tags = {data} + for tag in stmnt_tags_get:rows() do + table.insert(tags,tag[1]) end + stmnt_tags_get:reset() + http_response_header(req,"Content-Type","text/plain") + http_response(req,200,table.concat(tags,";")) end local function api_get(req) diff --git a/src/pages/edit.etlua.in b/src/pages/edit.etlua.in index 525c2ef..5aa05c4 100644 --- a/src/pages/edit.etlua.in +++ b/src/pages/edit.etlua.in @@ -6,7 +6,7 @@
- + -
+
<%= err %><% end %>
- + -
+
diff --git a/src/smr.c b/src/smr.c index e477952..11f0f00 100644 --- a/src/smr.c +++ b/src/smr.c @@ -104,61 +104,51 @@ do_lua(struct http_request *req, const char *name){ int post_story(struct http_request *req){ - printf("We want to post!\n"); return do_lua(req,"paste"); } int edit_story(struct http_request *req){ - printf("We want to edit!\n"); return do_lua(req,"edit"); } int edit_bio(struct http_request *req){ - printf("We want to edit bio!\n"); return do_lua(req,"edit_bio"); } int read_story(struct http_request *req){ - printf("We want to read!\n"); return do_lua(req,"read"); } int login(struct http_request *req){ - printf("We want to login!\n"); return do_lua(req,"login"); } int logout(struct http_request *req){ - printf("We want to log out!\n"); return do_lua(req,"logout"); } int claim(struct http_request *req){ - printf("We want to claim!\n"); return do_lua(req,"claim"); } int download(struct http_request *req){ - printf("We want to do download!\n"); return do_lua(req,"download"); } int preview(struct http_request *req){ - printf("We want to do preview!\n"); return do_lua(req,"preview"); } int search(struct http_request *req){ - printf("We want to do search!\n"); return do_lua(req,"search"); } @@ -176,13 +166,11 @@ archive(struct http_request *req){ return KORE_RESULT_OK; } */ - printf("We want to do archive!\n"); return do_lua(req,"archive"); } int api(struct http_request *req){ - printf("Api call!\n"); return do_lua(req,"api"); }