1
1
Fork 0

YEEEEEEEEEEEEEEEEET TAG SELECT INPUT,

finally goddamn it
dev
Jan Kužílek 5 years ago
parent d9cb0b996a
commit cafe8130c2

@ -16,7 +16,7 @@ def post_index():
def tag_autocomplete(): def tag_autocomplete():
query = request.args.get('q', '') query = request.args.get('q', '')
if query != '': if query != '':
tags = Tag.query.filter(Tag.content.like("%{}%".format(query))).limit(5).all() tags = Tag.query.filter(Tag.content.like("%{}%".format(query))).limit(15).all()
return jsonify([{"id": t.id, "content": t.content, "content_deser": t.content_deser, "category": {"id": t.category.value, "name": t.category.name}} for t in tags]) return jsonify([{"id": t.id, "content": t.content, "content_deser": t.content_deser, "category": {"id": t.category.value, "name": t.category.name}} for t in tags])
return jsonify() return jsonify()

@ -108,9 +108,10 @@ def upload():
file = request.files.get(form.post_img.name) file = request.files.get(form.post_img.name)
file.data = io.BytesIO(file.read()) file.data = io.BytesIO(file.read())
# tags = form.tags.data.split() f_tags = form.tags.data.split()
tags = Tag.query.filter(Tag.content.in_(f_tags)).all()
post = Post(file, source=form.sauce.data, rating=RATING[form.rating.data], author=current_user) post = Post(file, source=form.sauce.data, tags=tags, rating=RATING[form.rating.data], author=current_user)
with open(post.image_path, "wb") as f: with open(post.image_path, "wb") as f:

