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 { #account-menu-header, #account-menu {
min-width: 10em; min-width: 13em;
} }
.navigation-secondary-link { .navigation-secondary-link {

View file

@ -57,3 +57,21 @@ function expandMarkdown(t,id) {
if (val.innerHTML == 'View source') val.innerHTML = 'Hide source' if (val.innerHTML == 'View source') val.innerHTML = 'Hide source'
else val.innerHTML = 'View 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) { function moderate(isPost, id, removing, removeButtonDesktopId, removeButtonMobileId, approveButtonDesktopId, approveButtonMobileId) {
url="/remove_comment/"+post_id const filterState = removing ? "removed" : "normal";
if (isPost) {
post(url) filter_new_status(id, filterState);
} else {
filter_new_comment_status(id, filterState);
} }
const removeButtonDesktop = document.getElementById(removeButtonDesktopId);
function approveCommentBackend(post_id) { const removeButtonMobile = document.getElementById(removeButtonMobileId);
url="/unremove_comment/"+post_id const approveButtonDesktop = document.getElementById(approveButtonDesktopId);
const approveButtonMobile = document.getElementById(approveButtonMobileId);
post(url) if (removing) {
} removeButtonDesktop.classList.add("d-none");
removeButtonMobile.classList.add("d-none");
function removeCommentDesktop(post_id,button1,button2) { approveButtonDesktop.classList.remove("d-none");
removeCommentBackend(post_id) approveButtonMobile.classList.remove("d-none");
} else {
try { removeButtonDesktop.classList.remove("d-none");
document.getElementById("comment-"+post_id+"-only").classList.add("banned"); removeButtonMobile.classList.remove("d-none");
} catch(e) { approveButtonDesktop.classList.add("d-none");
document.getElementById("context").classList.add("banned"); approveButtonMobile.classList.add("d-none");
}
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");
} }
} }

View file

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

View file

