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' 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): def feed_entry(self, post, context=None, omit_rotate_info=False, is_activity_feed=False):
is_issue_tracker = self.is_context_tracker is_issue_tracker = self.is_context_tracker
is_comment = post.parent != 0 # Flat feeds intermingle comments with posts. is_comment = post.parent != 0 # Flat feeds intermingle comments with posts.
@ -276,7 +283,6 @@ Bubble is open source:
else: else:
age = post.age(tz=self.tz) age = post.age(tz=self.tz)
bell = ' 🔔' if post.num_notifs else '' bell = ' 🔔' if post.num_notifs else ''
flair = f' [{post.poster_flair}]' if post.poster_flair else ''
SHORT_PREVIEW_LEN = 160 SHORT_PREVIEW_LEN = 160
@ -320,7 +326,7 @@ Bubble is open source:
post_icon = post.poster_avatar post_icon = post.poster_avatar
post_label = post.poster_name post_label = post.poster_name
if not (is_user_post and context and context.id == post.subspace): 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}' post_path = f'/u/{post.poster_name}'
meta_icon = '💬' meta_icon = '💬'
@ -352,7 +358,7 @@ Bubble is open source:
# Last line in the metadata. # Last line in the metadata.
meta = [] meta = []
if is_comment or (sub and not is_activity_feed): 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: if cmt:
meta.append(cmt) meta.append(cmt)
if likes: if likes:

View File

@ -32,4 +32,7 @@ UPDATE users SET notif=notif|0x040000;
UPDATE users SET notif=notif|0x100000; UPDATE users SET notif=notif|0x100000;
-- Migration from v7 to v8 -- -- 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: if session.c_user:
page = f'# {session.c_user.avatar} {session.c_user.name}\n' page = f'# {session.c_user.avatar} {session.c_user.name}\n'
if session.c_user.flair: 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: else:
page = f'# {subspace.title()}\n' page = f'# {subspace.title()}\n'
page += f'=> /{subspace.title()} {subspace.title()}\n' page += f'=> /{subspace.title()} {subspace.title()}\n'
@ -345,13 +348,13 @@ def make_post_page(session, post):
page = '' page = ''
focused_cmt = None focused_cmt = None
def commenter_flair(cmt, post): def commenter_flair(cmt, post, abbreviate, with_context=None):
cmt_flair = [cmt.poster_flair] if cmt.poster_flair else [] flair = User.render_flair(cmt.poster_flair,
if post and cmt.user == post.user: with_context if with_context else session.context,
cmt_flair = ['op'] + cmt_flair abbreviate=abbreviate,
elif cmt.user in session.context_mod_ids: user_mod=cmt.user in session.context_mod_ids,
cmt_flair = ['mod'] + cmt_flair user_op=post and cmt.user == post.user)
return f" [{', '.join(cmt_flair)}]" if cmt_flair else "" return f' [{flair}]' if flair else ''
if is_comment_page: if is_comment_page:
# Switch to the parent post, but display it in preview mode. # 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' return 51, 'Not found'
page += f'=> /help/deleted-post 🔒 Comment on a deleted post (ID:{post_id})\n\n' page += f'=> /help/deleted-post 🔒 Comment on a deleted post (ID:{post_id})\n\n'
page += session.render_post(focused_cmt) 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'\n=> /u/{focused_cmt.poster_name} {focused_cmt.poster_avatar} {focused_cmt.poster_name}{flair}\n'
page += f'{focused_cmt.age()}\n' page += f'{focused_cmt.age()}\n'
@ -449,7 +454,13 @@ def make_post_page(session, post):
page += '\n' page += '\n'
if post.tags: if post.tags:
page += '### ' + post.tags + '\n' 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' poster_link = f'=> /u/{post.poster_name} {post.poster_avatar} {post.poster_name}{flair}\n'
if session.is_context_tracker: if session.is_context_tracker:
page += f'=> /{session.context.title()} 🐞 Issue #{post.issueid} in {session.context.title()}\n' 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, date_fmt='%b %d', time_prefix='at ') if elapsed_hours < 24 * 180 else \
cmt.ymd_hm(tz=session.tz, time_prefix='at ') 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: if not session.is_archive:
src = f'=> /u/{cmt.poster_name}/{cmt.id} {cmt.poster_avatar} {cmt.poster_name}{cmt_flair} · {comment_age}:\n' src = f'=> /u/{cmt.poster_name}/{cmt.id} {cmt.poster_avatar} {cmt.poster_name}{cmt_flair} · {comment_age}:\n'
else: else:
@ -715,13 +726,14 @@ def make_feed_page(session):
elif not context: elif not context:
topinfo += f"{session.bubble.site_info if session.user else session.bubble.site_info_nouser}\n" topinfo += f"{session.bubble.site_info if session.user else session.bubble.site_info_nouser}\n"
else: 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: if c_user.info:
topinfo += c_user.info + '\n' topinfo += c_user.info + '\n'
if c_user.flair:
topinfo += f"[{c_user.flair}]\n"
if c_user.url: if c_user.url:
topinfo += f'=> {c_user.url}\n' 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: elif context:
if context.info: if context.info:
topinfo += context.info + '\n' topinfo += context.info + '\n'

View File

@ -233,6 +233,15 @@ class User:
# Roles: # Roles:
BASIC, ADMIN, LIMITED = range(3) BASIC, ADMIN, LIMITED = range(3)
# Flair types:
FLAIRS = {
'': 'Self description',
'🏝️': 'Absence',
'✍️': 'Writing style',
'🗣️': 'Interaction style',
'🛂': 'Note from moderator',
}
# Sort modes: # Sort modes:
SORT_POST_RECENT = 'r' SORT_POST_RECENT = 'r'
SORT_POST_HOTNESS = 'h' SORT_POST_HOTNESS = 'h'
@ -289,6 +298,90 @@ class User:
return None 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: class Subspace:
OMIT_FROM_ALL_FLAG = 0x1 OMIT_FROM_ALL_FLAG = 0x1
@ -500,7 +593,7 @@ class Database:
db.execute("""CREATE TABLE IF NOT EXISTS users ( db.execute("""CREATE TABLE IF NOT EXISTS users (
id INT PRIMARY KEY AUTO_INCREMENT, id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(30) UNIQUE, name VARCHAR(30) UNIQUE,
flair VARCHAR(30) DEFAULT '', flair VARCHAR(1000) DEFAULT '',
info VARCHAR(1000) DEFAULT '', info VARCHAR(1000) DEFAULT '',
url VARCHAR(1000) DEFAULT '', url VARCHAR(1000) DEFAULT '',
recovery VARCHAR(1000) DEFAULT '', recovery VARCHAR(1000) DEFAULT '',