Merge remote-tracking branch 'upstream/frost' into 601_convert_created_utc_to_datetimez_for_flags

This commit is contained in:
Viet Than 2023-07-25 21:21:16 -04:00
commit 04b310fba0
26 changed files with 474 additions and 686 deletions

View file

@ -5134,7 +5134,7 @@ th, td {
}
#account-menu-header, #account-menu {
min-width: 10em;
min-width: 13em;
}
.navigation-secondary-link {

View file

@ -56,4 +56,22 @@ function expandMarkdown(t,id) {
let val = t.getElementsByTagName('span')[0]
if (val.innerHTML == 'View source') val.innerHTML = 'Hide source'
else val.innerHTML = 'View source'
};
};
function commentsAddUnreadIndicator(commentIds) {
commentIds.forEach(element => {
const commentOnly = document.getElementById(`comment-${element}-only`);
if (!commentOnly) {
console.warn(`Couldn't find comment (comment ID ${element}) in page while attempting to add an unread indicator.`);
return;
}
if (commentOnly.classList.contains("unread")) return;
commentOnly.classList.add("unread");
const commentUserInfo = document.getElementById(`comment-${element}`)?.querySelector(".comment-user-info");
if (!commentUserInfo) {
console.warn(`Couldn't find comment user info (comment ID ${element}) in page while attempting to add an unread indicator.`);
return;
}
commentUserInfo.innerHTML += "<span class=\"new-indicator\">~new~</span>";
});
}

View file