@ -64,13 +64,14 @@ class VisibilityState:
op_name_safe=target.author_name 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'] \ if v and (v.admin_level >= PERMS['POST_COMMENT_MODERATION'] \
or v.id == self.op_id): or v.id == self.op_id):
return None return None
if self.deleted: return 'Deleted' if self.deleted: return 'Deleted'
if self.appear_removed(v): return 'Removed' if self.appear_removed(v): return 'Removed'
if self.filtered: return 'Filtered' if self.filtered: return 'Filtered'
if is_blocking: return f'You are blocking @{self.op_name_safe}'
return None return None
def visibility_and_message(self, v: User | None, is_blocking: bool) -> tuple[bool, str]: 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: 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 if moderated: return moderated
body = target.body_html if is_html else target.body 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) # (also rename Unremove to Approve, sigh)
@app.post("/admin/update_filter_status") @app.post("/admin/update_filter_status")
@limiter.exempt @limiter.exempt
@admin_level_required(2) @admin_level_required(PERMS['POST_COMMENT_MODERATION'])
def update_filter_status(v): def update_filter_status(v):
update_body = request.get_json() update_body = request.get_json()
new_status = update_body.get('new_status') new_status = update_body.get('new_status')
post_id = update_body.get('post_id') post_id = update_body.get('post_id')
comment_id = update_body.get('comment_id') comment_id = update_body.get('comment_id')
if new_status not in ['normal', 'removed', 'ignored']: 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': if new_status == 'normal':
state_mod_new = StateMod.VISIBLE state_mod_new = StateMod.VISIBLE
@ -292,43 +292,45 @@ def update_filter_status(v):
state_report_new = StateReport.IGNORED state_report_new = StateReport.IGNORED
if post_id: if post_id:
target = g.db.get(Submission, post_id) target: Submission = get_post(post_id, graceful=True)
old_status = target.state_mod modlog_target_type: str = 'post'
# 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})
elif comment_id: elif comment_id:
target = g.db.get(Comment, comment_id) target: Comment = get_comment(comment_id, graceful=True)
modlog_target_type: str = 'comment'
else:
return {"result": "No valid item ID provided"}, 404
if not target:
return {"result": "Item ID does not exist"}, 404
old_status = target.state_mod old_status = target.state_mod
if state_mod_new is not None: if state_mod_new is not None:
g.db.query(Comment).where(Comment.id == comment_id) \ target.state_mod = state_mod_new
.update({Comment.state_mod: state_mod_new, Comment.state_mod_set_by: v.username}) target.state_mod_set_by = v.username
g.db.query(Comment).where(Comment.id == comment_id) \ target.state_report = state_report_new
.update({Comment.state_report: state_report_new})
else:
return { 'result': f'No valid item ID provided' }
if target is not None: making_visible: bool = old_status != StateMod.VISIBLE and state_mod_new == StateMod.VISIBLE
# If comment now visible, update state to reflect publication. making_invisible: bool = old_status == StateMod.VISIBLE and state_mod_new != StateMod.VISIBLE and state_mod_new is not None
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
if (isinstance(target, Comment) if making_visible:
and old_status == StateMod.VISIBLE modlog_action: str = "approve"
and state_mod_new != StateMod.VISIBLE and state_mod_new is not None): if isinstance(target, Comment): comment_on_publish(target)
comment_on_unpublish(target) # XXX: can cause discrepancies if removal state ≠ filter state 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() g.db.commit()
invalidate_cache(frontlist=True)
return { 'result': 'Update successful' } return { 'result': 'Update successful' }
else:
return { 'result': 'Item ID does not exist' }
@app.get("/admin/image_posts") @app.get("/admin/image_posts")
@limiter.exempt @limiter.exempt
@ -809,7 +811,7 @@ def admin_removed_comments(v):
try: page = int(request.values.get("page", 1)) try: page = int(request.values.get("page", 1))
except: 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] 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!"} 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>") @app.post("/distinguish/<post_id>")
@limiter.exempt @limiter.exempt
@admin_level_required(1) @admin_level_required(1)
@ -1307,59 +1241,10 @@ def unsticky_comment(cid, v):
return {"message": "Comment unpinned!"} 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>") @app.post("/distinguish_comment/<c_id>")
@limiter.exempt @limiter.exempt
@admin_level_required(1) @admin_level_required(1)
def admin_distinguish_comment(c_id, v): def admin_distinguish_comment(c_id, v):
comment = get_comment(c_id, v=v) comment = get_comment(c_id, v=v)
if comment.author_id != v.id: abort(403) 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) return render_template("viewers.html", v=v, viewers=viewers)
@app.get("/@<username>") @app.get("/@<username>/posts")
@auth_desired @auth_desired
def u_username(username, v=None): def u_username(username, v=None):
u = get_user(username, v=v, include_blocks=True) u = get_user(username, v=v, include_blocks=True)
if username != u.username: if username != u.username: return redirect(f'/@{u.username}/posts')
return redirect(SITE_FULL + request.full_path.replace(username, u.username)[:-1])
if u.reserved: if u.reserved:
if request.headers.get("Authorization") or request.headers.get("xhr"): abort(403, f"That username is reserved for: {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 u.unban_utc:
if request.headers.get("Authorization"): {"data": [x.json for x in listing]} 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, unban=u.unban_string,
u=u, u=u,
v=v, 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]} 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, u=u,
v=v, v=v,
listing=listing, listing=listing,
@ -723,12 +722,13 @@ def u_username(username, v=None):
is_following=(v and u.has_follower(v))) is_following=(v and u.has_follower(v)))
@app.get("/@<username>/comments") @app.get("/@<username>/")
@auth_desired @auth_desired
def u_username_comments(username, v=None): def u_username_comments(username, v=None):
user = get_user(username, v=v, include_blocks=True) 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 u = user
if u.reserved: if u.reserved:
@ -935,7 +935,8 @@ def saved_posts(v, username):
listing = get_posts(ids, v=v, eager=True) listing = get_posts(ids, v=v, eager=True)
if request.headers.get("Authorization"): return {"data": [x.json for x in listing]} 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=v, u=v,
v=v, v=v,
listing=listing, listing=listing,
@ -959,13 +960,15 @@ def saved_comments(v, username):
if request.headers.get("Authorization"): return {"data": [x.json for x in listing]} if request.headers.get("Authorization"): return {"data": [x.json for x in listing]}
return render_template("userpage_comments.html", return render_template(
"userpage_comments.html",
u=v, u=v,
v=v, v=v,
listing=listing, listing=listing,
page=page, page=page,
next_exists=next_exists, next_exists=next_exists,
standalone=True) standalone=True
)
@app.post("/fp/<fp>") @app.post("/fp/<fp>")

