forked from rDrama/rDrama
1
0
Fork 0

walls: remove duplication among routes/templates (#52)

removes a bunch of duplicated code in commenting and userpages

Co-authored-by: justcool393 <justcool393@gmail.com>
Reviewed-on: rDrama/rDrama#52
Co-authored-by: justcool393 <justcool393@noreply.fsdfsd.net>
Co-committed-by: justcool393 <justcool393@noreply.fsdfsd.net>
master
justcool393 2022-12-09 03:35:28 +00:00 committed by Snakes
parent 3e8c98494f
commit f848f68799
15 changed files with 780 additions and 1251 deletions

View File

@ -50,7 +50,7 @@ function getSelectionTextHtml() {
return html;
}
function ToggleReplyBox(id) {
function toggleReplyBox(id) {
const element = document.getElementById(id);
const textarea = element.getElementsByTagName('textarea')[0]
element.classList.toggle('d-none')
@ -142,7 +142,7 @@ function post_reply(id){
document.getElementById('reply-form-body-'+id).value = ''
document.getElementById('message-reply-'+id).innerHTML = ''
ToggleReplyBox('reply-message-'+id)
toggleReplyBox('reply-message-'+id)
document.getElementById('file-upload').value = null;
} else {
showToast(false, getMessageFromJsonData(false, data));
@ -193,7 +193,7 @@ function comment_edit(id){
xhr[0].send(xhr[1]);
}
function post_comment(fullname, wall_user_id, hide){
function postComment(fullname, hide){
const btn = document.getElementById('save-reply-to-'+fullname)
btn.disabled = true
btn.classList.add('disabled');
@ -202,7 +202,6 @@ function post_comment(fullname, wall_user_id, hide){
form.append('formkey', formkey());
form.append('parent_fullname', fullname);
form.append('submission', document.getElementById('reply-form-submission-'+fullname).value);
form.append('body', document.getElementById('reply-form-body-'+fullname).value);
try {
@ -212,8 +211,7 @@ function post_comment(fullname, wall_user_id, hide){
catch(e) {}
const xhr = new XMLHttpRequest();
if (wall_user_id == 'None') url = "/comment"
else url = '/wall_comment'
url = '/comments/';
xhr.open("post", url);
xhr.setRequestHeader('xhr', 'xhr');
xhr.onload=function(){

View File

@ -21,6 +21,7 @@ from files.helpers.sanitize import *
from files.helpers.settings import get_setting
from files.helpers.slots import check_slots_command
post_target_type = Union[Submission, User]
def _archiveorg(url):
headers = {'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'}
@ -43,7 +44,7 @@ def archive_url(url):
url = url.replace('https://instagram.com/', 'https://imginn.com/')
gevent.spawn(_archiveorg, url)
def execute_snappy(post, v):
def execute_snappy(post:Submission, v:User):
snappy = get_account(SNAPPY_ID)
if post.sub == 'dankchristianmemes' or post.sub == 'truth':
@ -199,10 +200,13 @@ def execute_snappy(post, v):
post.comment_count += 1
post.replies = [c]
def execute_zozbot(c, level, parent_submission, v):
def execute_zozbot(c:Comment, level:int, post_target:post_target_type, v):
if SITE_NAME != 'rDrama': return
posting_to_submission = isinstance(post_target, Submission)
if random.random() >= 0.001: return
c2 = Comment(author_id=ZOZBOT_ID,
parent_submission=parent_submission,
parent_submission=post_target.id if posting_to_submission else None,
wall_user_id=post_target.id if not posting_to_submission else None,
parent_comment_id=c.id,
level=level+1,
is_bot=True,
@ -219,7 +223,8 @@ def execute_zozbot(c, level, parent_submission, v):
g.db.add(n)
c3 = Comment(author_id=ZOZBOT_ID,
parent_submission=parent_submission,
parent_submission=post_target.id if posting_to_submission else None,
wall_user_id=post_target.id if not posting_to_submission else None,
parent_comment_id=c2.id,
level=level+2,
is_bot=True,
@ -235,7 +240,8 @@ def execute_zozbot(c, level, parent_submission, v):
c4 = Comment(author_id=ZOZBOT_ID,
parent_submission=parent_submission,
parent_submission=post_target.id if posting_to_submission else None,
wall_user_id=post_target.id if not posting_to_submission else None,
parent_comment_id=c3.id,
level=level+3,
is_bot=True,
@ -253,7 +259,9 @@ def execute_zozbot(c, level, parent_submission, v):
zozbot.pay_account('coins', 1)
g.db.add(zozbot)
def execute_longpostbot(c, level, body, body_html, parent_submission, v):
def execute_longpostbot(c:Comment, level:int, body, body_html, post_target:post_target_type, v:User):
if SITE_NAME != 'rDrama': return
posting_to_submission = isinstance(post_target, Submission)
if not len(c.body.split()) >= 200: return
if "</blockquote>" in body_html: return
body = random.choice(LONGPOST_REPLIES)
@ -268,7 +276,8 @@ def execute_longpostbot(c, level, body, body_html, parent_submission, v):
c.downvotes = 1
c2 = Comment(author_id=LONGPOSTBOT_ID,
parent_submission=parent_submission,
parent_submission=post_target.id if posting_to_submission else None,
wall_user_id=post_target.id if not posting_to_submission else None,
parent_comment_id=c.id,
level=level+1,
is_bot=True,
@ -288,9 +297,12 @@ def execute_longpostbot(c, level, body, body_html, parent_submission, v):
n = Notification(comment_id=c2.id, user_id=v.id)
g.db.add(n)
def execute_basedbot(c, level, body, parent_post, v):
def execute_basedbot(c:Comment, level:int, body, post_target:post_target_type, v:User):
if SITE != "pcmemes.net": return
if not c.body.lower().startswith("based"): return
posting_to_submission = isinstance(post_target, Submission)
pill = based_regex.match(body)
if level == 1: basedguy = get_account(parent_post.author_id)
if level == 1: basedguy = get_account(post_target.author_id)
else: basedguy = get_account(c.parent_comment.author_id)
basedguy.basedcount += 1
if pill:
@ -303,7 +315,8 @@ def execute_basedbot(c, level, body, parent_post, v):
body_based_html = sanitize(body2)
c_based = Comment(author_id=BASEDBOT_ID,
parent_submission=parent_post.id,
parent_submission=post_target.id if posting_to_submission else None,
wall_user_id=post_target.id if not posting_to_submission else None,
distinguish_level=6,
parent_comment_id=c.id,
level=level+1,
@ -514,3 +527,13 @@ def process_poll_options(target:Union[Submission, Comment],
exclusive=exclusive,
)
db.add(option)
def execute_wordle(post_target:post_target_type, c:Comment, body:str, rts:bool):
if not FEATURES['WORDLE']: return
if not "!wordle" in body: return
answer = random.choice(WORDLE_LIST)
c.wordle_result = f'_active_{answer}'
if not c.wordle_result and not rts:
post_target.comment_count += 1
g.db.add(post_target)

View File

@ -79,11 +79,12 @@ def post_pid_comment_cid(cid, pid=None, anything=None, v=None, sub=None):
else: template = "submission.html"
return render_template(template, v=v, p=post, sort=sort, comment_info=comment_info, render_replies=True, sub=post.subr)
@app.post("/comments/")
@app.post("/comment")
@limiter.limit("1/second;20/minute;200/hour;1000/day")
@auth_required
@ratelimit_user("1/second;20/minute;200/hour;1000/day")
def comment(v):
def comment(v:User):
if v.is_suspended: abort(403, "You can't perform this action while banned.")
parent_fullname = request.values.get("parent_fullname").strip()
@ -92,33 +93,43 @@ def comment(v):
parent_comment_id = None
rts = False
if parent_fullname.startswith("p_"):
post_target = None
parent = None
if parent_fullname.startswith("u_"):
parent = get_account(id, v=v)
post_target = parent
elif parent_fullname.startswith("p_"):
parent = get_post(id, v=v)
parent_post = parent
post_target = parent
if POLL_THREAD and parent.id == POLL_THREAD and v.admin_level < PERMS['POST_TO_POLL_THREAD']: abort(403)
elif parent_fullname.startswith("c_"):
parent = get_comment(id, v=v)
parent_post = get_post(parent.parent_submission, v=v)
post_target = get_post(parent.parent_submission, v=v, graceful=True) or get_account(parent.wall_user_id, v=v, include_blocks=True, include_shadowbanned=False)
parent_comment_id = parent.id
if parent.author_id == v.id: rts = True
if not v.can_post_in_ghost_threads and parent_post.ghost: abort(403, f"You need {TRUESCORE_GHOST_MINIMUM} truescore to post in ghost threads")
else: abort(400)
if not v.can_post_in_ghost_threads and isinstance(post_target, Submission) and post_target.ghost:
abort(403, f"You need {TRUESCORE_GHOST_MINIMUM} truescore to post in ghost threads")
else: abort(404)
level = 1 if isinstance(parent, Submission) else parent.level + 1
sub = parent_post.sub
if sub and v.exiled_from(sub): abort(403, f"You're exiled from /h/{sub}")
if sub in ('furry','vampire','racist','femboy') and not v.client and not v.house.lower().startswith(sub):
abort(403, f"You need to be a member of House {sub.capitalize()} to comment in /h/{sub}")
level = 1 if isinstance(parent, (Submission, User)) else int(parent.level) + 1
parent_user = parent if isinstance(parent, User) else parent.author
posting_to_submission = isinstance(post_target, Submission)
if not User.can_see(v, parent): abort(404)
if parent.deleted_utc != 0: abort(404)
if not isinstance(parent, User) and parent.deleted_utc != 0: abort(404)
if posting_to_submission:
sub = post_target.sub
if sub and v.exiled_from(sub): abort(403, f"You're exiled from /h/{sub}")
if sub in ('furry','vampire','racist','femboy') and not v.client and not v.house.lower().startswith(sub):
abort(403, f"You need to be a member of House {sub.capitalize()} to comment in /h/{sub}")
if level > COMMENT_MAX_DEPTH: abort(400, f"Max comment level is {COMMENT_MAX_DEPTH}")
body = sanitize_raw_body(request.values.get("body", ""), False)
if parent_post.id not in ADMIGGER_THREADS:
if not posting_to_submission or post_target.id not in ADMIGGER_THREADS:
if v.longpost and (len(body) < 280 or ' [](' in body or body.startswith('[](')):
abort(403, "You have to type more than 280 characters!")
elif v.bird and len(body) > 140:
@ -126,9 +137,9 @@ def comment(v):
if not body and not request.files.get('file'): abort(400, "You need to actually write something!")
if v.admin_level < PERMS['POST_COMMENT_MODERATION'] and parent.author.any_block_exists(v):
if not v.admin_level >= PERMS['POST_COMMENT_MODERATION'] and parent_user.any_block_exists(v):
abort(403, "You can't reply to users who have blocked you or users that you have blocked.")
body, _, options, choices = sanitize_poll_options(v, body, False)
if request.files.get("file") and not g.is_tor:
@ -139,7 +150,7 @@ def comment(v):
file.save(oldname)
image = process_image(oldname, v)
if image == "": abort(400, "Image upload failed")
if v.admin_level >= PERMS['SITE_SETTINGS_SIDEBARS_BANNERS_BADGES'] and level == 1:
if posting_to_submission and v.admin_level >= PERMS['SITE_SETTINGS_SIDEBARS_BANNERS_BADGES']:
def process_sidebar_or_banner(type, resize=0):
li = sorted(os.listdir(f'files/assets/images/{SITE_NAME}/{type}'),
key=lambda e: int(e.split('.webp')[0]))[-1]
@ -148,12 +159,12 @@ def comment(v):
copyfile(oldname, filename)
process_image(filename, v, resize=resize)
if parent_post.id == SIDEBAR_THREAD:
if post_target.id == SIDEBAR_THREAD:
process_sidebar_or_banner('sidebar', 400)
elif parent_post.id == BANNER_THREAD:
elif post_target.id == BANNER_THREAD:
banner_width = 1200 if not SITE_NAME == 'PCM' else 0
process_sidebar_or_banner('banners', banner_width)
elif parent_post.id == BADGE_THREAD:
elif post_target.id == BADGE_THREAD:
try:
badge_def = loads(body)
name = badge_def["name"]
@ -180,48 +191,51 @@ def comment(v):
body = body.strip()[:COMMENT_BODY_LENGTH_LIMIT]
if v.admin_level >= PERMS['SITE_SETTINGS_SNAPPY_QUOTES'] and parent_post.id == SNAPPY_THREAD and level == 1:
if v.admin_level >= PERMS['SITE_SETTINGS_SNAPPY_QUOTES'] and posting_to_submission and post_target.id == SNAPPY_THREAD and level == 1:
with open(f"snappy_{SITE_NAME}.txt", "a", encoding="utf-8") as f:
f.write('\n{[para]}\n' + body)
body_for_sanitize = body
if v.owoify:
body_for_sanitize = owoify(body_for_sanitize)
if v.marsify:
body_for_sanitize = marsify(body_for_sanitize)
if v.owoify: body_for_sanitize = owoify(body_for_sanitize)
if v.marsify: body_for_sanitize = marsify(body_for_sanitize)
torture = (v.agendaposter and not v.marseyawarded and parent_post.sub != 'chudrama' and parent_post.id not in ADMIGGER_THREADS)
torture = (v.agendaposter and not v.marseyawarded and post_target.sub != 'chudrama' and post_target.id not in ADMIGGER_THREADS)
body_html = sanitize(body_for_sanitize, limit_pings=5, count_marseys=not v.marsify, torture=torture)
if parent_post.id not in ADMIGGER_THREADS and '!wordle' not in body.lower() and AGENDAPOSTER_PHRASE not in body.lower():
if post_target.id not in ADMIGGER_THREADS and '!wordle' not in body.lower() and AGENDAPOSTER_PHRASE not in body.lower():
existing = g.db.query(Comment.id).filter(
Comment.author_id == v.id,
Comment.deleted_utc == 0,
Comment.parent_comment_id == parent_comment_id,
Comment.parent_submission == parent_post.id,
Comment.parent_submission == post_target.id if posting_to_submission else None,
Comment.wall_user_id == post_target.id if not posting_to_submission else None,
Comment.body_html == body_html
).first()
if existing: abort(409, f"You already made that comment: /comment/{existing.id}")
execute_antispam_comment_check(body, v)
execute_antispam_duplicate_comment_check(v, body_html)
if v.marseyawarded and posting_to_submission and post_target.id not in ADMIGGER_THREADS and marseyaward_body_regex.search(body_html):
abort(403, "You can only type marseys!")
if len(body_html) > COMMENT_BODY_HTML_LENGTH_LIMIT: abort(400)
is_bot = (v.client is not None
and v.id not in PRIVILEGED_USER_BOTS
or (SITE == 'pcmemes.net' and v.id == SNAPPY_ID))
execute_antispam_comment_check(body, v)
execute_antispam_duplicate_comment_check(v, body_html)
if len(body_html) > COMMENT_BODY_HTML_LENGTH_LIMIT: abort(400)
c = Comment(author_id=v.id,
parent_submission=parent_post.id,
parent_submission=post_target.id if posting_to_submission else None,
wall_user_id=post_target.id if not posting_to_submission else None,
parent_comment_id=parent_comment_id,
level=level,
over_18=parent_post.over_18,
over_18=post_target.over_18 if posting_to_submission else False,
is_bot=is_bot,
app_id=v.client.application.id if v.client else None,
body_html=body_html,
body=body,
ghost=parent_post.ghost
ghost=post_target.ghost if posting_to_submission else False,
)
c.upvotes = 1
@ -237,10 +251,9 @@ def comment(v):
process_poll_options(c, CommentOption, options, 0, "Poll", g.db)
process_poll_options(c, CommentOption, choices, 1, "Poll", g.db)
if SITE == 'pcmemes.net' and c.body.lower().startswith("based"):
execute_basedbot(c, level, body, parent_post, v)
execute_basedbot(c, level, body, post_target, v)
if parent_post.id not in ADMIGGER_THREADS and v.agendaposter and not v.marseyawarded and AGENDAPOSTER_PHRASE not in c.body.lower() and parent_post.sub != 'chudrama':
if post_target.id not in ADMIGGER_THREADS and v.agendaposter and not v.marseyawarded and AGENDAPOSTER_PHRASE not in c.body.lower() and post_target.sub != 'chudrama':
c.is_banned = True
c.ban_reason = "AutoJanny"
g.db.add(c)
@ -249,7 +262,8 @@ def comment(v):
body_jannied_html = AGENDAPOSTER_MSG_HTML.format(id=v.id, username=v.username, type='comment', AGENDAPOSTER_PHRASE=AGENDAPOSTER_PHRASE)
c_jannied = Comment(author_id=AUTOJANNY_ID,
parent_submission=parent_post.id,
parent_submission=post_target.id if posting_to_submission else None,
wall_user_id=post_target.id if not posting_to_submission else None,
distinguish_level=6,
parent_comment_id=c.id,
level=level+1,
@ -266,46 +280,41 @@ def comment(v):
n = Notification(comment_id=c_jannied.id, user_id=v.id)
g.db.add(n)
if SITE_NAME == 'rDrama':
execute_longpostbot(c, level, body, body_html, parent_post.id, v)
execute_zozbot(c, level, parent_post.id, v)
execute_longpostbot(c, level, body, body_html, post_target, v)
execute_zozbot(c, level, post_target, v)
if not v.shadowbanned:
notify_users = NOTIFY_USERS(body, v)
if c.level == 1:
subscribers = g.db.query(Subscription.user_id).filter(Subscription.submission_id == parent_post.id, Subscription.user_id != v.id).all()
if c.level == 1 and posting_to_submission:
subscribers = g.db.query(Subscription.user_id).filter(Subscription.submission_id == post_target.id, Subscription.user_id != v.id).all()
for x in subscribers:
notify_users.add(x[0])
if parent.author.id != v.id:
notify_users.add(parent.author.id)
if parent_user.id != v.id:
notify_users.add(parent_user.id)
for x in notify_users-bots:
n = Notification(comment_id=c.id, user_id=x)
g.db.add(n)
if VAPID_PUBLIC_KEY != DEFAULT_CONFIG_VALUE and parent.author.id != v.id and not v.shadowbanned:
if VAPID_PUBLIC_KEY != DEFAULT_CONFIG_VALUE and parent_user.id != v.id and not v.shadowbanned:
title = f'New reply by @{c.author_name}'
if not posting_to_submission: title = f"New comment on your wall by @{c.author_name}"
if len(c.body) > 500: notifbody = c.body[:500] + '...'
if len(c.body) > PUSH_NOTIF_LIMIT: notifbody = c.body[:PUSH_NOTIF_LIMIT] + '...'
else: notifbody = c.body
url = f'{SITE_FULL}/comment/{c.id}?context=8&read=true#context'
push_notif(parent.author.id, title, notifbody, url)
push_notif(parent_user.id, title, notifbody, url)
vote = CommentVote(user_id=v.id,
comment_id=c.id,
vote_type=1,
)
g.db.add(vote)
cache.delete_memoized(comment_idlist)
v.comment_count = g.db.query(Comment).filter(
@ -316,20 +325,9 @@ def comment(v):
g.db.add(v)
c.voted = 1
if v.marseyawarded and parent_post.id not in ADMIGGER_THREADS and marseyaward_body_regex.search(body_html):
abort(403, "You can only type marseys!")
check_for_treasure(body, c)
if FEATURES['WORDLE'] and "!wordle" in body:
answer = random.choice(WORDLE_LIST)
c.wordle_result = f'_active_{answer}'
if not c.wordle_result and not rts:
parent_post.comment_count += 1
g.db.add(parent_post)
execute_wordle(post_target or parent_user, c, body, rts)
check_slots_command(v, v, c)
if c.level > 5:
@ -344,216 +342,6 @@ def comment(v):
if v.client: return c.json(db=g.db)
return {"comment": render_template("comments.html", v=v, comments=[c])}
@app.post("/wall_comment")
@limiter.limit("1/second;20/minute;200/hour;1000/day")
@auth_required
@ratelimit_user("1/second;20/minute;200/hour;1000/day")
def wall_comment(v):
if v.is_suspended: abort(403, "You can't perform this action while banned.")
parent_fullname = request.values.get("parent_fullname").strip()
if len(parent_fullname) < 3: abort(400)
id = parent_fullname[2:]
parent_comment_id = None
if parent_fullname.startswith("u_"):
parent = get_account(id, v=v)
parent_user = parent
parent_author = parent
elif parent_fullname.startswith("c_"):
parent = get_comment(id, v=v)
if parent.deleted_utc != 0: abort(404)
parent_user = parent.wall_user
parent_comment_id = parent.id
parent_author = parent.author
else: abort(400)
level = 1 if isinstance(parent, User) else parent.level + 1
# if not User.can_see(v, parent): abort(404)
if level > COMMENT_MAX_DEPTH: abort(400, f"Max comment level is {COMMENT_MAX_DEPTH}")
body = sanitize_raw_body(request.values.get("body", ""), False)
if v.longpost and (len(body) < 280 or ' [](' in body or body.startswith('[](')):
abort(403, "You have to type more than 280 characters!")
elif v.bird and len(body) > 140:
abort(403, "You have to type less than 140 characters!")
if not body and not request.files.get('file'):
abort(400, "You need to actually write something!")
if v.admin_level < PERMS['POST_COMMENT_MODERATION'] and parent_author.any_block_exists(v):
abort(403, "You can't reply to users who have blocked you or users that you have blocked.")
body, _, options, choices = sanitize_poll_options(v, body, False)
if request.files.get("file") and not g.is_tor:
files = request.files.getlist('file')[:4]
for file in files:
if file.content_type.startswith('image/'):
oldname = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(oldname)
image = process_image(oldname, v)
if image == "": abort(400, "Image upload failed")
body += f"\n\n![]({image})"
elif file.content_type.startswith('video/'):
body += f"\n\n{SITE_FULL}{process_video(file, v)}"
elif file.content_type.startswith('audio/'):
body += f"\n\n{SITE_FULL}{process_audio(file, v)}"
else:
abort(415)
body = body.strip()[:COMMENT_BODY_LENGTH_LIMIT]
body_for_sanitize = body
if v.owoify:
body_for_sanitize = owoify(body_for_sanitize)
if v.marsify:
body_for_sanitize = marsify(body_for_sanitize)
torture = (v.agendaposter and not v.marseyawarded)
body_html = sanitize(body_for_sanitize, limit_pings=5, count_marseys=not v.marsify, torture=torture)
if '!wordle' not in body.lower() and AGENDAPOSTER_PHRASE not in body.lower():
existing = g.db.query(Comment.id).filter(
Comment.author_id == v.id,
Comment.deleted_utc == 0,
Comment.parent_comment_id == parent_comment_id,
Comment.parent_submission == None,
Comment.wall_user_id == parent_user.id,
Comment.body_html == body_html
).first()
if existing: abort(409, f"You already made that comment: /comment/{existing.id}")
is_bot = (v.client is not None
and v.id not in PRIVILEGED_USER_BOTS
or (SITE == 'pcmemes.net' and v.id == SNAPPY_ID))
execute_antispam_comment_check(body, v)
execute_antispam_duplicate_comment_check(v, body_html)
if len(body_html) > COMMENT_BODY_HTML_LENGTH_LIMIT: abort(400)
c = Comment(author_id=v.id,
wall_user_id=parent_user.id,
parent_comment_id=parent_comment_id,
level=level,
is_bot=is_bot,
app_id=v.client.application.id if v.client else None,
body_html=body_html,
body=body,
)
c.upvotes = 1
g.db.add(c)
g.db.flush()
execute_blackjack(v, c, c.body, "comment")
execute_under_siege(v, c, c.body, "comment")
if c.level == 1: c.top_comment_id = c.id
else: c.top_comment_id = parent.top_comment_id
process_poll_options(c, CommentOption, options, 0, "Poll", g.db)
process_poll_options(c, CommentOption, choices, 1, "Poll", g.db)
if v.agendaposter and not v.marseyawarded and AGENDAPOSTER_PHRASE not in c.body.lower():
c.is_banned = True
c.ban_reason = "AutoJanny"
g.db.add(c)
body = AGENDAPOSTER_MSG.format(username=v.username, type='comment', AGENDAPOSTER_PHRASE=AGENDAPOSTER_PHRASE)
body_jannied_html = AGENDAPOSTER_MSG_HTML.format(id=v.id, username=v.username, type='comment', AGENDAPOSTER_PHRASE=AGENDAPOSTER_PHRASE)
c_jannied = Comment(author_id=AUTOJANNY_ID,
parent_submission=None,
wall_user_id=parent_user.id,
distinguish_level=6,
parent_comment_id=c.id,
level=level+1,
is_bot=True,
body=body,
body_html=body_jannied_html,
top_comment_id=c.top_comment_id,
)
g.db.add(c_jannied)
g.db.flush()
n = Notification(comment_id=c_jannied.id, user_id=v.id)
g.db.add(n)
if not v.shadowbanned:
notify_users = NOTIFY_USERS(body, v)
if parent_author.id != v.id:
notify_users.add(parent_author.id)
for x in notify_users-bots:
n = Notification(comment_id=c.id, user_id=x)
g.db.add(n)
if VAPID_PUBLIC_KEY != DEFAULT_CONFIG_VALUE and parent_author.id != v.id and not v.shadowbanned:
if parent_author.id == c.wall_user_id:
title = f"New comment on your profile wall by @{c.author_name}"
else:
title = f"New reply by @{c.author_name}"
if len(c.body) > 500: notifbody = c.body[:500] + '...'
else: notifbody = c.body
url = f'{SITE_FULL}/@{c.wall_user.username}/wall/comment/{c.id}?context=8&read=true#context'
push_notif(parent_author.id, title, notifbody, url)
vote = CommentVote(user_id=v.id,
comment_id=c.id,
vote_type=1,
)
g.db.add(vote)
v.comment_count = g.db.query(Comment).filter(
Comment.author_id == v.id,
or_(Comment.parent_submission != None, Comment.wall_user_id != None),
Comment.deleted_utc == 0
).count()
g.db.add(v)
c.voted = 1
if v.marseyawarded and marseyaward_body_regex.search(body_html):
abort(403, "You can only type marseys!")
check_for_treasure(body, c)
if FEATURES['WORDLE'] and "!wordle" in body:
answer = random.choice(WORDLE_LIST)
c.wordle_result = f'_active_{answer}'
check_slots_command(v, v, c)
if c.level > 5:
n = g.db.query(Notification).filter_by(
comment_id=c.parent_comment.parent_comment.parent_comment.parent_comment_id,
user_id=c.parent_comment.author_id,
).one_or_none()
if n: g.db.delete(n)
g.db.flush()
cache.delete_memoized(comment_idlist)
if v.client: return c.json(db=g.db)
return {"comment": render_template("comments.html", v=v, comments=[c])}
@app.post("/edit_comment/<cid>")
@limiter.limit("1/second;10/minute;100/hour;200/day")
@is_not_permabanned

View File

@ -538,7 +538,7 @@ def messagereply(v:User):
execute_blackjack(v, c, c.body_html, 'message')
execute_under_siege(v, c, c.body_html, 'message')
if user_id and user_id not in (v.id, 2, bots):
if user_id and user_id not in (v.id, MODMAIL_ID, bots):
notif = g.db.query(Notification).filter_by(comment_id=c.id, user_id=user_id).one_or_none()
if not notif:
notif = Notification(comment_id=c.id, user_id=user_id)
@ -878,7 +878,7 @@ def u_username(v:Optional[User]=None, username:str):
if (v and v.client) or request.path.endswith(".json"):
return {"data": [x.json(g.db) for x in listing]}
return render_template("userpage/userpage.html",
return render_template("userpage/submissions.html",
unban=u.unban_string,
u=u,
v=v,
@ -892,7 +892,7 @@ def u_username(v:Optional[User]=None, username:str):
if (v and v.client) or request.path.endswith(".json"):
return {"data": [x.json(g.db) for x in listing]}
return render_template("userpage/userpage.html",
return render_template("userpage/submissions.html",
u=u,
v=v,
listing=listing,
@ -1126,7 +1126,7 @@ def saved_posts(v:User, username):
try: page = max(1, int(request.values.get("page", 1)))
except: abort(400, "Invalid page input!")
return get_saves_and_subscribes(v, "userpage/userpage.html", SaveRelationship, page, False)
return get_saves_and_subscribes(v, "userpage/submissions.html", SaveRelationship, page, False)
@app.get("/@<username>/saved/comments")
@auth_required
@ -1142,7 +1142,7 @@ def subscribed_posts(v:User, username):
try: page = max(1, int(request.values.get("page", 1)))
except: abort(400, "Invalid page input!")
return get_saves_and_subscribes(v, "userpage/userpage.html", Subscription, page, False)
return get_saves_and_subscribes(v, "userpage/submissions.html", Subscription, page, False)
@app.post("/fp/<fp>")
@auth_required

View File

@ -275,6 +275,7 @@
{% if c.parent_submission or c.wall_user_id %}
{% if v and v.id==c.author_id %}
<div id="comment-edit-{{c.id}}" class="d-none comment-write collapsed child">
<form id="comment-edit-form-{{c.id}}" action="/edit_comment/{{c.id}}" method="post" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v|formkey}}">
@ -301,9 +302,6 @@
</div>
{% endif %}
<div id="comment-{{c.id}}-actions" class="comment-actions{% if voted==1 %} upvoted{% elif voted==-1 %} downvoted{% endif %}">
<div class="d-md-none mt-1">
<div class="post-actions">
<ul class="list-inline text-right d-flex">
@ -316,7 +314,7 @@
</li>
{% if v and not c.deleted_utc %}
<button type="button" class="list-inline-item mr-3 btn nobackground" onclick="ToggleReplyBox('reply-to-{{c.id}}')"><i class="fas fa-reply" style="margin-top:0.35rem"></i></button>
<button type="button" class="list-inline-item mr-3 btn nobackground" onclick="toggleReplyBox('reply-to-{{c.fullname}}')"><i class="fas fa-reply" style="margin-top:0.35rem"></i></button>
{% endif %}
<li class="list-inline-item">
@ -365,15 +363,6 @@
</ul>
</div>
</div>
<ul class="d-none d-md-flex list-inline text-right text-md-left"><li>
{% if v and (request.path.startswith('/@') and not wall) and v.admin_level < PERMS['VIEW_VOTE_BUTTONS_ON_USER_PAGE'] %}
@ -414,7 +403,7 @@
{% if v %}
{% if not c.deleted_utc %}
<button type="button" class="btn caction py-0 nobackground px-1 text-muted" onclick="ToggleReplyBox('reply-to-{{c.id}}')"><i class="fas fa-reply" aria-hidden="true"></i>Reply</button>
<button type="button" class="btn caction py-0 nobackground px-1 text-muted" onclick="toggleReplyBox('reply-to-{{c.fullname}}')"><i class="fas fa-reply" aria-hidden="true"></i>Reply</button>
{% endif %}
<button type="button" class="btn caction py-0 nobackground px-1 text-muted" data-bs-toggle="modal" data-bs-target="#reportCommentModal" onclick="report_commentModal('{{c.id}}','{{c.author_name}}',)"><i class="fas fa-flag fa-fw"></i>Report</button>
@ -517,39 +506,7 @@
{% if v and v.id != c.author_id and c.body %}
<div autocomplete="off" class="markdown d-none card border my-2 p-3 comment-box form-control rounded" id="markdown-{{c.id}}" readonly>{{c.body.strip()}}</div>
{% endif %}
<div id="reply-to-{{c.id}}" class="d-none">
<div id="comment-form-space-{{c.fullname}}" class="comment-write collapsed child">
<form id="reply-to-c_{{c.id}}" action="/comment" method="post" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v|formkey}}">
<input type="hidden" name="parent_fullname" value="{{c.fullname}}">
<input autocomplete="off" id="reply-form-submission-{{c.fullname}}" type="hidden" name="submission" value="{{c.post.id}}">
<textarea required autocomplete="off" {% if v.longpost %}minlength="280"{% else %}minlength="1"{% endif %} maxlength="{% if v.bird %}140{% else %}10000{% endif %}" data-preview="form-preview-{{c.fullname}}" oninput="markdown(this);charLimit('reply-form-body-{{c.fullname}}','charcount-{{c.fullname}}')" id="reply-form-body-{{c.fullname}}" data-fullname="{{c.fullname}}" name="body" form="reply-to-c_{{c.id}}" class="comment-box form-control rounded" aria-label="With textarea" placeholder="Add your comment..." rows="3"></textarea>
<div class="text-small font-weight-bold mt-1" id="charcount-{{c.fullname}}" style="right: 1rem; bottom: 0.5rem; z-index: 3;"></div>
<div class="comment-format" id="comment-format-bar-{{c.id}}">
<label class="btn btn-secondary format m-0" for="gif-reply-btn-{{c.fullname}}" onclick="commentForm('reply-form-body-{{c.fullname}}');getGif()" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#gifModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add GIF">
<span id="gif-reply-btn-{{c.fullname}}" class="font-weight-bolder text-uppercase">GIF</span>
</label>
&nbsp;
<label class="btn btn-secondary format m-0" for="gif-reply-btn-{{c.fullname}}" onclick="loadEmojis('reply-form-body-{{c.fullname}}')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji">
<i id="emoji-reply-btn-{{c.fullname}}" class="fas fa-smile-beam"></i>
</label>
&nbsp;
<label class="btn btn-secondary format m-0" for="file-upload-reply-{{c.fullname}}">
<div id="filename-show-reply-{{c.fullname}}"><i class="fas fa-file"></i></div>
<input autocomplete="off" id="file-upload-reply-{{c.fullname}}" accept="image/*, video/*, audio/*" type="file" multiple="multiple" name="file" {% if g.is_tor %}disabled{% endif %} onchange="changename('filename-show-reply-{{c.fullname}}','file-upload-reply-{{c.fullname}}')" hidden>
</label>
</div>
<button type="button" id="save-reply-to-{{c.fullname}}" class="btn btn-primary ml-2 fl-r commentmob" onclick="post_comment('{{c.fullname}}', '{{c.wall_user_id}}', 'reply-to-{{c.id}}');remove_dialog()">Comment</button>
<button type="button" onclick="document.getElementById('reply-to-{{c.id}}').classList.add('d-none');remove_dialog()" class="btn btn-link text-muted ml-auto cancel-form fl-r commentmob">Cancel</button>
</form>
<div id="form-preview-{{c.fullname}}" class="preview mb-3 mt-5"></div>
<div class="form-text text-small p-0 m-0"><a href="/formatting" {% if v and v.newtab %}data-target="t"target="_blank"{% endif %}>Formatting help</a></div>
</div>
</div>
{{macros.comment_reply_box(c.fullname, "reply-to-" + c.fullname, "d-none", "collapsed child", 'reply-to-' + c.fullname, true, '/comments/')}}
{% if request.path.startswith('/transfers') %}
<a href="{{c.log_link}}"><i class="far fa-link ml-1 text-muted"></i></a>
@ -578,9 +535,10 @@
<button type="button" class="btn btn-primary mr-3 {% if not c.author.is_muted %}d-none{% endif %}" id="unmute-user-{{c.id}}" onclick="postToastSwitch(this,'/unmute_user/{{c.author.id}}','mute-user-{{c.id}}','unmute-user-{{c.id}}','d-none')">Unmute</button>
{% endif %}
<button type="button" class="btn btn-primary nobackground" onclick="ToggleReplyBox('reply-message-{{c.id}}')">Reply</button>
<button type="button" class="btn btn-primary nobackground" onclick="toggleReplyBox('reply-message-{{c.fullname}}')">Reply</button>
<div id="reply-message-{{c.id}}" class="d-none">
{# TODO: swap to comment_reply_box macro #}
<div id="reply-message-{{c.fullname}}" class="d-none">
<div id="comment-form-space-{{c.id}}" class="comment-write collapsed child">
<form id="reply-to-message-{{c.id}}" action="/reply" method="post" class="input-group" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v|formkey}}">

View File

@ -194,7 +194,6 @@
<div class="form-text text-small p-0 m-0"><a href="/formatting" {% if v and v.newtab %}data-target="t"target="_blank"{% endif %}>Formatting help</a></div>
</div>
{% endif %}
{% if p.embed_url and "http" not in p.embed_url and "<" not in p.embed_url %}
<div id="crosspost-embed" class="mb-3">
<div class="row no-gutters">
@ -209,7 +208,6 @@
<div class="d-none d-md-flex justify-content-between align-items-center mt-2">
<div class="post-actions mt-2">
<ul class="list-inline text-right d-flex">
<a class="list-inline-item" {% if v and v.newtab %}data-target="t"target="_blank"{% endif %} href="{{p.permalink}}">
<i class="fas fa-comment-dots mr-2"></i>{{p.comment_count}}
<span class="text-info d-none {{p.id}}-new-comments"></span>
@ -226,13 +224,10 @@
{% include 'post_actions.html' %}
</ul>
</div>
</div>
</div>
{% if v %}
<div id="voting" class="voting d-mob-none mb-auto">
<div tabindex="0" role="button" onclick="vote('post', '{{p.id}}', '1')" class="post-{{p.id}}-up mx-auto arrow-up upvote-button post-{{p.id}}-up {% if voted==1 %}active{% endif %}"></div>
@ -248,31 +243,21 @@
<span class="post-{{p.id}}-score-none score text-muted{% if p.controversial %} controversial{% endif %}"{% if not p.is_banned %} data-bs-toggle="tooltip" data-bs-placement="right" title="+{{ups}} | -{{downs}}"{% endif %}style="cursor: default">{{score}}</span>
<div {% if DISABLE_DOWNVOTES %}style="display:None!important"{% endif %} tabindex="0" role="button" onclick="vote('post', '{{p.id}}', '-1')" class="post-{{p.id}}-down arrow-down mx-auto" onclick="location.href='/login?redirect={{request.full_path | urlencode}}';"></div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<div class="row mb-3 d-md-none">
<div class="col-12">
<div class="post-actions">
<ul class="list-inline text-right d-flex">
<li class="list-inline-item mr-auto">
<a href="{{p.permalink}}">
<i class="fas fa-comment-dots mr-2"></i>{{p.comment_count}}
<span class="text-info d-none {{p.id}}-new-comments"></span>
</a>
{% if v and v.admin_level >= PERMS['POST_COMMENT_MODERATION_TOOLS_VISIBLE'] %}
<button type="button" class="ml-2" data-bs-toggle="modal" data-bs-target="#adminModal-{{p.id}}">
<i class="fas fa-broom"></i>
@ -341,49 +326,7 @@
</div>
{% if not p.deleted_utc %}
{% if v %}
<div id="comment-form-space-{{p.fullname}}" class="comment-write mb-3">
<form id="reply-to-{{p.fullname}}" action="/comment" method="post">
<input type="hidden" name="formkey" value="{{v|formkey}}">
<input type="hidden" name="parent_fullname" value="p_{{p.id}}">
<input autocomplete="off" id="reply-form-submission-{{p.fullname}}" type="hidden" name="submission" value="{{p.id}}">
<textarea required autocomplete="off" {% if not (p and p.id in ADMIGGER_THREADS) %}{% if v.longpost %}minlength="280"{% elif v.bird %}maxlength="140"{% endif %}{% endif %} minlength="1" maxlength="10000" data-preview="form-preview-{{p.fullname}}" oninput="markdown(this);charLimit('reply-form-body-{{p.fullname}}','charcount-{{p.fullname}}')" id="reply-form-body-{{p.fullname}}" data-fullname="{{p.fullname}}" class="comment-box form-control rounded" name="body" form="reply-to-{{p.fullname}}" aria-label="With textarea" placeholder="Add your comment..." rows="3"></textarea>
<div class="text-small font-weight-bold mt-1" id="charcount-{{p.fullname}}" style="right: 1rem; bottom: 0.5rem; z-index: 3;"></div>
<div class="comment-format">
<label class="btn btn-secondary format d-inline-block m-0" for="gif-reply-btn-{{p.fullname}}">
<span id="gif-reply-btn-{{p.fullname}}" class="font-weight-bolder text-uppercase" onclick="commentForm('reply-form-body-{{p.fullname}}');getGif()" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#gifModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add GIF">GIF</span>
</label>
&nbsp;
<div onclick="loadEmojis('reply-form-body-{{p.fullname}}')" class="btn btn-secondary format d-inline-block m-0" id="emoji-reply-btn-{{p.fullname}}" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"><i class="fas fa-smile-beam"></i></div>
&nbsp;
<label class="format btn btn-secondary m-0 ml-1 {% if v %}d-inline-block{% else %}d-none{% endif %}" for="file-upload-reply-{{p.fullname}}">
<div id="filename-show-reply-{{p.fullname}}"><i class="fas fa-file"></i></div>
<input autocomplete="off" id="file-upload-reply-{{p.fullname}}" accept="image/*, video/*, audio/*" type="file" multiple="multiple" name="file" {% if g.is_tor %}disabled{% endif %} onchange="changename('filename-show-reply-{{p.fullname}}','file-upload-reply-{{p.fullname}}')" hidden>
</label>
</div>
<button type="button" id="save-reply-to-{{p.fullname}}" form="reply-to-{{p.fullname}}" class="btn btn-primary text-whitebtn ml-auto fl-r" onclick="post_comment('{{p.fullname}}', 'None');remove_dialog()">Comment</button>
</form>
<div id="form-preview-{{p.fullname}}" class="preview mb-3 mt-5"></div>
<div class="form-text text-small p-0 m-0"><a href="/formatting" {% if v and v.newtab %}data-target="t"target="_blank"{% endif %}>Formatting help</a></div>
</div>
{% else %}
<div class="comment-write mb-3">
<textarea autocomplete="off" maxlength="10000" class="comment-box form-control rounded" name="body" aria-label="With textarea" placeholder="Add your comment..." rows="3" onclick="location.href='/login?redirect={{request.full_path | urlencode}}';"></textarea>
</div>
<div class="card border-0 mt-4">
<div class="card-body">
<h5 class="card-title">Jump in the discussion.</h5>
<p class="card-text">No email address required.</p>
<div>
<a href="/signup?redirect={{request.full_path | urlencode}}" class="btn btn-primary">Sign up</a>
<a href="/login?redirect={{request.full_path | urlencode}}" class="btn btn-link text-muted">Sign in</a>
</div>
</div>
</div>
{% endif %}
{{macros.comment_reply_box(p.fullname, 'comment-reply-' + p.fullname, '', 'mb-3', '', true, '/comments/')}}
{% endif %}
{% if comment_info %}

View File

@ -0,0 +1,505 @@
{% import 'userpage/admintools.html' as userpage_admintools with context %}
{% set hats_total = u.hats_owned_proportion_display[1] if u else 0 %}
{% set hats_owned_percent = u.hats_owned_proportion_display[0] if u else '' %}
{% block desktopUserBanner %}
<div class="row d-mob-none">
<div class="col px-0">
<div class="jumbotron jumbotron-fluid jumbotron-guild d-mob-none" {% if FEATURES['USERS_PROFILE_BANNER'] %}style="background-image: url({{u.banner_url}})"{% endif %}>
<div class="jumbotron-overlay"></div>
<div class="w-100 my-3">
<div class="container-fluid nobackground">
<div class="d-md-flex text-center text-md-left">
<div id="profile--pfp" {% if u.hat_active %}class="profile--pfp--hat hat"{% endif %}>
<a rel="nofollow noopener" href="{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}" class="profile-pic-100-wrapper">
<img onclick="expandDesktopImage('{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}')" loading="lazy" src="{{u.profile_url}}" class="profile-pic profile-pic-100 mb-5">
{% if u.hat_active -%}
<img onclick="expandDesktopImage('{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}')" class="profile-pic-100-hat hat" loading="lazy" src="{{u.hat_active}}?h=7" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{u.hat_tooltip(v)}}">
{%- endif %}
</a>
</div>
<div id="profilestuff" class="ml-3 w-100">
{{userpage_admintools.userBanBlock('desktop')}}
<div class="d-flex align-items-center mt-1 mb-2">
<h3 class="font-weight-bolder my-0 mr-2" id="profile--name" style="color: #{{u.name_color}}"><span {% if u.patron %}class="patron" style="background-color:#{{u.name_color}}"{% endif %}>{{u.user_name}}</span></h3>
{% if u.username != u.original_username %}
<span id="profile--origname">
<i class="fas fa-user-tag text-info align-middle ml-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Original Username: @{{u.original_username}}"></i>
</span>
{% endif %}
{% if FEATURES['PATRON_ICONS'] and u.patron %}
<img loading="lazy" class="ml-3" src="/i/{{SITE_NAME}}/patron_badges/2{{u.patron}}.webp?v=1" height="20" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{u.patron_tooltip}}" alt="{{u.patron_tooltip}}">
{% endif %}
{% if FEATURES['HOUSES'] and u.house %}
<img loading="lazy" class="ml-3" id="profile--house" src="/i/{{SITE_NAME}}/houses/{{u.house}}.webp?v=2000" height="20" data-bs-toggle="tooltip" data-bs-placement="bottom" title="House {{u.house}}" alt="House {{u.house}}">
{% endif %}
{% if u.verified %}
<span id="profile--verified"><i class="fas fa-badge-check align-middle ml-2 {% if u.verified=='Glowiefied' %}glow{% endif %}" style="color:{% if u.verifiedcolor %}#{{u.verifiedcolor}}{% else %}#1DA1F2{% endif %}" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{u.verified}}"></i></span>
{% endif %}
{% if u.admin_level >= PERMS['ADMIN_MOP_VISIBLE'] %}
<span id="profile--mop">
<i class="fas fa-broom text-admin align-middle ml-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Admin"></i>
</span>
{% endif %}
{% if v and v.id != u.id and v.has_follower(u) %}
<span class="followsyou badge badge-secondary text-small align-middle ml-2" id="profile--follows-you">Follows you</span>
{% endif %}
</div>
{% if FEATURES['PRONOUNS'] %}
<p class="font-weight-bolder" id="profile--pronouns" style="color: #{{u.titlecolor}}">{{u.pronouns}}</p>
{% endif %}
{% if u.customtitle %}
<p class="font-weight-bolder" id="profile--flair" style="color: #{{u.titlecolor}}">{{u.customtitle | safe}}</p>
{% endif %}
{% if v and (v.id == u.id or v.admin_level >= PERMS['USER_VOTERS_VISIBLE']) -%}
<div class="font-weight-bolder mb-2" id="profile--simphate">
<a class="mr-1" href="/@{{u.username}}/views">Profile Views</a> | <a class="mx-1" href="/@{{u.username}}/upvoters">Simps</a> | <a class="mx-1" href="/@{{u.username}}/downvoters">Haters</a> | <a class="mx-1" href="/@{{u.username}}/upvoting">Simps For</a> | <a class="mx-1" href="/@{{u.username}}/downvoting">Hates</a> | <a class="ml-1" href="/@{{u.username}}/voted/posts">Voted</a>
</div>
{%- endif %}
<div class="font-weight-bolder">
<span id="profile-coins-amount">{{u.coins}}</span>
<img alt="coins" class="ml-1 mb-1 mr-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Coins" height="20" src="{{'coins.webp' | asset_siteimg}}">
{% if FEATURES['MARSEYBUX'] %}
<span id="profile-bux-amount">{{u.marseybux}}</span>
<img alt="marseybux" class="ml-1 mb-1 mr-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Marseybux" height="20" width="46" src="/i/marseybux.webp?v=2000">
{% endif %}
{% if PERMS['USER_FOLLOWS_VISIBLE'] == 0 or (v and v.admin_level >= PERMS['USER_FOLLOWS_VISIBLE']) -%}
<a class="mr-2" href="/@{{u.username}}/followers" id="profile--followers">{{u.stored_subscriber_count}} follower{{'s' if u.stored_subscriber_count != 1 else ''}}</a>
<a class="mr-2" href="/@{{u.username}}/following" id="profile--following">follows {{u.follow_count}} user{{'s' if u.follow_count != 1 else ''}}</a>
{%- endif %}
<span id="profile--joined">joined <span id="profile--joined--time" data-bs-toggle="tooltip" data-bs-placement="bottom" onmouseover="timestamp('profile--joined--time','{{u.created_utc}}')">{{u.created_date}}</span></span>
{% if v and v.admin_level >= PERMS['VIEW_LAST_ACTIVE'] -%}
<span id="profile--lastactive" class="ml-2">last active <span id="profile--lastactive--time" data-bs-toggle="tooltip" data-bs-placement="bottom" onmouseover="timestamp('profile--lastactive--time','{{u.last_active}}')">{{u.last_active_date}}</span></span>
{%- endif %}
</div>
{% if u.basedcount %}<p class="text-muted" id="profile--based">Based Count: {{u.basedcount}}</p>{% endif %}
{% if FEATURES['USERS_PROFILE_BODYTEXT'] -%}
{% if u.bio_html %}
<div class="text-muted font-weight-bolder mt-1" id="profile--bio">{{u.bio_html | safe}}</div>
{% else %}
<p class="text-muted" id="profile--bio">No bio...</p>
{% endif %}
{% if u.friends_html %}
<p class="text-muted font-weight-bold">Friends:</p>
<div id="profile--friends">{{u.friends_html | safe}}</div>
{% endif %}
{% if u.enemies_html %}
<p class="text-muted font-weight-bold">Enemies:</p>
<div id="profile--enemies">{{u.enemies_html | safe}}</div>
{% endif %}
{%- endif %}
{% if u.received_awards and FEATURES['AWARDS'] %}
<div class="text-white rounded p-2 mb-3" id="profile--awards" style="background-color: rgba(50, 50, 50, 0.6); width: 30%;">
<p class="text-uppercase my-0" style="font-weight: bold; font-size: 12px;">Awards received</p>
{% for a in u.received_awards %}
<span class="d-inline-block mx-1 profile--awards--award">
<i class="{{a['icon']}} {{a['color']}} fa-fw" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{a['title']}} Awards received"></i>
x{{a['count']}}
</span>
{% endfor %}
</div>
{% endif %}
{% if u.moderated_subs %}
<div class="text-white rounded p-2 mb-3" id="profile--holes" style="background-color: rgba(50, 50, 50, 0.6); width: 30%;">
<p class="text-uppercase my-0 pb-1" style="font-weight: bold; font-size: 12px;">Moderator of</p>
{% for a in u.moderated_subs %}
<span class="d-inline-block mx-1">
<a href="/h/{{a}}">/h/{{a}}</a>
</span>
{% endfor %}
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-center">
<div>
{% if v and v.id != u.id %}
<div id="profile--actionbtns">
<div class="actionbtns mb-3">
<button type="button" id="button-unsub" class="btn btn-secondary {% if not is_following %}d-none{% endif %}" onclick="postToastSwitch(this,'/unfollow/{{u.username}}','button-unsub','button-sub','d-none')">Unfollow</button>
<button type="button" id="button-sub" class="btn btn-primary {% if is_following or u.is_blocked %}d-none{% endif %}" onclick="postToastSwitch(this,'/follow/{{u.username}}','button-unsub','button-sub','d-none')">Follow</button>
<button type="button" class="btn btn-primary" onclick="toggleElement('message', 'input-message')">Message</button>
{% if FEATURES['USERS_SUICIDE'] -%}
<button type="button" class="btn btn-primary" onclick="postToastSwitch(this,'/@{{u.username}}/suicide')">Get Them Help</button>
{%- endif %}
<button type="button" class="btn btn-primary" onclick="toggleElement('coin-transfer', 'coin-transfer-amount')">Gift Coins</button>
{% if FEATURES['MARSEYBUX'] -%}
<button type="button" class="btn btn-primary" onclick="toggleElement('bux-transfer', 'bux-transfer-amount')">Gift Marseybux</button>
{%- endif %}
<button type="button" class="btn btn-primary" onclick="postToastReload(this,'/settings/block?username={{u.username}}')">Block</button>
</div>
</div>
<form class="d-none toggleable" id="message" action="/@{{u.username}}/message" onsubmit="sendMessage(event)">
<input type="hidden" name="formkey" value="{{v|formkey}}">
<textarea autocomplete="off" id="input-message" form="message" name="message" rows="3" minlength="1" maxlength="10000" class="form-control b2 mt-1" data-preview="message-preview" oninput="markdown(this)" required></textarea>
<pre class="btn btn-secondary format d-inline-block m-0 fas fa-smile-beam" onclick="loadEmojis('input-message')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"></pre>
&nbsp;
<input type="submit" onclick="disable(this);remove_dialog()" value="Submit" class="btn btn-primary">
</form>
<div id="message-preview" class="preview mt-2"></div>
<div class="d-none mt-3 toggleable" id="coin-transfer">
<input autocomplete="off" id="coin-transfer-amount" class="form-control" name="amount" type="number" oninput="updateTax()">
<input autocomplete="off" id="coin-transfer-reason" maxlength=200 type="text" class="form-control" name="reason" placeholder="Gift message! (optional)">
<div>{{u.username}} will receive <span id="coins-transfer-taxed">0</span> coins</div>
<button type="button" class="btn btn-primary mt-2 mb-3" onclick="transferCoins(this)">Gift</button>
</div>
<div class="d-none mt-3 toggleable" id="bux-transfer">
<input autocomplete="off" id="bux-transfer-amount" class="form-control" name="amount" type="number" oninput="updateBux()">
<input autocomplete="off" id="bux-transfer-reason" type="text" class="form-control" name="reason" placeholder="Gift message! (optional)">
<div>{{u.username}} will receive <span id="bux-transfer-taxed">0</span> marseybux</div>
<button type="button" class="btn btn-primary mt-2 mb-3" onclick="transferBux(this)">Gift</button>
</div>
{{userpage_admintools.userAdminTools('desktop')}}
{% endif %}
<div class="actionbtns">
{% if v and v.id == u.id %}
<a href="/settings/personal" class="btn btn-secondary">Edit Profile</a>
<form id="upload-profile-background" action="/settings/profile_background" method="post" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v|formkey}}">
<label class="format btn btn-primary" for="upload-profile-background-file" style="margin-bottom:0!important">
<i class="fas fa-image mr-1"></i>
{% if v.profile_background -%}
{{v.profile_background}}
{% else -%}
Upload Profile Background
{% endif %}
</label>
<input autocomplete="off" id="upload-profile-background-file" accept="image/*", type="file" name="file" onchange="this.form.submit()" hidden>
</form>
{% if v.profile_background -%}
<div class="d-flex mb-3">
<button type="button" class="btn btn-danger" onclick="postToastReload(this,'/settings/profile_background', 'DELETE')">
<i class="fas fa-image-slash mr-1"></i>
Remove current background
</button>
</div>
{%- endif %}
{% endif %}
{% if FEATURES['USERS_PROFILE_SONG'] and u.song and v and (v.id == u.id or v.mute and not u.unmutable) %}
<button type="button" class="btn btn-secondary" onclick="toggle()" {% if v.id == u.id %}style="margin-bottom:0!important;padding:0.3rem 0.75rem!important"{% endif %}>Toggle Anthem</button>
{% endif %}
</div>
<div class="mt-3" id="profile--info">
<p id="profile--info--id">User ID: {{u.id}}</p>
<p id="profile--info--spent">Coins spent: {{u.coins_spent}}</p>
<p id="profile--info--truescore">True score: {{u.truescore}}</p>
<p id="profile--info--winnings">Winnings: {{u.winnings}}</p>
<p id="profile--info--hats-owned" {% if u.num_of_owned_hats >= hats_total %}class="profile-owned-all-hats"{% endif %}>{{u.num_of_owned_hats}} / {{hats_total}} hats owned ({{hats_owned_percent}})</p>
{% if u.is_private %}
<p id="profile--info--private">User has private mode enabled</p>
{% endif %}
{% if v and (v.admin_level >= PERMS['VIEW_ALTS'] or v.alt) %}
{% if v.admin_level >= PERMS['USER_LINK'] %}
<span id="profile--alts"><a href="/@{{u.username}}/alts">Alts</a>:</span>
{% else %}
<span id="profile--alts">Alts:</span>
{% endif %}
<ul id="profile--alts-list">
{% for account in u.alts_unique if not account._alt_deleted and (v.can_see_shadowbanned or not account.shadowbanned) %}
<li><a href="{{account.url}}">@{{account.username}}</a>{% if account._is_manual %} [m]{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
</div>
{% if FEATURES['BADGES'] -%}
<div id="profile--badges">
{% for b in u.badges %}
{% if b.url %}
<a class="contain" rel="nofollow noopener" href="{{b.url}}">
<img alt="{{b.name}}" width=55 height=60 loading="lazy" src="{{b.path}}?b=5" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{b.text}}" {% if b.until %}data-until="{{b.until}}" onmouseover="badge_timestamp(this)"{% endif %}>
</a>
{% else %}
<img class="contain" alt="{{b.name}}" width=55 height=60 loading="lazy" src="{{b.path}}?b=5" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{b.text}}" {% if b.until %}data-until="{{b.until}}" onmouseover="badge_timestamp(this)"{% endif %}>
{% endif %}
{% endfor %}
</div>
{%- endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block mobileUserBanner %}
<div class="container-fluid pb-0 text-center bg-white d-md-none" style="margin-top:-6px;border-radius:0!important;">
<div class="row">
<div class="col px-0">
<a rel="nofollow noopener" href="{{u.banner_url}}">
<img alt="@{{u.username}}'s banner" onclick="expandDesktopImage()" loading="lazy" src="{{u.banner_url}}" width=100% style="object-fit:cover;max-height:30vh!important">
</a>
</div>
</div>
<div class="row border-bottom">
<div class="col">
<div style="margin-top: -34px;" id="profile-mobile--pfp">
<a rel="nofollow noopener" href="{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}" class="profile-pic-65-wrapper">
<img onclick="expandDesktopImage('{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}')" loading="lazy" src="{{u.profile_url}}" class="profile-pic-65 bg-white mb-2">
{% if u.hat_active -%}
<img onclick="expandDesktopImage('{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}')" class="profile-pic-65-hat hat" loading="lazy" src="{{u.hat_active}}?h=7">
{%- endif %}
</a>
</div>
<div class="mt-n3 py-3">
{{userpage_admintools.userBanBlock('mobile')}}
<h5 class=" d-inline-block" id="profile-mobile--name" style="color: #{{u.name_color}}"><span {% if u.patron %}class="patron" style="background-color:#{{u.name_color}}"{% endif %}>{{u.user_name}}</span></h5>
{% if u.username != u.original_username %}
<span id="profile-mobile--origname">
<i class="fas fa-user-tag text-info align-middle ml-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Original Username: @{{u.original_username}}"></i>
</span>
{% endif %}
{% if FEATURES['PATRON_ICONS'] and u.patron %}
<img loading="lazy" class="ml-2" src="/i/{{SITE_NAME}}/patron_badges/2{{u.patron}}.webp?v=1" height="20" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{u.patron_tooltip}}" alt="{{u.patron_tooltip}}">
{% endif %}
{% if FEATURES['HOUSES'] and u.house %}
<img loading="lazy" class="ml-2" id="profile-mobile--house" src="/i/{{SITE_NAME}}/houses/{{u.house}}.webp?v=2000" height="20" data-bs-toggle="tooltip" data-bs-placement="bottom" title="House {{u.house}}" alt="House {{u.house}}">
{% endif %}
{% if u.verified %}
<span id="profile-mobile--verified"><i class="fas fa-badge-check align-middle ml-2 {% if u.verified=='Glowiefied' %}glow{% endif %}" style="color:{% if u.verifiedcolor %}#{{u.verifiedcolor}}{% else %}#1DA1F2{% endif %}" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{u.verified}}"></i></span>&nbsp;
{% endif %}
{% if u.admin_level >= PERMS['ADMIN_MOP_VISIBLE'] %}
<span id="profile-mobile--mop">
<i class="fas fa-broom text-admin align-middle ml-1" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Admin"></i>
</span>
{% endif %}
{% if v and v.id != u.id and v.has_follower(u) %}
<span class="followsyou badge badge-secondary text-small align-middle mx-1" id="profile-mobile--follows-you">Follows you</span>
{% endif %}
{% if FEATURES['PRONOUNS'] %}
<p style="color: #{{u.titlecolor}}" id="profile-mobile--pronouns">{{u.pronouns}}</p>
{% endif %}
{% if u.customtitle %}
<p style="color: #{{u.titlecolor}}" id="profile-mobile--flair">{{u.customtitle | safe}}</p>
{% endif %}
{% if v and (v.id == u.id or v.admin_level >= PERMS['USER_VOTERS_VISIBLE']) -%}
<div class="font-weight-bolder mb-2" id="profile-mobile--simphate">
<a class="mr-1" href="/@{{u.username}}/views">Profile Views</a> | <a class="mx-1" href="/@{{u.username}}/upvoters">Simps</a> | <a class="mx-1" href="/@{{u.username}}/downvoters">Haters</a> | <a class="mx-1" href="/@{{u.username}}/upvoting">Simps For</a> | <a class="mx-1" href="/@{{u.username}}/downvoting">Hates</a> | <a class="ml-1" href="/@{{u.username}}/voted/posts">Voted</a>
</div>
{%- endif %}
<div class="font-weight-normal">
<span id="profile-coins-amount-mobile" class="font-weight-bold">{{u.coins}}</span>
<img alt="coins" class="ml-1 mb-1 mr-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Coins" height="15" src="{{'coins.webp' | asset_siteimg}}">
{% if FEATURES['MARSEYBUX'] %}
<span id="profile-bux-amount-mobile" class="font-weight-bold">{{u.marseybux}}</span>
<img alt="marseybux" class="ml-1 mb-1 mr-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Marseybux" height="15" width="35" src="/i/marseybux.webp?v=2000">
{% endif %}
{% if PERMS['USER_FOLLOWS_VISIBLE'] == 0 or (v and v.admin_level >= PERMS['USER_FOLLOWS_VISIBLE']) -%}
<a href="/@{{u.username}}/followers" class="font-weight-bold mr-2" id="profile-mobile--followers">{{u.stored_subscriber_count}} follower{{'s' if u.stored_subscriber_count != 1 else ''}}</a>
<a href="/@{{u.username}}/following" class="font-weight-bold mr-2" id="profile-mobile--following" style="display:block">follows {{u.follow_count}} user{{'s' if u.follow_count != 1 else ''}}</a>
{%- endif %}
{% if u.basedcount %}
<br><span id="profile-mobile--based">Based count: {{u.basedcount}}</span>
{% endif %}
<br><span id="profile-mobile--joined">joined <span id="profile-mobile--joined--time" data-bs-toggle="tooltip" data-bs-placement="bottom" onmouseover="timestamp('profile-mobile--joined--time','{{u.created_utc}}')" class="font-weight-bold">{{u.created_date}}</span></span>
{% if v and v.admin_level >= PERMS['VIEW_LAST_ACTIVE'] -%}
<br><span id="profile-mobile--lastactive">last active <span id="profile-mobile--lastactive--time" data-bs-toggle="tooltip" data-bs-placement="bottom" onmouseover="timestamp('profile-mobile--lastactive--time','{{u.last_active}}')" class="font-weight-bold">{{u.last_active_date}}</span></span>
{%- endif %}
</div>
{% if FEATURES['USERS_PROFILE_BODYTEXT'] -%}
{% if u.bio_html %}
<div class="text-muted text-break" id="profile-mobile--bio">{{u.bio_html | safe}}</div>
{% endif %}
{% if u.friends_html %}
<p class="text-muted font-weight-bold mt-3">Friends:</p>
<div id="profile-mobile--friends">{{u.friends_html | safe}}</div>
{% endif %}
{% if u.enemies_html %}
<p class="text-muted font-weight-bold mt-3">Enemies:</p>
<div id="profile-mobile--enemies">{{u.enemies_html | safe}}</div>
{% endif %}
{%- endif %}
{% if u.received_awards and FEATURES['AWARDS'] %}
<div class="text-white rounded p-2 my-3 text-center" id="profile-mobile--awards" style="background-color: rgba(50, 50, 50, 0.6);">
<p class="text-uppercase my-0" style="font-weight: bold; font-size: 12px;">Awards received</p>
{% for a in u.received_awards %}
<span class="d-inline-block mx-1 profile-mobile--awards--award">
<i class="{{a['icon']}} {{a['color']}} fa-fw" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{a['title']}} Awards received"></i>
x{{a['count']}}
</span>
{% endfor %}
</div>
{% endif %}
{% if u.moderated_subs %}
<div class="text-white rounded p-2 mb-3" id="profile-mobile--holes" style="background-color: rgba(50, 50, 50, 0.6);">
<p class="text-uppercase my-0 pb-1" style="font-weight: bold; font-size: 12px;">Moderator of</p>
{% for a in u.moderated_subs %}
<span class="d-inline-block mx-1">
<a href="/h/{{a}}">/h/{{a}}</a>
</span>
{% endfor %}
</div>
{% endif %}
<div class="mb-3" id="profile-mobile--badges">
{% for b in u.badges %}
{% if b.url %}
<a rel="nofollow noopener" href="{{b.url}}">
<img class="contain" alt="{{b.name}}" width=29.33 height=32 loading="lazy" src="{{b.path}}?b=5" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{b.text}}" {% if b.until %}data-until="{{b.until}}" onmouseover="badge_timestamp(this)"{% endif %}>
</a>
{% else %}
<img class="contain" alt="{{b.name}}" width=29.33 height=32 loading="lazy" src="{{b.path}}?b=5" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{b.text}}" {% if b.until %}data-until="{{b.until}}" onmouseover="badge_timestamp(this)"{% endif %}>
{% endif %}
{% endfor %}
</div>
<div class="actionbtns">
{% if v and v.id == u.id %}
<a href="/settings/personal" class="btn btn-secondary ">Edit Profile</a>
{% endif %}
{% if FEATURES['USERS_PROFILE_SONG'] and u.song and v and (v.id == u.id or v.mute and not u.unmutable) %}
<button type="button" class="btn btn-secondary" onclick="toggle()" {% if v.id == u.id %}style="margin-bottom:0!important;padding:0.3rem 0.75rem!important"{% endif %}>Toggle Anthem</button>
{% endif %}
{% if v and v.id == u.id and (v.profile_background or v.theme == 'transparent') %}
<div>
<form class="mt-3" id="upload-profile-background-mobile" action="/settings/profile_background" method="post" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v|formkey}}">
<label class="format btn btn-primary" for="upload-profile-background-file-mobile" style="margin-bottom:0!important">
<i class="fas fa-image mr-1"></i>
{% if v.profile_background %}
{{v.profile_background}}
{% else %}
Upload Profile Background
{% endif %}
</label>
<input autocomplete="off" id="upload-profile-background-file-mobile" accept="image/*", type="file" name="file" onchange="this.form.submit()" hidden>
</form>
{% if v.profile_background -%}
<div class="d-block mt-3 mb-3">
<button type="button" class="btn btn-danger" onclick="postToastReload(this,'/settings/profile_background', 'DELETE')">
<i class="fas fa-image-slash mr-1"></i>
Remove current background
</button>
</div>
{%- endif %}
</div>
{% endif %}
{% if v and v.id != u.id %}
<button type="button" id="button-unsub2" class="btn btn-secondary {% if not is_following %}d-none{% endif %}" onclick="postToastSwitch(this,'/unfollow/{{u.username}}','button-unsub2','button-sub2','d-none')">Unfollow</button>
<button type="button" id="button-sub2" class="btn btn-primary {% if is_following or u.is_blocked %}d-none{% endif %}" onclick="postToastSwitch(this,'/follow/{{u.username}}','button-unsub2','button-sub2','d-none')">Follow</button>
<button type="button" class="btn btn-primary" onclick="toggleElement('message-mobile', 'input-message-mobile')">Message</button>
{% if FEATURES['USERS_SUICIDE'] -%}
<button type="button" class="btn btn-primary" onclick="postToastSwitch(this,'/@{{u.username}}/suicide')">Get Them Help</button>
{%- endif %}
<button type="button" class="btn btn-primary" onclick="toggleElement('coin-transfer-mobile', 'coin-transfer-amount-mobile')">Gift Coins</button>
{% if FEATURES['MARSEYBUX'] -%}
<button type="button" class="btn btn-primary" onclick="toggleElement('bux-transfer-mobile', 'bux-transfer-amount-mobile')">Gift Marseybux</button>
{%- endif %}
<button type="button" class="btn btn-primary" onclick="postToastReload(this,'/settings/block?username={{u.username}}')">Block</button>
{% endif %}
</div>
{% if v and v.id != u.id %}
<form class="d-none toggleable" id='message-mobile' action="/@{{u.username}}/message" onsubmit="sendMessage(event)">
<input class="mt-1" type="hidden" name="formkey" value="{{v|formkey}}">
<textarea autocomplete="off" id="input-message-mobile" form="message-mobile" name="message" rows="3" minlength="1" maxlength="10000" class="form-control" data-preview="message-preview-mobile" oninput="markdown(this)" required></textarea>
<pre class="mt-1 btn btn-secondary format d-inline-block m-0 fas fa-smile-beam" onclick="loadEmojis('input-message-mobile')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"></pre>
&nbsp;
<input type="submit" onclick="disable(this);remove_dialog()" value="Submit" class="btn btn-primary">
</form>
<div id="message-preview-mobile" class="preview my-3"></div>
<div class="d-none mt-3 toggleable" id="coin-transfer-mobile">
<input autocomplete="off" id="coin-transfer-amount-mobile" class="form-control" name="amount" type="number" oninput="updateTax(true)">
<input autocomplete="off" id="coin-transfer-reason-mobile" maxlength=200 type="text" class="form-control" name="reason" placeholder="Gift message! (optional)">
<div>{{u.username}} will receive <span id="coins-transfer-taxed-mobile">0</span> coins</div>
<button type="button" class="btn btn-primary mt-2 mb-3" onclick="transferCoins(this, true)">Gift</button>
</div>
<div class="d-none mt-3 toggleable" id="bux-transfer-mobile">
<input autocomplete="off" id="bux-transfer-amount-mobile" class="form-control" name="amount" type="number" oninput="updateBux(true)">
<input autocomplete="off" id="bux-transfer-reason-mobile" type="text" class="form-control" name="reason" placeholder="Gift message! (optional)">
<div>{{u.username}} will receive <span id="bux-transfer-taxed-mobile">0</span> marseybux</div>
<button type="button" class="btn btn-primary mt-2 mb-3" onclick="transferBux(this, true)">Gift</button>
</div>
{{userpage_admintools.userAdminTools('mobile')}}
{% endif %}
<div id="profile-mobile--info">
<p id="profile-mobile--info--id">User ID: {{u.id}}</p>
<p id="profile-mobile--info--spent">Coins spent: {{u.coins_spent}}</p>
<p id="profile-mobile--info--truescore">True score: {{u.truescore}}</p>
<p id="profile-mobile--info--winnings">Winnings: {{u.winnings}}</p>
<p id="profile-mobile--info--hats-owned" {% if u.num_of_owned_hats >= hats_total %}class="profile-owned-all-hats"{% endif %}>{{u.num_of_owned_hats}} / {{hats_total}} hats owned ({{hats_owned_percent}})</p>
{% if u.is_private %}
<p id="profile-mobile--info--private">User has private mode enabled</p>
{% endif %}
{% if v and (v.admin_level >= PERMS['VIEW_ALTS'] or v.alt) %}
{% if v.admin_level >= PERMS['USER_LINK'] %}
<span id="profile-mobile--alts"><a href="/@{{u.username}}/alts">Alts</a>:</span>
{% else %}
<span id="profile-mobile--alts">Alts:</span>
{% endif %}
<ul id="profile-mobile--alts-list">
{% for account in u.alts_unique if not account._alt_deleted and (v.can_see_shadowbanned or not account.shadowbanned) %}
<li><a href="{{account.url}}">@{{account.username}}</a>{% if account._is_manual %} [m]{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,86 +1,7 @@
{% extends "userpage/userpage.html" %}
{% block content %}
<div class="row no-gutters">
{% block userpage_content %}
<div class="row no-gutters {% if listing %}mt-md-3{% elif not listing %}my-md-3{% endif %} userpage-comments" style="margin-top: 10px;">
<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" id="profile-content--nav">
<li class="nav-item mr-1">
<a class="nav-link" href="/@{{u.username}}">Wall</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/@{{u.username}}/posts">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 %}" 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" 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 %}" 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" href="/@{{u.username}}/subscribed/posts">Subscribed <span class="count">({{u.subscribed_count}})</span></a>
</li>
{% endif %}
</ul>
</div>
</div>
</div>
{% if "/saved/" not in request.path and "/subscribed/" not in request.path %}
<div class="d-flex justify-content-between align-items-center pt-4">
<div class="d-flex align-items-center">
<div class="dropdown dropdown-actions">
<button type="button" class="btn btn-secondary dropdown-toggle" 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="dropdown dropdown-actions ml-3">
<button type="button" class="btn btn-secondary dropdown-toggle" id="dropdownMenuButton2" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% if sort=="top" %}<i class="fas fa-arrow-alt-circle-up mr-1"></i>{% endif %}
{% if sort=="bottom" %}<i class="fas fa-arrow-alt-circle-down mr-1"></i>{% endif %}
{% if sort=="new" %}<i class="fas fa-sparkles mr-1"></i>{% endif %}
{% if sort=="old" %}<i class="fas fa-book mr-1"></i>{% endif %}
{% if sort=="controversial" %}<i class="fas fa-bullhorn mr-1"></i>{% endif %}
{{sort | capitalize}}
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton2" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 31px, 0px);">
{% if sort != "top" %}<a class="dropdown-item" href="?sort=top&t={{t}}"><i class="fas fa-arrow-alt-circle-up mr-2"></i>Top</a>{% endif %}
{% if sort != "bottom" %}<a class="dropdown-item" href="?sort=bottom&t={{t}}"><i class="fas fa-arrow-alt-circle-down mr-2"></i>Bottom</a>{% endif %}
{% if sort != "new" %}<a class="dropdown-item" href="?sort=new&t={{t}}"><i class="fas fa-sparkles mr-2"></i>New</a>{% endif %}
{% if sort != "old" %}<a class="dropdown-item" href="?sort=old&t={{t}}"><i class="fas fa-book mr-2"></i>Old</a>{% endif %}
{% if sort != "controversial" %}<a class="dropdown-item" href="?sort=controversial&t={{t}}"><i class="fas fa-bullhorn mr-2"></i>Controversial</a>{% endif %}
</div>
</div>
</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">
{% if listing %}
<div class="posts p-3 p-md-0">
{% with comments=listing %}
@ -107,20 +28,4 @@
{% endif %}
</div>
</div>
{% if u.song %}
{% if v and v.id == u.id %}
<div id="v_username" class="d-none">{{v.username}}</div>
{% else %}
<div id="u_username" class="d-none">{{u.username}}</div>
{% endif %}
{% endif %}
{% if v %}
<div id='tax' class="d-none">{% if v.patron or u.patron %}0{% else %}0.03{% endif %}</div>
<div id="username" class="d-none">{{u.username}}</div>
<script defer src="{{'js/userpage_v.js' | asset}}"></script>
{% endif %}
<script defer src="{{'js/userpage.js' | asset}}"></script>
{% endblock %}

View File

@ -0,0 +1,74 @@
{% set path = request.path.replace("/@" + u.username, '') %}
<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" id="profile-content--nav">
<li class="nav-item mr-1">
<a class="nav-link{% if path == '' %} active{% endif %}" href="/@{{u.username}}">Wall</a>
</li>
<li class="nav-item">
<a class="nav-link{% if path == '/posts' %} active{% endif %}" href="/@{{u.username}}/posts">Posts <span class="count">({{u.post_count}})</span></a>
</li>
<li class="nav-item">
<a class="nav-link{% if path == '/comments' %} active{% endif %}" 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{% if path == '/saved/posts' %} active{% endif %}" 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 path == '/saved/comments' %} active{% endif %}" 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{% if path == '/subscribed/posts' %} active{% endif %}" href="/@{{u.username}}/subscribed/posts">Subscribed <span class="count">({{u.subscribed_count}})</span></a>
</li>
{% endif %}
</ul>
</div>
</div>
</div>
{% if "/saved/" not in request.path and "/subscribed/" not in request.path and not path == '' %}
<div class="d-flex justify-content-between align-items-center" style="padding-top:10px">
<div class="d-flex align-items-center">
<div class="dropdown dropdown-actions">
<button type="button" class="btn btn-secondary dropdown-toggle" 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="dropdown dropdown-actions ml-3">
<button type="button" class="btn btn-secondary dropdown-toggle" id="dropdownMenuButton2" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% if sort=="top" %}<i class="fas fa-arrow-alt-circle-up mr-1"></i>{% endif %}
{% if sort=="bottom" %}<i class="fas fa-arrow-alt-circle-down mr-1"></i>{% endif %}
{% if sort=="new" %}<i class="fas fa-sparkles mr-1"></i>{% endif %}
{% if sort=="old" %}<i class="fas fa-book mr-1"></i>{% endif %}
{% if sort=="controversial" %}<i class="fas fa-bullhorn mr-1"></i>{% endif %}
{{sort | capitalize}}
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton2" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 31px, 0px);">
{% if sort != "top" %}<a class="dropdown-item" href="?sort=top&t={{t}}"><i class="fas fa-arrow-alt-circle-up mr-2"></i>Top</a>{% endif %}
{% if sort != "bottom" %}<a class="dropdown-item" href="?sort=bottom&t={{t}}"><i class="fas fa-arrow-alt-circle-down mr-2"></i>Bottom</a>{% endif %}
{% if sort != "new" %}<a class="dropdown-item" href="?sort=new&t={{t}}"><i class="fas fa-sparkles mr-2"></i>New</a>{% endif %}
{% if sort != "old" %}<a class="dropdown-item" href="?sort=old&t={{t}}"><i class="fas fa-book mr-2"></i>Old</a>{% endif %}
{% if sort != "controversial" %}<a class="dropdown-item" href="?sort=controversial&t={{t}}"><i class="fas fa-bullhorn mr-2"></i>Controversial</a>{% endif %}
</div>
</div>
</div>
</div>
{% endif %}

View File

@ -1,8 +1,6 @@
{% extends "userpage/userpage.html" %}
{% block content %}
<div class="row justify-content-center">
{% block userpage_content %}
<div class="row justify-content-center userpage-private">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-3">
<span class="fa-stack fa-2x text-muted mb-4">
@ -14,29 +12,4 @@
</div>
</div>
</div>
{% if u.song %}
{% if v and v.id == u.id %}
<div id="v_username" class="d-none">{{v.username}}</div>
{% else %}
<div id="u_username" class="d-none">{{u.username}}</div>
{% endif %}
{% endif %}
{% endblock %}
{% block pagenav %}
{% if u.song %}
<div id="uid" class="d-none">{{u.id}}</div>
{% endif %}
{% if v %}
<div id='tax' class="d-none">{% if v.patron or u.patron %}0{% else %}0.03{% endif %}</div>
<div id="username" class="d-none">{{u.username}}</div>
{%- include "modals/emoji.html" -%}
<script defer src="{{'js/userpage_v.js' | asset}}"></script>
{% endif %}
<script defer src="{{'js/userpage.js' | asset}}"></script>
{% endblock %}

View File

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

View File

@ -14,606 +14,10 @@
<link rel="stylesheet" href="/{{u.id}}/profilecss">
{% endif %}
{% endblock %}
{% import 'userpage/admintools.html' as userpage_admintools with context %}
{% set hats_total = u.hats_owned_proportion_display[1] if u else 0 %}
{% set hats_owned_percent = u.hats_owned_proportion_display[0] if u else '' %}
{% block desktopUserBanner %}
<div class="row d-mob-none">
<div class="col px-0">
<div class="jumbotron jumbotron-fluid jumbotron-guild d-mob-none" {% if FEATURES['USERS_PROFILE_BANNER'] %}style="background-image: url({{u.banner_url}})"{% endif %}>
<div class="jumbotron-overlay"></div>
<div class="w-100 my-3">
<div class="container-fluid nobackground">
<div class="d-md-flex text-center text-md-left">
<div id="profile--pfp" {% if u.hat_active %}class="profile--pfp--hat hat"{% endif %}>
<a rel="nofollow noopener" href="{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}" class="profile-pic-100-wrapper">
<img onclick="expandDesktopImage('{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}')" loading="lazy" src="{{u.profile_url}}" class="profile-pic profile-pic-100 mb-5">
{% if u.hat_active -%}
<img onclick="expandDesktopImage('{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}')" class="profile-pic-100-hat hat" loading="lazy" src="{{u.hat_active}}?h=7" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{u.hat_tooltip(v)}}">
{%- endif %}
</a>
</div>
<div id="profilestuff" class="ml-3 w-100">
{{userpage_admintools.userBanBlock('desktop')}}
<div class="d-flex align-items-center mt-1 mb-2">
<h3 class="font-weight-bolder my-0 mr-2" id="profile--name" style="color: #{{u.name_color}}"><span {% if u.patron %}class="patron" style="background-color:#{{u.name_color}}"{% endif %}>{{u.user_name}}</span></h3>
{% if u.username != u.original_username %}
<span id="profile--origname">
<i class="fas fa-user-tag text-info align-middle ml-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Original Username: @{{u.original_username}}"></i>
</span>
{% endif %}
{% if FEATURES['PATRON_ICONS'] and u.patron %}
<img loading="lazy" class="ml-3" src="/i/{{SITE_NAME}}/patron_badges/2{{u.patron}}.webp?v=1" height="20" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{u.patron_tooltip}}" alt="{{u.patron_tooltip}}">
{% endif %}
{% if FEATURES['HOUSES'] and u.house %}
<img loading="lazy" class="ml-3" id="profile--house" src="/i/{{SITE_NAME}}/houses/{{u.house}}.webp?v=2000" height="20" data-bs-toggle="tooltip" data-bs-placement="bottom" title="House {{u.house}}" alt="House {{u.house}}">
{% endif %}
{% if u.verified %}
<span id="profile--verified"><i class="fas fa-badge-check align-middle ml-2 {% if u.verified=='Glowiefied' %}glow{% endif %}" style="color:{% if u.verifiedcolor %}#{{u.verifiedcolor}}{% else %}#1DA1F2{% endif %}" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{u.verified}}"></i></span>
{% endif %}
{% if u.admin_level >= PERMS['ADMIN_MOP_VISIBLE'] %}
<span id="profile--mop">
<i class="fas fa-broom text-admin align-middle ml-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Admin"></i>
</span>
{% endif %}
{% if v and v.id != u.id and v.has_follower(u) %}
<span class="followsyou badge badge-secondary text-small align-middle ml-2" id="profile--follows-you">Follows you</span>
{% endif %}
</div>
{% if FEATURES['PRONOUNS'] %}
<p class="font-weight-bolder" id="profile--pronouns" style="color: #{{u.titlecolor}}">{{u.pronouns}}</p>
{% endif %}
{% if u.customtitle %}
<p class="font-weight-bolder" id="profile--flair" style="color: #{{u.titlecolor}}">{{u.customtitle | safe}}</p>
{% endif %}
{% if v and (v.id == u.id or v.admin_level >= PERMS['USER_VOTERS_VISIBLE']) -%}
<div class="font-weight-bolder mb-2" id="profile--simphate">
<a class="mr-1" href="/@{{u.username}}/views">Profile Views</a> | <a class="mx-1" href="/@{{u.username}}/upvoters">Simps</a> | <a class="mx-1" href="/@{{u.username}}/downvoters">Haters</a> | <a class="mx-1" href="/@{{u.username}}/upvoting">Simps For</a> | <a class="mx-1" href="/@{{u.username}}/downvoting">Hates</a> | <a class="ml-1" href="/@{{u.username}}/voted/posts">Voted</a>
</div>
{%- endif %}
<div class="font-weight-bolder">
<span id="profile-coins-amount">{{u.coins}}</span>
<img alt="coins" class="ml-1 mb-1 mr-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Coins" height="20" src="{{'coins.webp' | asset_siteimg}}">
{% if FEATURES['MARSEYBUX'] %}
<span id="profile-bux-amount">{{u.marseybux}}</span>
<img alt="marseybux" class="ml-1 mb-1 mr-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Marseybux" height="20" width="46" src="/i/marseybux.webp?v=2000">
{% endif %}
{% if PERMS['USER_FOLLOWS_VISIBLE'] == 0 or (v and v.admin_level >= PERMS['USER_FOLLOWS_VISIBLE']) -%}
<a class="mr-2" href="/@{{u.username}}/followers" id="profile--followers">{{u.stored_subscriber_count}} follower{{'s' if u.stored_subscriber_count != 1 else ''}}</a>
<a class="mr-2" href="/@{{u.username}}/following" id="profile--following">follows {{u.follow_count}} user{{'s' if u.follow_count != 1 else ''}}</a>
{%- endif %}
<span id="profile--joined">joined <span id="profile--joined--time" data-bs-toggle="tooltip" data-bs-placement="bottom" onmouseover="timestamp('profile--joined--time','{{u.created_utc}}')">{{u.created_date}}</span></span>
{% if v and v.admin_level >= PERMS['VIEW_LAST_ACTIVE'] -%}
<span id="profile--lastactive" class="ml-2">last active <span id="profile--lastactive--time" data-bs-toggle="tooltip" data-bs-placement="bottom" onmouseover="timestamp('profile--lastactive--time','{{u.last_active}}')">{{u.last_active_date}}</span></span>
{%- endif %}
</div>
{% if u.basedcount %}<p class="text-muted" id="profile--based">Based Count: {{u.basedcount}}</p>{% endif %}
{% if FEATURES['USERS_PROFILE_BODYTEXT'] -%}
{% if u.bio_html %}
<div class="text-muted font-weight-bolder mt-1" id="profile--bio">{{u.bio_html | safe}}</div>
{% else %}
<p class="text-muted" id="profile--bio">No bio...</p>
{% endif %}
{% if u.friends_html %}
<p class="text-muted font-weight-bold">Friends:</p>
<div id="profile--friends">{{u.friends_html | safe}}</div>
{% endif %}
{% if u.enemies_html %}
<p class="text-muted font-weight-bold">Enemies:</p>
<div id="profile--enemies">{{u.enemies_html | safe}}</div>
{% endif %}
{%- endif %}
{% if u.received_awards and FEATURES['AWARDS'] %}
<div class="text-white rounded p-2 mb-3" id="profile--awards" style="background-color: rgba(50, 50, 50, 0.6); width: 30%;">
<p class="text-uppercase my-0" style="font-weight: bold; font-size: 12px;">Awards received</p>
{% for a in u.received_awards %}
<span class="d-inline-block mx-1 profile--awards--award">
<i class="{{a['icon']}} {{a['color']}} fa-fw" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{a['title']}} Awards received"></i>
x{{a['count']}}
</span>
{% endfor %}
</div>
{% endif %}
{% if u.moderated_subs %}
<div class="text-white rounded p-2 mb-3" id="profile--holes" style="background-color: rgba(50, 50, 50, 0.6); width: 30%;">
<p class="text-uppercase my-0 pb-1" style="font-weight: bold; font-size: 12px;">Moderator of</p>
{% for a in u.moderated_subs %}
<span class="d-inline-block mx-1">
<a href="/h/{{a}}">/h/{{a}}</a>
</span>
{% endfor %}
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-center">
<div>
{% if v and v.id != u.id %}
<div id="profile--actionbtns">
<div class="actionbtns mb-3">
<button type="button" id="button-unsub" class="btn btn-secondary {% if not is_following %}d-none{% endif %}" onclick="postToastSwitch(this,'/unfollow/{{u.username}}','button-unsub','button-sub','d-none')">Unfollow</button>
<button type="button" id="button-sub" class="btn btn-primary {% if is_following or u.is_blocked %}d-none{% endif %}" onclick="postToastSwitch(this,'/follow/{{u.username}}','button-unsub','button-sub','d-none')">Follow</button>
<button type="button" class="btn btn-primary" onclick="toggleElement('message', 'input-message')">Message</button>
{% if FEATURES['USERS_SUICIDE'] -%}
<button type="button" class="btn btn-primary" onclick="postToastSwitch(this,'/@{{u.username}}/suicide')">Get Them Help</button>
{%- endif %}
<button type="button" class="btn btn-primary" onclick="toggleElement('coin-transfer', 'coin-transfer-amount')">Gift Coins</button>
{% if FEATURES['MARSEYBUX'] -%}
<button type="button" class="btn btn-primary" onclick="toggleElement('bux-transfer', 'bux-transfer-amount')">Gift Marseybux</button>
{%- endif %}
<button type="button" class="btn btn-primary" onclick="postToastReload(this,'/settings/block?username={{u.username}}')">Block</button>
</div>
</div>
<form class="d-none toggleable" id="message" action="/@{{u.username}}/message" onsubmit="sendMessage(event)">
<input type="hidden" name="formkey" value="{{v|formkey}}">
<textarea autocomplete="off" id="input-message" form="message" name="message" rows="3" minlength="1" maxlength="10000" class="form-control b2 mt-1" data-preview="message-preview" oninput="markdown(this)" required></textarea>
<pre class="btn btn-secondary format d-inline-block m-0 fas fa-smile-beam" onclick="loadEmojis('input-message')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"></pre>
&nbsp;
<input type="submit" onclick="disable(this);remove_dialog()" value="Submit" class="btn btn-primary">
</form>
<div id="message-preview" class="preview mt-2"></div>
<div class="d-none mt-3 toggleable" id="coin-transfer">
<input autocomplete="off" id="coin-transfer-amount" class="form-control" name="amount" type="number" oninput="updateTax()">
<input autocomplete="off" id="coin-transfer-reason" maxlength=200 type="text" class="form-control" name="reason" placeholder="Gift message! (optional)">
<div>{{u.username}} will receive <span id="coins-transfer-taxed">0</span> coins</div>
<button type="button" class="btn btn-primary mt-2 mb-3" onclick="transferCoins(this)">Gift</button>
</div>
<div class="d-none mt-3 toggleable" id="bux-transfer">
<input autocomplete="off" id="bux-transfer-amount" class="form-control" name="amount" type="number" oninput="updateBux()">
<input autocomplete="off" id="bux-transfer-reason" type="text" class="form-control" name="reason" placeholder="Gift message! (optional)">
<div>{{u.username}} will receive <span id="bux-transfer-taxed">0</span> marseybux</div>
<button type="button" class="btn btn-primary mt-2 mb-3" onclick="transferBux(this)">Gift</button>
</div>
{{userpage_admintools.userAdminTools('desktop')}}
{% endif %}
<div class="actionbtns">
{% if v and v.id == u.id %}
<a href="/settings/personal" class="btn btn-secondary">Edit Profile</a>
<form id="upload-profile-background" action="/settings/profile_background" method="post" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v|formkey}}">
<label class="format btn btn-primary" for="upload-profile-background-file" style="margin-bottom:0!important">
<i class="fas fa-image mr-1"></i>
{% if v.profile_background -%}
{{v.profile_background}}
{% else -%}
Upload Profile Background
{% endif %}
</label>
<input autocomplete="off" id="upload-profile-background-file" accept="image/*", type="file" name="file" onchange="this.form.submit()" hidden>
</form>
{% if v.profile_background -%}
<div class="d-flex mb-3">
<button type="button" class="btn btn-danger" onclick="postToastReload(this,'/settings/profile_background', 'DELETE')">
<i class="fas fa-image-slash mr-1"></i>
Remove current background
</button>
</div>
{%- endif %}
{% endif %}
{% if FEATURES['USERS_PROFILE_SONG'] and u.song and v and (v.id == u.id or v.mute and not u.unmutable) %}
<button type="button" class="btn btn-secondary" onclick="toggle()" {% if v.id == u.id %}style="margin-bottom:0!important;padding:0.3rem 0.75rem!important"{% endif %}>Toggle Anthem</button>
{% endif %}
</div>
<div class="mt-3" id="profile--info">
<p id="profile--info--id">User ID: {{u.id}}</p>
<p id="profile--info--spent">Coins spent: {{u.coins_spent}}</p>
<p id="profile--info--truescore">True score: {{u.truescore}}</p>
<p id="profile--info--winnings">Winnings: {{u.winnings}}</p>
<p id="profile--info--hats-owned" {% if u.num_of_owned_hats >= hats_total %}class="profile-owned-all-hats"{% endif %}>{{u.num_of_owned_hats}} / {{hats_total}} hats owned ({{hats_owned_percent}})</p>
{% if u.is_private %}
<p id="profile--info--private">User has private mode enabled</p>
{% endif %}
{% if v and (v.admin_level >= PERMS['VIEW_ALTS'] or v.alt) %}
{% if v.admin_level >= PERMS['USER_LINK'] %}
<span id="profile--alts"><a href="/@{{u.username}}/alts">Alts</a>:</span>
{% else %}
<span id="profile--alts">Alts:</span>
{% endif %}
<ul id="profile--alts-list">
{% for account in u.alts_unique if not account._alt_deleted and (v.can_see_shadowbanned or not account.shadowbanned) %}
<li><a href="{{account.url}}">@{{account.username}}</a>{% if account._is_manual %} [m]{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
</div>
{% if FEATURES['BADGES'] -%}
<div id="profile--badges">
{% for b in u.badges %}
{% if b.url %}
<a class="contain" rel="nofollow noopener" href="{{b.url}}">
<img alt="{{b.name}}" width=55 height=60 loading="lazy" src="{{b.path}}?b=5" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{b.text}}" {% if b.until %}data-until="{{b.until}}" onmouseover="badge_timestamp(this)"{% endif %}>
</a>
{% else %}
<img class="contain" alt="{{b.name}}" width=55 height=60 loading="lazy" src="{{b.path}}?b=5" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{b.text}}" {% if b.until %}data-until="{{b.until}}" onmouseover="badge_timestamp(this)"{% endif %}>
{% endif %}
{% endfor %}
</div>
{%- endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block mobileUserBanner %}
<div class="container-fluid pb-0 text-center bg-white d-md-none" style="margin-top:-6px;border-radius:0!important;">
<div class="row">
<div class="col px-0">
<a rel="nofollow noopener" href="{{u.banner_url}}">
<img alt="@{{u.username}}'s banner" onclick="expandDesktopImage()" loading="lazy" src="{{u.banner_url}}" width=100% style="object-fit:cover;max-height:30vh!important">
</a>
</div>
</div>
<div class="row border-bottom">
<div class="col">
<div style="margin-top: -34px;" id="profile-mobile--pfp">
<a rel="nofollow noopener" href="{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}" class="profile-pic-65-wrapper">
<img onclick="expandDesktopImage('{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}')" loading="lazy" src="{{u.profile_url}}" class="profile-pic-65 bg-white mb-2">
{% if u.hat_active -%}
<img onclick="expandDesktopImage('{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}')" class="profile-pic-65-hat hat" loading="lazy" src="{{u.hat_active}}?h=7">
{%- endif %}
</a>
</div>
<div class="mt-n3 py-3">
{{userpage_admintools.userBanBlock('mobile')}}
<h5 class=" d-inline-block" id="profile-mobile--name" style="color: #{{u.name_color}}"><span {% if u.patron %}class="patron" style="background-color:#{{u.name_color}}"{% endif %}>{{u.user_name}}</span></h5>
{% if u.username != u.original_username %}
<span id="profile-mobile--origname">
<i class="fas fa-user-tag text-info align-middle ml-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Original Username: @{{u.original_username}}"></i>
</span>
{% endif %}
{% if FEATURES['PATRON_ICONS'] and u.patron %}
<img loading="lazy" class="ml-2" src="/i/{{SITE_NAME}}/patron_badges/2{{u.patron}}.webp?v=1" height="20" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{u.patron_tooltip}}" alt="{{u.patron_tooltip}}">
{% endif %}
{% if FEATURES['HOUSES'] and u.house %}
<img loading="lazy" class="ml-2" id="profile-mobile--house" src="/i/{{SITE_NAME}}/houses/{{u.house}}.webp?v=2000" height="20" data-bs-toggle="tooltip" data-bs-placement="bottom" title="House {{u.house}}" alt="House {{u.house}}">
{% endif %}
{% if u.verified %}
<span id="profile-mobile--verified"><i class="fas fa-badge-check align-middle ml-2 {% if u.verified=='Glowiefied' %}glow{% endif %}" style="color:{% if u.verifiedcolor %}#{{u.verifiedcolor}}{% else %}#1DA1F2{% endif %}" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{u.verified}}"></i></span>&nbsp;
{% endif %}
{% if u.admin_level >= PERMS['ADMIN_MOP_VISIBLE'] %}
<span id="profile-mobile--mop">
<i class="fas fa-broom text-admin align-middle ml-1" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Admin"></i>
</span>
{% endif %}
{% if v and v.id != u.id and v.has_follower(u) %}
<span class="followsyou badge badge-secondary text-small align-middle mx-1" id="profile-mobile--follows-you">Follows you</span>
{% endif %}
{% if FEATURES['PRONOUNS'] %}
<p style="color: #{{u.titlecolor}}" id="profile-mobile--pronouns">{{u.pronouns}}</p>
{% endif %}
{% if u.customtitle %}
<p style="color: #{{u.titlecolor}}" id="profile-mobile--flair">{{u.customtitle | safe}}</p>
{% endif %}
{% if v and (v.id == u.id or v.admin_level >= PERMS['USER_VOTERS_VISIBLE']) -%}
<div class="font-weight-bolder mb-2" id="profile-mobile--simphate">
<a class="mr-1" href="/@{{u.username}}/views">Profile Views</a> | <a class="mx-1" href="/@{{u.username}}/upvoters">Simps</a> | <a class="mx-1" href="/@{{u.username}}/downvoters">Haters</a> | <a class="mx-1" href="/@{{u.username}}/upvoting">Simps For</a> | <a class="mx-1" href="/@{{u.username}}/downvoting">Hates</a> | <a class="ml-1" href="/@{{u.username}}/voted/posts">Voted</a>
</div>
{%- endif %}
<div class="font-weight-normal">
<span id="profile-coins-amount-mobile" class="font-weight-bold">{{u.coins}}</span>
<img alt="coins" class="ml-1 mb-1 mr-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Coins" height="15" src="{{'coins.webp' | asset_siteimg}}">
{% if FEATURES['MARSEYBUX'] %}
<span id="profile-bux-amount-mobile" class="font-weight-bold">{{u.marseybux}}</span>
<img alt="marseybux" class="ml-1 mb-1 mr-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Marseybux" height="15" width="35" src="/i/marseybux.webp?v=2000">
{% endif %}
{% if PERMS['USER_FOLLOWS_VISIBLE'] == 0 or (v and v.admin_level >= PERMS['USER_FOLLOWS_VISIBLE']) -%}
<a href="/@{{u.username}}/followers" class="font-weight-bold mr-2" id="profile-mobile--followers">{{u.stored_subscriber_count}} follower{{'s' if u.stored_subscriber_count != 1 else ''}}</a>
<a href="/@{{u.username}}/following" class="font-weight-bold mr-2" id="profile-mobile--following" style="display:block">follows {{u.follow_count}} user{{'s' if u.follow_count != 1 else ''}}</a>
{%- endif %}
{% if u.basedcount %}
<br><span id="profile-mobile--based">Based count: {{u.basedcount}}</span>
{% endif %}
<br><span id="profile-mobile--joined">joined <span id="profile-mobile--joined--time" data-bs-toggle="tooltip" data-bs-placement="bottom" onmouseover="timestamp('profile-mobile--joined--time','{{u.created_utc}}')" class="font-weight-bold">{{u.created_date}}</span></span>
{% if v and v.admin_level >= PERMS['VIEW_LAST_ACTIVE'] -%}
<br><span id="profile-mobile--lastactive">last active <span id="profile-mobile--lastactive--time" data-bs-toggle="tooltip" data-bs-placement="bottom" onmouseover="timestamp('profile-mobile--lastactive--time','{{u.last_active}}')" class="font-weight-bold">{{u.last_active_date}}</span></span>
{%- endif %}
</div>
{% if FEATURES['USERS_PROFILE_BODYTEXT'] -%}
{% if u.bio_html %}
<div class="text-muted text-break" id="profile-mobile--bio">{{u.bio_html | safe}}</div>
{% endif %}
{% if u.friends_html %}
<p class="text-muted font-weight-bold mt-3">Friends:</p>
<div id="profile-mobile--friends">{{u.friends_html | safe}}</div>
{% endif %}
{% if u.enemies_html %}
<p class="text-muted font-weight-bold mt-3">Enemies:</p>
<div id="profile-mobile--enemies">{{u.enemies_html | safe}}</div>
{% endif %}
{%- endif %}
{% if u.received_awards and FEATURES['AWARDS'] %}
<div class="text-white rounded p-2 my-3 text-center" id="profile-mobile--awards" style="background-color: rgba(50, 50, 50, 0.6);">
<p class="text-uppercase my-0" style="font-weight: bold; font-size: 12px;">Awards received</p>
{% for a in u.received_awards %}
<span class="d-inline-block mx-1 profile-mobile--awards--award">
<i class="{{a['icon']}} {{a['color']}} fa-fw" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{a['title']}} Awards received"></i>
x{{a['count']}}
</span>
{% endfor %}
</div>
{% endif %}
{% if u.moderated_subs %}
<div class="text-white rounded p-2 mb-3" id="profile-mobile--holes" style="background-color: rgba(50, 50, 50, 0.6);">
<p class="text-uppercase my-0 pb-1" style="font-weight: bold; font-size: 12px;">Moderator of</p>
{% for a in u.moderated_subs %}
<span class="d-inline-block mx-1">
<a href="/h/{{a}}">/h/{{a}}</a>
</span>
{% endfor %}
</div>
{% endif %}
<div class="mb-3" id="profile-mobile--badges">
{% for b in u.badges %}
{% if b.url %}
<a rel="nofollow noopener" href="{{b.url}}">
<img class="contain" alt="{{b.name}}" width=29.33 height=32 loading="lazy" src="{{b.path}}?b=5" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{b.text}}" {% if b.until %}data-until="{{b.until}}" onmouseover="badge_timestamp(this)"{% endif %}>
</a>
{% else %}
<img class="contain" alt="{{b.name}}" width=29.33 height=32 loading="lazy" src="{{b.path}}?b=5" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{b.text}}" {% if b.until %}data-until="{{b.until}}" onmouseover="badge_timestamp(this)"{% endif %}>
{% endif %}
{% endfor %}
</div>
<div class="actionbtns">
{% if v and v.id == u.id %}
<a href="/settings/personal" class="btn btn-secondary ">Edit Profile</a>
{% endif %}
{% if FEATURES['USERS_PROFILE_SONG'] and u.song and v and (v.id == u.id or v.mute and not u.unmutable) %}
<button type="button" class="btn btn-secondary" onclick="toggle()" {% if v.id == u.id %}style="margin-bottom:0!important;padding:0.3rem 0.75rem!important"{% endif %}>Toggle Anthem</button>
{% endif %}
{% if v and v.id == u.id %}
<div>
<form class="mt-3" id="upload-profile-background-mobile" action="/settings/profile_background" method="post" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v|formkey}}">
<label class="format btn btn-primary" for="upload-profile-background-file-mobile" style="margin-bottom:0!important">
<i class="fas fa-image mr-1"></i>
{% if v.profile_background %}
{{v.profile_background}}
{% else %}
Upload Profile Background
{% endif %}
</label>
<input autocomplete="off" id="upload-profile-background-file-mobile" accept="image/*", type="file" name="file" onchange="this.form.submit()" hidden>
</form>
{% if v.profile_background -%}
<div class="d-block mt-3 mb-3">
<button type="button" class="btn btn-danger" onclick="postToastReload(this,'/settings/profile_background', 'DELETE')">
<i class="fas fa-image-slash mr-1"></i>
Remove current background
</button>
</div>
{%- endif %}
</div>
{% endif %}
{% if v and v.id != u.id %}
<button type="button" id="button-unsub2" class="btn btn-secondary {% if not is_following %}d-none{% endif %}" onclick="postToastSwitch(this,'/unfollow/{{u.username}}','button-unsub2','button-sub2','d-none')">Unfollow</button>
<button type="button" id="button-sub2" class="btn btn-primary {% if is_following or u.is_blocked %}d-none{% endif %}" onclick="postToastSwitch(this,'/follow/{{u.username}}','button-unsub2','button-sub2','d-none')">Follow</button>
<button type="button" class="btn btn-primary" onclick="toggleElement('message-mobile', 'input-message-mobile')">Message</button>
{% if FEATURES['USERS_SUICIDE'] -%}
<button type="button" class="btn btn-primary" onclick="postToastSwitch(this,'/@{{u.username}}/suicide')">Get Them Help</button>
{%- endif %}
<button type="button" class="btn btn-primary" onclick="toggleElement('coin-transfer-mobile', 'coin-transfer-amount-mobile')">Gift Coins</button>
{% if FEATURES['MARSEYBUX'] -%}
<button type="button" class="btn btn-primary" onclick="toggleElement('bux-transfer-mobile', 'bux-transfer-amount-mobile')">Gift Marseybux</button>
{%- endif %}
<button type="button" class="btn btn-primary" onclick="postToastReload(this,'/settings/block?username={{u.username}}')">Block</button>
{% endif %}
</div>
{% if v and v.id != u.id %}
<form class="d-none toggleable" id='message-mobile' action="/@{{u.username}}/message" onsubmit="sendMessage(event)">
<input class="mt-1" type="hidden" name="formkey" value="{{v|formkey}}">
<textarea autocomplete="off" id="input-message-mobile" form="message-mobile" name="message" rows="3" minlength="1" maxlength="10000" class="form-control" data-preview="message-preview-mobile" oninput="markdown(this)" required></textarea>
<pre class="mt-1 btn btn-secondary format d-inline-block m-0 fas fa-smile-beam" onclick="loadEmojis('input-message-mobile')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"></pre>
&nbsp;
<input type="submit" onclick="disable(this);remove_dialog()" value="Submit" class="btn btn-primary">
</form>
<div id="message-preview-mobile" class="preview my-3"></div>
<div class="d-none mt-3 toggleable" id="coin-transfer-mobile">
<input autocomplete="off" id="coin-transfer-amount-mobile" class="form-control" name="amount" type="number" oninput="updateTax(true)">
<input autocomplete="off" id="coin-transfer-reason-mobile" maxlength=200 type="text" class="form-control" name="reason" placeholder="Gift message! (optional)">
<div>{{u.username}} will receive <span id="coins-transfer-taxed-mobile">0</span> coins</div>
<button type="button" class="btn btn-primary mt-2 mb-3" onclick="transferCoins(this, true)">Gift</button>
</div>
<div class="d-none mt-3 toggleable" id="bux-transfer-mobile">
<input autocomplete="off" id="bux-transfer-amount-mobile" class="form-control" name="amount" type="number" oninput="updateBux(true)">
<input autocomplete="off" id="bux-transfer-reason-mobile" type="text" class="form-control" name="reason" placeholder="Gift message! (optional)">
<div>{{u.username}} will receive <span id="bux-transfer-taxed-mobile">0</span> marseybux</div>
<button type="button" class="btn btn-primary mt-2 mb-3" onclick="transferBux(this, true)">Gift</button>
</div>
{{userpage_admintools.userAdminTools('mobile')}}
{% endif %}
<div id="profile-mobile--info">
<p id="profile-mobile--info--id">User ID: {{u.id}}</p>
<p id="profile-mobile--info--spent">Coins spent: {{u.coins_spent}}</p>
<p id="profile-mobile--info--truescore">True score: {{u.truescore}}</p>
<p id="profile-mobile--info--winnings">Winnings: {{u.winnings}}</p>
<p id="profile-mobile--info--hats-owned" {% if u.num_of_owned_hats >= hats_total %}class="profile-owned-all-hats"{% endif %}>{{u.num_of_owned_hats}} / {{hats_total}} hats owned ({{hats_owned_percent}})</p>
{% if u.is_private %}
<p id="profile-mobile--info--private">User has private mode enabled</p>
{% endif %}
{% if v and (v.admin_level >= PERMS['VIEW_ALTS'] or v.alt) %}
{% if v.admin_level >= PERMS['USER_LINK'] %}
<span id="profile-mobile--alts"><a href="/@{{u.username}}/alts">Alts</a>:</span>
{% else %}
<span id="profile-mobile--alts">Alts:</span>
{% endif %}
<ul id="profile-mobile--alts-list">
{% for account in u.alts_unique if not account._alt_deleted and (v.can_see_shadowbanned or not account.shadowbanned) %}
<li><a href="{{account.url}}">@{{account.username}}</a>{% if account._is_manual %} [m]{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<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" id="profile-content--nav">
<li class="nav-item mr-1">
<a class="nav-link" href="/@{{u.username}}">Wall</a>
</li>
<li class="nav-item">
<a class="nav-link {% if not 'saved' in request.path %}active{% endif %}" href="/@{{u.username}}/posts">Posts <span class="count">({{u.post_count}})</span></a>
</li>
<li class="nav-item">
<a class="nav-link" 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 {% if '/saved/' in request.path %}active{% endif %}" href="/@{{u.username}}/saved/posts">Saved Posts <span class="count">({{u.saved_count}})</span></a>
</li>
<li class="nav-item">
<a class="nav-link" 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 {% if '/subscribed/' in request.path %}active{% endif %}" href="/@{{u.username}}/subscribed/posts">Subscribed <span class="count">({{u.subscribed_count}})</span></a>
</li>
{% endif %}
</ul>
</div>
</div>
</div>
{% if "/saved/" not in request.path and '/subscribed/' not in request.path %}
<div class="d-flex justify-content-between align-items-center pt-4">
<div class="d-flex align-items-center">
<div class="dropdown dropdown-actions">
<button type="button" class="btn btn-secondary dropdown-toggle" 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="dropdown dropdown-actions ml-3">
<button type="button" class="btn btn-secondary dropdown-toggle" id="dropdownMenuButton2" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% if sort=="top" %}<i class="fas fa-arrow-alt-circle-up mr-1"></i>{% endif %}
{% if sort=="bottom" %}<i class="fas fa-arrow-alt-circle-down mr-1"></i>{% endif %}
{% if sort=="new" %}<i class="fas fa-sparkles mr-1"></i>{% endif %}
{% if sort=="old" %}<i class="fas fa-book mr-1"></i>{% endif %}
{% if sort=="controversial" %}<i class="fas fa-bullhorn mr-1"></i>{% endif %}
{% if sort=="comments" %}<i class="fas fa-comments mr-1"></i>{% endif %}
{{sort | capitalize}}
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton2" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 31px, 0px);">
{% if sort != "top" %}<a class="dropdown-item" href="?sort=top&t={{t}}"><i class="fas fa-arrow-alt-circle-up mr-2"></i>Top</a>{% endif %}
{% if sort != "bottom" %}<a class="dropdown-item" href="?sort=bottom&t={{t}}"><i class="fas fa-arrow-alt-circle-down mr-2"></i>Bottom</a>{% endif %}
{% if sort != "new" %}<a class="dropdown-item" href="?sort=new&t={{t}}"><i class="fas fa-sparkles mr-2"></i>New</a>{% endif %}
{% if sort != "old" %}<a class="dropdown-item" href="?sort=old&t={{t}}"><i class="fas fa-book mr-2"></i>Old</a>{% endif %}
{% if sort != "controversial" %}<a class="dropdown-item" href="?sort=controversial&t={{t}}"><i class="fas fa-bullhorn mr-2"></i>Controversial</a>{% endif %}
{% if sort != "comments" %}<a class="dropdown-item" href="?sort=comments&t={{t}}"><i class="fas fa-comments mr-2"></i>Comments</a>{% endif %}
</div>
</div>
</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>
{%- include 'userpage/banner.html' -%}
{%- include 'userpage/header.html' -%}
{% block userpage_content required %}{% endblock %}
{% if FEATURES['USERS_PROFILE_SONG'] and u.song %}
{% if v and v.id == u.id %}
<div id="v_username" class="d-none">{{v.username}}</div>
@ -627,9 +31,7 @@
<script defer src="{{'js/userpage_v.js' | asset}}"></script>
<div id="username" class="d-none">{{u.username}}</div>
{% endif %}
<script defer src="{{'js/userpage.js' | asset}}"></script>
{% endblock %}
{% block pagenav %}
@ -662,7 +64,5 @@
{% if v and v.id != u.id and not request.path.endswith('/comments') and not request.path.endswith(u.username) %}
{% include "modals/emoji.html" %}
{% endif %}
{% endblock %}
{% block GIFpicker %}{% endblock %}