@ -1,78 +1,23 @@
function removeCommentBackend(post_id) {
url="/remove_comment/"+post_id
post(url)
}
function approveCommentBackend(post_id) {
url="/unremove_comment/"+post_id
post(url)
}
function removeCommentDesktop(post_id,button1,button2) {
removeCommentBackend(post_id)
try {
document.getElementById("comment-"+post_id+"-only").classList.add("banned");
} catch(e) {
document.getElementById("context").classList.add("banned");
function moderate(isPost, id, removing, removeButtonDesktopId, removeButtonMobileId, approveButtonDesktopId, approveButtonMobileId) {
const filterState = removing ? "removed" : "normal";
if (isPost) {
filter_new_status(id, filterState);
} else {
filter_new_comment_status(id, filterState);
}
var button=document.getElementById("remove-"+post_id);
button.onclick=function(){approveCommentDesktop(post_id)};
button.innerHTML='<i class="fas fa-clipboard-check"></i>Approve'
if (typeof button1 !== 'undefined') {
document.getElementById(button1).classList.toggle("d-md-inline-block");
document.getElementById(button2).classList.toggle("d-md-inline-block");
}
};
function approveCommentDesktop(post_id,button1,button2) {
approveCommentBackend(post_id)
try {
document.getElementById("comment-"+post_id+"-only").classList.remove("banned");
} catch(e) {
document.getElementById("context").classList.remove("banned");
}
var button=document.getElementById("remove-"+post_id);
button.onclick=function(){removeCommentDesktop(post_id)};
button.innerHTML='<i class="fas fa-trash-alt"></i>Remove'
if (typeof button1 !== 'undefined') {
document.getElementById(button1).classList.toggle("d-md-inline-block");
document.getElementById(button2).classList.toggle("d-md-inline-block");
}
}
function removeCommentMobile(post_id,button1,button2) {
removeCommentBackend(post_id)
document.getElementById("comment-"+post_id+"-only").classList.add("banned");
var button=document.getElementById("remove-"+post_id);
button.onclick=function(){approveCommentMobile(post_id)};
button.innerHTML='<i class="fas fa-clipboard-check"></i>Approve'
if (typeof button1 !== 'undefined') {
document.getElementById(button1).classList.toggle("d-none");
document.getElementById(button2).classList.toggle("d-none");
}
};
function approveCommentMobile(post_id,button1,button2) {
approveCommentBackend(post_id)
document.getElementById("comment-"+post_id+"-only").classList.remove("banned");
var button=document.getElementById("remove-"+post_id);
button.onclick=function(){removeCommentMobile(post_id)};
button.innerHTML='<i class="fas fa-trash-alt"></i>Remove'
if (typeof button1 !== 'undefined') {
document.getElementById(button1).classList.toggle("d-none");
document.getElementById(button2).classList.toggle("d-none");
const removeButtonDesktop = document.getElementById(removeButtonDesktopId);
const removeButtonMobile = document.getElementById(removeButtonMobileId);
const approveButtonDesktop = document.getElementById(approveButtonDesktopId);
const approveButtonMobile = document.getElementById(approveButtonMobileId);
if (removing) {
removeButtonDesktop.classList.add("d-none");
removeButtonMobile.classList.add("d-none");
approveButtonDesktop.classList.remove("d-none");
approveButtonMobile.classList.remove("d-none");
} else {
removeButtonDesktop.classList.remove("d-none");
removeButtonMobile.classList.remove("d-none");
approveButtonDesktop.classList.add("d-none");
approveButtonMobile.classList.add("d-none");
}
}

View file

@ -85,6 +85,26 @@ class ModAction(CreatedBase):
return f"/log/{self.id}"
ACTIONTYPES = {
'approve_post': {
"str": 'approved post {self.target_link}',
"icon": 'fa-feather-alt',
"color": 'bg-success'
},
'approve_comment': {
"str": 'approved {self.target_link}',
"icon": 'fa-comment',
"color": 'bg-success'
},
'remove_post': {
"str": 'removed post {self.target_link}',
"icon": 'fa-feather-alt',
"color": 'bg-danger'
},
'remove_comment': {
"str": 'removed {self.target_link}',
"icon": 'fa-comment',
"color": 'bg-danger'
},
'approve_app': {
"str": 'approved an application by {self.target_link}',
"icon": 'fa-robot',
@ -100,21 +120,11 @@ ACTIONTYPES = {
"icon": 'fa-badge',
"color": 'bg-danger'
},
'remove_comment': {
"str": 'removed {self.target_link}',
"icon": 'fa-comment',
"color": 'bg-danger'
},
'ban_domain': {
"str": 'banned a domain',
"icon": 'fa-globe',
"color": 'bg-danger'
},
'remove_post': {
"str": 'removed post {self.target_link}',
"icon": 'fa-feather-alt',
"color": 'bg-danger'
},
'ban_user': {
"str": 'banned user {self.target_link}',
"icon": 'fa-user-slash',
@ -300,21 +310,11 @@ ACTIONTYPES = {
"icon": 'fa-eye-slash',
"color": 'bg-danger'
},
'unremove_comment': {
"str": 'reinstated {self.target_link}',
"icon": 'fa-comment',
"color": 'bg-success'
},
'unban_domain': {
"str": 'unbanned a domain',
"icon": 'fa-globe',
"color": 'bg-success'
},
'unremove_post': {
"str": 'reinstated post {self.target_link}',
"icon": 'fa-feather-alt',
"color": 'bg-success'
},
'unban_user': {
"str": 'unbanned user {self.target_link}',
"icon": 'fa-user',

View file

@ -64,13 +64,14 @@ class VisibilityState:
op_name_safe=target.author_name
)
def moderated_body(self, v: User | None) -> str | None:
def moderated_body(self, v: User | None, is_blocking: bool=False) -> str | None:
if v and (v.admin_level >= PERMS['POST_COMMENT_MODERATION'] \
or v.id == self.op_id):
return None
if self.deleted: return 'Deleted'
if self.appear_removed(v): return 'Removed'
if self.filtered: return 'Filtered'
if is_blocking: return f'You are blocking @{self.op_name_safe}'
return None
def visibility_and_message(self, v: User | None, is_blocking: bool) -> tuple[bool, str]:

View file

@ -87,7 +87,10 @@ def canonicalize_url2(url:str, *, httpsify:bool=False) -> urllib.parse.ParseResu
def body_displayed(target:Submittable, v:Optional[User], is_html:bool) -> str:
moderated:Optional[str] = target.visibility_state.moderated_body(v)
moderated:Optional[str] = target.visibility_state.moderated_body(
v=v,
is_blocking=getattr(target, 'is_blocking', False)
)
if moderated: return moderated
body = target.body_html if is_html else target.body

View file

@ -272,14 +272,14 @@ def filtered_comments(v):
# (also rename Unremove to Approve, sigh)
@app.post("/admin/update_filter_status")
@limiter.exempt
@admin_level_required(2)
@admin_level_required(PERMS['POST_COMMENT_MODERATION'])
def update_filter_status(v):
update_body = request.get_json()
new_status = update_body.get('new_status')
post_id = update_body.get('post_id')
comment_id = update_body.get('comment_id')
if new_status not in ['normal', 'removed', 'ignored']:
return { 'result': f'Status of {new_status} is not permitted' }
return {'result': f'Status of {new_status} is not permitted'}, 403
if new_status == 'normal':
state_mod_new = StateMod.VISIBLE
@ -292,43 +292,45 @@ def update_filter_status(v):
state_report_new = StateReport.IGNORED
if post_id:
target = g.db.get(Submission, post_id)
old_status = target.state_mod
# this could totally be one query but it would be kinda ugly
if state_mod_new is not None:
g.db.query(Submission).where(Submission.id == post_id) \
.update({Submission.state_mod: state_mod_new, Submission.state_mod_set_by: v.username})
g.db.query(Submission).where(Submission.id == post_id) \
.update({Submission.state_report: state_report_new})
target: Submission = get_post(post_id, graceful=True)
modlog_target_type: str = 'post'
elif comment_id:
target = g.db.get(Comment, comment_id)
old_status = target.state_mod
if state_mod_new is not None:
g.db.query(Comment).where(Comment.id == comment_id) \
.update({Comment.state_mod: state_mod_new, Comment.state_mod_set_by: v.username})
g.db.query(Comment).where(Comment.id == comment_id) \
.update({Comment.state_report: state_report_new})
target: Comment = get_comment(comment_id, graceful=True)
modlog_target_type: str = 'comment'
else:
return { 'result': f'No valid item ID provided' }
return {"result": "No valid item ID provided"}, 404
if not target:
return {"result": "Item ID does not exist"}, 404
if target is not None:
# If comment now visible, update state to reflect publication.
if (isinstance(target, Comment)
and old_status != StateMod.VISIBLE
and state_mod_new == StateMod.VISIBLE):
comment_on_publish(target) # XXX: can cause discrepancies if removal state ≠ filter state
old_status = target.state_mod
if (isinstance(target, Comment)
and old_status == StateMod.VISIBLE
and state_mod_new != StateMod.VISIBLE and state_mod_new is not None):
comment_on_unpublish(target) # XXX: can cause discrepancies if removal state ≠ filter state
if state_mod_new is not None:
target.state_mod = state_mod_new
target.state_mod_set_by = v.username
target.state_report = state_report_new
g.db.commit()
return { 'result': 'Update successful' }
else:
return { 'result': 'Item ID does not exist' }
making_visible: bool = old_status != StateMod.VISIBLE and state_mod_new == StateMod.VISIBLE
making_invisible: bool = old_status == StateMod.VISIBLE and state_mod_new != StateMod.VISIBLE and state_mod_new is not None
if making_visible:
modlog_action: str = "approve"
if isinstance(target, Comment): comment_on_publish(target)
elif making_invisible:
modlog_action: str = "remove"
if isinstance(target, Comment): comment_on_unpublish(target)
if making_visible or making_invisible:
g.db.add(ModAction(
kind=f"{modlog_action}_{modlog_target_type}",
user_id=v.id,
target_submission_id=target.id if isinstance(target, Submission) else None,
target_comment_id=target.id if isinstance(target, Comment) else None
))
g.db.commit()
invalidate_cache(frontlist=True)
return { 'result': 'Update successful' }
@app.get("/admin/image_posts")
@limiter.exempt
@ -809,7 +811,7 @@ def admin_removed_comments(v):
try: page = int(request.values.get("page", 1))
except: page = 1
ids = g.db.query(Comment.id).join(User, User.id == Comment.author_id).filter(or_(Comment.state_mode == StateMod.REMOVED, User.shadowbanned != None)).order_by(Comment.id.desc()).offset(25 * (page - 1)).limit(26).all()
ids = g.db.query(Comment.id).join(User, User.id == Comment.author_id).filter(or_(Comment.state_mod == StateMod.REMOVED, User.shadowbanned != None)).order_by(Comment.id.desc()).offset(25 * (page - 1)).limit(26).all()
ids=[x[0] for x in ids]
@ -1100,74 +1102,6 @@ def unban_user(user_id, v):
else: return {"message": f"@{user.username} was unbanned!"}
@app.post("/remove_post/<post_id>")
@limiter.exempt
@admin_level_required(2)
def remove_post(post_id, v):
post = g.db.query(Submission).filter_by(id=post_id).one_or_none()
if not post:
abort(400)
post.state_mod = StateMod.REMOVED
post.state_mod_set_by = v.username
post.stickied = None
post.is_pinned = False
g.db.add(post)
ma=ModAction(
kind="remove_post",
user_id=v.id,
target_submission_id=post.id,
)
g.db.add(ma)
invalidate_cache(frontlist=True)
v.coins += 1
g.db.add(v)
requests.post(f'https://api.cloudflare.com/client/v4/zones/{CF_ZONE}/purge_cache', headers=CF_HEADERS, json={'files': [f"{SITE_FULL}/logged_out/"]}, timeout=5)
g.db.commit()
return {"message": "Post removed!"}
@app.post("/unremove_post/<post_id>")
@limiter.exempt
@admin_level_required(2)
def unremove_post(post_id, v):
post = g.db.query(Submission).filter_by(id=post_id).one_or_none()
if not post:
abort(400)
if post.state_mod != StateMod.VISIBLE:
ma=ModAction(
kind="unremove_post",
user_id=v.id,
target_submission_id=post.id,
)
g.db.add(ma)
post.state_mod = StateMod.VISIBLE
post.state_mod_set_by = v.username
g.db.add(post)
invalidate_cache(frontlist=True)
v.coins -= 1
g.db.add(v)
g.db.commit()
return {"message": "Post approved!"}
@app.post("/distinguish/<post_id>")
@limiter.exempt
@admin_level_required(1)
@ -1307,59 +1241,10 @@ def unsticky_comment(cid, v):
return {"message": "Comment unpinned!"}
@app.post("/remove_comment/<c_id>")
@limiter.exempt
@admin_level_required(2)
def api_remove_comment(c_id, v):
comment = g.db.query(Comment).filter_by(id=c_id).one_or_none()
if not comment:
abort(404)
comment.state_mod = StateMod.REMOVED
comment.state_mod_set_by = v.username
comment_on_unpublish(comment) # XXX: can cause discrepancies if removal state ≠ filter state
ma=ModAction(
kind="remove_comment",
user_id=v.id,
target_comment_id=comment.id,
)
g.db.add(ma)
g.db.commit()
return {"message": "Comment removed!"}
@app.post("/unremove_comment/<c_id>")
@limiter.exempt
@admin_level_required(2)
def api_unremove_comment(c_id, v):
comment = g.db.query(Comment).filter_by(id=c_id).one_or_none()
if not comment: abort(404)
if comment.state_mod == StateMod.REMOVED:
ma=ModAction(
kind="unremove_comment",
user_id=v.id,
target_comment_id=comment.id,
)
g.db.add(ma)
comment.state_mod = StateMod.VISIBLE
comment.state_mod_set_by = v.username
comment_on_publish(comment) # XXX: can cause discrepancies if removal state ≠ filter state
g.db.add(comment)
g.db.commit()
return {"message": "Comment approved!"}
@app.post("/distinguish_comment/<c_id>")
@limiter.exempt
@admin_level_required(1)
def admin_distinguish_comment(c_id, v):
comment = get_comment(c_id, v=v)
if comment.author_id != v.id: abort(403)

View file

@ -646,13 +646,12 @@ def visitors(v):
return render_template("viewers.html", v=v, viewers=viewers)
@app.get("/@<username>")
@app.get("/@<username>/posts")
@auth_desired
def u_username(username, v=None):
u = get_user(username, v=v, include_blocks=True)
if username != u.username:
return redirect(SITE_FULL + request.full_path.replace(username, u.username)[:-1])
if username != u.username: return redirect(f'/@{u.username}/posts')
if u.reserved:
if request.headers.get("Authorization") or request.headers.get("xhr"): abort(403, f"That username is reserved for: {u.reserved}")
@ -698,7 +697,7 @@ def u_username(username, v=None):
if u.unban_utc:
if request.headers.get("Authorization"): {"data": [x.json for x in listing]}
return render_template("userpage.html",
return render_template("userpage_submissions.html",
unban=u.unban_string,
u=u,
v=v,
@ -712,7 +711,7 @@ def u_username(username, v=None):
if request.headers.get("Authorization"): return {"data": [x.json for x in listing]}
return render_template("userpage.html",
return render_template("userpage_submissions.html",
u=u,
v=v,
listing=listing,
@ -723,12 +722,13 @@ def u_username(username, v=None):
is_following=(v and u.has_follower(v)))
@app.get("/@<username>/comments")
@app.get("/@<username>/")
@auth_desired
def u_username_comments(username, v=None):
user = get_user(username, v=v, include_blocks=True)
if username != user.username: return redirect(f'/@{user.username}/comments')
if username != user.username:
return redirect(SITE_FULL + request.full_path.replace(username, user.username)[:-1])
u = user
if u.reserved:
@ -935,13 +935,14 @@ def saved_posts(v, username):
listing = get_posts(ids, v=v, eager=True)
if request.headers.get("Authorization"): return {"data": [x.json for x in listing]}
return render_template("userpage.html",
u=v,
v=v,
listing=listing,
page=page,
next_exists=next_exists,
)
return render_template(
"userpage_submissions.html",
u=v,
v=v,
listing=listing,
page=page,
next_exists=next_exists,
)
@app.get("/@<username>/saved/comments")
@ -959,13 +960,15 @@ def saved_comments(v, username):
if request.headers.get("Authorization"): return {"data": [x.json for x in listing]}
return render_template("userpage_comments.html",
u=v,
v=v,
listing=listing,
page=page,
next_exists=next_exists,
standalone=True)
return render_template(
"userpage_comments.html",
u=v,
v=v,
listing=listing,
page=page,
next_exists=next_exists,
standalone=True
)
@app.post("/fp/<fp>")

View file

@ -65,7 +65,8 @@ def api_vote_post(post_id, new, v):
new = int(new)
# get the post
post = get_post(post_id)
post = get_post(post_id, v=v)
if getattr(post, 'is_blocking', False): abort(403, "Can't vote on things from users you've blocked")
# get the old vote, if we have one
vote = g.db.query(Vote).filter_by(user_id=v.id, submission_id=post.id).one_or_none()
@ -132,7 +133,8 @@ def api_vote_comment(comment_id, new, v):
new = int(new)
# get the comment
comment = get_comment(comment_id)
comment = get_comment(comment_id, v=v)
if getattr(comment, 'is_blocking', False): abort(403, "Can't vote on things from users you've blocked")
# get the old vote, if we have one
vote = g.db.query(CommentVote).filter_by(user_id=v.id, comment_id=comment.id).one_or_none()

View file

@ -17,13 +17,13 @@
<h3>Content</h3>
<ul>
<li><a href="/admin/image_posts">Image Posts</a></li>
<li><a href="/admin/reported/posts">Reported Posts/Comments</a></li>
<li><a href="/admin/removed/posts">Removed Posts/Comments</a></li>
<li><a href="/admin/reported/comments">Reported Comments/Posts</a></li>
<li><a href="/admin/removed/comments">Removed Comments/Posts</a></li>
</ul>
<h3>Filtering</h3>
<ul>
<li><a href="/admin/filtered/posts">Filtered Posts/Comments</a></li>
<li><a href="/admin/filtered/comments">Filtered Comments/Posts</a></li>
</ul>
<h3>Users</h3>

View file

@ -23,13 +23,13 @@
<ul class="nav post-nav py-2">
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/filtered/posts" %} active{% endif %}" href="/admin/filtered/posts">
<div>Posts</div>
<a class="nav-link {% if request.path=="/admin/filtered/comments" %} active{% endif %}" href="/admin/filtered/comments">
<div>Comments</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/filtered/comments" %} active{% endif %}" href="/admin/filtered/comments">
<div>Comments</div>
<a class="nav-link {% if request.path=="/admin/filtered/posts" %} active{% endif %}" href="/admin/filtered/posts">
<div>Posts</div>
</a>
</li>
</ul>

View file

@ -20,13 +20,13 @@
<ul class="nav post-nav py-2">
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/removed/posts" %} active{% endif %}" href="/admin/removed/posts">
<div>Posts</div>
<a class="nav-link {% if request.path=="/admin/removed/comments" %} active{% endif %}" href="/admin/removed/comments">
<div>Comments</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/removed/comments" %} active{% endif %}" href="/admin/removed/comments">
<div>Comments</div>
<a class="nav-link {% if request.path=="/admin/removed/posts" %} active{% endif %}" href="/admin/removed/posts">
<div>Posts</div>
</a>
</li>
</ul>

View file

@ -20,13 +20,13 @@
<ul class="nav post-nav py-2">
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/reported/posts" %} active{% endif %}" href="/admin/reported/posts">
<div>Posts</div>
<a class="nav-link {% if request.path=="/admin/reported/comments" %} active{% endif %}" href="/admin/reported/comments">
<div>Comments</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/reported/comments" %} active{% endif %}" href="/admin/reported/comments">
<div>Comments</div>
<a class="nav-link {% if request.path=="/admin/reported/posts" %} active{% endif %}" href="/admin/reported/posts">
<div>Posts</div>
</a>
</li>
</ul>

View file

@ -201,23 +201,23 @@
<script src="{{ 'js/comments+submission_listing.js' | asset }}"></script>
<script src="{{ 'js/comments.js' | asset }}"></script>
{# See https://github.com/themotte/rDrama/pull/642#issuecomment-1646649781 for info on this section of the code #}
<script>
{% if p and (not v or v.highlightcomments) %}
comments = JSON.parse(localStorage.getItem("comment-counts")) || {}
lastCount = comments['{{p.id}}']
if (lastCount)
{
{% for c in p.comments %}
{% if not (v and v.id==c.author_id) and not c.voted %}
if ({{c.created_utc*1000}} > lastCount.t)
try {
document.getElementById("comment-{{c.id}}-only").classList.add('unread')
document.getElementById("comment-{{c.id}}").querySelector(".comment-user-info").innerHTML += "<span>~new~</span>";
}
catch(e) {}
{% endif %}
var commentsToCheck = [
{% for c in p.comments -%}
{%- if not (v and v.id==c.author_id) and not c.voted %}
[{{c.id}}, {{c.created_utc * 1000}}],
{%- endif -%}
{% endfor %}
}
];
commentsToCheck = commentsToCheck.filter(comment => comment[1] > lastCount.t)
.map(comment => comment[0]);
if (lastCount) commentsAddUnreadIndicator(commentsToCheck);
{% endif %}
{% if p and not (request.values and ('context' in request.values)) %}
@ -250,19 +250,19 @@
{% if p %}
comments = JSON.parse(localStorage.getItem("old-comment-counts")) || {}
lastCount = comments['{{p.id}}']
if (lastCount)
{
{% for c in p.comments %}
{% if not (v and v.id==c.author_id) and not c.voted %}
if ({{c.created_utc*1000}} > lastCount.t)
try {
document.getElementById("comment-{{c.id}}-only").classList.add('unread')
document.getElementById("comment-{{c.id}}").querySelector(".comment-user-info").innerHTML += "<span>~new~</span>";
}
catch(e) {}
{% endif %}
var commentsToCheck = [
{% for c in p.comments -%}
{%- if not (v and v.id==c.author_id) and not c.voted %}
[{{c.id}}, {{c.created_utc * 1000}}],
{%- endif -%}
{% endfor %}
}
];
commentsToCheck = commentsToCheck.filter(comment => comment[1] > lastCount.t)
.map(comment => comment[0]);
if (lastCount) commentsAddUnreadIndicator(commentsToCheck);
{% endif %}
}
btn.disabled = false;

View file

@ -19,10 +19,12 @@
{% endif %}
{% endif %}
{% if v and v.admin_level >= 2 and c.state_mod == StateMod.FILTERED %}
<button class="btn" role="button" id="filter-approve" onclick="filter_new_comment_status({{ c.id }}, 'normal')"><span>Approve</span></button>
<button class="btn" role="button" id="filter-remove" onclick="filter_new_comment_status({{ c.id }}, 'removed')"><span>Remove</span></button>
{% endif %}
{%- if v and v.admin_level >= PERMS['POST_COMMENT_MODERATION'] -%}
{%- set show_approve = c.state_mod != StateMod.VISIBLE or "/reported/" in request.path -%}
{%- set show_remove = c.state_mod != StateMod.REMOVED -%}
<button id="remove-{{c.id}}" class="btn caction py-0 nobackground px-1 text-muted{% if not show_remove %} d-none{% endif %}" role="button" onclick="moderate(false, {{c.id}}, true, 'remove-{{c.id}}', 'remove2-{{c.id}}', 'approve-{{c.id}}', 'approve2-{{c.id}}');"><i class="fas fa-ban"></i>Remove</a>
<button id="approve-{{c.id}}" class="btn caction py-0 nobackground px-1 text-muted{% if not show_approve %} d-none{% endif %}" role="button" onclick="moderate(false, {{c.id}}, false, 'remove-{{c.id}}', 'remove2-{{c.id}}', 'approve-{{c.id}}', 'approve2-{{c.id}}');"><i class="fas fa-check"></i>Approve</a>
{%- endif -%}
{% if v %}
<button style="margin-top:0.2rem" class="btn caction py-0 nobackground px-1 text-muted" data-bs-toggle="dropdown" aria-expanded="false"><i class="fas fa-ellipsis-h fa-fw"></i></button>
@ -45,16 +47,6 @@
{% endif %}
{% endif %}
{% if v.admin_level >= 2 %}
{% if "/reported/" in request.path %}
<button class="dropdown-item list-inline-item text-success" onclick="approveCommentDesktop('{{c.id}}')"><i class="fas fa-check text-success fa-fw"></i>Approve</button>
<button class="dropdown-item list-inline-item text-danger" onclick="removeCommentDesktop('{{c.id}}')"><i class="fas fa-ban text-danger fa-fw"></i>Remove</button>
{% else %}
<button id="approve-{{c.id}}" class="dropdown-item list-inline-item d-none {% if c.state_mod != StateMod.VISIBLE %}d-md-block{% endif %} text-success" onclick="approveCommentDesktop('{{c.id}}','approve-{{c.id}}','remove-{{c.id}}')"><i class="fas fa-check text-success fa-fw"></i>Approve</button>
<button id="remove-{{c.id}}" class="dropdown-item list-inline-item d-none {% if c.state_mod != StateMod.REMOVED %}d-md-block{% endif %} text-danger" onclick="removeCommentDesktop('{{c.id}}','approve-{{c.id}}','remove-{{c.id}}')"><i class="fas fa-ban text-danger fa-fw"></i>Remove</button>
{% endif %}
{% endif %}
{% if c.parent_submission and (c.author_id==v.id or v.admin_level >= 2) %}
<button id="unmark-{{c.id}}" class="dropdown-item list-inline-item d-none {% if c.over_18 %}d-md-block{% endif %} text-danger" onclick="post_toast3(this,'/toggle_comment_nsfw/{{c.id}}','mark-{{c.id}}','unmark-{{c.id}}')"><i class="fas fa-eye-evil text-danger fa-fw"></i>Unmark +18</button>
<button id="mark-{{c.id}}" class="dropdown-item list-inline-item d-none {% if not c.over_18 %}d-md-block{% endif %} text-danger" onclick="post_toast3(this,'/toggle_comment_nsfw/{{c.id}}','mark-{{c.id}}','unmark-{{c.id}}')"><i class="fas fa-eye-evil text-danger fa-fw"></i>Mark +18</button>

View file

@ -27,13 +27,12 @@
<a id="unban2-{{c.id}}" class="{% if not c.author.is_banned %}d-none{% endif %} list-group-item text-success" role="button" onclick="post_toast2(this,'/unban_user/{{c.author_id}}','ban2-{{c.id}}','unban2-{{c.id}}')" data-bs-dismiss="modal"><i class="fas fa-user-minus fa-fw text-success mr-2"></i>Unban user</a>
{% endif %}
{% if "/reported/" in request.path %}
<a class="list-group-item text-danger" role="button" onclick="removeCommentMobile('{{c.id}}')" data-bs-dismiss="modal"><i class="fas fa-ban text-danger mr-2"></i>Remove</a>
<a class="list-group-item text-success" role="button" onclick="approveCommentMobile('{{c.id}}')" data-bs-dismiss="modal"><i class="fas fa-check text-success mr-2"></i>Approve</a>
{% else %}
<a id="remove2-{{c.id}}" class="{% if c.state_mod != StateMod.VISIBLE %}d-none{% endif %} list-group-item text-danger" role="button" onclick="removeCommentMobile('{{c.id}}','approve2-{{c.id}}','remove2-{{c.id}}')" data-bs-dismiss="modal"><i class="fas fa-ban text-danger mr-2"></i>Remove</a>
<a id="approve2-{{c.id}}" class="{% if c.state_mod != StateMod.REMOVED %}d-none{% endif %} list-group-item text-success" role="button" onclick="approveCommentMobile('{{c.id}}','approve2-{{c.id}}','remove2-{{c.id}}')" data-bs-dismiss="modal"><i class="fas fa-check text-success mr-2"></i>Approve</a>
{% endif %}
{%- if v and v.admin_level >= PERMS['POST_COMMENT_MODERATION'] -%}
{%- set show_approve = c.state_mod != StateMod.VISIBLE or "/reported/" in request.path -%}
{%- set show_remove = c.state_mod == StateMod.VISIBLE -%}
<a id="remove2-{{c.id}}" class="dropdown-item list-inline-item text-danger{% if not show_remove %} d-none{% endif %}" role="button" onclick="moderate(false, {{c.id}}, true, 'remove-{{c.id}}', 'remove2-{{c.id}}', 'approve-{{c.id}}', 'approve2-{{c.id}}');"><i class="fas fa-ban"></i>Remove</a>
<a id="approve2-{{c.id}}" class="dropdown-item list-inline-item text-success{% if not show_approve %} d-none{% endif %}" role="button" onclick="moderate(false, {{c.id}}, false, 'remove-{{c.id}}', 'remove2-{{c.id}}', 'approve-{{c.id}}', 'approve2-{{c.id}}');"></i>Approve</a>
{%- endif -%}
{% if c.oauth_app %}
<a href="{{c.oauth_app.permalink}}/comments" class="list-group-item text-info"><i class="fas fa-code text-info mr-2"></i>API App</a>

View file

@ -58,15 +58,14 @@
<a id="unclub-{{p.id}}" class="dropdown-item {% if not p.club %}d-none{% endif %} list-inline-item text-info" role="button" onclick="post_toast2(this,'/toggle_club/{{p.id}}','club-{{p.id}}','unclub-{{p.id}}')"><i class="fas fa-eye"></i>Unmark club</a>
{% endif %} #}
{% if v.admin_level > 1 %}
{% if "/reported/" in request.path %}
{% if v.id != p.author.id %}<a class="dropdown-item list-inline-item text-danger" role="button" onclick="post_toast(this,'/remove_post/{{p.id}}')"><i class="fas fa-ban"></i>Remove</a>{% endif %}
<a class="dropdown-item list-inline-item text-success" role="button" onclick="post_toast(this,'/unremove_post/{{p.id}}')"><i class="fas fa-check"></i>Approve</a>
{% else %}
{% if v.id != p.author.id %}<a id="remove-{{p.id}}" class="dropdown-item {% if p.state_mod == StateMod.REMOVED %}d-none{% endif %} list-inline-item text-danger" role="button" onclick="post_toast2(this,'/remove_post/{{p.id}}','remove-{{p.id}}','approve-{{p.id}}')"><i class="fas fa-ban"></i>Remove</a>{% endif %}
<a id="approve-{{p.id}}" class="dropdown-item {% if p.state_mod == StateMod.VISIBLE %}d-none{% endif %} list-inline-item text-success" role="button" onclick="post_toast2(this,'/unremove_post/{{p.id}}','remove-{{p.id}}','approve-{{p.id}}')"><i class="fas fa-check"></i>Approve</a>
{% endif %}
{%- if v and v.admin_level >= PERMS['POST_COMMENT_MODERATION'] -%}
{%- set show_approve = p.state_mod != StateMod.VISIBLE or "/reported/" in request.path -%}
{%- set show_remove = p.state_mod == StateMod.VISIBLE -%}
<a id="remove-{{p.id}}" class="dropdown-item list-inline-item text-danger{% if not show_remove %} d-none{% endif %}" role="button" onclick="moderate(true, {{p.id}}, true, 'remove-{{p.id}}', 'remove2-{{p.id}}', 'approve-{{p.id}}', 'approve2-{{p.id}}');"><i class="fas fa-ban"></i>Remove</a>
<a id="approve-{{p.id}}" class="dropdown-item list-inline-item text-success{% if not show_approve %} d-none{% endif %}" role="button" onclick="moderate(true, {{p.id}}, false, 'remove-{{p.id}}', 'remove2-{{p.id}}', 'approve-{{p.id}}', 'approve2-{{p.id}}');"><i class="fas fa-check"></i>Approve</a>
{%- endif -%}
{% if v.admin_level >= 2 %}
{% if p.oauth_app %}
<a class="dropdown-item list-inline-item" href="{{p.oauth_app.permalink}}"><i class="fas fa-code"></i>API App</a>
{% endif %}

View file

@ -20,21 +20,14 @@
<button id="undistinguish2-{{p.id}}" class="{% if not p.distinguish_level %}d-none{% endif %} nobackground btn btn-link btn-block btn-lg text-left text-primary" role="button" onclick="post_toast2(this,'/distinguish/{{p.id}}','distinguish2-{{p.id}}','undistinguish2-{{p.id}}')" data-bs-dismiss="modal"><i class="fas fa-crown text-center text-primary mr-3"></i>Undistinguish</button> #}
<button id="pin2-{{p.id}}" class="{% if p.stickied %}d-none{% endif %} nobackground btn btn-link btn-block btn-lg text-left text-primary" role="button" onclick="post_toast2(this,'/sticky/{{p.id}}','pin2-{{p.id}}','unpin2-{{p.id}}')" data-bs-dismiss="modal"><i class="fas fa-thumbtack fa-rotate--45 text-center text-primary mr-3"></i>Pin</button>
<button id="unpin2-{{p.id}}" class="{% if not p.stickied %}d-none{% endif %} nobackground btn btn-link btn-block btn-lg text-left text-primary" role="button" onclick="post_toast2(this,'/unsticky/{{p.id}}','pin2-{{p.id}}','unpin2-{{p.id}}')" data-bs-dismiss="modal"><i class="fas fa-thumbtack fa-rotate--45 text-center text-primary mr-3"></i>Unpin</button>
{% if "/reported/" in request.path %}
<button class="nobackground btn btn-link btn-block btn-lg text-danger text-left" role="button" onclick="post_toast(this,'/remove_post/{{p.id}}')" data-bs-dismiss="modal"><i class="far fa-ban text-center mr-3"></i>Remove</button>
<button class="nobackground btn btn-link btn-block btn-lg text-success text-left" role="button" onclick="post_toast(this,'/unremove_post/{{p.id}}')" data-bs-dismiss="modal"><i class="far fa-check text-center mr-3"></i>Approve</button>
{% else %}
<button id="remove2-{{p.id}}" class="{% if p.state_mod == StateMod.REMOVED %}d-none{% endif %} nobackground btn btn-link btn-block btn-lg text-danger text-left" role="button" onclick="post_toast2(this,'/remove_post/{{p.id}}','remove2-{{p.id}}','approve2-{{p.id}}')" data-bs-dismiss="modal"><i class="far fa-ban text-center mr-3"></i>Remove</button>
<button id="approve2-{{p.id}}" class="{% if p.state_mod == StateMod.VISIBLE %}d-none{% endif %} nobackground btn btn-link btn-block btn-lg text-success text-left" role="button" onclick="post_toast2(this,'/unremove_post/{{p.id}}','remove2-{{p.id}}','approve2-{{p.id}}')" data-bs-dismiss="modal"><i class="far fa-check text-center mr-3"></i>Approve</button>
{% endif %}
{% if v and v.admin_level >= 2 and p.state_mod == StateMod.FILTERED %}
<button id="mobile-filter-approve-{{p.id}}" class="nobackground btn btn-link btn-block btn-lg text-success text-left" role="button" onclick="filter_new_status({{p.id}}, 'normal')"><i class="far fa-check text-center mr-3"></i>Filter Approve</a>
<button id="mobile-filter-remove-{{p.id}}" class="nobackground btn btn-link btn-block btn-lg text-danger text-left" role="button" onclick="filter_new_status({{p.id}}, 'removed')"><i class="far fa-ban text-center mr-3"></i>Filter Remove</a>
{% endif %}
{%- if v and v.admin_level >= PERMS['POST_COMMENT_MODERATION'] -%}
{%- set show_approve = p.state_mod != StateMod.VISIBLE or "/reported/" in request.path -%}
{%- set show_remove = p.state_mod == StateMod.VISIBLE -%}
<a id="remove2-{{p.id}}" class="dropdown-item list-inline-item text-danger{% if not show_remove %} d-none{% endif %}" role="button" onclick="moderate(true, {{p.id}}, true, 'remove-{{p.id}}', 'remove2-{{p.id}}', 'approve-{{p.id}}', 'approve2-{{p.id}}');"><i class="fas fa-ban"></i>Remove</a>
<a id="approve2-{{p.id}}" class="dropdown-item list-inline-item text-success{% if not show_approve %} d-none{% endif %}" role="button" onclick="moderate(true, {{p.id}}, false, 'remove-{{p.id}}', 'remove2-{{p.id}}', 'approve-{{p.id}}', 'approve2-{{p.id}}');"><i class="fas fa-check"></i>Approve</a>
{%- endif -%}
{% if p.oauth_app %}
<a href="{{p.oauth_app.permalink}}"><button class="nobackground btn btn-link btn-block btn-lg text-muted text-left"><i class="far fa-code text-center text-info mr-3"></i>API App</button></a>
@ -47,7 +40,6 @@
<button id="ban2-{{p.id}}" data-bs-dismiss="modal" data-bs-toggle="modal" data-bs-target="#banModal" onclick="banModal('/post/{{p.id}}', '{{p.author.id}}', '{{p.author_name}}')" class="{% if p.author.is_suspended %}d-none{% endif %} nobackground btn btn-link btn-block btn-lg text-danger text-left" role="button"><i class="fas fa-user-minus mr-3"></i>Ban user</button>
<button id="unban2-{{p.id}}" class="{% if not p.author.is_suspended %}d-none{% endif %} nobackground btn btn-link btn-block btn-lg text-success text-left" role="button" onclick="post_toast2(this,'/unban_user/{{p.author_id}}','ban2-{{p.id}}','unban2-{{p.id}}')" data-bs-dismiss="modal"><i class="fas fa-user-minus mr-3"></i>Unban user</button>
{% endif %}
</ul>
</div>
</div>

View file

@ -11,7 +11,7 @@
</a>
<div class="flex-grow-1 d-fl d-none d-md-block {% if not v %}pad{% endif %}">
<form id="searchform" class="form-inline search flex-nowrap mx-0 mx-lg-auto" {% if err %}style="margin-right:40rem!important"{% endif %} action="{% if request.path.startswith('/search') %}{{request.path}}{% else %}/search/posts/{% endif %}" method="get">
<form id="searchform" class="form-inline search flex-nowrap mx-0 mx-lg-auto" {% if err %}style="margin-right:40rem!important"{% endif %} action="{% if request.path.startswith('/search') %}{{request.path}}{% else %}/search/comments/{% endif %}" method="get">
<input autocomplete="off" class="form-control w-100" placeholder="Search" aria-label="Search" name="q" value="{{request.values.get('q', '')}}">
<span class="input-group-append">
<span class="input-group-text border-0 bg-transparent" style="margin-left: -2.5rem; cursor: pointer;" onclick="document.getElementById('searchform').submit()">

View file

@ -61,10 +61,10 @@
<div class="flex-row box-shadow-bottom d-flex justify-content-center justify-content-md-between align-items-center">
<ul class="nav settings-nav">
<li class="nav-item">
<a class="nav-link{% if '/posts/' in request.path %} active{% endif %}" href="/search/posts/?sort={{sort}}&q={{query | urlencode}}&t={{t}}">Posts</a>
<a class="nav-link{% if '/comments/' in request.path %} active{% endif %}" href="/search/comments/?sort={{sort}}&q={{query | urlencode}}&t={{t}}">Comments</a>
</li>
<li class="nav-item">
<a class="nav-link{% if '/comments/' in request.path %} active{% endif %}" href="/search/comments/?sort={{sort}}&q={{query | urlencode}}&t={{t}}">Comments</a>
<a class="nav-link{% if '/posts/' in request.path %} active{% endif %}" href="/search/posts/?sort={{sort}}&q={{query | urlencode}}&t={{t}}">Posts</a>
</li>
<li class="nav-item">
<a class="nav-link{% if '/users/' in request.path %} active{% endif %}" href="/search/users/?sort={{sort}}&q={{query | urlencode}}&t={{t}}">Users</a>

View file

@ -7,52 +7,50 @@
<div class="settings">
<h2 class="h5" name="referral">Theme</h2>
<div class="settings-section rounded">
<div class="d-lg-flex border-bottom">
<div class="title w-lg-25">
<label for="cardview">Card View</label>
</div>
<div class="body w-lg-100">
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="cardview" name="cardview"{% if v.cardview %} checked{% endif %} onchange="post_toast(this,'/settings/profile?cardview='+document.getElementById('cardview').checked);">
<label class="custom-control-label" for="cardview"></label>
</div>
<span class="text-small-extra text-muted">Enable if you would like to display images and videos in full size on the frontpage.</span>
<div class="d-lg-flex border-bottom">
<div class="title w-lg-25">
<label for="cardview">Card View</label>
</div>
<div class="body w-lg-100">
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="cardview" name="cardview"{% if v.cardview %} checked{% endif %} onchange="post_toast(this,'/settings/profile?cardview='+document.getElementById('cardview').checked);">
<label class="custom-control-label" for="cardview"></label>
</div>
<span class="text-small-extra text-muted">Enable if you would like to display images and videos in full size on the frontpage.</span>
</div>
</div>
<div class="d-lg-flex border-bottom">
<div class="title w-lg-25">
<label for="highlightcomments">Highlight New Comments</label>
</div>
<div class="d-lg-flex border-bottom">
<div class="title w-lg-25">
<label for="highlightcomments">Highlight New Comments</label>
</div>
<div class="body w-lg-100">
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="highlightcomments" name="highlightcomments"{% if v.highlightcomments %} checked{% endif %} onchange="post_toast(this,'/settings/profile?highlightcomments='+document.getElementById('highlightcomments').checked);">
<label class="custom-control-label" for="highlightcomments"></label>
</div>
<span class="text-small-extra text-muted">Enable if you would like to highlight comments made after the last time you visited a thread.</span>
<div class="body w-lg-100">
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="highlightcomments" name="highlightcomments"{% if v.highlightcomments %} checked{% endif %} onchange="post_toast(this,'/settings/profile?highlightcomments='+document.getElementById('highlightcomments').checked);">
<label class="custom-control-label" for="highlightcomments"></label>
</div>
<span class="text-small-extra text-muted">Enable if you would like to highlight comments made after the last time you visited a thread.</span>
</div>
</div>
<div class="d-lg-flex border-bottom">
<div class="title w-lg-25">
<label for="theme">Website Theme</label>
</div>
<div class="body w-lg-100">
<p>Change the theme for the website.</p>
<div class="input-group mb2">
<select autocomplete="off" id='theme' class="form-control" form="profile-settings" name="theme" onchange="post_toast(this,'/settings/profile?theme='+document.getElementById('theme').value, '1')">
{% for entry in THEMES %}
<option value="{{entry}}" {% if v.theme==entry %} selected {% endif %}>
{{entry}}
</option>
{% endfor %}
<div class="d-lg-flex border-bottom">
<div class="title w-lg-25">
<label for="theme">Website Theme</label>
</div>
<div class="body w-lg-100">
<p>Change the theme for the website.</p>
<div class="input-group mb2">
<select autocomplete="off" id='theme' class="form-control" form="profile-settings" name="theme" onchange="post_toast(this,'/settings/profile?theme='+document.getElementById('theme').value, '1')">
{% for entry in THEMES %}
<option value="{{entry}}" {% if v.theme==entry %} selected {% endif %}>
{{entry}}
</option>
{% endfor %}
</select>
</div>
<span class="text-small-extra text-muted">Themes are not officially supported. If you run into an issue, please report it or submit a pull request.</span>
</div>
<span class="text-small-extra text-muted">Themes are not officially supported. If you run into an issue, please report it or submit a pull request.</span>
</div>
</div>
</div>
@ -77,8 +75,8 @@
</div>
</div>
</div>
<h2 class="h5">Profile Banner</h2>
<h2 class="h5">Profile Banner</h2>
<div class="settings-section rounded">
<div class="d-flex">
<div class="title w-lg-75 text-md-center">
@ -112,204 +110,197 @@
<input autocomplete="off" type="text" readonly="" class="form-control copy-link" id="referral_code" value="{{SITE_FULL}}/signup?ref={{v.username}}" data-clipboard-text="{{SITE_FULL}}/signup?ref={{v.username}}">
<span class="input-group-append" data-bs-toggle="tooltip" data-bs-placement="top" title="You have referred {{v.referral_count}} user{{'s' if v.referral_count != 1 else ''}} so far. {% if v.referral_count==0 %}¯\_(ツ)_/¯{% elif v.referral_count>10%}Wow!{% endif %}">
<span class="input-group-text text-primary border-0">
<i class="far fa-user mr-1" aria-hidden="true"></i>{{v.referral_count}}</span>
<i class="far fa-user mr-1" aria-hidden="true"></i>{{v.referral_count}}
</span>
</div>
<div class="text-small-extra text-muted mt-3">Share this link with a friend. {% if v.referral_count==0 %} When they sign up, you'll get the bronze recruitment badge.{% elif v.referral_count<10 %} When you refer 10 friends, you'll receive the silver recruitment badge.{% elif v.referral_count<100 %} When you refer 100 friends, you'll receive the gold recruitment badge.{% endif %}</div>
</div>
</span>
</div>
</div>
<h2 class="h5" name="referral">RSS Feed</h2>
<p class="text-small text-muted">Subscribe to the {{SITE_TITLE}} RSS feed.</p>
<div class="settings-section rounded">
<div class="d-lg-flex">
<div class="body w-lg-100">
<input autocomplete="off" type="text" readonly="" class="form-control copy-link" id="rss_feed" value="{{SITE_FULL}}/rss/new/all" data-clipboard-text="{{SITE_FULL}}/rss/new/all">
<div class="text-small-extra text-muted mt-3">You can change the feed by replacing "new" with whatever sorting you want and "all" with whatever time filter you want.</div>
</div>
<div class="text-small-extra text-muted mt-3">
Share this link with a friend. {% if v.referral_count==0 %} When they sign up, you'll get the bronze recruitment badge.{% elif v.referral_count<10 %} When you refer 10 friends, you'll receive the silver recruitment badge.{% elif v.referral_count<100 %} When you refer 100 friends, you'll receive the gold recruitment badge.{% endif %}
</div>
</div>
<h2 class="h5" id="bio" name="bio">Your Profile</h2>
<p class="text-small text-muted">Edit how others see you on {{SITE_TITLE}}.</p>
<div class="settings-section rounded mb-0">
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Username</label>
<div class="w-lg-100">
<p>Your original username will always stay reserved for you: <code>{{v.original_username}}</code></p>
<form action="/settings/name_change" method="post">
{{forms.formkey(v)}}
<input autocomplete="off" type="text" name="name" class="form-control" value="{{v.username}}">
<small>3-25 characters, including letters, numbers, _ , and -</small>
<div class="d-flex mt-2">
<input autocomplete="off" class="btn btn-primary ml-auto" type="submit" value="Change Display Name">
</div>
</form>
</div>
</div>
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Flair</label>
<div class="w-lg-100">
<form id="profile-settings" action="/settings/title_change" method="post">
{{forms.formkey(v)}}
<input maxlength=100 {% if v.flairchanged %}disabled{% endif %} autocomplete="off" id="customtitlebody" type="text" name="title" class="form-control" placeholder='Enter a flair here' value="{% if v.flairchanged %}Your flair has been locked until {{ti}}{% elif v.customtitleplain %}{{v.customtitleplain}}{% endif %}">
<div class="d-flex mt-2">
<small>Limit of 100 characters</small>
<input {% if v.flairchanged %}disabled{% endif %} autocomplete="off" class="btn btn-primary ml-auto" id="titleSave" type="submit" value="Change Flair">
</div>
</form>
</div>
</div>
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Flair Color</label>
<div class="d-flex">
<form action="/settings/titlecolor" id="titlecolor-form" method="post" class="color-picker" style="line-height: 0">
{{forms.formkey(v)}}
{% for titlecolor in COLORS %}
<input autocomplete="off" type="radio" name="titlecolor" id="titlecolor-{{titlecolor}}" value="{{titlecolor}}" {% if v.titlecolor == titlecolor %}checked{% endif %} onclick="document.getElementById('titlecolor-form').submit()">
<label class="color-radio" for="titlecolor-{{titlecolor}}">
<span style="background-color: #{{titlecolor}}">
{% if v.titlecolor.lower() == titlecolor %}
<i class="fas fa-check text-white"></i>
{% else %}
&nbsp;
{% endif %}
</span>
</label>
{% endfor %}
</form>
</div>
<p class="text-small mb-2">Or type a color code:</p>
<div class="d-flex">
<form action="/settings/titlecolor" id="color-code-form" method="post">
{{forms.formkey(v)}}
<input autocomplete="off" class="form-control" type="text" name="titlecolor" id="color-code" maxlength="6" value="{% if v.titlecolor %}{{v.titlecolor}}{% endif %}">
<label class="btn btn-secondary text-capitalize mr-2 mt-2 mb-0">Update<input autocomplete="off" type="text" for="color-code" onclick="form.submit()" hidden=""></label>
</form>
</div>
</div>
{% if v.verified %}
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Bluecheck Color</label>
<div class="d-flex">
<form action="/settings/verifiedcolor" id="verifiedcolor-form" method="post" class="color-picker" style="line-height: 0">
{{forms.formkey(v)}}
{% for verifiedcolor in COLORS %}
<input autocomplete="off" type="radio" name="verifiedcolor" id="verifiedcolor-{{verifiedcolor}}" value="{{verifiedcolor}}" {% if v.verifiedcolor == verifiedcolor %}checked{% endif %} onclick="document.getElementById('verifiedcolor-form').submit()">
<label class="color-radio" for="verifiedcolor-{{verifiedcolor}}">
<span style="background-color: #{{verifiedcolor}}">
{% if v.verifiedcolor and v.verifiedcolor.lower() == verifiedcolor %}
<i class="fas fa-check text-white"></i>
{% else %}
&nbsp;
{% endif %}
</span>
</label>
{% endfor %}
</form>
</div>
<p class="text-small mb-2">Or type a color code:</p>
<div class="d-flex">
<form action="/settings/verifiedcolor" id="color-code-form" method="post">
{{forms.formkey(v)}}
<input autocomplete="off" class="form-control" type="text" name="verifiedcolor" id="color-code" maxlength="6" value="{% if v.verifiedcolor %}{{v.verifiedcolor}}{% endif %}">
<label class="btn btn-secondary text-capitalize mr-2 mt-2 mb-0">Update<input autocomplete="off" type="text" for="color-code" onclick="form.submit()" hidden=""></label>
</form>
</div>
</div>
{% endif %}
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Bio</label>
<div class="w-lg-100">
<form id="profile-bio" action="/settings/profile" method="post" enctype="multipart/form-data">
{{forms.formkey(v)}}
<div class="input-group mb-2">
<textarea autocomplete="off" id="bio-text" class="form-control rounded" aria-label="With textarea" placeholder="Tell the community a bit about yourself." rows="3" name="bio" form="profile-bio" maxlength="1500">{% if v.bio %}{{v.bio}}{% endif %}</textarea>
</div>
<div class="d-flex">
<pre style="padding-top:0.7rem" class="btn btn-secondary format d-inline-block m-0 fas fa-bold" aria-hidden="true" onclick="makeBold('bio-text')" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Bold"></pre>
&nbsp;
<pre style="padding-top:0.7rem" class="btn btn-secondary format d-inline-block m-0 fas fa-italic" aria-hidden="true" onclick="makeItalics('bio-text')" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Italicize"></pre>
&nbsp;
<pre style="padding-top:0.7rem" class="btn btn-secondary format d-inline-block m-0 fas fa-quote-right" aria-hidden="true" onclick="makeQuote('bio-text')" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Quote"></pre>
&nbsp;
<pre style="padding-top:0.7rem;line-height:1" class="btn btn-secondary format d-inline-block m-0 font-weight-bolder text-uppercase" onclick="commentForm('bio-text');getGif()" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#gifModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add GIF">GIF</pre>
&nbsp;
<label class="btn btn-secondary format d-inline-block m-0">
<div id="filename-show"><i class="far fa-image"></i></div>
<input autocomplete="off" id="file-upload" type="file" name="file" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} accept="image/*" onchange="changename('filename-show','file-upload')" hidden>
</label>
</div>
<pre></pre>
<div class="d-flex">
<small>Limit of 1500 characters</small>
<input autocomplete="off" class="btn btn-primary ml-auto" id="bioSave" type="submit" value="Save Changes">
</div>
</form>
</div>
</div>
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Friends</label>
<div class="w-lg-100">
<form id="profile-friends" action="/settings/profile" method="post" enctype="multipart/form-data">
{{forms.formkey(v)}}
<div class="input-group mb-2">
<textarea autocomplete="off" id="friends-text" class="form-control rounded" aria-label="With textarea" placeholder="Enter your friends on the site..." rows="3" name="friends" form="profile-friends" maxlength="1500">{% if v.friends %}{{v.friends}}{% endif %}</textarea>
</div>
<pre></pre>
<div class="d-flex">
<small>Limit of 500 characters</small>
<input autocomplete="off" class="btn btn-primary ml-auto" id="friendsSave" type="submit" value="Save Changes">
</div>
</form>
</div>
</div>
<div class="d-lg-flex border-bottom">
<div class="title w-lg-25">
<label for="privateswitch">Private Mode</label>
</div>
<div class="body w-lg-100">
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="privateswitch" name="private"{% if v.is_private%} checked{% endif %} onchange="post_toast(this,'/settings/profile?private='+document.getElementById('privateswitch').checked)">
<label class="custom-control-label" for="privateswitch"></label>
</div>
<span class="text-small-extra text-muted">This will hide your post and comment history from others. We will also ask search engines to not index your profile page. (Your content will still be accessible via direct link.)</span>
</div>
</div>
<div class="d-lg-flex border-bottom">
<div class="title w-lg-25">
<label for="nofollowswitch">Disable Subscriptions</label>
</div>
<div class="body w-lg-100">
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="nofollowswitch" name="nofollow"{% if v.is_nofollow%} checked{% endif %} onchange="post_toast(this,'/settings/profile?nofollow='+document.getElementById('nofollowswitch').checked)">
<label class="custom-control-label" for="nofollowswitch"></label>
</div>
<span class="text-small-extra text-muted">Prevent other users from following you.</span>
</div>
</div>
</div>
</div>
</div>
</div>
<h2 class="h5" name="referral">RSS Feed</h2>
<p class="text-small text-muted">Subscribe to the {{SITE_TITLE}} RSS feed.</p>
<div class="settings-section rounded">
<div class="d-lg-flex">
<div class="body w-lg-100">
<input autocomplete="off" type="text" readonly="" class="form-control copy-link" id="rss_feed" value="{{SITE_FULL}}/rss/new/all" data-clipboard-text="{{SITE_FULL}}/rss/new/all">
<div class="text-small-extra text-muted mt-3">You can change the feed by replacing "new" with whatever sorting you want and "all" with whatever time filter you want.</div>
</div>
</div>
</div>
<h2 class="h5" id="bio" name="bio">Your Profile</h2>
<p class="text-small text-muted">Edit how others see you on {{SITE_TITLE}}.</p>
<div class="settings-section rounded mb-0">
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Username</label>
<div class="w-lg-100">
<p>Your original username will always stay reserved for you: <code>{{v.original_username}}</code></p>
<form action="/settings/name_change" method="post">
{{forms.formkey(v)}}
<input autocomplete="off" type="text" name="name" class="form-control" value="{{v.username}}">
<small>3-25 characters, including letters, numbers, _ , and -</small>
<div class="d-flex mt-2">
<input autocomplete="off" class="btn btn-primary ml-auto" type="submit" value="Change Display Name">
</div>
</form>
</div>
</div>
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Flair</label>
<div class="w-lg-100">
<form id="profile-settings" action="/settings/title_change" method="post">
{{forms.formkey(v)}}
<input maxlength=100 {% if v.flairchanged %}disabled{% endif %} autocomplete="off" id="customtitlebody" type="text" name="title" class="form-control" placeholder='Enter a flair here' value="{% if v.flairchanged %}Your flair has been locked until {{ti}}{% elif v.customtitleplain %}{{v.customtitleplain}}{% endif %}">
<div class="d-flex mt-2">
<small>Limit of 100 characters</small>
<input {% if v.flairchanged %}disabled{% endif %} autocomplete="off" class="btn btn-primary ml-auto" id="titleSave" type="submit" value="Change Flair">
</div>
</form>
</div>
</div>
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Flair Color</label>
<div class="d-flex">
<form action="/settings/titlecolor" id="titlecolor-form" method="post" class="color-picker" style="line-height: 0">
{{forms.formkey(v)}}
{% for titlecolor in COLORS %}
<input autocomplete="off" type="radio" name="titlecolor" id="titlecolor-{{titlecolor}}" value="{{titlecolor}}" {% if v.titlecolor == titlecolor %}checked{% endif %} onclick="document.getElementById('titlecolor-form').submit()">
<label class="color-radio" for="titlecolor-{{titlecolor}}">
<span style="background-color: #{{titlecolor}}">
{% if v.titlecolor.lower() == titlecolor %}
<i class="fas fa-check text-white"></i>
{% else %}
&nbsp;
{% endif %}
</span>
</label>
{% endfor %}
</form>
</div>
<p class="text-small mb-2">Or type a color code:</p>
<div class="d-flex">
<form action="/settings/titlecolor" id="color-code-form" method="post">
{{forms.formkey(v)}}
<input autocomplete="off" class="form-control" type="text" name="titlecolor" id="color-code" maxlength="6" value="{% if v.titlecolor %}{{v.titlecolor}}{% endif %}">
<label class="btn btn-secondary text-capitalize mr-2 mt-2 mb-0">Update<input autocomplete="off" type="text" for="color-code" onclick="form.submit()" hidden=""></label>
</form>
</div>
</div>
{% if v.verified %}
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Bluecheck Color</label>
<div class="d-flex">
<form action="/settings/verifiedcolor" id="verifiedcolor-form" method="post" class="color-picker" style="line-height: 0">
{{forms.formkey(v)}}
{% for verifiedcolor in COLORS %}
<input autocomplete="off" type="radio" name="verifiedcolor" id="verifiedcolor-{{verifiedcolor}}" value="{{verifiedcolor}}" {% if v.verifiedcolor == verifiedcolor %}checked{% endif %} onclick="document.getElementById('verifiedcolor-form').submit()">
<label class="color-radio" for="verifiedcolor-{{verifiedcolor}}">
<span style="background-color: #{{verifiedcolor}}">
{% if v.verifiedcolor and v.verifiedcolor.lower() == verifiedcolor %}
<i class="fas fa-check text-white"></i>
{% else %}
&nbsp;
{% endif %}
</span>
</label>
{% endfor %}
</form>
</div>
<p class="text-small mb-2">Or type a color code:</p>
<div class="d-flex">
<form action="/settings/verifiedcolor" id="color-code-form" method="post">
{{forms.formkey(v)}}
<input autocomplete="off" class="form-control" type="text" name="verifiedcolor" id="color-code" maxlength="6" value="{% if v.verifiedcolor %}{{v.verifiedcolor}}{% endif %}">
<label class="btn btn-secondary text-capitalize mr-2 mt-2 mb-0">Update<input autocomplete="off" type="text" for="color-code" onclick="form.submit()" hidden=""></label>
</form>
</div>
</div>
{% endif %}
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Bio</label>
<div class="w-lg-100">
<form id="profile-bio" action="/settings/profile" method="post" enctype="multipart/form-data">
{{forms.formkey(v)}}
<div class="input-group mb-2">
<textarea autocomplete="off" id="bio-text" class="form-control rounded" aria-label="With textarea" placeholder="Tell the community a bit about yourself." rows="3" name="bio" form="profile-bio" maxlength="1500">{% if v.bio %}{{v.bio}}{% endif %}</textarea>
</div>
<div class="d-flex">
<pre style="padding-top:0.7rem" class="btn btn-secondary format d-inline-block m-0 fas fa-bold" aria-hidden="true" onclick="makeBold('bio-text')" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Bold"></pre>
&nbsp;
<pre style="padding-top:0.7rem" class="btn btn-secondary format d-inline-block m-0 fas fa-italic" aria-hidden="true" onclick="makeItalics('bio-text')" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Italicize"></pre>
&nbsp;
<pre style="padding-top:0.7rem" class="btn btn-secondary format d-inline-block m-0 fas fa-quote-right" aria-hidden="true" onclick="makeQuote('bio-text')" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Quote"></pre>
&nbsp;
<pre style="padding-top:0.7rem;line-height:1" class="btn btn-secondary format d-inline-block m-0 font-weight-bolder text-uppercase" onclick="commentForm('bio-text');getGif()" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#gifModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add GIF">GIF</pre>
&nbsp;
<label class="btn btn-secondary format d-inline-block m-0">
<div id="filename-show"><i class="far fa-image"></i></div>
<input autocomplete="off" id="file-upload" type="file" name="file" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} accept="image/*" onchange="changename('filename-show','file-upload')" hidden>
</label>
</div>
<pre></pre>
<div class="d-flex">
<small>Limit of 1500 characters</small>
<input autocomplete="off" class="btn btn-primary ml-auto" id="bioSave" type="submit" value="Save Changes">
</div>
</form>
</div>
</div>
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Friends</label>
<div class="w-lg-100">
<form id="profile-friends" action="/settings/profile" method="post" enctype="multipart/form-data">
{{forms.formkey(v)}}
<div class="input-group mb-2">
<textarea autocomplete="off" id="friends-text" class="form-control rounded" aria-label="With textarea" placeholder="Enter your friends on the site..." rows="3" name="friends" form="profile-friends" maxlength="1500">{% if v.friends %}{{v.friends}}{% endif %}</textarea>
</div>
<pre></pre>
<div class="d-flex">
<small>Limit of 500 characters</small>
<input autocomplete="off" class="btn btn-primary ml-auto" id="friendsSave" type="submit" value="Save Changes">
</div>
</form>
</div>
</div>
<div class="d-lg-flex border-bottom">
<div class="title w-lg-25">
<label for="privateswitch">Private Mode</label>
</div>
<div class="body w-lg-100">
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="privateswitch" name="private"{% if v.is_private%} checked{% endif %} onchange="post_toast(this,'/settings/profile?private='+document.getElementById('privateswitch').checked)">
<label class="custom-control-label" for="privateswitch"></label>
</div>
<span class="text-small-extra text-muted">This will hide your post and comment history from others. We will also ask search engines to not index your profile page. (Your content will still be accessible via direct link.)</span>
</div>
</div>
<div class="d-lg-flex border-bottom">
<div class="title w-lg-25">
<label for="nofollowswitch">Disable Subscriptions</label>
</div>
<div class="body w-lg-100">
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="nofollowswitch" name="nofollow"{% if v.is_nofollow%} checked{% endif %} onchange="post_toast(this,'/settings/profile?nofollow='+document.getElementById('nofollowswitch').checked)">
<label class="custom-control-label" for="nofollowswitch"></label>
</div>
<span class="text-small-extra text-muted">Prevent other users from following you.</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="{{ 'js/settings_profile.js' | asset }}"></script>
{% endblock %}

View file

@ -489,19 +489,19 @@
comments = JSON.parse(localStorage.getItem("old-comment-counts")) || {}
lastCount = comments['{{p.id}}']
if (lastCount)
{
{% for c in p.comments %}
var commentsToCheck = [
{% for c in p.comments -%}
{% if not (v and v.id==c.author_id) and not c.voted %}
if ({{c.created_utc*1000}} > lastCount.t)
try {
document.getElementById("comment-{{c.id}}-only").classList.add('unread')
document.getElementById("comment-{{c.id}}").querySelector(".comment-user-info").innerHTML += "<span>~new~</span>";
}
catch(e) {}
{% endif %}
[{{c.id}}, {{c.created_utc * 1000}}],
{%- endif -%}
{% endfor %}
}
];
commentsToCheck = commentsToCheck.filter(comment => comment[1] > lastCount.t)
.map(comment => comment[0]);
if (lastCount) commentsAddUnreadIndicator(commentsToCheck);
collapsedCommentStorageApply();
}

View file

@ -520,23 +520,23 @@
{% endblock %}
{% block content %}
{% block userpage_nav %}
<div id="profilecontent" class="row no-gutters">
<div class="col">
<div class="flex-row box-shadow-bottom d-flex justify-content-center justify-content-md-between align-items-center">
<ul class="nav settings-nav">
<li class="nav-item">
<a class="nav-link {% if not 'saved' in request.path %}active{% endif %}" style="font-size: .9rem !important; padding: .75rem .4rem !important" href="/@{{u.username}}">Posts <span class="count">({{u.post_count}})</span></a>
<a class="nav-link {% if 'posts' not in request.path and not 'saved' in request.path %}active{% endif %}" style="font-size: .9rem !important; padding: .75rem .4rem !important" href="/@{{u.username}}">Comments <span class="count">({{u.comment_count}})</span></a>
</li>
<li class="nav-item">
<a class="nav-link" style="font-size: .9rem !important; padding: .75rem .4rem !important" href="/@{{u.username}}/comments">Comments <span class="count">({{u.comment_count}})</span></a>
<a class="nav-link {% if 'posts' in request.path and not 'saved' in request.path %}active{% endif %}" style="font-size: .9rem !important; padding: .75rem .4rem !important" href="/@{{u.username}}/posts">Posts <span class="count">({{u.post_count}})</span></a>
</li>
{% if u.id == v.id %}
<li class="nav-item">
<a class="nav-link {% if 'saved' in request.path %}active{% endif %}" style="font-size: .9rem !important; padding: .75rem .4rem !important" href="/@{{u.username}}/saved/posts">Saved Posts <span class="count">({{u.saved_count}})</span></a>
<a class="nav-link {% if 'posts' not in request.path and 'saved' in request.path %}active{% endif %}" style="font-size: .9rem !important; padding: .75rem .4rem !important" href="/@{{u.username}}/saved/comments">Saved Comments <span class="count">({{u.saved_comment_count}})</span></a>
</li>
<li class="nav-item">
<a class="nav-link" style="font-size: .9rem !important; padding: .75rem .4rem !important" href="/@{{u.username}}/saved/comments">Saved Comments <span class="count">({{u.saved_comment_count}})</span></a>
<a class="nav-link {% if 'posts' in request.path and 'saved' in request.path %}active{% endif %}" style="font-size: .9rem !important; padding: .75rem .4rem !important" href="/@{{u.username}}/saved/posts">Saved Posts <span class="count">({{u.saved_count}})</span></a>
</li>
{% endif %}
</ul>
@ -575,18 +575,8 @@
</div>
</div>
{% endif %}
<div class="row no-gutters {% if listing %}mt-md-3{% elif not listing %}my-md-3{% endif %}" style="margin-top: 10px;">
<div class="col">
<div class="posts">
{% include "submission_listing.html" %}
</div>
</div>
</div>
{% endblock %}
{% block userpage_content required %}{% endblock %}
{% if v %}
<div id='tax' class="d-none">{% if v.patron or u.patron or v.alts_patron or u.alts_patron %}0{% else %}0.03{% endif %}</div>

View file

@ -1,67 +1,7 @@
{% extends "userpage.html" %}
{%- import 'component/sorting_time.html' as sorting_time -%}
{% block content %}
<div class="row no-gutters">
<div class="col">
<div class="flex-row box-shadow-bottom d-flex justify-content-center justify-content-md-between align-items-center">
<ul class="nav settings-nav">
<li class="nav-item">
<a class="nav-link" style="font-size: .9rem !important; padding: .75rem .4rem !important" href="/@{{u.username}}">Posts <span class="count">({{u.post_count}})</span></a>
</li>
<li class="nav-item">
<a class="nav-link {% if not 'saved' in request.path %}active{% endif %}" style="font-size: .9rem !important; padding: .75rem .4rem !important" href="/@{{u.username}}/comments">Comments <span class="count">({{u.comment_count}})</span></a>
</li>
{% if u.id == v.id %}
<li class="nav-item">
<a class="nav-link" style="font-size: .9rem !important; padding: .75rem .4rem !important" href="/@{{u.username}}/saved/posts">Saved Posts <span class="count">({{u.saved_count}})</span></a>
</li>
<li class="nav-item">
<a class="nav-link {% if 'saved' in request.path %}active{% endif %}" style="font-size: .9rem !important; padding: .75rem .4rem !important" href="/@{{u.username}}/saved/comments">Saved Comments <span class="count">({{u.saved_comment_count}})</span></a>
</li>
{% endif %}
</ul>
</div>
</div>
</div>
{% if not "saved" in request.full_path %}
<div class="d-flex justify-content-between align-items-center" style="padding-top:10px">
<div class="d-flex align-items-center">
<div class="text-small font-weight-bold mr-2"></div>
<div class="dropdown dropdown-actions">
<button class="btn btn-secondary dropdown-toggle" role="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% if t=="hour" %}<i class="fas fa-clock mr-1"></i>
{% elif t=="day" %}<i class="fas fa-calendar-day mr-1"></i>
{% elif t=="week" %}<i class="fas fa-calendar-week mr-1"></i>
{% elif t=="month" %}<i class="fas fa-calendar-alt mr-1"></i>
{% elif t=="year" %}<i class="fas fa-calendar mr-1"></i>
{% elif t=="all" %}<i class="fas fa-infinity mr-1"></i>
{% endif %}
{{t | capitalize}}
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 31px, 0px);">
{% if t != "hour" %}<a class="dropdown-item" href="?sort={{sort}}&t=hour"><i class="fas fa-clock mr-2"></i>Hour</a>{% endif %}
{% if t != "day" %}<a class="dropdown-item" href="?sort={{sort}}&t=day"><i class="fas fa-calendar-day mr-2"></i>Day</a>{% endif %}
{% if t != "week" %}<a class="dropdown-item" href="?sort={{sort}}&t=week"><i class="fas fa-calendar-week mr-2"></i>Week</a>{% endif %}
{% if t != "month" %}<a class="dropdown-item" href="?sort={{sort}}&t=month"><i class="fas fa-calendar-alt mr-2"></i>Month</a>{% endif %}
{% if t != "year" %}<a class="dropdown-item" href="?sort={{sort}}&t=year"><i class="fas fa-calendar mr-2"></i>Year</a>{% endif %}
{% if t != "all" %}<a class="dropdown-item" href="?sort={{sort}}&t=all"><i class="fas fa-infinity mr-2"></i>All</a>{% endif %}
</div>
</div>
<div class="text-small font-weight-bold ml-3 mr-2"></div>
{{sorting_time.sort_dropdown(sort, t, SORTS_COMMENTS)}}
</div>
</div>
{% endif %}
{% block userpage_content %}
<div class="row no-gutters {% if listing %}mt-md-3{% elif not listing %}my-md-3{% endif %}" style="margin-top: 10px;">
<div class="col">
{% if listing %}
<div class="posts p-3 p-md-0">
{% with comments=listing %}
@ -86,13 +26,6 @@
</div>
{% endif %}
</div>
</div>
</div>
{% if v %}
<div id='tax' class="d-none">{% if v.patron or u.patron %}0{% else %}0.03{% endif %}</div>
<script src="{{ 'js/userpage_v.js' | asset }}"></script>
<div id="username" class="d-none">{{u.username}}</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,10 @@
{% extends "userpage.html" %}
{% block userpage_content %}
<div class="row no-gutters {% if listing %}mt-md-3{% elif not listing %}my-md-3{% endif %}" style="margin-top: 10px;">
<div class="col">
<div class="posts">
{% include "submission_listing.html" %}
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,25 @@
"""Rename approve mod actions
Revision ID: a23fe2f1515c
Revises: 7ae4658467d7
Create Date: 2023-07-22 14:37:20.485816+00:00
"""
from alembic import op
# revision identifiers, used by Alembic.
revision = 'a23fe2f1515c'
down_revision = '7ae4658467d7'
branch_labels = None
depends_on = None
def upgrade():
op.execute("UPDATE modactions SET kind='approve_post' WHERE kind='unremove_post';")
op.execute("UPDATE modactions SET kind='approve_comment' WHERE kind='unremove_comment';")
def downgrade():
op.execute("UPDATE modactions SET kind='unremove_post' WHERE kind='approve_post';")
op.execute("UPDATE modactions SET kind='unremove_comment' WHERE kind='approve_comment';")