Bubble [main]

Auto-update post title from first segment

=> 25daafcf5fa2626d03107d510c744ee15d575749

diff --git a/composer.py b/composer.py
index 07dbf23..27d5caa 100644
--- a/composer.py
+++ b/composer.py
@@ -35,6 +35,7 @@ def edit_segment(session):
                    'Move to which § number? (Other actions: "X" to remove; "." to insert text; ":" to insert long text; "S" to split text; "M" to merge text; "/" to insert a link; "P" to insert poll)'
 
     post = db.get_post(segment.post)
+    title = None
 
     if not session.is_editable(post):
         return 61, 'Cannot edit posts by other users'
@@ -47,6 +48,10 @@ def edit_segment(session):
                 if req.content_mime and not req.content_mime.startswith('text/'):
                     return 50, 'Bad content format (must be text)'
                 seg_text = req.content.decode('utf-8')
+                if db.is_first_segment_of_post(post, segment):
+                    title, seg_text = extract_title_heading(session, seg_text)
+                    if title is not None:
+                        db.update_post(post, title=title)
                 db.update_segment(segment, content=seg_text)
         elif segment.type == Segment.LINK:
             if session.is_gemini:
@@ -75,6 +80,10 @@ def edit_segment(session):
                 seg_text = clean_text(req.content.decode('utf-8'))
 
             seg_text = seg_text.rstrip()
+            if db.is_first_segment_of_post(post, segment):
+                title, seg_text = extract_title_heading(session, seg_text)
+                if title is not None:
+                    db.update_post(post, title=title)
             if user.flags & User.COMPOSER_SPLIT_FLAG:
                 parts = split_paragraphs(seg_text)
             else:
@@ -220,12 +229,19 @@ def make_composer_page(session):
         if not is_empty_query(req) or (session.is_titan and len(req.content)):
             seg_text = clean_query(req) if session.is_gemini \
                 else clean_text(req.content.decode('utf-8'))
+            # In the first text segment, always detect a heading and make it the post title.
+            if db.get_segment_count(post) == 0:
+                title, seg_text = extract_title_heading(session, seg_text)
+            else:
+                title = None
             if session.user.flags & User.COMPOSER_SPLIT_FLAG:
                 parts = split_paragraphs(seg_text)
             else:
                 parts = [seg_text.rstrip()]
             for part in parts:
                 db.create_segment(post, Segment.TEXT, content=part)
+            if title is not None:
+                db.update_post(post, title=title)
             return 30, gemini_link
         return 10, 'Add text segment:'
 
diff --git a/feeds.py b/feeds.py
index 9055aa7..76e4d3c 100644
--- a/feeds.py
+++ b/feeds.py
@@ -172,15 +172,7 @@ def make_post_page_or_configure_feed(session):
                 title = ''
 
         if not url:
-            found = re.match(r'^\s*#\s*(.+)$', lines[0])
-            if found:
-                title = found[1]
-                body = '\n'.join(lines[1:]).strip()
-            elif session.is_context_tracker:
-                title = lines[0]
-                body = '\n'.join(lines[1:]).strip()
-            else:
-                title = ''
+            title, body = extract_title_heading(session, body)
 
         post_id = db.create_post(session.user, session.context.id, title=title)
         post = db.get_post(post_id, draft=True)
diff --git a/model.py b/model.py
index cf3802a..e710cdb 100644
--- a/model.py
+++ b/model.py
@@ -505,7 +505,7 @@ class Post:
 
     SORT_CREATED, SORT_ACTIVE, SORT_HOTNESS = range(3)
 
-    def __init__(self, id, subspace, parent, user, issueid, title, flags,  is_draft, is_pinned,
+    def __init__(self, id, subspace, parent, user, issueid, title, flags, is_draft, is_pinned,
                  num_cmts, num_likes, tags, ts_created, ts_edited, summary,
                  sub_name=None, sub_owner=None, poster_avatar=None, poster_name=None,
                  poster_flair=None, num_notifs=0, num_per_day=None, ts_comment=None):
@@ -1777,6 +1777,22 @@ class Database:
 
         return segments
 
+    def get_segment_count(self, post):
+        cur = self.conn.cursor()
+        cur.execute("SELECT COUNT(id) FROM segments WHERE post=? AND type!=?",
+                    (post.id, Segment.POLL))
+        for (count,) in cur:
+            return count
+        return 0
+
+    def is_first_segment_of_post(self, post, segment):
+        cur = self.conn.cursor()
+        cur.execute("SELECT MIN(pos) FROM segments WHERE post=? AND type!=?",
+                    (post.id, Segment.POLL))
+        for (min_pos,) in cur:
+            return min_pos == segment.pos
+        return False
+
     def get_segments_of_similar_type(self, post, is_poll):
         return list(filter(lambda s:
                            (is_poll     and s.type == Segment.POLL or
@@ -1885,7 +1901,7 @@ class Database:
                                self.get_segments_of_similar_type(post, is_poll)))
         segments = segments[:new_pos] + [moved_segment] + segments[new_pos:]
         cur = self.conn.cursor()
-        pos = 0
+        pos = 1
         for seg in segments:
             cur.execute('UPDATE segments SET pos=? WHERE id=?', (pos, seg.id))
             pos += 1
diff --git a/settings.py b/settings.py
index b7143d6..d8efa51 100644
--- a/settings.py
+++ b/settings.py
@@ -898,7 +898,7 @@ def make_settings_page(session):
         page += f'=> /settings/omit-feed {CHECKS[nonzero(user_space.flags & Subspace.OMIT_FROM_FEED_BY_DEFAULT)]} ' + \
                 'Omit posts from Gemini/Atom feed by default\n'
         page += 'Individual posts can be included or excluded from All Posts and Gemini/Atom feeds using the composer.\n'
-        page += f'=> /settings/autosplit {CHECKS[nonzero(user.flags & User.COMPOSER_SPLIT_FLAG)]} Auto-split paragraphs in composer\n'
+        page += f'\n=> /settings/autosplit {CHECKS[nonzero(user.flags & User.COMPOSER_SPLIT_FLAG)]} Auto-split paragraphs in composer\n'
         page += f'=> /settings/titan-edit {CHECKS[nonzero(user.flags & User.COMPOSER_TITAN_EDIT_FLAG)]} Edit text with Titan\nYour client must support the Titan "edit" parameter.\n'
 
         page += '\n=> /settings/profile ⚙️ Profile\n'
diff --git a/utils.py b/utils.py
index c7b9722..dbb8588 100644
--- a/utils.py
+++ b/utils.py
@@ -241,6 +241,19 @@ def shorten_text(text, n):
     return text.strip()
 
 
+def extract_title_heading(session, body):
+    lines = body.split('\n')
+    title = None
+    found = re.match(r'^\s*#\s*(.+)$', lines[0])
+    if found:
+        title = found[1]
+        body = '\n'.join(lines[1:]).strip()
+    elif session.is_context_tracker:
+        title = lines[0]
+        body = '\n'.join(lines[1:]).strip()
+    return title, body
+
+
 def time_delta_text(sec, date_ts, suffix='ago', now='Now',
                     date_prefix='',
                     date_fmt='%Y-%m-%d',
Proxy Information
Original URL
gemini://git.skyjake.fi/bubble/main/cdiff/25daafcf5fa2626d03107d510c744ee15d575749
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
25.009224 milliseconds
Gemini-to-HTML Time
0.390458 milliseconds

This content has been proxied by September (ba2dc).