View File

@ -17,11 +17,8 @@
</a>
</li>
</ul>
<div class="row no-gutters">
<div class="col">
{% block listing %}
<div class="posts">
{% include "submission_listing.html" %}

View File

@ -1,118 +1,24 @@
{% extends "userpage/userpage.html" %}
{% 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" id="profile-content--nav">
<li class="nav-item mr-1">
<a class="nav-link active" href="/@{{u.username}}">Wall</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/@{{u.username}}/posts">Posts <span class="count">({{u.post_count}})</span></a>
</li>
<li class="nav-item">
<a class="nav-link" 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" 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 %}" 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" href="/@{{u.username}}/subscribed/posts">Subscribed <span class="count">({{u.subscribed_count}})</span></a>
</li>
{% endif %}
</ul>
</div>
{% block userpage_content %}
<div class="userpage-wall">
{{macros.comment_reply_box(u.fullname, 'replying-to-' + u.fullname, '', 'mb-3', '', true, '/comments/')}}
<div class="row no-gutters {% if listing %}mt-md-3{% elif not listing %}my-md-3{% endif %} px-3 p-md-0 userpage-wall" style="margin-top: 10px;">
<div class="col">
<div class="comment-section" id="replies-of-{{u.fullname}}">
{% with comments=listing %}
{% include "comments.html" %}
{% endwith %}
</div>
{% if not listing %}
<div class="text-center px-3 my-3">
<span class="fa-stack fa-2x text-muted mb-4">
<i class="fas fa-square text-gray-500 opacity-25 fa-stack-2x"></i>
<i class="fas text-gray-500 fa-scroll-old fa-stack-1x text-lg"></i>
</span>
<h5>There's no comments on {% if u.id == v.id %}your{% else %}@{{u.username}}'s{% endif %} wall yet!</h5>
</div>
{% endif %}
</div>
</div>
</div>
{% if v %}
<div id="comment-form-space-{{u.fullname}}" class="comment-write mb-3 mt-4 px-3 p-md-0">
<form id="reply-to-{{u.fullname}}" action="/comment" method="post">
<input type="hidden" name="formkey" value="{{v|formkey}}">
<input type="hidden" name="parent_fullname" value="p_{{u.id}}">
<input autocomplete="off" id="reply-form-submission-{{u.fullname}}" type="hidden" name="submission" value="{{u.id}}">
<textarea required autocomplete="off" {% if v.longpost %}minlength="280"{% elif v.bird %}maxlength="140"{% endif %} minlength="1" maxlength="10000" data-preview="form-preview-{{u.fullname}}" oninput="markdown(this);charLimit('reply-form-body-{{u.fullname}}','charcount-{{u.fullname}}')" id="reply-form-body-{{u.fullname}}" data-fullname="{{u.fullname}}" class="comment-box form-control rounded" name="body" form="reply-to-{{u.fullname}}" aria-label="With textarea" placeholder="Add your comment..." rows="3"></textarea>
<div class="text-small font-weight-bold mt-1" id="charcount-{{u.fullname}}" style="right: 1rem; bottom: 0.5rem; z-index: 3;"></div>
<div class="comment-format">
<label class="btn btn-secondary format d-inline-block m-0" for="gif-reply-btn-{{u.fullname}}">
<span id="gif-reply-btn-{{u.fullname}}" class="font-weight-bolder text-uppercase" onclick="commentForm('reply-form-body-{{u.fullname}}');getGif()" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#gifModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add GIF">GIF</span>
</label>
&nbsp;
<div onclick="loadEmojis('reply-form-body-{{u.fullname}}')" class="btn btn-secondary format d-inline-block m-0" id="emoji-reply-btn-{{u.fullname}}" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"><i class="fas fa-smile-beam"></i></div>
&nbsp;
<label class="format btn btn-secondary m-0 ml-1 {% if v %}d-inline-block{% else %}d-none{% endif %}" for="file-upload-reply-{{u.fullname}}">
<div id="filename-show-reply-{{u.fullname}}"><i class="fas fa-file"></i></div>
<input autocomplete="off" id="file-upload-reply-{{u.fullname}}" accept="image/*, video/*, audio/*" type="file" multiple="multiple" name="file" {% if g.is_tor %}disabled{% endif %} onchange="changename('filename-show-reply-{{u.fullname}}','file-upload-reply-{{u.fullname}}')" hidden>
</label>
</div>
<button type="button" id="save-reply-to-{{u.fullname}}" form="reply-to-{{u.fullname}}" class="btn btn-primary text-whitebtn ml-auto fl-r" onclick="post_comment('{{u.fullname}}', '{{u.id}}');remove_dialog()">Comment</button>
</form>
<div id="form-preview-{{u.fullname}}" class="preview mb-3 mt-5"></div>
<div class="form-text text-small p-0 m-0"><a href="/formatting" {% if v and v.newtab %}data-target="t"target="_blank"{% endif %}>Formatting help</a></div>
</div>
{% else %}
<div class="comment-write mb-3 mt-4">
<textarea autocomplete="off" maxlength="10000" class="comment-box form-control rounded" name="body" aria-label="With textarea" placeholder="Add your comment..." rows="3" onclick="location.href='/login?redirect={{request.full_path | urlencode}}';"></textarea>
</div>
<div class="card border-0 mt-4">
<div class="card-body">
<h5 class="card-title">Jump in the discussion.</h5>
<p class="card-text">No email address required.</p>
<div>
<a href="/signup?redirect={{request.full_path | urlencode}}" class="btn btn-primary">Sign up</a>
<a href="/login?redirect={{request.full_path | urlencode}}" class="btn btn-link text-muted">Sign in</a>
</div>
</div>
</div>
{% endif %}
{% if comment_info %}
<div class="total mt-2"><a href="/@{{u.username}}">View entire wall</a></div>
{% endif %}
<div class="row no-gutters {% if listing %}mt-md-3{% elif not listing %}my-md-3{% endif %} px-3 p-md-0" style="margin-top: 10px;">
<div class="col">
<div class="comment-section" id="replies-of-{{u.fullname}}">
{% with comments=listing %}
{% include "comments.html" %}
{% endwith %}
</div>
{% if not listing %}
<div class="text-center px-3 my-3">
<span class="fa-stack fa-2x text-muted mb-4">
<i class="fas fa-square text-gray-500 opacity-25 fa-stack-2x"></i>
<i class="fas text-gray-500 fa-scroll-old fa-stack-1x text-lg"></i>
</span>
<h5>There's no comments on your wall yet!</h5>
</div>
{% endif %}
</div>
</div>
{% if u.song %}
{% if v and v.id == u.id %}
<div id="v_username" class="d-none">{{v.username}}</div>
{% else %}
<div id="u_username" class="d-none">{{u.username}}</div>
{% endif %}
{% endif %}
{% if v %}
<div id='tax' class="d-none">{% if v.patron or u.patron %}0{% else %}0.03{% endif %}</div>
<div id="username" class="d-none">{{u.username}}</div>
<script defer src="{{'js/userpage_v.js' | asset}}"></script>
{% endif %}
<script defer src="{{'js/userpage.js' | asset}}"></script>
{% endblock %}

View File

@ -109,3 +109,52 @@
{% endif %}
<span class="ml-2">{{p.views}} thread views</span>
{% endmacro %}
{% macro comment_reply_box(target_fullname, html_id, wrapper_css_classes="", subwrapper_css_classes="", hide="", allow_file_upload=true, action="/comments/") %}
<div class="comment-box-wrapper{% if wrapper_css_classes %} {{wrapper_css_classes}}{% endif %}" id="{{html_id}}">
{% if v %}
<div id="comment-form-space-{{target_fullname}}" class="comment-write {{subwrapper_css_classes}}">
<form id="reply-to-{{target_fullname}}" action="{{action}}" method="post">
<input type="hidden" name="formkey" value="{{v|formkey}}">
<input type="hidden" name="parent_fullname" value="{target_fullname}}">
<textarea required autocomplete="off" {% if not (p and p.id in ADMIGGER_THREADS) %}{% if v.longpost %}minlength="280"{% elif v.bird %}maxlength="140"{% endif %}{% endif %} minlength="1" maxlength="10000" data-preview="form-preview-{{target_fullname}}" oninput="markdown(this);charLimit('reply-form-body-{{target_fullname}}','charcount-{{target_fullname}}')" id="reply-form-body-{{target_fullname}}" data-fullname="{{target_fullname}}" class="comment-box form-control rounded" name="body" form="reply-to-{{target_fullname}}" aria-label="With textarea" placeholder="Add your comment..." rows="3"></textarea>
<div class="text-small font-weight-bold mt-1" id="charcount-{{target_fullname}}" style="right: 1rem; bottom: 0.5rem; z-index: 3;"></div>
<div class="comment-format">
<label class="btn btn-secondary format d-inline-block m-0" for="gif-reply-btn-{{target_fullname}}">
<span id="gif-reply-btn-{{target_fullname}}" class="font-weight-bolder text-uppercase" onclick="commentForm('reply-form-body-{{target_fullname}}');getGif()" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#gifModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add GIF">GIF</span>
</label>
&nbsp;
<div onclick="loadEmojis('reply-form-body-{{target_fullname}}')" class="btn btn-secondary format d-inline-block m-0" id="emoji-reply-btn-{{target_fullname}}" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"><i class="fas fa-smile-beam"></i></div>
&nbsp;
{% if allow_file_upload %}
<label class="format btn btn-secondary m-0 ml-1 {% if v %}d-inline-block{% else %}d-none{% endif %}" for="file-upload-reply-{{target_fullname}}">
<div id="filename-show-reply-{{target_fullname}}"><i class="fas fa-file"></i></div>
<input autocomplete="off" id="file-upload-reply-{{target_fullname}}" accept="image/*, video/*, audio/*" type="file" multiple="multiple" name="file" {% if g.is_tor %}disabled{% endif %} onchange="changename('filename-show-reply-{{target_fullname}}','file-upload-reply-{{target_fullname}}')" hidden>
</label>
{% endif %}
</div>
<button type="button" id="save-reply-to-{{target_fullname}}" form="reply-to-{{target_fullname}}" class="btn btn-primary text-whitebtn ml-auto fl-r" onclick="postComment('{{target_fullname}}', '{{hide}}');remove_dialog();">Comment</button>
</form>
<div id="form-preview-{{target_fullname}}" class="preview mb-3 mt-5"></div>
<div class="form-text text-small p-0 m-0"><a href="/formatting" {% if v and v.newtab %}data-target="t"target="_blank"{% endif %}>Formatting help</a></div>
</div>
{% else %}
<div class="comment-write mb-3">
<textarea autocomplete="off" maxlength="10000" class="comment-box form-control rounded" name="body" aria-label="With textarea" placeholder="Add your comment..." rows="3" onclick="location.href='/login?redirect={{request.full_path | urlencode}}';"></textarea>
</div>
<div class="card border-0 mt-4">
<div class="card-body">
<h5 class="card-title">Jump in the discussion.</h5>
<p class="card-text">No email address required.</p>
<div>
<a href="/signup?redirect={{request.full_path | urlencode}}" class="btn btn-primary">Sign up</a>
<a href="/login?redirect={{request.full_path | urlencode}}" class="btn btn-link text-muted">Sign in</a>
</div>
</div>
</div>
{% endif %}
</div>
{% endmacro %}