View file

@ -65,7 +65,8 @@ def api_vote_post(post_id, new, v):
new = int(new) new = int(new)
# get the post # 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 # 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() 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) new = int(new)
# get the comment # 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 # 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() 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> <h3>Content</h3>
<ul> <ul>
<li><a href="/admin/image_posts">Image Posts</a></li> <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/reported/comments">Reported Comments/Posts</a></li>
<li><a href="/admin/removed/posts">Removed Posts/Comments</a></li> <li><a href="/admin/removed/comments">Removed Comments/Posts</a></li>
</ul> </ul>
<h3>Filtering</h3> <h3>Filtering</h3>
<ul> <ul>
<li><a href="/admin/filtered/posts">Filtered Posts/Comments</a></li> <li><a href="/admin/filtered/comments">Filtered Comments/Posts</a></li>
</ul> </ul>
<h3>Users</h3> <h3>Users</h3>

View file

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

View file

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

View file

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

View file

@ -201,23 +201,23 @@
<script src="{{ 'js/comments+submission_listing.js' | asset }}"></script> <script src="{{ 'js/comments+submission_listing.js' | asset }}"></script>
<script src="{{ 'js/comments.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> <script>
{% if p and (not v or v.highlightcomments) %} {% if p and (not v or v.highlightcomments) %}
comments = JSON.parse(localStorage.getItem("comment-counts")) || {} comments = JSON.parse(localStorage.getItem("comment-counts")) || {}
lastCount = comments['{{p.id}}'] lastCount = comments['{{p.id}}']
if (lastCount) var commentsToCheck = [
{ {% for c in p.comments -%}
{% for c in p.comments %} {%- if not (v and v.id==c.author_id) and not c.voted %}
{% if not (v and v.id==c.author_id) and not c.voted %} [{{c.id}}, {{c.created_utc * 1000}}],
if ({{c.created_utc*1000}} > lastCount.t) {%- endif -%}
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 %}
{% endfor %} {% endfor %}
} ];
commentsToCheck = commentsToCheck.filter(comment => comment[1] > lastCount.t)
.map(comment => comment[0]);
if (lastCount) commentsAddUnreadIndicator(commentsToCheck);
{% endif %} {% endif %}
{% if p and not (request.values and ('context' in request.values)) %} {% if p and not (request.values and ('context' in request.values)) %}
@ -250,19 +250,19 @@
{% if p %} {% if p %}
comments = JSON.parse(localStorage.getItem("old-comment-counts")) || {} comments = JSON.parse(localStorage.getItem("old-comment-counts")) || {}
lastCount = comments['{{p.id}}'] lastCount = comments['{{p.id}}']
if (lastCount)
{ var commentsToCheck = [
{% for c in p.comments %} {% for c in p.comments -%}
{% if not (v and v.id==c.author_id) and not c.voted %} {%- if not (v and v.id==c.author_id) and not c.voted %}
if ({{c.created_utc*1000}} > lastCount.t) [{{c.id}}, {{c.created_utc * 1000}}],
try { {%- endif -%}
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 %}
{% endfor %} {% endfor %}
} ];
commentsToCheck = commentsToCheck.filter(comment => comment[1] > lastCount.t)
.map(comment => comment[0]);
if (lastCount) commentsAddUnreadIndicator(commentsToCheck);
{% endif %} {% endif %}
} }
btn.disabled = false; btn.disabled = false;

View file

@ -19,10 +19,12 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if v and v.admin_level >= 2 and c.state_mod == StateMod.FILTERED %} {%- if v and v.admin_level >= PERMS['POST_COMMENT_MODERATION'] -%}
<button class="btn" role="button" id="filter-approve" onclick="filter_new_comment_status({{ c.id }}, 'normal')"><span>Approve</span></button> {%- set show_approve = c.state_mod != StateMod.VISIBLE or "/reported/" in request.path -%}
<button class="btn" role="button" id="filter-remove" onclick="filter_new_comment_status({{ c.id }}, 'removed')"><span>Remove</span></button> {%- set show_remove = c.state_mod != StateMod.REMOVED -%}
{% endif %} <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 %} {% 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> <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 %}
{% 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) %} {% 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="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> <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> <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 %} {% endif %}
{% if "/reported/" in request.path %} {%- if v and v.admin_level >= PERMS['POST_COMMENT_MODERATION'] -%}
<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> {%- set show_approve = c.state_mod != StateMod.VISIBLE or "/reported/" in request.path -%}
<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> {%- set show_remove = c.state_mod == StateMod.VISIBLE -%}
{% else %} <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="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="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>
<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 -%}
{% endif %}
{% if c.oauth_app %} {% 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> <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> <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 %} #} {% endif %} #}
{% if v.admin_level > 1 %} {%- if v and v.admin_level >= PERMS['POST_COMMENT_MODERATION'] -%}
{% if "/reported/" in request.path %} {%- set show_approve = p.state_mod != StateMod.VISIBLE or "/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 %} {%- set show_remove = p.state_mod == StateMod.VISIBLE -%}
<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> <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>
{% else %} <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>
{% 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 %} {%- 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.admin_level >= 2 %}
{% if p.oauth_app %} {% 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> <a class="dropdown-item list-inline-item" href="{{p.oauth_app.permalink}}"><i class="fas fa-code"></i>API App</a>
{% endif %} {% 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="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="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> <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 %} {%- if v and v.admin_level >= PERMS['POST_COMMENT_MODERATION'] -%}
<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> {%- set show_approve = p.state_mod != StateMod.VISIBLE or "/reported/" in request.path -%}
<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> {%- set show_remove = p.state_mod == StateMod.VISIBLE -%}
{% else %} <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>
<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> <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>
<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 -%}
{% 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 p.oauth_app %} {% 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> <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="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> <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 %} {% endif %}
</ul> </ul>
</div> </div>
</div> </div>

View file

@ -11,7 +11,7 @@
</a> </a>
<div class="flex-grow-1 d-fl d-none d-md-block {% if not v %}pad{% endif %}"> <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', '')}}"> <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-append">
<span class="input-group-text border-0 bg-transparent" style="margin-left: -2.5rem; cursor: pointer;" onclick="document.getElementById('searchform').submit()"> <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"> <div class="flex-row box-shadow-bottom d-flex justify-content-center justify-content-md-between align-items-center">
<ul class="nav settings-nav"> <ul class="nav settings-nav">
<li class="nav-item"> <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>
<li class="nav-item"> <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>
<li class="nav-item"> <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> <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

@ -20,7 +20,6 @@
</div> </div>
</div> </div>
<div class="d-lg-flex border-bottom"> <div class="d-lg-flex border-bottom">
<div class="title w-lg-25"> <div class="title w-lg-25">
<label for="highlightcomments">Highlight New Comments</label> <label for="highlightcomments">Highlight New Comments</label>
@ -35,7 +34,6 @@
</div> </div>
</div> </div>
<div class="d-lg-flex border-bottom"> <div class="d-lg-flex border-bottom">
<div class="title w-lg-25"> <div class="title w-lg-25">
<label for="theme">Website Theme</label> <label for="theme">Website Theme</label>
@ -77,8 +75,8 @@
</div> </div>
</div> </div>
</div> </div>
<h2 class="h5">Profile Banner</h2>
<h2 class="h5">Profile Banner</h2>
<div class="settings-section rounded"> <div class="settings-section rounded">
<div class="d-flex"> <div class="d-flex">
<div class="title w-lg-75 text-md-center"> <div class="title w-lg-75 text-md-center">
@ -112,10 +110,13 @@
<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}}"> <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-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"> <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>
</span> </span>
</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 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> </div>
</div> </div>
</div> </div>
@ -138,7 +139,6 @@
<label class="text-black w-lg-25">Username</label> <label class="text-black w-lg-25">Username</label>
<div class="w-lg-100"> <div class="w-lg-100">
<p>Your original username will always stay reserved for you: <code>{{v.original_username}}</code></p> <p>Your original username will always stay reserved for you: <code>{{v.original_username}}</code></p>
<form action="/settings/name_change" method="post"> <form action="/settings/name_change" method="post">
{{forms.formkey(v)}} {{forms.formkey(v)}}
<input autocomplete="off" type="text" name="name" class="form-control" value="{{v.username}}"> <input autocomplete="off" type="text" name="name" class="form-control" value="{{v.username}}">
@ -193,7 +193,6 @@
<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> <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> </form>
</div> </div>
</div> </div>
{% if v.verified %} {% if v.verified %}
@ -202,7 +201,6 @@
<div class="d-flex"> <div class="d-flex">
<form action="/settings/verifiedcolor" id="verifiedcolor-form" method="post" class="color-picker" style="line-height: 0"> <form action="/settings/verifiedcolor" id="verifiedcolor-form" method="post" class="color-picker" style="line-height: 0">
{{forms.formkey(v)}} {{forms.formkey(v)}}
{% for verifiedcolor in COLORS %} {% 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()"> <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}}"> <label class="color-radio" for="verifiedcolor-{{verifiedcolor}}">
@ -215,9 +213,7 @@
</span> </span>
</label> </label>
{% endfor %} {% endfor %}
</form> </form>
</div> </div>
<p class="text-small mb-2">Or type a color code:</p> <p class="text-small mb-2">Or type a color code:</p>
@ -228,7 +224,6 @@
<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> <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> </form>
</div> </div>
</div> </div>
{% endif %} {% endif %}
@ -279,7 +274,6 @@
</form> </form>
</div> </div>
</div> </div>
<div class="d-lg-flex border-bottom"> <div class="d-lg-flex border-bottom">
<div class="title w-lg-25"> <div class="title w-lg-25">
<label for="privateswitch">Private Mode</label> <label for="privateswitch">Private Mode</label>
@ -308,8 +302,5 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</div>
<script src="{{ 'js/settings_profile.js' | asset }}"></script> <script src="{{ 'js/settings_profile.js' | asset }}"></script>
{% endblock %} {% endblock %}

View file

@ -489,19 +489,19 @@
comments = JSON.parse(localStorage.getItem("old-comment-counts")) || {} comments = JSON.parse(localStorage.getItem("old-comment-counts")) || {}
lastCount = comments['{{p.id}}'] lastCount = comments['{{p.id}}']
if (lastCount)
{ var commentsToCheck = [
{% for c in p.comments %} {% for c in p.comments -%}
{% if not (v and v.id==c.author_id) and not c.voted %} {% if not (v and v.id==c.author_id) and not c.voted %}
if ({{c.created_utc*1000}} > lastCount.t) [{{c.id}}, {{c.created_utc * 1000}}],
try { {%- endif -%}
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 %}
{% endfor %} {% endfor %}
} ];
commentsToCheck = commentsToCheck.filter(comment => comment[1] > lastCount.t)
.map(comment => comment[0]);
if (lastCount) commentsAddUnreadIndicator(commentsToCheck);
collapsedCommentStorageApply(); collapsedCommentStorageApply();
} }

View file

@ -520,23 +520,23 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% block userpage_nav %}
<div id="profilecontent" class="row no-gutters"> <div id="profilecontent" class="row no-gutters">
<div class="col"> <div class="col">
<div class="flex-row box-shadow-bottom d-flex justify-content-center justify-content-md-between align-items-center"> <div class="flex-row box-shadow-bottom d-flex justify-content-center justify-content-md-between align-items-center">
<ul class="nav settings-nav"> <ul class="nav settings-nav">
<li class="nav-item"> <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>
<li class="nav-item"> <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> </li>
{% if u.id == v.id %} {% if u.id == v.id %}
<li class="nav-item"> <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>
<li class="nav-item"> <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> </li>
{% endif %} {% endif %}
</ul> </ul>
@ -575,18 +575,8 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% endblock %}
<div class="row no-gutters {% if listing %}mt-md-3{% elif not listing %}my-md-3{% endif %}" style="margin-top: 10px;"> {% block userpage_content required %}{% endblock %}
<div class="col">
<div class="posts">
{% include "submission_listing.html" %}
</div>
</div>
</div>
{% if v %} {% 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> <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" %} {% extends "userpage.html" %}
{%- import 'component/sorting_time.html' as sorting_time -%} {% block userpage_content %}
{% 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 %}
<div class="row no-gutters {% if listing %}mt-md-3{% elif not listing %}my-md-3{% endif %}" style="margin-top: 10px;"> <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="col">
{% if listing %} {% if listing %}
<div class="posts p-3 p-md-0"> <div class="posts p-3 p-md-0">
{% with comments=listing %} {% with comments=listing %}
@ -88,11 +28,4 @@
{% endif %} {% endif %}
</div> </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 %} {% 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';")