Finish adding tag suggestions
Lots of changes, did an inital wack at adding tag suggestions.
This commit is contained in:
parent
9d2c95bb33
commit
701800cfe2
|
@ -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;}
|
||||||
|
}
|
|
@ -1,11 +1,41 @@
|
||||||
console.log("Hello, world!");
|
|
||||||
|
|
||||||
tag_suggestion_list = {
|
/*Singleton object*/
|
||||||
list_element: null,
|
var tag_suggestion_list = {
|
||||||
|
input_el: null,
|
||||||
|
list_element: document.createElement('ol'),
|
||||||
suggestion_elements: [],
|
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
|
* Stolen from medium.com/@jh3y
|
||||||
|
@ -15,75 +45,114 @@ var lel = document.createElement('div');
|
||||||
* @param {number} selectionPoint - the selection point for the input
|
* @param {number} selectionPoint - the selection point for the input
|
||||||
*/
|
*/
|
||||||
function getCursorXY(input, selectionPoint){
|
function getCursorXY(input, selectionPoint){
|
||||||
const {
|
const {
|
||||||
offsetLeft: inputX,
|
offsetLeft: inputX,
|
||||||
offsetTop: inputY,
|
offsetTop: inputY,
|
||||||
} = input
|
} = input
|
||||||
// create a dummy element that will be a clone of our input
|
const div = document.createElement('div')
|
||||||
const div = document.createElement('div')
|
const copyStyle = getComputedStyle(input)
|
||||||
// get the computed style of the input and clone it onto the dummy element
|
for (const prop of copyStyle) {
|
||||||
const copyStyle = getComputedStyle(input)
|
div.style[prop] = copyStyle[prop]
|
||||||
for (const prop of copyStyle) {
|
}
|
||||||
div.style[prop] = copyStyle[prop]
|
const swap = '.'
|
||||||
}
|
const inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value
|
||||||
// we need a character that will replace whitespace when filling our dummy element if it's a single line <input/>
|
const textContent = inputValue.substr(0, selectionPoint)
|
||||||
const swap = '.'
|
div.textContent = textContent
|
||||||
const inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value
|
if (input.tagName === 'TEXTAREA') div.style.height = 'auto'
|
||||||
// set the div content to that of the textarea up until selection
|
if (input.tagName === 'INPUT') div.style.width = 'auto'
|
||||||
const textContent = inputValue.substr(0, selectionPoint)
|
const span = document.createElement('span')
|
||||||
// set the text content of the dummy element div
|
span.textContent = inputValue.substr(selectionPoint) || '.'
|
||||||
div.textContent = textContent
|
div.appendChild(span)
|
||||||
if (input.tagName === 'TEXTAREA') div.style.height = 'auto'
|
document.body.appendChild(div)
|
||||||
// if a single line input then the div needs to be single line and not break out like a text area
|
const { offsetLeft: spanX, offsetTop: spanY } = span
|
||||||
if (input.tagName === 'INPUT') div.style.width = 'auto'
|
document.body.removeChild(div)
|
||||||
// create a marker element to obtain caret position
|
return {
|
||||||
const span = document.createElement('span')
|
x: inputX + spanX,
|
||||||
// give the span the textContent of remaining content so that the recreated dummy element is as close as possible
|
y: inputY + spanY,
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function display_suggestions(elem, sugg, event){
|
function display_suggestions(elem, sugg, event){
|
||||||
console.log("sugg:",sugg);
|
|
||||||
//Check that the value hasn't change since we fired
|
//Check that the value hasn't change since we fired
|
||||||
//off the request
|
//off the request
|
||||||
|
var tags_so_far = elem.value.split(";");
|
||||||
recent = elem.value.split(";").pop().trim();
|
recent = elem.value.split(";").pop().trim();
|
||||||
if(recent == sugg[0]){
|
if(recent == sugg[0]){
|
||||||
var sugx, sugy = getCursorXY(elem,elem.value.length);
|
var v = getCursorXY(elem,elem.value.length);
|
||||||
console.log("Looking at position to display suggestions:",sugx, sugy);
|
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){
|
for(var i in sugg){
|
||||||
console.log("Displaying suggestion:",sugg[i]);
|
if(i == 0){
|
||||||
lel.setAttribute('style',`left: $(sugx)px; top: $(sugy)px;`);
|
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){
|
function hint_tags(elem, event){
|
||||||
//Get the most recent tag
|
//Get the most recent tag
|
||||||
recent = elem.value.split(";").pop().trim();
|
var recent = elem.value.split(";").pop().trim();
|
||||||
if(recent.length > 0){
|
if(recent.length > 0){
|
||||||
console.log("Most recent tag:",recent);
|
|
||||||
//Ask the server for tags that look like this
|
//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.open("GET", "/_api?call=suggest&data=" + recent);
|
||||||
xhr.onreadystatechange = function(e){
|
xhr.onreadystatechange = function(e){
|
||||||
if(xhr.readyState === 4){
|
if(xhr.readyState === 4){
|
||||||
console.log("Event:",e);
|
|
||||||
suggestions = xhr.response.split(";");
|
suggestions = xhr.response.split(";");
|
||||||
console.log("suggestions:",suggestions);
|
|
||||||
display_suggestions(elem,suggestions, event);
|
display_suggestions(elem,suggestions, event);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -93,13 +162,34 @@ function hint_tags(elem, event){
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(){
|
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);
|
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){
|
tag_el.onkeyup = function(event){
|
||||||
console.log("Looking at tag:", event);
|
|
||||||
console.log("And element:",tag_el);
|
|
||||||
hint_tags(tag_el, 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);
|
document.addEventListener("DOMContentLoaded",init,false);
|
||||||
|
|
|
@ -40,6 +40,7 @@ domain * {
|
||||||
|
|
||||||
route / home
|
route / home
|
||||||
route /_css/style.css asset_serve_style_css
|
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.css asset_serve_milligram_css
|
||||||
route /_css/milligram.min.css.map asset_serve_milligram_min_css_map
|
route /_css/milligram.min.css.map asset_serve_milligram_min_css_map
|
||||||
route /_faq asset_serve_faq_html
|
route /_faq asset_serve_faq_html
|
||||||
|
|
|
@ -13,35 +13,16 @@ function configure(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function suggest_tags(req,data)
|
local function suggest_tags(req,data)
|
||||||
print("Suggesting tags!")
|
|
||||||
stmnt_tags_get:bind_names{
|
stmnt_tags_get:bind_names{
|
||||||
match = data .. "%"
|
match = data .. "%"
|
||||||
}
|
}
|
||||||
local err = util.do_sql(stmnt_tags_get)
|
local tags = {data}
|
||||||
if err == sql.ROW or err == sql.DONE then
|
for tag in stmnt_tags_get:rows() do
|
||||||
local tags = {data}
|
table.insert(tags,tag[1])
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
|
stmnt_tags_get:reset()
|
||||||
|
http_response_header(req,"Content-Type","text/plain")
|
||||||
|
http_response(req,200,table.concat(tags,";"))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function api_get(req)
|
local function api_get(req)
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<form action="https://<%= user %>.<%= domain %>/_edit" method="post" class="container">
|
<form action="https://<%= user %>.<%= domain %>/_edit" method="post" class="container">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input type="text" name="title" placeholder="Title" class="column column-70" value="<%= title %>"></input>
|
<input type="text" name="title" placeholder="Title" class="column column-60" value="<%= title %>"></input>
|
||||||
<input type="hidden" name="story" value="<%= story %>">
|
<input type="hidden" name="story" value="<%= story %>">
|
||||||
<select id="pasteas" name="pasteas" class="column column-10">
|
<select id="pasteas" name="pasteas" class="column column-10">
|
||||||
<% if isanon then %>
|
<% if isanon then %>
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
<option value="plain">Plain</option>
|
<option value="plain">Plain</option>
|
||||||
<option value="imageboard">Imageboard</option>
|
<option value="imageboard">Imageboard</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="column column-10">
|
<div class="column column-20">
|
||||||
<label for="unlisted" class="label-inline">Unlisted</label>
|
<label for="unlisted" class="label-inline">Unlisted</label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
<% if err then %><em class="error"><%= err %></em><% end %>
|
<% if err then %><em class="error"><%= err %></em><% end %>
|
||||||
<form action="https://<%= domain %>/_paste" method="post" class="container"><fieldset>
|
<form action="https://<%= domain %>/_paste" method="post" class="container"><fieldset>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input type="text" name="title" placeholder="Title" class="column column-70"></input>
|
<input type="text" name="title" placeholder="Title" class="column column-60"></input>
|
||||||
<select id="markup" name="markup" class="column column-20">
|
<select id="markup" name="markup" class="column column-20">
|
||||||
<option value="plain">Plain</option>
|
<option value="plain">Plain</option>
|
||||||
<option value="imageboard">Imageboard</option>
|
<option value="imageboard">Imageboard</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="column column-10">
|
<div class="column column-20">
|
||||||
<label for="unlisted" class="label-inline">Unlisted</label>
|
<label for="unlisted" class="label-inline">Unlisted</label>
|
||||||
<input type="checkbox" name="unlisted" id="unlisted"></input>
|
<input type="checkbox" name="unlisted" id="unlisted"></input>
|
||||||
</div>
|
</div>
|
||||||
|
|
12
src/smr.c
12
src/smr.c
|
@ -104,61 +104,51 @@ do_lua(struct http_request *req, const char *name){
|
||||||
|
|
||||||
int
|
int
|
||||||
post_story(struct http_request *req){
|
post_story(struct http_request *req){
|
||||||
printf("We want to post!\n");
|
|
||||||
return do_lua(req,"paste");
|
return do_lua(req,"paste");
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
edit_story(struct http_request *req){
|
edit_story(struct http_request *req){
|
||||||
printf("We want to edit!\n");
|
|
||||||
return do_lua(req,"edit");
|
return do_lua(req,"edit");
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
edit_bio(struct http_request *req){
|
edit_bio(struct http_request *req){
|
||||||
printf("We want to edit bio!\n");
|
|
||||||
return do_lua(req,"edit_bio");
|
return do_lua(req,"edit_bio");
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
read_story(struct http_request *req){
|
read_story(struct http_request *req){
|
||||||
printf("We want to read!\n");
|
|
||||||
return do_lua(req,"read");
|
return do_lua(req,"read");
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
login(struct http_request *req){
|
login(struct http_request *req){
|
||||||
printf("We want to login!\n");
|
|
||||||
return do_lua(req,"login");
|
return do_lua(req,"login");
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
logout(struct http_request *req){
|
logout(struct http_request *req){
|
||||||
printf("We want to log out!\n");
|
|
||||||
return do_lua(req,"logout");
|
return do_lua(req,"logout");
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
claim(struct http_request *req){
|
claim(struct http_request *req){
|
||||||
printf("We want to claim!\n");
|
|
||||||
return do_lua(req,"claim");
|
return do_lua(req,"claim");
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
download(struct http_request *req){
|
download(struct http_request *req){
|
||||||
printf("We want to do download!\n");
|
|
||||||
return do_lua(req,"download");
|
return do_lua(req,"download");
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
preview(struct http_request *req){
|
preview(struct http_request *req){
|
||||||
printf("We want to do preview!\n");
|
|
||||||
return do_lua(req,"preview");
|
return do_lua(req,"preview");
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
search(struct http_request *req){
|
search(struct http_request *req){
|
||||||
printf("We want to do search!\n");
|
|
||||||
return do_lua(req,"search");
|
return do_lua(req,"search");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,13 +166,11 @@ archive(struct http_request *req){
|
||||||
return KORE_RESULT_OK;
|
return KORE_RESULT_OK;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
printf("We want to do archive!\n");
|
|
||||||
return do_lua(req,"archive");
|
return do_lua(req,"archive");
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
api(struct http_request *req){
|
api(struct http_request *req){
|
||||||
printf("Api call!\n");
|
|
||||||
return do_lua(req,"api");
|
return do_lua(req,"api");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue