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, MAX_CONTENT_LENGTH=10*1024*1024,
POST_LIST_THUMB_HEIGHT=200, POST_LIST_THUMB_HEIGHT=200,
POST_UPLOADS=os.path.join(app.instance_path, 'post'), POST_UPLOADS=os.path.join(app.instance_path, 'post'),
INSTANCE_NAME='YaDc', INSTANCE_NAME='Darkne.su',
POSTS_PER_PAGE=8, POSTS_PER_PAGE=8,
MANAGE_USERS_PER_PAGE=2, MANAGE_USERS_PER_PAGE=2,
MANAGE_POSTS_PER_PAGE=2, MANAGE_POSTS_PER_PAGE=2,
ALLOW_REGISTER=False,
SQLALCHEMY_ECHO=True, 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.Tag, db.session, endpoint='admin_tag'))
admin.add_view(ModelView(models.Comment, db.session, endpoint='admin_comment')) 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(main.bp)
app.register_blueprint(post.bp, url_prefix='/post') app.register_blueprint(post.bp, url_prefix='/post')
app.register_blueprint(auth.bp, url_prefix='/auth') app.register_blueprint(auth.bp, url_prefix='/auth')
app.register_blueprint(user.bp, url_prefix='/user') app.register_blueprint(user.bp, url_prefix='/user')
app.register_blueprint(api.bp)
login.login_view = 'auth.login' login.login_view = 'auth.login'
from yadc import utils from yadc import utils
@ -66,8 +70,17 @@ def create_app():
return dict(utils=utils) return dict(utils=utils)
#assets.url = app.static_url_path #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) 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 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 import flask_login as fl
from flask import Blueprint, flash, redirect, render_template, request, url_for from flask import Blueprint, flash, redirect, render_template, request, url_for, current_app
from wtforms import BooleanField, PasswordField, StringField, SubmitField
from wtforms.validators import DataRequired
from yadc import db from yadc import db
from yadc.forms import LoginForm, RegisterForm, ResetPasswordForm from yadc.forms import LoginForm, RegisterForm, ResetPasswordForm
@ -63,6 +61,10 @@ def register():
if fl.current_user.is_authenticated: if fl.current_user.is_authenticated:
return redirect(url_for('main.index')) 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) form = RegisterForm(request.form)
if request.method == 'POST' and form.validate(): if request.method == 'POST' and form.validate():
user = User(username=form.username.data, email=form.email.data) 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): def uploaded_jpeg(*args, **kwargs):
return uploaded_img(*args, **kwargs, store='jpeg', exten='jpg') 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>') @bp.route('/thumb/<path:path>')
def uploaded_thumb(*args, **kwargs): def uploaded_thumb(*args, **kwargs):
return uploaded_img(*args, **kwargs, store='thumb', exten='jpg') return uploaded_img(*args, **kwargs, store='thumb', exten='jpg')

@ -9,7 +9,7 @@ from sqlalchemy import func
from sqlalchemy.orm import aliased from sqlalchemy.orm import aliased
from yadc import db 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.models import FILETYPE, RATING, Post, Tag, Comment
from yadc.utils import query_replace from yadc.utils import query_replace
@ -20,46 +20,31 @@ bp = Blueprint('post', __name__)
@bp.route('/<int:page>') @bp.route('/<int:page>')
def posts(page): def posts(page):
def tags_prepare(posts): 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() 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: for tag,p_ids in tags:
tag.count = len(p_ids) tag.count = len(p_ids)
tag.post_ids = p_ids tag.post_ids = p_ids
tag.endpoint = query_replace({'tags': tag.content.replace(' ','_')}, url_for('.posts')) tag.endpoint = query_replace({'tags': tag.content}, url_for('.posts'))
# tags = list(tagset)
tags = [t[0] for t in tags] tags = [t[0] for t in tags]
tags.sort(key=lambda x: (x.category.value, x.content) ) tags.sort(key=lambda x: (x.category.value, x.content) )
return tags return tags
def parse_args(): # PARSING ARGUMENTS
args = request.args f_tags = request.args.get('tags', '').split()
tags = (args.get('tags') or '').split(' ') m_ratings = RATING.matched(request.args.get('rating'))
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)
f_tags, f_rating = parse_args()
posts_query = Post.query 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.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')) posts = posts_query.paginate(page, current_app.config.get('POSTS_PER_PAGE'))
tags = tags_prepare(posts.items) tags = tags_prepare(posts.items)
flash(parse_args()) flash(f_tags)
flash(m_ratings)
# flash(posts.items) # flash(posts.items)
# flash(tags) # flash(tags)
@ -71,17 +56,50 @@ def post_show(id):
# flash(post) # 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() 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: form = CommentForm(post_id=post.id)
tag, count = tag_count
tag.count = count
tag.endpoint = query_replace({'tags': tag.content.replace(' ','_')}, url_for('.posts'))
form = CommentForm() for comment in post.comments:
form.post_id.data = post.id 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) 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']) @bp.route('/upload', methods=['GET', 'POST'])
@login_required @login_required
def upload(): def upload():
@ -90,7 +108,7 @@ 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())
# tagy # tags = form.tags.data.split()
post = Post(file, source=form.sauce.data, rating=RATING[form.rating.data], author=current_user) post = Post(file, source=form.sauce.data, rating=RATING[form.rating.data], author=current_user)
@ -106,6 +124,10 @@ def upload():
if post.jpeg_path is not None: if post.jpeg_path is not None:
im.save(post.jpeg_path, 'JPEG', quality=80) 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.thumbnail([512,512])
im.save(post.thumb_path, 'JPEG', quality=80) im.save(post.thumb_path, 'JPEG', quality=80)
@ -119,19 +141,3 @@ def upload():
return redirect(url_for('.posts')) return redirect(url_for('.posts'))
return render_template('post/upload.html', form=form) 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'))

@ -10,13 +10,14 @@ bp = Blueprint('user', __name__)
@bp.route('/@<username>') @bp.route('/@<username>')
def profile(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"
@bp.route('/settings', methods=['GET','POST'])
return "OH HELLO, HERETIC!"
@bp.route('/settings')
@login_required @login_required
def settings(): def settings():
form = ChangePassForm(request.form) form = ChangePassForm(request.form)
@ -51,6 +52,21 @@ def manage_users(page):
return render_template('manage/users.html', users=users.items, pagination=users) 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']) @bp.route('user_modify', methods=['POST'])
@login_required @login_required
def modify_user(): def modify_user():
@ -74,27 +90,12 @@ def modify_user():
user.op_level = form.op_level.data user.op_level = form.op_level.data
db.session.commit() 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('.manage_users'))
return redirect(url_for('main.index')) 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']) @bp.route('post_modify', methods=['POST'])
@login_required @login_required
def modify_post(): def modify_post():
@ -115,6 +116,9 @@ def modify_post():
if form.source.data: if form.source.data:
post.source = 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('.manage_posts'))
return redirect(url_for('main.index')) return redirect(url_for('main.index'))

@ -64,7 +64,7 @@ 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()])
tags = StringField('Tags') tags = StringField('Tags', validators=[DataRequired()]) # 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',
@ -120,3 +120,8 @@ class EditPostForm(CSRFForm):
edit = SubmitField('Modify') 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 questionable = 1
explicit = 2 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): class POST_STATUS(enum.Enum):
pending = 0 pending = 0
active = 1 active = 1
@ -83,6 +88,8 @@ class User(UserMixin, TimestampMixin, db.Model):
self.pass_hash = generate_password_hash(password, salt_length=16) self.pass_hash = generate_password_hash(password, salt_length=16)
def check_password(self, password): def check_password(self, password):
if self.pass_hash is None:
return True
return check_password_hash(self.pass_hash, password) return check_password_hash(self.pass_hash, password)
def login(self, remember): def login(self, remember):
@ -170,7 +177,9 @@ class Post(TimestampMixin, db.Model):
if endpoint == 'img': if endpoint == 'img':
return url_for('main.uploaded_img', path="{}.{}".format(path,'jpg' if self.filetype is FILETYPE.jpeg else 'png')) return url_for('main.uploaded_img', path="{}.{}".format(path,'jpg' if self.filetype is FILETYPE.jpeg else 'png'))
elif endpoint == 'jpeg': 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': elif endpoint == 'thumb':
return url_for('main.uploaded_thumb', path="{}.{}".format(path,'jpg')) 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') jpeg_filename = "{}.{}".format(self.md5, 'jpg')
return os.path.join(current_app.config.get('POST_UPLOADS'), 'jpeg', jpeg_filename) 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 @property
def thumb_path(self): def thumb_path(self):
jpeg_filename = "{}.{}".format(self.md5, 'jpg') 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') #posts = db.relationship('Post', secondary=post_tags, back_populates='tags')
@property
def content_deser(self):
return self.content.replace('_',' ')
def __repr__(self): def __repr__(self):
return '<Tag {}, {}>'.format(self.content, self.category.name) 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; font-size: 1.3em;
} }
$side-panel-width: 14rem; // $side-panel-width: 14rem;
$side-panel-width: 18rem;
.important_subwrap { .important_subwrap {
display: flex; display: flex;
@ -296,9 +297,28 @@ header {
} }
article.tags { article.tags {
.tag_container { .searchbox {
width: calc(#{$side-panel-width} - 50px);
position: relative;
input[name=tagsearch] {
width: 100%;
}
> .search_dropdown {
display: flex; 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 { > a {
margin: 2px 2px; margin: 2px 2px;
// padding: .4em .75em; // padding: .4em .75em;
@ -306,7 +326,10 @@ header {
//text-align: center; //text-align: center;
border-radius: 4px; border-radius: 4px;
background-color: #0005; background-color: #121212ff;
cursor: pointer;
> * { pointer-events: none; }
> .fa-tag { > .fa-tag {
font-size: .9em; font-size: .9em;
@ -317,11 +340,33 @@ header {
// display: none; // display: none;
font-size: .8em; 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) { @include media($bp-tablet) {
.tag_container { .tag_container {
flex-flow: row wrap; flex-flow: row wrap;
@ -431,14 +476,22 @@ header {
> .comment_container { > .comment_container {
padding: 0 10px; padding: 0 10px;
article { overflow: hidden; } max-width: 500px;
article {
overflow: hidden;
margin-bottom: 1em;
}
h4 { h4 {
// margin-top: .5em; // margin-top: .5em;
margin: 0; margin: 0;
margin-bottom: .5em; margin-bottom: .5em;
} }
p { p, textarea {
margin-top: .5em; // margin-top: .5em;
margin: 0;
// margin-bottom: 1em;
&.deleted { &.deleted {
color: red; color: red;
@ -454,9 +507,20 @@ header {
> .form > form { > .form > form {
margin-left: 10px; margin-left: 10px;
max-width: 500px;
input:required { input:required {
box-shadow: none; 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 { form {
margin: 0 auto; // margin: 0 auto;
width: 300px; // width: 300px;
//text-align: center; //text-align: center;
padding: 5px; // padding: 5px;
// input[type=text] { // input[type=text] {
// @extend .fb-input; // @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 --> <!-- https://flask-sqlalchemy.palletsprojects.com/en/2.x/api/#flask_sqlalchemy.Pagination.iter_pages -->
{% macro render_pagination(endpoint) %} {% macro render_pagination(endpoint) %}
<div class="pagin"> <div class="pagin">

@ -3,13 +3,15 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<!--<link rel="stylesheet" href="{{ url_for('static', filename='default.css')}}">-->
{% assets "scss_all" %} {% assets "scss_all" %}
<link rel="stylesheet" href="{{ ASSET_URL }}"> <link rel="stylesheet" href="{{ ASSET_URL }}">
{% endassets %} {% endassets %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> <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> </head>
<body> <body>
<header> <header>
@ -61,53 +63,5 @@
<footer> <footer>
Produced by KuxaBeast Produced by KuxaBeast
</footer> </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> </body>
</html> </html>

@ -1,7 +1,35 @@
{% extends 'layout/base_sidebar.html' %} {% extends 'layout/base_sidebar.html' %}
{% from '_includes.html' import render_tags with context %}
{% block sidebar %} {% 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 %} {% endblock %}

@ -7,7 +7,7 @@
{% for post in posts %} {% for post in posts %}
<figure style="{{ post.flex }}"> <figure style="{{ post.flex }}">
<a href="{{ url_for('post.post_show', id=post.id) }}"> <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> </a>
</figure> </figure>
{% endfor %} {% endfor %}

@ -1,5 +1,5 @@
{% extends 'layout/base_sidebar_tags.html' %} {% 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 %} {% from "_formhelpers.html" import errors %}
{% block sidebar %} {% block sidebar %}
@ -31,7 +31,10 @@
{{ super() }} {{ super() }}
{% if post.can_edit %} {% if post.can_edit %}
{{ render_post_edit() }} <article class="edit">
<h3>Edit</h3>
<a href="">edit?</a>
</article>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
@ -47,7 +50,39 @@
{{ super() }} {{ super() }}
<section class="comments"> <section class="comments">
<h3>Comments</h3> <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 %} {% if current_user.is_authenticated %}
<div class="form"> <div class="form">
@ -56,13 +91,14 @@
{{ comment_form.csrf_token }} {{ comment_form.csrf_token }}
{{ comment_form.post_id() }} {{ comment_form.post_id() }}
<div> <div>
{{ comment_form.content(cols=55, rows=6) }} {{ comment_form.content() }}
{{ errors(comment_form.content) }} {{ errors(comment_form.content) }}
</div> </div>
<div>{{ comment_form.submit() }}</div> <div>{{ comment_form.submit() }}</div>
</form> </form>
</div> </div>
{% else %} {% else %}
<h4>To comment, please log in.</h3> <h4>To comment, please log in.</h3>
{% endif %} {% endif %}
</section> </section>

Loading…
Cancel
Save