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():
query = request.args.get('q', '')
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()

@ -108,9 +108,10 @@ def upload():
file = request.files.get(form.post_img.name)
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:

@ -63,8 +63,8 @@ def validate_file(form, field):
class UploadForm(CSRFForm):
post_img = FileField('Image', validators=[validate_file], render_kw={'required':''})
sauce = StringField('Sauce', validators=[DataRequired()])
tags = StringField('Tags', validators=[DataRequired()]) # CUSTOM VALIDATOR (also for Post edits)
sauce = StringField('Sauce', validators=[DataRequired()], render_kw={'placeholder':'Source URL','autocomplete':'off'})
tags = StringField('Tags', validators=[DataRequired()], render_kw={'placeholder':'Tags','autocomplete':'off'}) # CUSTOM VALIDATOR (also for Post edits)
rating = RadioField('Rating',
choices=[('safe', 'Safe'), ('questionable', 'Questionable'), ('explicit', 'Explicit')],
default='safe',
@ -82,7 +82,7 @@ class UploadForm(CSRFForm):
class CommentForm(CSRFForm):
post_id = HiddenField(validators=[DataRequired()])
content = TextAreaField('Comment', validators=[DataRequired()])
content = TextAreaField('Comment', validators=[DataRequired()], render_kw={'autocomplete':'off'})
submit = SubmitField('Send')
class ChangePassForm(CSRFForm):
@ -116,7 +116,7 @@ class EditPostForm(CSRFForm):
status = SelectField('Status',
choices=[('pending', 'Pending'), ('active', 'Active'), ('deleted', 'Deleted')],
validators=[optional()])
source = StringField('Source')
source = StringField('Source', render_kw={'autocomplete':'off'})
edit = SubmitField('Modify')
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_event = function(ev) {
function nav_menu_event(event) {
let drop = document.getElementById("main-nav")
let html = document.getElementsByTagName('html')[0]
if (!drop.classList.contains("_drop")) {

@ -264,48 +264,16 @@ header {
font-size: 1.3em;
}
// $side-panel-width: 14rem;
$side-panel-width: 18rem;
.important_subwrap {
display: flex;
@include media($bp-tablet) {
flex-flow: column nowrap;
}
@include media($bp-desktop) {
flex-flow: row nowrap;
}
overflow: visible; // for negative margin of .post_single
> section.side_panel {
flex-shrink: 0;
@include media($bp-desktop) {
// width: 14em;
width: $side-panel-width;
height: 0; // for overflow
}
padding: 10px;
article {
// &:not(:last-child) {
// margin-bottom: 10px;
// }
margin-bottom: 10px;
}
article.tags {
.searchbox {
width: calc(#{$side-panel-width} - 50px);
form .tag_input, .tag_input form {
.tag_suggester {
width: calc(100% - 50px);
position: relative;
input[name=tagsearch] {
width: 100%;
}
// input[type=text] {
// width: 100%;
// }
> .search_dropdown {
> .tag_suggest_dropdown {
display: flex;
flex-flow: column nowrap;
align-items: start;
@ -320,7 +288,9 @@ header {
background-color: #2d2d2de0;
}
}
.tag_container, .search_dropdown {
.tag_container {
display: flex;
> a {
margin: 2px 2px;
// padding: .4em .75em;
@ -365,10 +335,41 @@ header {
}
}
}
}
// $side-panel-width: 14rem;
$side-panel-width: 18rem;
.important_subwrap {
.tag_container {
display: flex;
@include media($bp-tablet) {
flex-flow: column nowrap;
}
@include media($bp-desktop) {
flex-flow: row nowrap;
}
overflow: visible; // for negative margin of .post_single
> section.side_panel {
flex-shrink: 0;
@include media($bp-desktop) {
// width: 14em;
width: $side-panel-width;
height: 0; // for overflow
}
padding: 10px;
article {
// &:not(:last-child) {
// margin-bottom: 10px;
// }
margin-bottom: 10px;
}
article.tags {
@include media($bp-tablet) {
.tag_container {
flex-flow: row wrap;

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

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

@ -65,3 +65,15 @@
{% endfor %}
</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 %}

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

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