@ -63,8 +63,8 @@ def validate_file(form, field):
class UploadForm(CSRFForm): class UploadForm(CSRFForm):
post_img = FileField('Image', validators=[validate_file], render_kw={'required':''}) post_img = FileField('Image', validators=[validate_file], render_kw={'required':''})
sauce = StringField('Sauce', validators=[DataRequired()]) sauce = StringField('Sauce', validators=[DataRequired()], render_kw={'placeholder':'Source URL','autocomplete':'off'})
tags = StringField('Tags', validators=[DataRequired()]) # CUSTOM VALIDATOR (also for Post edits) tags = StringField('Tags', validators=[DataRequired()], render_kw={'placeholder':'Tags','autocomplete':'off'}) # CUSTOM VALIDATOR (also for Post edits)
rating = RadioField('Rating', rating = RadioField('Rating',
choices=[('safe', 'Safe'), ('questionable', 'Questionable'), ('explicit', 'Explicit')], choices=[('safe', 'Safe'), ('questionable', 'Questionable'), ('explicit', 'Explicit')],
default='safe', default='safe',
@ -82,7 +82,7 @@ class UploadForm(CSRFForm):
class CommentForm(CSRFForm): class CommentForm(CSRFForm):
post_id = HiddenField(validators=[DataRequired()]) post_id = HiddenField(validators=[DataRequired()])
content = TextAreaField('Comment', validators=[DataRequired()]) content = TextAreaField('Comment', validators=[DataRequired()], render_kw={'autocomplete':'off'})
submit = SubmitField('Send') submit = SubmitField('Send')
class ChangePassForm(CSRFForm): class ChangePassForm(CSRFForm):
@ -116,7 +116,7 @@ class EditPostForm(CSRFForm):
status = SelectField('Status', status = SelectField('Status',
choices=[('pending', 'Pending'), ('active', 'Active'), ('deleted', 'Deleted')], choices=[('pending', 'Pending'), ('active', 'Active'), ('deleted', 'Deleted')],
validators=[optional()]) validators=[optional()])
source = StringField('Source') source = StringField('Source', render_kw={'autocomplete':'off'})
edit = SubmitField('Modify') edit = SubmitField('Modify')
delete = SubmitField('Delete') delete = SubmitField('Delete')

File diff suppressed because one or more lines are too long

@ -1,5 +1,5 @@
let nav_menu = document.querySelectorAll("#nav-menu, nav#main-nav > ._overlay") let nav_menu = document.querySelectorAll("#nav-menu, nav#main-nav > ._overlay")
let nav_menu_event = function(ev) { function nav_menu_event(event) {
let drop = document.getElementById("main-nav") let drop = document.getElementById("main-nav")
let html = document.getElementsByTagName('html')[0] let html = document.getElementsByTagName('html')[0]
if (!drop.classList.contains("_drop")) { if (!drop.classList.contains("_drop")) {

@ -264,6 +264,79 @@ header {
font-size: 1.3em; font-size: 1.3em;
} }
form .tag_input, .tag_input form {
.tag_suggester {
width: calc(100% - 50px);
position: relative;
// input[type=text] {
// width: 100%;
// }
> .tag_suggest_dropdown {
display: flex;
flex-flow: column nowrap;
align-items: start;
// width: 100%;
top: 100%;
left: 0;
position: absolute;
z-index: 10;
overflow: auto;
background-color: #2d2d2de0;
}
}
.tag_container {
display: flex;
> a {
margin: 2px 2px;
// padding: .4em .75em;
padding: .35em .6em;
//text-align: center;
border-radius: 4px;
background-color: #121212ff;
cursor: pointer;
> * { pointer-events: none; }
> .fa-tag {
font-size: .9em;
margin-right: 2px;
}
> .count {
// display: none;
font-size: .8em;
}
&:not(:hover) {
> span.close {
display: none;
}
> span.plus {
display: none;
}
}
&:hover {
> span.count {
display: none;
}
}
&.tagselected {
background-color: #400808ff;
}
&.tag_hide {
display: none;
}
}
}
}
// $side-panel-width: 14rem; // $side-panel-width: 14rem;
$side-panel-width: 18rem; $side-panel-width: 18rem;
.important_subwrap { .important_subwrap {
@ -297,78 +370,6 @@ header {
} }
article.tags { article.tags {
.searchbox {
width: calc(#{$side-panel-width} - 50px);
position: relative;
input[name=tagsearch] {
width: 100%;
}
> .search_dropdown {
display: flex;
flex-flow: column nowrap;
align-items: start;
// width: 100%;
top: 100%;
left: 0;
position: absolute;
z-index: 10;
overflow: auto;
background-color: #2d2d2de0;
}
}
.tag_container, .search_dropdown {
> a {
margin: 2px 2px;
// padding: .4em .75em;
padding: .35em .6em;
//text-align: center;
border-radius: 4px;
background-color: #121212ff;
cursor: pointer;
> * { pointer-events: none; }
> .fa-tag {
font-size: .9em;
margin-right: 2px;
}
> .count {
// display: none;
font-size: .8em;
}
&:not(:hover) {
> span.close {
display: none;
}
> span.plus {
display: none;
}
}
&:hover {
> span.count {
display: none;
}
}
&.tagselected {
background-color: #400808ff;
}
&.tag_hide {
display: none;
}
}
}
.tag_container {
display: flex;
}
@include media($bp-tablet) { @include media($bp-tablet) {
.tag_container { .tag_container {
flex-flow: row wrap; flex-flow: row wrap;

@ -1,6 +1,6 @@
let rows = document.querySelectorAll("section.management_table tbody > tr") let rows = document.querySelectorAll("section.management_table tbody > tr")
let show_edit_controls = function(ev) { function show_edit_controls(event) {
let row = ev.target.closest("tr") let row = event.target.closest("tr")
console.log(row) console.log(row)
if (!row.classList.contains("edit")) { if (!row.classList.contains("edit")) {
row.classList.add("edit") row.classList.add("edit")

@ -1,14 +1,21 @@
let tagroot = document.querySelector('article.tags') let tagroot = document.querySelector('.tag_input')
let sel_tags = tagroot.querySelector('div.tag_container.tags_selected') let sel_tags = tagroot.querySelector('div.tags_selected')
let page_tags = tagroot.querySelector('div.tag_container.tags_inpage') let predef_tags = tagroot.querySelector('div.tags_inpage') // Should be optional
let searchroot = tagroot.querySelector('.searchbox') let suggestroot = tagroot.querySelector('.tag_suggester')
searchroot.insertAdjacentHTML("beforeend", `<input type="hidden" name="tags">`)
let search_input = searchroot.querySelector('input[name=tags][type=text]') suggestroot.insertAdjacentHTML("beforeend", `<input type="hidden" name="${suggestroot.dataset.inputname}">`)
search_input.removeAttribute('name') let suggest_hidden = suggestroot.querySelector(`input[name=${suggestroot.dataset.inputname}][type=hidden]`)
search_input.value = ''
let search_hidden = document.querySelector('input[name=tags][type=hidden]') let suggest_input = suggestroot.querySelector(`input[name=${suggestroot.dataset.inputname}][type=text]`)
let search_dropdown = searchroot.querySelector('.search_dropdown') suggest_input.removeAttribute('name')
if (suggest_input.hasAttribute('required')) {
suggest_input.removeAttribute('required')
suggest_hidden.setAttribute('required', '')
}
suggest_input.value = ''
let tag_suggest_dropdown = suggestroot.querySelector('.tag_suggest_dropdown')
// https://stackoverflow.com/questions/8486099/how-do-i-parse-a-url-query-parameters-in-javascript // https://stackoverflow.com/questions/8486099/how-do-i-parse-a-url-query-parameters-in-javascript
function getJsonFromUrl(url) { function getJsonFromUrl(url) {
@ -22,7 +29,7 @@ function getJsonFromUrl(url) {
return result; return result;
} }
function newseltag(tagname) { function create_selection_tag(tagname) {
let tag = document.createElement("a") let tag = document.createElement("a")
tag.classList.add("tagselected") tag.classList.add("tagselected")
tag.dataset.tagname = tagname tag.dataset.tagname = tagname
@ -37,60 +44,65 @@ function newseltag(tagname) {
return tag return tag
} }
function tags_input_addtag(tagname) { function form_addtag(tagname) {
let val = search_hidden.value.split(' ') let val = suggest_hidden.value.split(' ')
val.push(tagname) val.push(tagname)
search_hidden.value = val.join(' ').trim() suggest_hidden.value = val.join(' ').trim()
// console.log(search_hidden.value)
} }
function tags_input_removetag(tagname) { function form_removetag(tagname) {
let val = search_hidden.value.split(' ') let val = suggest_hidden.value.split(' ')
val.splice(val.indexOf(tagname), 1) val.splice(val.indexOf(tagname), 1)
search_hidden.value = val.join(' ').trim() suggest_hidden.value = val.join(' ').trim()
// console.log(search_hidden.value)
} }
function addseltag(tagname) { function add_selected(tagname) {
// let pagetag = page_tags.querySelector("a.tag-"+tagname) if (predef_tags) {
let pagetag = page_tags.querySelector(`a[data-tagname=${tagname}]`) let pagetag = predef_tags.querySelector(`a[data-tagname=${tagname}]`)
if (pagetag) { if (pagetag) {
pagetag.classList.add('tag_hide') pagetag.classList.add('tag_hide')
}
} }
let newtag = newseltag(tagname) let newtag = create_selection_tag(tagname)
newtag.addEventListener('click', (event) => { newtag.addEventListener('click', (event) => {
removeseltag(event.target.dataset.tagname) remove_selected(event.target.dataset.tagname)
// console.log(`Deselected: ${event.target.dataset.tagname}`) // console.log(`Deselected: ${event.target.dataset.tagname}`)
}) })
sel_tags.appendChild(newtag) sel_tags.appendChild(newtag)
tags_input_addtag(tagname) form_addtag(tagname)
} }
function removeseltag(tagname) { function remove_selected(tagname) {
let pagetag = page_tags.querySelector(`a[data-tagname=${tagname}]`) if (predef_tags) {
if (pagetag) { let pagetag = predef_tags.querySelector(`a[data-tagname=${tagname}]`)
pagetag.classList.remove('tag_hide') if (pagetag) {
pagetag.classList.remove('tag_hide')
}
} }
sel_tags.querySelector(`a[data-tagname=${tagname}]`).remove() sel_tags.querySelector(`a[data-tagname=${tagname}]`).remove()
tags_input_removetag(tagname) form_removetag(tagname)
} }
page_tags.querySelectorAll("a").forEach(element => {
element.addEventListener('click', (event) => { // Page tags click to select
addseltag(event.target.dataset.tagname) if (predef_tags) {
// console.log(`Selected: ${event.target.dataset.tagname}`) predef_tags.querySelectorAll("a").forEach(element => {
event.preventDefault() element.addEventListener('click', (event) => {
add_selected(event.target.dataset.tagname)
event.preventDefault()
})
}) })
}) }
// Generate selected by url query
let query = getJsonFromUrl() let query = getJsonFromUrl()
if (query['tags']) { if (query['tags']) {
query['tags'].split('+').forEach((tag) => { query['tags'].split('+').forEach((tag) => {
addseltag(tag) add_selected(tag)
}) })
} }
// Suggestions // Suggestions
function newsugtag(tagname) { function create_suggestion_tag(tagname) {
let tag = document.createElement("a") let tag = document.createElement("a")
tag.classList.add("tagsuggestion") tag.classList.add("tagsuggestion")
tag.dataset.tagname = tagname tag.dataset.tagname = tagname
@ -103,43 +115,50 @@ function newsugtag(tagname) {
tag.querySelector("span.name").textContent = tagname.replace(/_/g, ' ') tag.querySelector("span.name").textContent = tagname.replace(/_/g, ' ')
return tag return tag
} }
function rendersuggestions(data) { function render_suggestions(data) {
search_dropdown.innerHTML = '' tag_suggest_dropdown.innerHTML = ''
// search_dropdown.querySelectorAll('a').forEach((element) => {
// search_dropdown.replaceChild(newsugtag)
// })
let tagnames = Array.from(sel_tags.querySelectorAll('a')).map(a => a.dataset.tagname)
let i = 0
data.forEach((el) => { data.forEach((el) => {
let sugtag = newsugtag(el.content) if (i > 5) return
if (tagnames.includes(el.content)) return
let sugtag = create_suggestion_tag(el.content)
sugtag.addEventListener('click', (event) => { sugtag.addEventListener('click', (event) => {
addseltag(event.target.dataset.tagname) add_selected(event.target.dataset.tagname)
console.log(`Selected: ${event.target.dataset.tagname}`) suggest_input.value = ''
render_suggestions([])
event.preventDefault() event.preventDefault()
}) })
search_dropdown.appendChild(sugtag) tag_suggest_dropdown.appendChild(sugtag)
i++
}); });
} }
var search_timeout
search_input.addEventListener('input', (event) => {
clearTimeout(search_timeout)
let value = search_input.value.trim() // Ajax suggestion handler
var ajax_timeout
suggest_input.addEventListener('input', (event) => {
clearTimeout(ajax_timeout)
let value = suggest_input.value.trim()
if (value.length < 2) { if (value.length < 2) {
rendersuggestions([]) render_suggestions([])
return return
} }
search_timeout = setTimeout(() => { ajax_timeout = setTimeout(() => {
fetch('/api/tags?q='+value).then((response) => { fetch('/api/tags?q='+value).then((response) => {
console.log(response) console.log(response)
return response.json() return response.json()
}).then((data) => { }).then((data) => {
// fill suggestions // fill suggestions
console.log(data) // console.log(data)
rendersuggestions(data) render_suggestions(data)
}) })
}, 500) }, 500)
}) })
@ -159,8 +178,3 @@ search_input.addEventListener('input', (event) => {
// console.log(event) // console.log(event)
// }) // })
// page_tags.querySelectorAll('a').forEach((el) => el.removeAttribute('href')) // page_tags.querySelectorAll('a').forEach((el) => el.removeAttribute('href'))
// console.log(page_tags)
// console.log(sel_tags)
// console.log(search_dropdown.children[0])

@ -64,4 +64,16 @@
<a href="{{ link }}">{{ name }}</a> <a href="{{ link }}">{{ name }}</a>
{% endfor %} {% endfor %}
</article> </article>
{% endmacro %}
{% macro render_tag_input(input_field) %}
<div class="tag_input">
<div class="tag_suggester" data-inputname="{{ input_field.name }}">
<!-- <input type="text" name="tags" autocomplete="off" value="{{ request.args.get('tags') }}"> -->
{{ input_field() }}
<div class="tag_container tag_suggest_dropdown"></div>
</div>
<div class="tag_container tags_selected"></div>
</div>
{% endmacro %} {% endmacro %}

@ -1,12 +1,12 @@
{% extends 'layout/base_sidebar.html' %} {% extends 'layout/base_sidebar.html' %}
{% block sidebar %} {% block sidebar %}
<article class="tags"> <article class="tags tag_input">
<form action="{{ url_for('post.posts') }}" method="get"> <form action="{{ url_for('post.posts') }}" method="get">
<h3>Search</h3> <h3>Search</h3>
<div class="searchbox"> <div class="tag_suggester" data-inputname="tags">
<input type="text" name="tags" id="search" autocomplete="off" value="{{ request.args.get('tags') }}"> <input type="text" name="tags" autocomplete="off" value="{{ request.args.get('tags') }}">
<div class="search_dropdown"></div> <div class="tag_container tag_suggest_dropdown"></div>
</div> </div>
<input type="submit" value="Search"> <input type="submit" value="Search">
@ -16,6 +16,7 @@
if (rating == request.args.get('rating') if request.args.get('rating') in ('safe', 'explicit', 'questionable') else rating == 'safe') if (rating == request.args.get('rating') if request.args.get('rating') in ('safe', 'explicit', 'questionable') else rating == 'safe')
%}checked{% endif %}> %}checked{% endif %}>
<label for="rating-{{ rating }}">{{ rating.capitalize() }}</label> <label for="rating-{{ rating }}">{{ rating.capitalize() }}</label>
<br>
{% endfor %} {% endfor %}
</div> </div>

@ -1,5 +1,6 @@
{% extends 'layout/base.html' %} {% extends 'layout/base.html' %}
{% from "_formhelpers.html" import errors %} {% from "_formhelpers.html" import errors %}
{% from "_includes.html" import render_tag_input %}
{% block content %} {% block content %}
<form action="" method="post" enctype="multipart/form-data"> <form action="" method="post" enctype="multipart/form-data">
@ -10,11 +11,12 @@
{{ errors(form.post_img) }} {{ errors(form.post_img) }}
</div> </div>
<div> <div>
{{ form.sauce(placeholder="Source URL") }} {{ form.sauce() }}
{{ errors(form.sauce) }} {{ errors(form.sauce) }}
</div> </div>
<div> <div>
{{ form.tags(placeholder="Tags") }} {{ render_tag_input(form.tags) }}
<!-- {{ form.tags(placeholder="Tags") }} -->
{{ errors(form.tags) }} {{ errors(form.tags) }}
</div> </div>
<div> <div>
@ -22,5 +24,7 @@
{{ errors(form.rating) }} {{ errors(form.rating) }}
</div> </div>
<div>{{ form.submit() }}</div> <div>{{ form.submit() }}</div>
<script src="{{ url_for('static', filename="search.js") }}"></script>
</form> </form>
{% endblock content %} {% endblock content %}
Loading…
Cancel
Save