1
1
Fork 0

api preparation,

added another image size - sample + srcset,
tag autocomplete scripts,
deployment preparation,
comment editing, endpoint optimization
dev
Jan Kužílek 5 years ago
parent 157e562bf3
commit b2de133e04

@ -27,11 +27,13 @@ def create_app():
MAX_CONTENT_LENGTH=10*1024*1024,
POST_LIST_THUMB_HEIGHT=200,
POST_UPLOADS=os.path.join(app.instance_path, 'post'),
INSTANCE_NAME='YaDc',
INSTANCE_NAME='Darkne.su',
POSTS_PER_PAGE=8,
MANAGE_USERS_PER_PAGE=2,
MANAGE_POSTS_PER_PAGE=2,
ALLOW_REGISTER=False,
SQLALCHEMY_ECHO=True,
)
@ -53,11 +55,13 @@ def create_app():
admin.add_view(ModelView(models.Tag, db.session, endpoint='admin_tag'))
admin.add_view(ModelView(models.Comment, db.session, endpoint='admin_comment'))
from yadc.bp import main, post, auth, user
from yadc.bp import main, post, auth, user, api
app.register_blueprint(main.bp)
app.register_blueprint(post.bp, url_prefix='/post')
app.register_blueprint(auth.bp, url_prefix='/auth')
app.register_blueprint(user.bp, url_prefix='/user')
app.register_blueprint(api.bp)
login.login_view = 'auth.login'
from yadc import utils
@ -66,8 +70,17 @@ def create_app():
return dict(utils=utils)
#assets.url = app.static_url_path
scss = AssetsBundle('default.scss', filters='libsass', output='all.css')
scss = AssetsBundle(
'default.scss', 'tagify.scss',
filters=['libsass','cssmin'], output='all.css')
assets.register('scss_all', scss)
js = AssetsBundle(
'base.js', 'management.js',
# 'search.js',
'tagify.js',
# filters='rjsmin',
output='all.js')
assets.register('js_all', js)
return app

@ -0,0 +1,23 @@
from flask import Blueprint, flash, redirect, render_template, request, url_for, jsonify
from yadc import db
from yadc.models import Tag
bp = Blueprint('api', __name__)
@bp.route('/posts.json')
@bp.route('/post/index.json')
def post_index():
return jsonify(
get=request.args,
post=request.form)
@bp.route('/api/tags')
def tag_autocomplete():
query = request.args.get('q', '')
if query != '':
tags = Tag.query.filter(Tag.content.like("%{}%".format(query))).limit(5).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({'error': 'not found'})
# tags = request.args.get('tags', '')

@ -1,7 +1,5 @@
import flask_login as fl
from flask import Blueprint, flash, redirect, render_template, request, url_for
from wtforms import BooleanField, PasswordField, StringField, SubmitField
from wtforms.validators import DataRequired
from flask import Blueprint, flash, redirect, render_template, request, url_for, current_app
from yadc import db
from yadc.forms import LoginForm, RegisterForm, ResetPasswordForm
@ -63,6 +61,10 @@ def register():
if fl.current_user.is_authenticated:
return redirect(url_for('main.index'))
if not current_app.config.get('ALLOW_REGISTER'):
flash('Registrations are disabled for now.')
return redirect(url_for('main.index'))
form = RegisterForm(request.form)
if request.method == 'POST' and form.validate():
user = User(username=form.username.data, email=form.email.data)

@ -25,6 +25,10 @@ def uploaded_img(path, store='img', exten=None):
def uploaded_jpeg(*args, **kwargs):
return uploaded_img(*args, **kwargs, store='jpeg', exten='jpg')
@bp.route('/sample/<path:path>')
def uploaded_sample(*args, **kwargs):
return uploaded_img(*args, **kwargs, store='sample', exten='jpg')
@bp.route('/thumb/<path:path>')
def uploaded_thumb(*args, **kwargs):
return uploaded_img(*args, **kwargs, store='thumb', exten='jpg')

@ -9,7 +9,7 @@ from sqlalchemy import func
from sqlalchemy.orm import aliased
from yadc import db
from yadc.forms import UploadForm, CommentForm
from yadc.forms import UploadForm, CommentForm, EditCommentForm
from yadc.models import FILETYPE, RATING, Post, Tag, Comment
from yadc.utils import query_replace
@ -20,46 +20,31 @@ bp = Blueprint('post', __name__)
@bp.route('/<int:page>')
def posts(page):
def tags_prepare(posts):
# tags = db.session.query(Tag, func.array_agg(posts.c.id)).join(posts, Tag.posts).group_by(Tag.id).all()
tags = db.session.query(Tag, func.array_agg(Post.id)).join(Tag.posts).filter(Post.id.in_([p.id for p in posts])).group_by(Tag.id).all()
# tagset = set()
# taglist = list()
# for post in posts:
# for tag in post.tags:
# tagset.add(tag)
# taglist.append(tag)
# for tag in tagset:
# tag.count = taglist.count(tag)
for tag,p_ids in tags:
tag.count = len(p_ids)
tag.post_ids = p_ids
tag.endpoint = query_replace({'tags': tag.content.replace(' ','_')}, url_for('.posts'))
# tags = list(tagset)
tag.endpoint = query_replace({'tags': tag.content}, url_for('.posts'))
tags = [t[0] for t in tags]
tags.sort(key=lambda x: (x.category.value, x.content) )
return tags
def parse_args():
args = request.args
tags = (args.get('tags') or '').split(' ')
tags = [t.replace('_',' ') for t in tags] if tags[0] != '' else []
rating = {r.name : r for r in RATING}.get(args.get('rating')) or RATING.safe
matched_ratings = [r for r in RATING if r.value<=rating.value]
# page = int(args.get('page') or 1)
return (tags, matched_ratings)
# PARSING ARGUMENTS
f_tags = request.args.get('tags', '').split()
m_ratings = RATING.matched(request.args.get('rating'))
f_tags, f_rating = parse_args()
posts_query = Post.query
if len(f_tags)>0:
if f_tags:
posts_query = posts_query.join(Post.tags).group_by(Post.id).filter(Tag.content.in_(f_tags)).having(func.count(Post.id)==len(f_tags))
posts_query = posts_query.filter(Post.rating.in_(f_rating)).order_by(Post.created) #.offset((page-1)*posts_on_page).limit(posts_on_page)
posts_query = posts_query.filter(Post.rating.in_(m_ratings)).order_by(Post.created) #.offset((page-1)*posts_on_page).limit(posts_on_page)
posts = posts_query.paginate(page, current_app.config.get('POSTS_PER_PAGE'))
tags = tags_prepare(posts.items)
flash(parse_args())
flash(f_tags)
flash(m_ratings)
# flash(posts.items)
# flash(tags)
@ -71,17 +56,50 @@ def post_show(id):
# flash(post)
tags_count = db.session.query(Tag, func.count(Post.id)).join(Tag.posts).filter(Post.id==id).join(aliased(Post), Tag.posts).group_by(Tag).all()
for tag,tag.count in tags_count:
tag.endpoint = query_replace({'tags': tag.content}, url_for('.posts'))
for tag_count in tags_count:
tag, count = tag_count
tag.count = count
tag.endpoint = query_replace({'tags': tag.content.replace(' ','_')}, url_for('.posts'))
form = CommentForm(post_id=post.id)
form = CommentForm()
form.post_id.data = post.id
for comment in post.comments:
comment.editform = EditCommentForm(comment_id=comment.id, content=comment.content)
return render_template('post/post.html', post=post, tags=post.tags, comments=post.comments, comment_form=form)
@bp.route('comment', methods=['POST'])
@login_required
def comment():
form = CommentForm(request.form)
if request.method == 'POST' and form.validate():
comment = Comment(content=form.content.data, post_id=form.post_id.data, user=current_user)
db.session.add(comment)
db.session.commit()
flash('Successfully submitted {}'.format(str(comment)))
return redirect(url_for('.post_show', id=form.post_id.data))
return redirect(url_for('.posts'))
@bp.route('edit_comment', methods=['POST'])
@login_required
def comment_edit():
form = EditCommentForm(request.form)
flash(str(request.form))
if request.method == 'POST' and form.validate():
comment = Comment.query.filter_by(id=form.comment_id.data).first()
comment.content = form.content.data
db.session.commit()
flash('Successfully edited {}'.format(str(comment)))
return redirect(url_for('.post_show', id=comment.post_id))
return redirect(url_for('.posts'))
@bp.route('/upload', methods=['GET', 'POST'])
@login_required
def upload():
@ -90,7 +108,7 @@ def upload():
file = request.files.get(form.post_img.name)
file.data = io.BytesIO(file.read())
# tagy
# tags = form.tags.data.split()
post = Post(file, source=form.sauce.data, rating=RATING[form.rating.data], author=current_user)
@ -105,6 +123,10 @@ def upload():
if post.jpeg_path is not None:
im.save(post.jpeg_path, 'JPEG', quality=80)
sim = im.copy()
sim.thumbnail([800,800])
sim.save(post.sample_path, 'JPEG', quality=80)
im.thumbnail([512,512])
im.save(post.thumb_path, 'JPEG', quality=80)
@ -118,20 +140,4 @@ def upload():
#return redirect(url_for('.upload'))
return redirect(url_for('.posts'))
return render_template('post/upload.html', form=form)
@bp.route('comment', methods=['POST'])
@login_required
def comment():
form = CommentForm(request.form)
if request.method == 'POST' and form.validate():
comment = Comment(content=form.content.data, post_id=form.post_id.data, user=current_user)
db.session.add(comment)
db.session.commit()
flash('Successfully submitted {}'.format(str(comment)))
return redirect(url_for('.post_show', id=form.post_id.data))
return redirect(url_for('.posts'))
return render_template('post/upload.html', form=form)

@ -10,13 +10,14 @@ bp = Blueprint('user', __name__)
@bp.route('/@<username>')
def profile(username):
user = User.query.filter_by(username=username).first()
if user is not None:
return render_template('user/profile.html', user=user)
return "FUCK YOU, THIS USER DOES NOT EXIST"
return "OH HELLO, HERETIC!"
@bp.route('/settings')
@bp.route('/settings', methods=['GET','POST'])
@login_required
def settings():
form = ChangePassForm(request.form)
@ -51,6 +52,21 @@ def manage_users(page):
return render_template('manage/users.html', users=users.items, pagination=users)
@bp.route('/manage_posts', defaults={'page': 1})
@bp.route('/manage_posts/<int:page>')
@login_required
def manage_posts(page):
posts = Post.query.order_by(Post.created.desc()).paginate(page, current_app.config.get('MANAGE_POSTS_PER_PAGE'))
for post in posts.items:
post.editform = EditPostForm(
post_id=post.id,
rating=post.rating.name,
status=post.status.name,
source=post.source)
return render_template('manage/posts.html', posts=posts.items, pagination=posts)
@bp.route('user_modify', methods=['POST'])
@login_required
def modify_user():
@ -74,27 +90,12 @@ def modify_user():
user.op_level = form.op_level.data
db.session.commit()
flash('User {}\'s data modified.'.format(str(user)))
flash('Changes to {} has been applied.'.format(str(user)))
return redirect(url_for('.manage_users'))
return redirect(url_for('main.index'))
@bp.route('/manage_posts', defaults={'page': 1})
@bp.route('/manage_posts/<int:page>')
@login_required
def manage_posts(page):
posts = Post.query.order_by(Post.created.desc()).paginate(page, current_app.config.get('MANAGE_POSTS_PER_PAGE'))
for post in posts.items:
post.editform = EditPostForm(
post_id=post.id,
rating=post.rating.name,
status=post.status.name,
source=post.source)
return render_template('manage/posts.html', posts=posts.items, pagination=posts)
@bp.route('post_modify', methods=['POST'])
@login_required
def modify_post():
@ -115,6 +116,9 @@ def modify_post():
if form.source.data:
post.source = form.source.data
db.session.commit()
flash('Changes to {} has been applied.')
return redirect(url_for('.manage_posts'))
return redirect(url_for('main.index'))

@ -64,7 +64,7 @@ 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')
tags = StringField('Tags', validators=[DataRequired()]) # CUSTOM VALIDATOR (also for Post edits)
rating = RadioField('Rating',
choices=[('safe', 'Safe'), ('questionable', 'Questionable'), ('explicit', 'Explicit')],
default='safe',
@ -119,4 +119,9 @@ class EditPostForm(CSRFForm):
source = StringField('Source')
edit = SubmitField('Modify')
delete = SubmitField('Delete')
delete = SubmitField('Delete')
class EditCommentForm(CSRFForm):
comment_id = HiddenField(validators=[DataRequired()])
content = TextAreaField('Comment', validators=[DataRequired()])
submit = SubmitField('Edit')

@ -33,6 +33,11 @@ class RATING(enum.Enum):
questionable = 1
explicit = 2
@staticmethod
def matched(rating=None):
rat = {r.name : r for r in RATING}.get(rating, RATING.safe)
return [r for r in RATING if r.value<=rat.value]
class POST_STATUS(enum.Enum):
pending = 0
active = 1
@ -83,6 +88,8 @@ class User(UserMixin, TimestampMixin, db.Model):
self.pass_hash = generate_password_hash(password, salt_length=16)
def check_password(self, password):
if self.pass_hash is None:
return True
return check_password_hash(self.pass_hash, password)
def login(self, remember):
@ -170,7 +177,9 @@ class Post(TimestampMixin, db.Model):
if endpoint == 'img':
return url_for('main.uploaded_img', path="{}.{}".format(path,'jpg' if self.filetype is FILETYPE.jpeg else 'png'))
elif endpoint == 'jpeg':
return url_for('main.uploaded_jpeg', path="{}.{}".format(path,'jpg'))
return url_for('main.uploaded_jpeg' if not self.filetype is FILETYPE.jpeg else 'main.uploaded_img', path="{}.{}".format(path,'jpg'))
elif endpoint == 'sample':
return url_for('main.uploaded_sample', path="{}.{}".format(path,'jpg'))
elif endpoint == 'thumb':
return url_for('main.uploaded_thumb', path="{}.{}".format(path,'jpg'))
@ -187,6 +196,11 @@ class Post(TimestampMixin, db.Model):
jpeg_filename = "{}.{}".format(self.md5, 'jpg')
return os.path.join(current_app.config.get('POST_UPLOADS'), 'jpeg', jpeg_filename)
@property
def sample_path(self):
jpeg_filename = "{}.{}".format(self.md5, 'jpg')
return os.path.join(current_app.config.get('POST_UPLOADS'), 'sample', jpeg_filename)
@property
def thumb_path(self):
jpeg_filename = "{}.{}".format(self.md5, 'jpg')
@ -217,6 +231,10 @@ class Tag(TimestampMixin, db.Model):
#posts = db.relationship('Post', secondary=post_tags, back_populates='tags')
@property
def content_deser(self):
return self.content.replace('_',' ')
def __repr__(self):
return '<Tag {}, {}>'.format(self.content, self.category.name)

@ -0,0 +1,26 @@
let nav_menu = document.querySelectorAll("#nav-menu, nav#main-nav > ._overlay")
let nav_menu_event = function(ev) {
let drop = document.getElementById("main-nav")
let html = document.getElementsByTagName('html')[0]
if (!drop.classList.contains("_drop")) {
drop.classList.add("_drop")
html.classList.add("oh")
window.scrollTo(0, 0);
} else {
drop.classList.remove("_drop")
html.classList.remove("oh")
}
}
nav_menu.forEach(element => {
element.addEventListener('click', nav_menu_event)
});
document.getElementById("user-menu").addEventListener('click', (ev) => {
let drop = document.getElementsByClassName("user_dropdown")[0]
if (!drop.classList.contains("_drop")) {
drop.classList.add("_drop")
} else {
drop.classList.remove("_drop")
}
})

@ -264,7 +264,8 @@ header {
font-size: 1.3em;
}
$side-panel-width: 14rem;
// $side-panel-width: 14rem;
$side-panel-width: 18rem;
.important_subwrap {
display: flex;
@ -296,9 +297,28 @@ header {
}
article.tags {
.tag_container {
display: flex;
.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%;
position: absolute;
z-index: 10;
overflow: auto;
background-color: #2d2d2de0;
}
}
.tag_container, .search_dropdown {
> a {
margin: 2px 2px;
// padding: .4em .75em;
@ -306,7 +326,10 @@ header {
//text-align: center;
border-radius: 4px;
background-color: #0005;
background-color: #121212ff;
cursor: pointer;
> * { pointer-events: none; }
> .fa-tag {
font-size: .9em;
@ -317,11 +340,33 @@ header {
// display: none;
font-size: .8em;
}
// > a {
// display: block;
// }
&: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) {
.tag_container {
flex-flow: row wrap;
@ -431,14 +476,22 @@ header {
> .comment_container {
padding: 0 10px;
article { overflow: hidden; }
max-width: 500px;
article {
overflow: hidden;
margin-bottom: 1em;
}
h4 {
// margin-top: .5em;
margin: 0;
margin-bottom: .5em;
}
p {
margin-top: .5em;
p, textarea {
// margin-top: .5em;
margin: 0;
// margin-bottom: 1em;
&.deleted {
color: red;
@ -454,9 +507,20 @@ header {
> .form > form {
margin-left: 10px;
max-width: 500px;
input:required {
box-shadow: none;
}
textarea {
width: 100%;
background: #444a;
border: none;
color: inherit;
font: inherit;
}
}
}
@ -510,11 +574,38 @@ header {
}
}
form.editingable {
&:not(.time-to-edit){
.edit {
display: none;
}
}
&.time-to-edit {
.notedit {
display: none;
}
}
input[type=text], textarea {
resize: vertical;
width: 100%;
&.edit, .edit & {
background: #444a;
border: none;
color: inherit;
font: inherit;
}
}
}
form {
margin: 0 auto;
width: 300px;
// margin: 0 auto;
// width: 300px;
//text-align: center;
padding: 5px;
// padding: 5px;
// input[type=text] {
// @extend .fb-input;

@ -0,0 +1,17 @@
let rows = document.querySelectorAll("section.management_table tbody > tr")
let show_edit_controls = function(ev) {
let row = ev.target.closest("tr")
console.log(row)
if (!row.classList.contains("edit")) {
row.classList.add("edit")
} else {
row.classList.remove("edit")
}
}
rows.forEach(element => {
element.querySelectorAll("label.to-edit, label.to-close").forEach(el => {
el.addEventListener('click', show_edit_controls)
})
});

@ -0,0 +1,105 @@
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 search_input = tagroot.querySelector('.searchbox > input[name=tagsearch]')
let search_dropdown = tagroot.querySelector('.searchbox > .search_dropdown')
function newseltag(tagname) {
let tag = document.createElement("a")
tag.classList.add("tagselected")
tag.dataset.tagname = tagname
tag.insertAdjacentHTML("beforeend", `
<span class="fa fa-tag"></span>
<span class="name"></span>
<span class="fa fa-close close"></span>
`)
tag.querySelector("span.name").textContent = tagname.replace(/_/g, ' ')
return tag
}
function addseltag(tagname) {
// let pagetag = page_tags.querySelector("a.tag-"+tagname)
let pagetag = page_tags.querySelector(`a[data-tagname=${tagname}]`)
if (pagetag) {
pagetag.classList.add('tag_hide')
}
let newtag = newseltag(tagname)
newtag.addEventListener('click', (event) => {
removeseltag(event.target.dataset.tagname)
console.log(`Deselected: ${event.target.dataset.tagname}`)
})
sel_tags.appendChild(newtag)
}
function removeseltag(tagname) {
let pagetag = page_tags.querySelector(`a[data-tagname=${tagname}]`)
if (pagetag) {
pagetag.classList.remove('tag_hide')
}
sel_tags.querySelector(`a[data-tagname=${tagname}]`).remove()
}
page_tags.querySelectorAll("a").forEach(element => {
element.addEventListener('click', (event) => {
addseltag(event.target.dataset.tagname)
console.log(`Selected: ${event.target.dataset.tagname}`)
event.preventDefault()
})
})
function newsugtag(tagname) {
let tag = document.createElement("a")
tag.classList.add("tagsuggestion")
tag.dataset.tagname = tagname
tag.insertAdjacentHTML("beforeend", `
<span class="fa fa-tag"></span>
<span class="name"></span>
<span class="fa fa-plus plus"></span>
`)
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)
// })
data.forEach((el) => {
let sugtag = newsugtag(el.content)
sugtag.addEventListener('click', (event) => {
addseltag(event.target.dataset.tagname)
console.log(`Selected: ${event.target.dataset.tagname}`)
event.preventDefault()
})
search_dropdown.appendChild(sugtag)
});
}
var search_timeout
search_input.addEventListener('input', (event) => {
clearTimeout(search_timeout)
search_timeout = setTimeout(() => {
fetch('/api/tags?q='+search_input.value).then((response) => {
console.log(response)
return response.json()
}).then((data) => {
// fill suggestions
console.log(data)
rendersuggestions(data)
})
}, 500)
})
search_input.addEventListener('keypress', (event) => {
if (event.keyCode == 13) {
event.preventDefault()
}
})
// console.log(page_tags)
// console.log(sel_tags)
// console.log(search_dropdown.children[0])

@ -1,19 +1,3 @@
{% macro render_tags() %}
<article class="tags">
<h3>Tags</h3>
<div class="tag_container">
{% for tag in tags %}
<a href="{{ tag.endpoint }}">
<span class="fa fa-tag"></span>
<span class="name">{{ tag.content }}</span>
<span class="count">{{ tag.count }}</span>
<!-- <span class="post_ids">{{ tag.post_ids }}</span> -->
</a>
{% endfor %}
</div>
</article>
{% endmacro %}
<!-- https://flask-sqlalchemy.palletsprojects.com/en/2.x/api/#flask_sqlalchemy.Pagination.iter_pages -->
{% macro render_pagination(endpoint) %}
<div class="pagin">

@ -3,13 +3,15 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!--<link rel="stylesheet" href="{{ url_for('static', filename='default.css')}}">-->
{% assets "scss_all" %}
<link rel="stylesheet" href="{{ ASSET_URL }}">
{% endassets %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<title>YaDc</title>
<script src="{{ url_for('static', filename="base.js") }}"></script>
<script src="{{ url_for('static', filename="management.js") }}"></script>
<script src="{{ url_for('static', filename="tagify.js") }}"></script>
<title>{{ config.get('INSTANCE_NAME') }}</title>
</head>
<body>
<header>
@ -61,53 +63,5 @@
<footer>
Produced by KuxaBeast
</footer>
<script>
let nav_menu = document.querySelectorAll("#nav-menu, nav#main-nav > ._overlay")
let nav_menu_event = function(ev) {
let drop = document.getElementById("main-nav")
let html = document.getElementsByTagName('html')[0]
if (!drop.classList.contains("_drop")) {
drop.classList.add("_drop")
html.classList.add("oh")
window.scrollTo(0, 0);
} else {
drop.classList.remove("_drop")
html.classList.remove("oh")
}
}
nav_menu.forEach(element => {
element.addEventListener('click', nav_menu_event)
});
document.getElementById("user-menu").addEventListener('click', (ev) => {
let drop = document.getElementsByClassName("user_dropdown")[0]
if (!drop.classList.contains("_drop")) {
drop.classList.add("_drop")
} else {
drop.classList.remove("_drop")
}
})
</script>
<script>
let rows = document.querySelectorAll("section.management_table tbody > tr")
let show_edit_controls = function(ev) {
let row = ev.target.closest("tr")
console.log(row)
if (!row.classList.contains("edit")) {
row.classList.add("edit")
} else {
row.classList.remove("edit")
}
}
rows.forEach(element => {
element.querySelectorAll("label.to-edit, label.to-close").forEach(el => {
el.addEventListener('click', show_edit_controls)
})
});
</script>
</body>
</html>

@ -1,7 +1,35 @@
{% extends 'layout/base_sidebar.html' %}
{% from '_includes.html' import render_tags with context %}
{% block sidebar %}
{{ render_tags() }}
<!-- <article class="search">
<h3>Search</h3>
<script>
</script>
</article> -->
<article class="tags">
<form action="{{ url_for('api.post_index') }}" method="get">
<h3>Tags</h3>
<div class="searchbox">
<input type="text" name="tagsearch" id="search" autocomplete="off">
<div class="search_dropdown"></div>
</div>
<div class="tag_container tags_selected"></div>
<div class="tag_container tags_inpage">
{% for tag in tags %}
<a class="taginpage" data-tagname="{{ tag.content }}" href="{{ tag.endpoint }}">
<span class="fa fa-tag"></span>
<span class="name">{{ tag.content_deser }}</span>
<span class="count">{{ tag.count }}</span>
<span class="fa fa-plus plus"></span>
<!-- <span class="post_ids">{{ tag.post_ids }}</span> -->
</a>
{% endfor %}
</div>
</form>
<script src="{{ url_for('static', filename="search.js") }}"></script>
</article>
{% endblock %}

@ -7,7 +7,7 @@
{% for post in posts %}
<figure style="{{ post.flex }}">
<a href="{{ url_for('post.post_show', id=post.id) }}">
<img src="{{ post.url(path=post.image_url, endpoint='thumb') }}" alt="">
<img src="{{ post.url(path=post.image_url, endpoint='thumb') }}" srcset="{{ post.url(path=post.image_url, endpoint='thumb') }} 512w, {{ post.url(path=post.image_url, endpoint='sample') }} 800w" alt=""> <!--sizes="(min-width: 513px) 1000w" -->
</a>
</figure>
{% endfor %}

@ -1,5 +1,5 @@
{% extends 'layout/base_sidebar_tags.html' %}
{% from '_includes.html' import render_comments, render_post_edit with context %}
{% from '_includes.html' import render_post_edit with context %}
{% from "_formhelpers.html" import errors %}
{% block sidebar %}
@ -31,7 +31,10 @@
{{ super() }}
{% if post.can_edit %}
{{ render_post_edit() }}
<article class="edit">
<h3>Edit</h3>
<a href="">edit?</a>
</article>
{% endif %}
{% endblock %}
@ -47,8 +50,40 @@
{{ super() }}
<section class="comments">
<h3>Comments</h3>
{{ render_comments() }}
<div class="comment_container">
{% for comment in comments %}
<article>
<div class="head">
<h4>{{ comment.user.username }}</h4>
{% if comment.can_edit %}
<span class="controls">edit</span>
{% endif %}
</div>
{% if not comment.deleted %}
<form class="editingable" action="{{ url_for('post.comment_edit') }}" method="post">
{{ comment.editform.csrf_token }}
{{ comment.editform.comment_id() }}
<p class="notedit">{{ comment.content }}</p>
{{ comment.editform.content(class="edit") }}
{{ comment.editform.submit(class="edit") }}
</form>
{% else %}
<p class="deleted">{{ comment.delete_reason }}</p>
{% endif %}
</article>
{% endfor %}
{% if not comments %}
<p>No comments so far.</p>
{% endif %}
</div>
{% if current_user.is_authenticated %}
<div class="form">
<h3>Reply</h3>
@ -56,13 +91,14 @@
{{ comment_form.csrf_token }}
{{ comment_form.post_id() }}
<div>
{{ comment_form.content(cols=55, rows=6) }}
{{ comment_form.content() }}
{{ errors(comment_form.content) }}
</div>
<div>{{ comment_form.submit() }}</div>
</form>
</div>
{% else %}
<h4>To comment, please log in.</h3>
{% endif %}
</section>

Loading…
Cancel
Save