diff --git a/50_bubble.py b/50_bubble.py

index a1fc72f..a0a236c 100644

--- a/50_bubble.py

+++ b/50_bubble.py

@@ -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:

diff --git a/db-migrate.sql b/db-migrate.sql

index 5728e8a..cc2fc41 100644

--- a/db-migrate.sql

+++ b/db-migrate.sql

@@ -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 '';

\ No newline at end of file

+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 '';

diff --git a/feeds.py b/feeds.py

index 66e86a4..5111fa7 100644

--- a/feeds.py

+++ b/feeds.py

@@ -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'

diff --git a/model.py b/model.py

index e839ec4..1330c77 100644

--- a/model.py

+++ b/model.py

@@ -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 '',

Proxy Information
Original URL
gemini://git.skyjake.fi/bubble/main/pcdiff/fb83c111e3aadd361ae323dbcb3c40aea54d803a
Status Code
Success (20)
Meta
text/plain
Capsule Response Time
28.679577 milliseconds
Gemini-to-HTML Time
3.748011 milliseconds

This content has been proxied by September (ba2dc).