2023-05-06 13:29:21 +00:00
import re
from utils import *
2023-05-10 12:20:32 +00:00
from model import Post , Segment , User , Subspace
2023-05-06 13:29:21 +00:00
def edit_segment ( session ) :
if not session . user :
return 60 , ' Must be signed in to edit posts '
db = session . db
req = session . req
2023-11-11 15:31:08 +00:00
user = session . user
2023-05-06 13:29:21 +00:00
2023-05-27 16:25:25 +00:00
try :
found = re . match ( r ' ^(edit|move|raw)-segment/( \ d+)$ ' , req . path [ len ( session . path ) : ] )
seg_action = found . group ( 1 )
seg_id = int ( found . group ( 2 ) )
except :
return 59 , ' Bad request '
2023-05-06 13:29:21 +00:00
segment = db . get_segment ( seg_id )
if not segment :
return 51 , ' Not found '
2023-05-21 21:07:37 +00:00
if session . is_gemini and is_empty_query ( req ) and seg_action != ' raw ' :
if seg_action == ' edit ' :
2023-05-07 21:04:14 +00:00
if segment . type == Segment . LINK :
return 10 , ' Edit link: (URL followed by label, separated with space) '
if segment . type == Segment . POLL :
return 10 , ' Edit poll option: '
else :
if segment . type == Segment . POLL :
return 10 , ' Move to which position in the poll: (X to remove) '
2023-05-21 21:07:37 +00:00
return 10 , ' Edited text: (see Help for special commands) ' if seg_action == ' edit ' else \
2023-11-11 15:31:08 +00:00
' 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) '
2023-05-06 13:29:21 +00:00
post = db . get_post ( segment . post )
if not session . is_editable ( post ) :
return 61 , ' Cannot edit posts by other users '
2023-05-21 21:07:37 +00:00
if seg_action == ' raw ' :
if segment . type != Segment . TEXT :
return 50 , ' Only text segments can be viewed in raw mode '
if session . is_gemini :
return segment . content
else :
if not req . content_mime . startswith ( ' text/ ' ) :
return 50 , ' Bad content format (must be text) '
seg_text = req . content . decode ( ' utf-8 ' )
db . update_segment ( segment , content = seg_text )
elif seg_action == ' edit ' :
2023-05-06 13:29:21 +00:00
if segment . type == Segment . LINK :
seg_url , seg_text = parse_link_segment_query ( req )
db . update_segment ( segment , url = seg_url , content = seg_text )
elif segment . type == Segment . TEXT :
if session . is_gemini :
seg_text = clean_query ( req )
2023-05-23 12:09:21 +00:00
if seg_text == ' : ' :
return 30 , f ' { session . server_root ( " titan " ) } { session . path } edit-segment/ { segment . id } '
2023-05-24 05:26:51 +00:00
if seg_text == ' \\ ' or seg_text == ' / ' :
2023-05-21 21:07:37 +00:00
return 30 , f ' { session . server_root ( ) } { session . path } raw-segment/ { segment . id } '
2023-05-06 13:29:21 +00:00
else :
seg_text = clean_text ( req . content . decode ( ' utf-8 ' ) )
2023-11-11 15:31:08 +00:00
seg_text = seg_text . rstrip ( )
if user . flags & User . COMPOSER_SPLIT_FLAG :
parts = split_paragraphs ( seg_text )
else :
parts = [ seg_text ]
db . update_segment ( segment , content = parts [ 0 ] . strip ( ) )
# Multiple segments will be added if the paragraphs were split.
if len ( parts ) > 1 :
pos_idx = db . get_segment_position_as_index ( post , segment )
for new_part in parts [ 1 : ] :
added = db . get_segment ( id = db . create_segment ( post , Segment . TEXT ,
content = new_part ) )
pos_idx + = 1
db . move_segment ( post , added , pos_idx )
2023-05-06 13:29:21 +00:00
2023-05-07 21:04:14 +00:00
elif segment . type == Segment . POLL :
opt_text = clean_title ( clean_query ( req ) )
db . update_segment ( segment , content = opt_text )
2023-05-06 13:29:21 +00:00
elif segment . type == Segment . IMAGE or segment . type == Segment . ATTACHMENT :
2023-10-28 11:28:58 +00:00
text = clean_query ( req ) . strip ( )
text = remove_prefix ( text , segment . url )
text = remove_prefix ( text , ' => ' + segment . url )
seg_text = clean_title ( text . strip ( ) )
2023-05-06 13:29:21 +00:00
db . update_segment ( segment , content = seg_text )
else :
2023-05-28 07:22:23 +00:00
# Moving and other actions.
2023-05-06 13:29:21 +00:00
arg = clean_query ( req )
2023-05-28 07:22:23 +00:00
cmd = arg . upper ( )
2023-07-27 18:04:10 +00:00
pos_idx = db . get_segment_position_as_index ( post , segment )
2023-11-11 15:31:08 +00:00
2023-05-28 07:22:23 +00:00
if cmd == ' X ' :
# TODO: Need a token here?
2023-05-06 13:29:21 +00:00
db . destroy_segment ( segment )
2023-11-11 15:31:08 +00:00
2023-05-28 07:22:23 +00:00
elif cmd == ' . ' :
inserted = db . get_segment ( id = db . create_segment ( post , Segment . TEXT , " " ) )
2023-07-27 18:04:10 +00:00
db . move_segment ( post , inserted , pos_idx + 1 )
2023-05-28 07:22:23 +00:00
return 30 , f ' { session . path } edit-segment/ { inserted . id } '
2023-11-11 15:31:08 +00:00
2023-05-28 07:22:23 +00:00
elif cmd == ' : ' :
inserted = db . get_segment ( id = db . create_segment ( post , Segment . TEXT , " " ) )
2023-07-27 18:04:10 +00:00
db . move_segment ( post , inserted , pos_idx + 1 )
2023-05-28 07:22:23 +00:00
return 30 , f ' { session . server_root ( " titan " ) } { session . path } edit-segment/ { inserted . id } '
2023-11-11 15:31:08 +00:00
elif cmd == ' S ' :
if segment . type != Segment . TEXT :
return 50 , ' Only text segments can be split '
new_segments = split_paragraphs ( segment . content )
if len ( new_segments ) > 1 :
# Update the first segment and insert additional new segments as needed.
db . update_segment ( segment , content = new_segments [ 0 ] )
for new_content in new_segments [ 1 : ] :
inserted = db . get_segment ( id = db . create_segment ( post , Segment . TEXT ,
content = new_content ) )
pos_idx + = 1
db . move_segment ( post , inserted , pos_idx )
return 30 , f ' { session . path } edit/ { post . id } '
elif cmd == ' M ' :
all_segments = db . get_segments ( post , poll = False )
try :
next_segment = all_segments [ pos_idx + 1 ]
except :
return 50 , ' Last segment cannot be merged '
if segment . type != Segment . TEXT or next_segment . type != Segment . TEXT :
return 50 , ' Only text segments can be merged '
db . update_segment ( segment , content = segment . content + ' \n \n ' + next_segment . content )
db . destroy_segment ( next_segment )
return 30 , f ' { session . path } edit/ { post . id } '
2023-05-28 07:22:23 +00:00
elif cmd == ' / ' :
inserted = db . get_segment ( id = db . create_segment ( post , Segment . LINK ) )
2023-07-27 18:04:10 +00:00
db . move_segment ( post , inserted , pos_idx + 1 )
2023-05-28 07:22:23 +00:00
return 30 , f ' { session . path } edit-segment/ { inserted . id } '
2023-11-11 15:31:08 +00:00
2023-05-28 07:22:23 +00:00
elif cmd == ' P ' :
inserted_id = db . create_segment ( post , Segment . POLL )
return 30 , f ' { session . path } edit-segment/ { inserted_id } '
2023-11-11 15:31:08 +00:00
2023-05-06 13:29:21 +00:00
else :
2023-05-27 16:25:25 +00:00
try :
2023-07-27 18:04:10 +00:00
db . move_segment ( post , segment , max ( 1 , int ( arg ) ) - 1 )
2023-05-27 16:25:25 +00:00
except :
return 50 , ' Expected a position number or X '
2023-05-06 13:29:21 +00:00
2023-05-13 14:55:24 +00:00
return 30 , f ' { session . server_root ( ) } { session . path } edit/ { segment . post } '
2023-05-06 13:29:21 +00:00
def make_composer_page ( session ) :
""" Post composer. """
db = session . db
req = session . req
2023-11-11 15:47:51 +00:00
CHECKS = session . CHECKS
2023-05-06 13:29:21 +00:00
2023-05-07 06:09:10 +00:00
found = re . match ( r ' ^( \ d+)(/([ \ w-]+)(/([0-9a-zA-Z] {10} ))?)?$ ' ,
req . path [ len ( session . path + ' edit/ ' ) : ] )
2023-05-29 06:37:20 +00:00
try :
post_id = int ( found . group ( 1 ) )
except :
2023-05-29 10:55:47 +00:00
return 30 , session . server_root ( )
2023-05-06 13:29:21 +00:00
post_action = found . group ( 3 )
2023-05-07 06:09:10 +00:00
req_token = found . group ( 5 )
2023-05-06 13:29:21 +00:00
post = db . get_post ( post_id )
2023-06-20 12:02:07 +00:00
if not post :
return 51 , ' Not found '
2023-05-10 12:20:32 +00:00
subspace = db . get_subspace ( id = post . subspace )
2023-06-20 12:02:07 +00:00
is_orphan_comment = post . parent and not db . get_post ( id = post . parent )
2023-05-06 13:29:21 +00:00
if not session . user :
return 60 , ' Must be signed in to edit posts '
2023-06-20 12:02:07 +00:00
if is_orphan_comment and post_action != ' delete ' :
return 61 , ' Locked comment '
2023-05-14 21:46:48 +00:00
if post_action == ' delete ' :
if not session . is_deletable ( post ) :
return 61 , ' Cannot delete post '
2023-05-27 16:11:30 +00:00
elif post_action == ' move ' :
if not session . is_movable ( post ) :
return 61 , ' Cannot move post '
2023-05-15 09:21:22 +00:00
elif post_action == ' mod-title ' :
if not session . is_title_editable ( post ) :
return 61 , ' Cannot edit post title '
2023-05-14 21:46:48 +00:00
elif not session . is_editable ( post ) :
2023-05-06 13:29:21 +00:00
return 61 , ' Cannot edit posts by other users '
2023-05-07 06:09:10 +00:00
user_token = db . get_token ( session . user )
2023-05-10 12:20:32 +00:00
is_issue_tracker = ( subspace . flags & Subspace . ISSUE_TRACKER ) != 0
2023-05-21 18:23:30 +00:00
post_type = ' Issue ' if is_issue_tracker else ' Post ' if not post . parent else ' Comment '
2023-05-07 06:09:10 +00:00
2023-05-06 13:29:21 +00:00
link = f ' { session . path } edit/ { post_id } '
2023-05-13 11:55:30 +00:00
gemini_link = f ' { session . server_root ( ) } ' + link
2023-05-06 13:29:21 +00:00
seg_link = session . path + ' edit-segment '
2023-05-13 11:55:30 +00:00
titan_seg_link = f ' titan:// { session . bubble . hostname } : { session . bubble . port } ' + seg_link
2023-05-06 13:29:21 +00:00
if post_action == ' add-text ' :
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 ' ) )
2023-11-11 15:31:08 +00:00
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 )
2023-05-06 13:29:21 +00:00
return 30 , gemini_link
return 10 , ' Add text segment: '
if post_action == ' add-link ' :
if not is_empty_query ( req ) :
seg_url , seg_text = parse_link_segment_query ( req )
db . create_segment ( post , Segment . LINK , url = seg_url , content = seg_text )
return 30 , link
return 10 , ' Add link: (URL followed by label, separated with space) '
2023-05-07 21:04:14 +00:00
if post_action == ' add-poll ' :
if not is_empty_query ( req ) :
db . create_segment ( post , Segment . POLL , content = clean_title ( clean_query ( req ) ) )
return 30 , link
return 10 , ' Add poll option: '
2023-05-06 13:29:21 +00:00
if post_action == ' add-file ' and session . is_titan :
if len ( req . content ) > session . bubble . max_file_size :
return 50 , f ' File attachments must be less than { int ( session . bubble . max_file_size / 1024 ) } KB '
if req . content_token :
fn = req . content_token . strip ( )
else :
fn = ' '
mime = ' application/octet-stream '
if req . content_mime :
mime = req . content_mime . lower ( ) . split ( ' ; ' ) [ 0 ]
file_id = db . create_file ( session . user , fn , mime , req . content )
is_image = mime . startswith ( ' image/ ' )
url_path = ' /u/ ' + session . user . name + ' / '
url_path + = ' image ' if is_image else ' file '
url_path + = f ' / { file_id } '
if len ( fn ) :
# TODO: Clean up the filename.
url_path + = ' / ' + fn
EXTENSIONS = {
' image/jpeg ' : ' .jpeg ' ,
' image/jpg ' : ' .jpg ' ,
' image/png ' : ' .png ' ,
' image/gif ' : ' .gif '
}
if len ( fn ) == 0 and mime in EXTENSIONS :
url_path + = EXTENSIONS [ mime ]
segment_id = db . create_segment ( post ,
Segment . IMAGE if is_image else Segment . ATTACHMENT ,
url = url_path , content = fn )
db . set_file_segment ( file_id , segment_id )
return 30 , gemini_link
2023-05-15 09:21:22 +00:00
if post_action == ' title ' or post_action == ' mod-title ' :
2023-05-06 13:29:21 +00:00
if req . query == None :
2023-05-10 12:20:32 +00:00
return 10 , f ' Enter { post_type . lower ( ) } title: '
2023-05-06 13:29:21 +00:00
title_text = clean_title ( clean_query ( req ) )
db . update_post ( post , title = title_text )
2023-05-15 09:21:22 +00:00
return 30 , link if post_action == ' title ' else post . page_url ( )
2023-05-06 13:29:21 +00:00
2023-05-11 18:44:22 +00:00
if post_action == ' omit-feed ' :
2023-10-26 12:59:30 +00:00
if session . user . role == User . LIMITED :
return 61 , " Not authorized "
2023-05-11 18:44:22 +00:00
db . update_post ( post , flags = post . flags ^ Post . OMIT_FROM_FEED_FLAG )
return 30 , link
2023-05-29 10:55:47 +00:00
if post_action == ' omit-all ' :
2023-10-26 12:59:30 +00:00
if session . user . role == User . LIMITED :
return 61 , " Not authorized "
2023-05-29 10:55:47 +00:00
db . update_post ( post , flags = post . flags ^ Post . OMIT_FROM_ALL_FLAG )
return 30 , link
2023-11-11 15:47:51 +00:00
if post_action == ' featured-link ' :
2024-01-10 06:51:57 +00:00
db . update_post ( post , flags = post . flags ^ Post . DISABLE_FEATURED_LINK_FLAG )
2023-11-11 15:47:51 +00:00
db . update_post_summary ( post )
return 30 , link
2023-05-06 13:29:21 +00:00
if post_action == ' preview ' :
db . update_post_summary ( post )
2023-05-21 12:01:22 +00:00
page = f ' => { link } /publish 📤 Publish { post_type . lower ( ) } (in { subspace . title ( ) } ) \n '
2023-05-06 13:29:21 +00:00
page + = f ' => { link } Edit draft \n '
2023-05-10 18:46:55 +00:00
session . context = subspace
session . is_context_tracker = is_issue_tracker
2023-05-21 18:23:30 +00:00
if not post . parent :
page + = ' \n # Feed Preview \n '
page + = session . feed_entry ( post , session . context )
2023-05-06 13:29:21 +00:00
if not post . title :
2023-05-10 12:20:32 +00:00
page + = f ' \n # { post_type } Preview \n '
2023-05-06 13:29:21 +00:00
else :
2023-05-08 19:39:56 +00:00
page + = ' \n \n ⁂ \n \n \n '
2023-05-06 13:29:21 +00:00
page + = session . render_post ( post )
2023-05-08 19:39:56 +00:00
poll = session . render_poll ( post )
if poll :
if not page . endswith ( ' \n \n ' ) : page + = ' \n '
page + = poll
2023-05-06 13:29:21 +00:00
return page
if post_action == ' publish ' :
2023-10-26 12:59:30 +00:00
if session . user . role == User . LIMITED and subspace . owner != session . user . id :
return 61 , " Not authorized "
2023-05-10 18:46:55 +00:00
if is_issue_tracker :
2023-10-28 10:35:35 +00:00
if len ( post . title ) == 0 and post . parent == 0 :
2023-05-10 18:46:55 +00:00
return 50 , " Issues must have a title "
if len ( post . summary . strip ( ) ) == 0 :
return 50 , " Cannot publish empty post "
2023-05-06 13:29:21 +00:00
db . publish_post ( post )
2023-10-26 12:59:30 +00:00
if session . user . role == User . LIMITED :
db . update_post ( post , flags = Post . OMIT_FROM_ALL_FLAG | Post . OMIT_FROM_FEED_FLAG )
2023-05-10 18:46:55 +00:00
return 30 , post . page_url ( )
2023-05-06 13:29:21 +00:00
2023-06-20 07:40:36 +00:00
if post_action == ' unpublish ' :
db . unpublish_post ( post )
return 30 , f ' /edit/ { post . id } '
2023-05-27 16:11:30 +00:00
if post_action == ' move ' :
if not db . verify_token ( session . user , req_token ) :
return 61 , " Not authorized "
2023-11-14 06:10:30 +00:00
if session . user . role == User . LIMITED :
return 61 , " Not authorized "
2023-05-27 16:11:30 +00:00
if is_empty_query ( req ) :
return 10 , f ' Move post { post . id } to which subspace? '
subname = clean_query ( req )
if subname . startswith ( ' s/ ' ) :
subname = subname [ 2 : ]
dst_sub = db . get_subspace ( name = subname )
2024-01-11 16:04:31 +00:00
if not dst_sub or dst_sub . id == post . subspace :
2023-05-27 16:11:30 +00:00
return 10 , f ' Move post { post . id } to which subspace? '
if dst_sub . flags & Subspace . ISSUE_TRACKER :
return 50 , " Cannot move to an issue tracker subspace "
if dst_sub . owner and dst_sub . owner != post . user :
return 50 , " Cannot move to another user ' s subspace "
2024-02-02 05:42:39 +00:00
oldsub_id = post . subspace
2023-05-27 16:11:30 +00:00
db . update_post ( post , subspace_id = dst_sub . id )
2024-02-02 05:42:39 +00:00
db . move_comments ( post , oldsub_id , dst_sub . id )
2024-01-11 16:04:31 +00:00
# Notify as if the post was new.
db . notify_followed_about_post ( post )
2023-05-27 16:11:30 +00:00
post . sub_name = dst_sub . name
return 30 , post . page_url ( )
2023-05-06 13:29:21 +00:00
if post_action == ' delete ' :
2023-05-07 06:09:10 +00:00
if not db . verify_token ( session . user , req_token ) :
return 61 , " Not authorized "
2023-05-06 13:29:21 +00:00
if is_empty_query ( req ) :
2023-05-10 12:20:32 +00:00
return 10 , f ' Delete { post_type . lower ( ) } " { post . ymd_date ( ) } { post . title_text ( ) } " in { " u " if post . sub_owner else " s " } / { post . sub_name } ? (Enter YES to confirm) '
2023-05-06 13:29:21 +00:00
if req . query . upper ( ) == ' YES ' :
dst = ' /dashboard ' if post . is_draft else ' /u/ ' + session . user . name
try :
if post . parent :
dst = db . get_post ( post . parent ) . page_url ( )
except :
pass
db . destroy_post ( post )
return 30 , dst
else :
return 30 , f ' /edit/ { post . id } '
is_draft = post . is_draft
is_comment = post . parent != 0
if is_comment :
kind = ' Comment '
elif is_draft :
kind = ' Draft '
else :
2023-05-10 12:20:32 +00:00
kind = post_type
2023-05-06 13:29:21 +00:00
if not is_comment :
page = f ' # Edit { kind } \n '
page + = session . dashboard_link ( )
if is_draft :
2023-05-10 12:20:32 +00:00
page + = f ' => { link } /preview 👁️ Preview { post_type . lower ( ) } \n '
2023-05-06 13:29:21 +00:00
2023-05-11 18:44:22 +00:00
# Options and metadata:
2023-05-06 13:29:21 +00:00
page + = f ' \n ## { post . title_text ( ) } \n '
2023-05-10 12:20:32 +00:00
page + = f ' => { link } /title ✏️ Edit { post_type . lower ( ) } title \n '
2023-10-26 12:59:30 +00:00
if session . user . role != User . LIMITED :
if not subspace . flags & Subspace . OMIT_FROM_ALL_FLAG :
2023-11-11 15:47:51 +00:00
page + = f ' => { link } /omit-all { CHECKS [ nonzero ( post . flags & Post . OMIT_FROM_ALL_FLAG ) ] } Omit { post_type . lower ( ) } from All Posts \n '
page + = f ' => { link } /omit-feed { CHECKS [ nonzero ( post . flags & Post . OMIT_FROM_FEED_FLAG ) ] } Omit { post_type . lower ( ) } from Gemini/Atom feed \n '
2024-01-10 06:51:57 +00:00
page + = f ' => { link } /featured-link { CHECKS [ is_zero ( post . flags & Post . DISABLE_FEATURED_LINK_FLAG ) ] } Feature first link \n '
2023-10-26 12:59:30 +00:00
else :
page + = f " => / { subspace . title ( ) } Limited account: post is visible only in { subspace . title ( ) } \n "
2023-05-10 18:46:55 +00:00
if is_issue_tracker :
2023-05-11 18:44:22 +00:00
page + = f ' => / { subspace . title ( ) } 🐞 Issue in: { subspace . title ( ) } \n '
if not is_draft :
page + = f ' \n This { post_type . lower ( ) } is published as: \n '
page + = session . gemini_feed_entry ( post , None )
2023-05-06 13:29:21 +00:00
else :
page = ' # Edit Comment \n '
page + = session . dashboard_link ( )
2023-05-21 18:23:30 +00:00
if is_draft :
page + = f ' => { link } /preview 👁️ Preview comment \n '
2023-05-06 13:29:21 +00:00
try :
page + = ' \n This is a comment on: \n '
parent_post = db . get_post ( id = post . parent )
page + = session . gemini_feed_entry ( parent_post , None )
except :
pass
segments = db . get_segments ( post )
sid = 0
2023-05-07 21:04:14 +00:00
# Split out the poll options.
poll_options = list ( filter ( lambda s : s . type == Segment . POLL , segments ) )
segments = list ( filter ( lambda s : s . type != Segment . POLL , segments ) )
if len ( poll_options ) :
page + = ' \n ## Poll \n '
opt_num = 1
for opt in poll_options :
page + = f ' \n => { seg_link } / { opt . id } ✏️ OPTION { opt_num } : { opt . content } \n '
2023-05-28 07:22:23 +00:00
page + = f " => { session . path } move-segment/ { opt . id } ↕︎ Move/actions \n "
2023-05-07 21:04:14 +00:00
opt_num + = 1
page + = f ' \n => { link } /add-poll Add poll option \n '
2023-05-06 13:29:21 +00:00
if len ( segments ) == 1 :
page + = ' \n ## Contents \n '
for segment in segments :
sid + = 1
if len ( segments ) > 1 :
page + = f ' \n ## — § { sid } — \n \n '
else :
page + = ' \n '
if segment . type == Segment . TEXT :
page + = segment . content + \
2023-05-27 12:41:05 +00:00
f " \n => { seg_link } / { segment . id } ✏️ Edit text \n "
2023-05-06 13:29:21 +00:00
elif segment . type == Segment . LINK :
page + = f ' => { segment . url } { segment . content } \n '
2023-05-07 21:04:14 +00:00
page + = f " => { seg_link } / { segment . id } ✏️ Edit link \n "
2023-05-06 13:29:21 +00:00
elif segment . type == Segment . IMAGE :
page + = f ' => { segment . url } { segment . content } \n '
2023-05-07 21:04:14 +00:00
page + = f ' => { seg_link } / { segment . id } ✏️ Edit caption \n '
2023-05-06 13:29:21 +00:00
elif segment . type == Segment . ATTACHMENT :
page + = f ' => { segment . url } { segment . content } \n '
2023-05-07 21:04:14 +00:00
page + = f ' => { seg_link } / { segment . id } ✏️ Edit label \n '
2023-05-06 13:29:21 +00:00
2023-07-18 09:30:55 +00:00
page + = f " => { session . path } move-segment/ { segment . id } ↕︎ Move/actions \n "
2023-05-06 13:29:21 +00:00
2023-11-11 15:47:51 +00:00
page + = f ' \n '
2023-05-06 13:29:21 +00:00
page + = ' ## Actions \n '
page + = f ' => { link } /add-text Add text \n '
page + = f ' => titan:// { session . bubble . hostname } /edit/ { post . id } /add-text Add long text \n '
page + = f ' => { link } /add-link Add link \n '
page + = f ' => titan:// { session . bubble . hostname } /edit/ { post . id } /add-file Add image or file attachment ( { int ( session . bubble . max_file_size / 1024 ) } KB) \n Optionally, you can set a filename with the token field. \n '
2023-05-07 21:04:14 +00:00
if not is_comment and not poll_options :
2023-05-28 07:22:23 +00:00
page + = f ' => { link } /add-poll Add poll \n '
2023-05-06 13:29:21 +00:00
2023-06-20 07:40:36 +00:00
actions = [ ]
2023-11-06 18:13:09 +00:00
if not post . parent :
cur_tags = ' : ' + post . tags if post . tags else ' '
actions . append ( f ' => /edit-tags/ { post . id } 🏷️ Add/remove tags { cur_tags } \n ' )
2023-06-20 07:40:36 +00:00
if not is_draft and session . is_unpublishable ( post ) :
actions . append ( f ' => { link } /unpublish Unpublish { kind . lower ( ) } \n The { kind . lower ( ) } is converted into a draft. \n ' )
actions . append ( f ' => { link } /delete/ { user_token } ❌ Delete { kind . lower ( ) } \n ' )
page + = ' \n ' + ' ' . join ( actions )
2023-05-06 13:29:21 +00:00
return page
def make_comment ( session ) :
if not session . user :
return 60 , ' Login required '
2023-10-26 12:59:30 +00:00
if session . user . role == User . LIMITED :
return 61 , ' Not authorized '
2023-05-06 13:29:21 +00:00
db = session . db
req = session . req
found = re . match ( r ' ^( \ d+)(/([ \ w-]+))?$ ' , req . path [ len ( session . path + ' comment/ ' ) : ] )
post_id = int ( found . group ( 1 ) )
post = db . get_post ( post_id )
if not post :
return 51 , ' Not found '
2024-01-10 06:51:57 +00:00
if post . flags & Post . LOCKED_FLAG and session . user . role != User . ADMIN :
return 61 , ' Post is locked '
2023-05-06 13:29:21 +00:00
2023-05-21 18:23:30 +00:00
special = None
if session . is_gemini :
com_text = clean_query ( req )
if len ( com_text ) == 0 :
2024-01-10 06:51:57 +00:00
return 10 , ' New comment: (draft a long comment by ending with a backslash) '
2023-05-21 18:23:30 +00:00
if com_text == ' . ' :
special = ' draft '
com_text = ' '
elif com_text == ' : ' :
return 30 , session . server_root ( ' titan ' ) + req . path
elif com_text . endswith ( ' \\ ' ) :
com_text = com_text [ : - 1 ] . strip ( )
special = ' draft '
else :
if not req . content_mime . startswith ( ' text/ ' ) :
return 50 , ' Content must be text '
com_text = req . content . decode ( ' utf-8 ' )
2023-05-06 13:29:21 +00:00
com_id = db . create_post ( session . user , post . subspace , parent = post . id )
com = db . get_post ( com_id , draft = True )
2023-05-21 18:23:30 +00:00
if com_text :
db . create_segment ( com , Segment . TEXT , content = com_text )
db . update_post_summary ( com )
if not special :
db . publish_post ( com )
return 30 , session . server_root ( ) + post . page_url ( )
# Keep as a draft.
return 30 , session . server_root ( ) + session . path + f ' edit/ { com_id } '
2023-05-06 13:29:21 +00:00
def make_tags_page ( session ) :
# Check the rights for changing tags.
if not session . user :
return 60 , ' Login required '
db = session . db
req = session . req
2023-05-10 12:20:32 +00:00
found = re . search ( r ' /( \ d+)(/(add|remove|open|close))?$ ' , req . path )
2023-05-06 13:29:21 +00:00
if not found :
return 59 , ' Bad request '
post_id = int ( found [ 1 ] )
action = found [ 3 ]
post = db . get_post ( id = post_id )
if not post :
return 51 , ' Not found '
if post . parent :
return 59 , ' Comments cannot have tags '
if not session . is_deletable ( post ) :
return 61 , ' Not authorized to edit tags '
2023-05-10 12:20:32 +00:00
subspace = db . get_subspace ( id = post . subspace )
is_issue_tracker = ( subspace . flags & Subspace . ISSUE_TRACKER ) != 0
2023-06-15 09:26:42 +00:00
session . is_context_tracker = is_issue_tracker
2023-05-10 12:20:32 +00:00
2023-05-06 13:29:21 +00:00
edit_link = f ' /edit-tags/ { post . id } '
2023-05-10 12:20:32 +00:00
if is_issue_tracker :
if action == ' open ' :
action = ' remove '
req . query = Post . TAG_CLOSED
edit_link = post . page_url ( )
elif action == ' close ' :
action = ' add '
req . query = Post . TAG_CLOSED
edit_link = post . page_url ( )
2023-05-06 13:29:21 +00:00
if action == ' add ' :
if req . query is None :
return 10 , " Tag to add: " + session . NAME_HINT
tag = clean_query ( req )
if not is_valid_name ( tag ) :
return 59 , ' Invalid tag name '
2023-05-10 12:20:32 +00:00
if tag == Post . TAG_ANNOUNCEMENT and ( session . user . role != User . ADMIN or
is_issue_tracker ) :
2023-05-06 13:29:21 +00:00
return 61 , ' Not authorized '
2023-05-10 16:03:00 +00:00
db . modify_tags ( post , tag , session . user , add = True )
2023-05-06 13:29:21 +00:00
# Only pinned or announcement can be used at a time.
if tag == Post . TAG_ANNOUNCEMENT :
2023-05-10 16:03:00 +00:00
db . modify_tags ( post , Post . TAG_PINNED , session . user , add = False )
2023-05-06 13:29:21 +00:00
elif tag == Post . TAG_PINNED :
2023-05-10 16:03:00 +00:00
db . modify_tags ( post , Post . TAG_ANNOUNCEMENT , session . user , add = False )
2023-05-06 13:29:21 +00:00
return 30 , edit_link
if action == ' remove ' :
if req . query is None :
return 10 , " Tag to remove: "
tag = clean_query ( req )
if tag == Post . TAG_ANNOUNCEMENT and session . user . role != User . ADMIN :
return 61 , ' Not authorized '
2023-05-10 16:03:00 +00:00
db . modify_tags ( post , tag , session . user , add = False )
2023-05-06 13:29:21 +00:00
return 30 , edit_link
if not req . query :
2023-06-15 09:26:42 +00:00
Kind = " Issue " if is_issue_tracker else " Post "
kind = Kind . lower ( )
page = f ' # { Kind } Tags \n \n '
2023-05-06 13:29:21 +00:00
page + = ' Editing tags on: \n '
2023-06-15 09:26:42 +00:00
page + = session . gemini_feed_entry ( post , subspace )
2023-05-09 03:36:42 +00:00
tags = list ( filter ( lambda tag : tag != Post . TAG_POLL , db . get_tags ( post ) ) )
2023-05-25 12:36:49 +00:00
popular_tags = db . get_popular_tags ( subspace )
2023-06-15 09:26:42 +00:00
#if len(tags):
# page += '### ' + ' '.join(map(lambda t: '#' + t, tags)) + '\n'
if tags :
page + = f ' \n Current tags on the { kind } (click to remove): \n '
for tag in tags :
page + = f ' => { edit_link } /remove? { tag } ❌ { tag } \n '
2023-05-06 13:29:21 +00:00
page + = ' \n ## Add Tag \n '
page + = f ' => { edit_link } /add New tag \n '
2023-05-07 06:09:10 +00:00
for tag in sorted ( popular_tags [ : 15 ] ) :
2023-05-10 12:20:32 +00:00
if tag in ( Post . TAG_PINNED , Post . TAG_ANNOUNCEMENT , Post . TAG_POLL , Post . TAG_CLOSED ) :
2023-05-06 13:29:21 +00:00
continue
2023-05-07 06:09:10 +00:00
if not tag in tags :
page + = f ' => { edit_link } /add? { tag } 🏷️ { tag } \n '
if session . user . role == User . ADMIN or post . is_pinned == 0 :
page + = ' \n '
2023-05-06 13:29:21 +00:00
if post . is_pinned != 1 :
2023-05-09 03:36:42 +00:00
page + = f ' => { edit_link } /add? { Post . TAG_PINNED } 📌 { Post . TAG_PINNED } \n '
2023-05-10 12:20:32 +00:00
if not is_issue_tracker and post . is_pinned != 2 and session . user . role == User . ADMIN :
2023-05-09 03:36:42 +00:00
page + = f ' => { edit_link } /add? { Post . TAG_ANNOUNCEMENT } 📣 { Post . TAG_ANNOUNCEMENT } \n '
2023-05-10 12:20:32 +00:00
if is_issue_tracker and Post . TAG_CLOSED not in tags :
page + = f ' => { edit_link } /add? { Post . TAG_CLOSED } ✔︎ { Post . TAG_CLOSED } \n '
2023-05-06 13:29:21 +00:00
return page
return 30 , post . page_url ( )