Flairs: Internal markup, improved rendering with detail levels

This commit is contained in:
Jaakko Keränen 2024-01-12 11:38:14 +02:00
parent 8737fc20de
commit fb83c111e3
No known key found for this signature in database
GPG Key ID: BACCFCFB98DB2EDC
4 changed files with 133 additions and 19 deletions

View File

@ -249,6 +249,13 @@ Bubble is open source:
return f'=> /dashboard {self.user.avatar} {self.user.name}{notifs}{mode}\n'
def feed_flair(self, post, context):
flair = User.render_flair(post.poster_flair,
context,
abbreviate=True,
user_mod=post.user in self.context_mod_ids)
return f' [{flair}]' if flair else ''
def feed_entry(self, post, context=None, omit_rotate_info=False, is_activity_feed=False):
is_issue_tracker = self.is_context_tracker
is_comment = post.parent != 0 # Flat feeds intermingle comments with posts.
@ -276,7 +283,6 @@ Bubble is open source:
else:
age = post.age(tz=self.tz)
bell = ' 🔔' if post.num_notifs else ''
flair = f' [{post.poster_flair}]' if post.poster_flair else ''
SHORT_PREVIEW_LEN = 160
@ -320,7 +326,7 @@ Bubble is open source:
post_icon = post.poster_avatar
post_label = post.poster_name
if not (is_user_post and context and context.id == post.subspace):
post_label += flair
post_label += self.feed_flair(post, context)
post_path = f'/u/{post.poster_name}'
meta_icon = '💬'
@ -352,7 +358,7 @@ Bubble is open source:
# Last line in the metadata.
meta = []
if is_comment or (sub and not is_activity_feed):
meta.append(post.poster_name + flair)
meta.append(post.poster_name + self.feed_flair(post, context))
if cmt:
meta.append(cmt)
if likes:

View File

@ -32,4 +32,7 @@ UPDATE users SET notif=notif|0x040000;
UPDATE users SET notif=notif|0x100000;
-- Migration from v7 to v8 --
ALTER TABLE users ADD COLUMN flair VARCHAR(30) DEFAULT '';
ALTER TABLE users ADD COLUMN flair VARCHAR(30) DEFAULT '';
-- Migration from v8.0 to v8.1 --
ALTER TABLE users MODIFY COLUMN flair VARCHAR(1000) DEFAULT '';

View File

@ -298,7 +298,10 @@ def make_post_page_or_configure_feed(session):
if session.c_user:
page = f'# {session.c_user.avatar} {session.c_user.name}\n'
if session.c_user.flair:
page += f"[{session.c_user.flair}]\n"
flair = User.render_flair(session.c_user.flair, session.context,
long_form=True, db=session.db)
if flair:
page += f"\n{flair}\n"
else:
page = f'# {subspace.title()}\n'
page += f'=> /{subspace.title()} {subspace.title()}\n'
@ -345,13 +348,13 @@ def make_post_page(session, post):
page = ''
focused_cmt = None
def commenter_flair(cmt, post):
cmt_flair = [cmt.poster_flair] if cmt.poster_flair else []
if post and cmt.user == post.user:
cmt_flair = ['op'] + cmt_flair
elif cmt.user in session.context_mod_ids:
cmt_flair = ['mod'] + cmt_flair
return f" [{', '.join(cmt_flair)}]" if cmt_flair else ""
def commenter_flair(cmt, post, abbreviate, with_context=None):
flair = User.render_flair(cmt.poster_flair,
with_context if with_context else session.context,
abbreviate=abbreviate,
user_mod=cmt.user in session.context_mod_ids,
user_op=post and cmt.user == post.user)
return f' [{flair}]' if flair else ''
if is_comment_page:
# Switch to the parent post, but display it in preview mode.
@ -370,7 +373,9 @@ def make_post_page(session, post):
return 51, 'Not found'
page += f'=> /help/deleted-post 🔒 Comment on a deleted post (ID:{post_id})\n\n'
page += session.render_post(focused_cmt)
flair = commenter_flair(focused_cmt, post)
flair = commenter_flair(focused_cmt, post,
abbreviate=False,
with_context=db.get_subspace(post.subspace))
page += f'\n=> /u/{focused_cmt.poster_name} {focused_cmt.poster_avatar} {focused_cmt.poster_name}{flair}\n'
page += f'{focused_cmt.age()}\n'
@ -449,7 +454,13 @@ def make_post_page(session, post):
page += '\n'
if post.tags:
page += '### ' + post.tags + '\n'
flair = f" [{post.poster_flair}]" if post.poster_flair else ""
#flair = f" [{post.poster_flair}]" if post.poster_flair else ""
flair = User.render_flair(post.poster_flair,
session.context,
user_mod=post.user in session.context_mod_ids)
if flair: flair = f" [{flair}]"
poster_link = f'=> /u/{post.poster_name} {post.poster_avatar} {post.poster_name}{flair}\n'
if session.is_context_tracker:
page += f'=> /{session.context.title()} 🐞 Issue #{post.issueid} in {session.context.title()}\n'
@ -590,7 +601,7 @@ def make_post_page(session, post):
cmt.ymd_hm(tz=session.tz, date_fmt='%b %d', time_prefix='at ') if elapsed_hours < 24 * 180 else \
cmt.ymd_hm(tz=session.tz, time_prefix='at ')
cmt_flair = commenter_flair(cmt, post)
cmt_flair = commenter_flair(cmt, post, abbreviate=True)
if not session.is_archive:
src = f'=> /u/{cmt.poster_name}/{cmt.id} {cmt.poster_avatar} {cmt.poster_name}{cmt_flair} · {comment_age}:\n'
else:
@ -715,13 +726,14 @@ def make_feed_page(session):
elif not context:
topinfo += f"{session.bubble.site_info if session.user else session.bubble.site_info_nouser}\n"
else:
if c_user and (c_user.info or c_user.url):
if c_user and (c_user.info or c_user.url or c_user.flair):
if c_user.info:
topinfo += c_user.info + '\n'
if c_user.flair:
topinfo += f"[{c_user.flair}]\n"
if c_user.url:
topinfo += f'=> {c_user.url}\n'
if c_user.flair:
flair = User.render_flair(c_user.flair, context=None, long_form=True, db=session.db)
topinfo += f'\n{flair}'
elif context:
if context.info:
topinfo += context.info + '\n'

