=> 25daafcf5fa2626d03107d510c744ee15d575749
[1mdiff --git a/composer.py b/composer.py[m [1mindex 07dbf23..27d5caa 100644[m [1m--- a/composer.py[m [1m+++ b/composer.py[m [36m@@ -35,6 +35,7 @@[m [mdef edit_segment(session):[m '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)'[m [m post = db.get_post(segment.post)[m [32m+[m[32m title = None[m [m if not session.is_editable(post):[m return 61, 'Cannot edit posts by other users'[m [36m@@ -47,6 +48,10 @@[m [mdef edit_segment(session):[m if req.content_mime and not req.content_mime.startswith('text/'):[m return 50, 'Bad content format (must be text)'[m seg_text = req.content.decode('utf-8')[m [32m+[m[32m if db.is_first_segment_of_post(post, segment):[m [32m+[m[32m title, seg_text = extract_title_heading(session, seg_text)[m [32m+[m[32m if title is not None:[m [32m+[m[32m db.update_post(post, title=title)[m db.update_segment(segment, content=seg_text)[m elif segment.type == Segment.LINK:[m if session.is_gemini:[m [36m@@ -75,6 +80,10 @@[m [mdef edit_segment(session):[m seg_text = clean_text(req.content.decode('utf-8'))[m [m seg_text = seg_text.rstrip()[m [32m+[m[32m if db.is_first_segment_of_post(post, segment):[m [32m+[m[32m title, seg_text = extract_title_heading(session, seg_text)[m [32m+[m[32m if title is not None:[m [32m+[m[32m db.update_post(post, title=title)[m if user.flags & User.COMPOSER_SPLIT_FLAG:[m parts = split_paragraphs(seg_text)[m else:[m [36m@@ -220,12 +229,19 @@[m [mdef make_composer_page(session):[m if not is_empty_query(req) or (session.is_titan and len(req.content)):[m seg_text = clean_query(req) if session.is_gemini \[m else clean_text(req.content.decode('utf-8'))[m [32m+[m[32m # In the first text segment, always detect a heading and make it the post title.[m [32m+[m[32m if db.get_segment_count(post) == 0:[m [32m+[m[32m title, seg_text = extract_title_heading(session, seg_text)[m [32m+[m[32m else:[m [32m+[m[32m title = None[m if session.user.flags & User.COMPOSER_SPLIT_FLAG:[m parts = split_paragraphs(seg_text)[m else:[m parts = [seg_text.rstrip()][m for part in parts:[m db.create_segment(post, Segment.TEXT, content=part)[m [32m+[m[32m if title is not None:[m [32m+[m[32m db.update_post(post, title=title)[m return 30, gemini_link[m return 10, 'Add text segment:'[m [m [1mdiff --git a/feeds.py b/feeds.py[m [1mindex 9055aa7..76e4d3c 100644[m [1m--- a/feeds.py[m [1m+++ b/feeds.py[m [36m@@ -172,15 +172,7 @@[m [mdef make_post_page_or_configure_feed(session):[m title = ''[m [m if not url:[m [31m- found = re.match(r'^\s*#\s*(.+)$', lines[0])[m [31m- if found:[m [31m- title = found[1][m [31m- body = '\n'.join(lines[1:]).strip()[m [31m- elif session.is_context_tracker:[m [31m- title = lines[0][m [31m- body = '\n'.join(lines[1:]).strip()[m [31m- else:[m [31m- title = ''[m [32m+[m[32m title, body = extract_title_heading(session, body)[m [m post_id = db.create_post(session.user, session.context.id, title=title)[m post = db.get_post(post_id, draft=True)[m [1mdiff --git a/model.py b/model.py[m [1mindex cf3802a..e710cdb 100644[m [1m--- a/model.py[m [1m+++ b/model.py[m [36m@@ -505,7 +505,7 @@[m [mclass Post:[m [m SORT_CREATED, SORT_ACTIVE, SORT_HOTNESS = range(3)[m [m [31m- def __init__(self, id, subspace, parent, user, issueid, title, flags, is_draft, is_pinned,[m [32m+[m[32m def __init__(self, id, subspace, parent, user, issueid, title, flags, is_draft, is_pinned,[m num_cmts, num_likes, tags, ts_created, ts_edited, summary,[m sub_name=None, sub_owner=None, poster_avatar=None, poster_name=None,[m poster_flair=None, num_notifs=0, num_per_day=None, ts_comment=None):[m [36m@@ -1777,6 +1777,22 @@[m [mclass Database:[m [m return segments[m [m [32m+[m[32m def get_segment_count(self, post):[m [32m+[m[32m cur = self.conn.cursor()[m [32m+[m[32m cur.execute("SELECT COUNT(id) FROM segments WHERE post=? AND type!=?",[m [32m+[m[32m (post.id, Segment.POLL))[m [32m+[m[32m for (count,) in cur:[m [32m+[m[32m return count[m [32m+[m[32m return 0[m [32m+[m [32m+[m[32m def is_first_segment_of_post(self, post, segment):[m [32m+[m[32m cur = self.conn.cursor()[m [32m+[m[32m cur.execute("SELECT MIN(pos) FROM segments WHERE post=? AND type!=?",[m [32m+[m[32m (post.id, Segment.POLL))[m [32m+[m[32m for (min_pos,) in cur:[m [32m+[m[32m return min_pos == segment.pos[m [32m+[m[32m return False[m [32m+[m def get_segments_of_similar_type(self, post, is_poll):[m return list(filter(lambda s:[m (is_poll and s.type == Segment.POLL or[m [36m@@ -1885,7 +1901,7 @@[m [mclass Database:[m self.get_segments_of_similar_type(post, is_poll)))[m segments = segments[:new_pos] + [moved_segment] + segments[new_pos:][m cur = self.conn.cursor()[m [31m- pos = 0[m [32m+[m[32m pos = 1[m for seg in segments:[m cur.execute('UPDATE segments SET pos=? WHERE id=?', (pos, seg.id))[m pos += 1[m [1mdiff --git a/settings.py b/settings.py[m [1mindex b7143d6..d8efa51 100644[m [1m--- a/settings.py[m [1m+++ b/settings.py[m [36m@@ -898,7 +898,7 @@[m [mdef make_settings_page(session):[m page += f'=> /settings/omit-feed {CHECKS[nonzero(user_space.flags & Subspace.OMIT_FROM_FEED_BY_DEFAULT)]} ' + \[m 'Omit posts from Gemini/Atom feed by default\n'[m page += 'Individual posts can be included or excluded from All Posts and Gemini/Atom feeds using the composer.\n'[m [31m- page += f'=> /settings/autosplit {CHECKS[nonzero(user.flags & User.COMPOSER_SPLIT_FLAG)]} Auto-split paragraphs in composer\n'[m [32m+[m[32m page += f'\n=> /settings/autosplit {CHECKS[nonzero(user.flags & User.COMPOSER_SPLIT_FLAG)]} Auto-split paragraphs in composer\n'[m 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'[m [m page += '\n=> /settings/profile ⚙️ Profile\n'[m [1mdiff --git a/utils.py b/utils.py[m [1mindex c7b9722..dbb8588 100644[m [1m--- a/utils.py[m [1m+++ b/utils.py[m [36m@@ -241,6 +241,19 @@[m [mdef shorten_text(text, n):[m return text.strip()[m [m [m [32m+[m[32mdef extract_title_heading(session, body):[m [32m+[m[32m lines = body.split('\n')[m [32m+[m[32m title = None[m [32m+[m[32m found = re.match(r'^\s*#\s*(.+)$', lines[0])[m [32m+[m[32m if found:[m [32m+[m[32m title = found[1][m [32m+[m[32m body = '\n'.join(lines[1:]).strip()[m [32m+[m[32m elif session.is_context_tracker:[m [32m+[m[32m title = lines[0][m [32m+[m[32m body = '\n'.join(lines[1:]).strip()[m [32m+[m[32m return title, body[m [32m+[m [32m+[m def time_delta_text(sec, date_ts, suffix='ago', now='Now',[m date_prefix='',[m date_fmt='%Y-%m-%d',[m
text/gemini; charset=utf-8
This content has been proxied by September (ba2dc).