[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/plain
This content has been proxied by September (ba2dc).