View File

@ -233,6 +233,15 @@ class User:
# Roles:
BASIC, ADMIN, LIMITED = range(3)
# Flair types:
FLAIRS = {
'': 'Self description',
'🏝️': 'Absence',
'✍️': 'Writing style',
'🗣️': 'Interaction style',
'🛂': 'Note from moderator',
}
# Sort modes:
SORT_POST_RECENT = 'r'
SORT_POST_HOTNESS = 'h'
@ -289,6 +298,90 @@ class User:
return None
def render_flair(user_flair, context, abbreviate=False, long_form=False, db=None,
user_mod=False, user_op=False):
"""
Arguments:
db (Database): database object for looking up subspace names
in the long form.
context (Subspace): where the flair is being shown. If None,
the flair is being shown in the home feed.
abbreviate (bool): user-provided text is omitted and only
icons are shown.
long_form (bool): a description of the flair type is included
and the output is formatted onto multiple lines instead
of being a single line.
"""
if not user_flair.strip():
return ''
out = ''
for flair in user_flair.split('\n'):
pos = flair.find(':')
scope = flair[:pos].strip() if pos >= 0 else None
is_admin_assigned = (scope and scope.startswith('*'))
if is_admin_assigned:
scope = scope[1:]
label = flair[pos + 1:].strip() if pos >= 0 else flair
icon = ''
if len(label):
for key in User.FLAIRS:
if label.startswith(key):
icon = key
label = label[len(key):].strip()
break
#print(user_flair, icon, label, scope)
has_abbrev = False
if long_form:
# Show everything in the long form.
if icon:
out += icon + ' '
if scope:
subspace = db.get_subspace(id=abs(int(scope)))
if subspace:
scope = f" (in {subspace.title()})"
else:
scope = " (in a deleted subspace)"
else:
scope = ''
if icon:
out += f"{User.FLAIRS[icon]}: "
elif not scope and is_admin_assigned:
out += '📛 Assigned flair: '
else:
out += '📛 Personal flair: '
out += f"{label}{scope}{' (set by admin)' if is_admin_assigned else ''}\n"
elif not scope or (context and int(scope) == context.id):
# A global flair is displayed everywhere, otherwise the scope must match
# current context.
if len(out): out += ' ' if abbreviate else ', '
out += icon
if not abbreviate or not scope:
if len(out): out += ' '
out += label
elif not icon:
has_abbrev = True
if not long_form:
if user_op or user_mod:
if len(out):
out = ', ' + out
if user_op and user_mod:
out = 'OP/mod' + out
elif user_op:
out = 'OP' + out
elif user_mod:
out = 'mod' + out
if has_abbrev:
out += '...'
return out
class Subspace:
OMIT_FROM_ALL_FLAG = 0x1
@ -500,7 +593,7 @@ class Database:
db.execute("""CREATE TABLE IF NOT EXISTS users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(30) UNIQUE,
flair VARCHAR(30) DEFAULT '',
flair VARCHAR(1000) DEFAULT '',
info VARCHAR(1000) DEFAULT '',
url VARCHAR(1000) DEFAULT '',
recovery VARCHAR(1000) DEFAULT '',