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-05-13 14:55:24 +00:00
found = re . match ( r ' ^(edit|move)-segment/( \ d+)$ ' , req . path [ len ( session . path ) : ] )
2023-05-06 13:29:21 +00:00
seg_action = found . group ( 1 )
seg_id = int ( found . group ( 2 ) )
segment = db . get_segment ( seg_id )
if not segment :
return 51 , ' Not found '
2023-05-13 14:55:24 +00:00
if is_empty_query ( req ) and session . is_gemini :
2023-05-07 21:04:14 +00:00
if seg_action == ' edit ' :
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) '
return 10 , ' Edited text: ' if seg_action == ' edit ' else \
2023-05-06 13:29:21 +00:00
' Move to which position §? (X to remove) '
post = db . get_post ( segment . post )
if not session . is_editable ( post ) :
return 61 , ' Cannot edit posts by other users '
if seg_action == ' edit ' :
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 )
else :
seg_text = clean_text ( req . content . decode ( ' utf-8 ' ) )
db . update_segment ( segment , content = seg_text )
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 :
seg_text = clean_title ( clean_query ( req ) )
db . update_segment ( segment , content = seg_text )
else :
arg = clean_query ( req )
if arg . upper ( ) == ' X ' :
db . destroy_segment ( segment )
else :
db . move_segment ( post , segment , int ( arg ) - 1 )
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-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-06 13:29:21 +00:00
post_id = int ( found . group ( 1 ) )
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-05-10 12:20:32 +00:00
subspace = db . get_subspace ( id = post . subspace )
2023-05-06 13:29:21 +00:00
if not session . user :
return 60 , ' Must be signed in to edit posts '
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-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
post_type = ' Issue ' if is_issue_tracker else ' Post '
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 ' ) )
db . create_segment ( post , Segment . TEXT , content = seg_text )
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 ' :
db . update_post ( post , flags = post . flags ^ Post . OMIT_FROM_FEED_FLAG )
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 '
page + = ' \n # Feed Preview \n '
2023-05-10 18:46:55 +00:00
session . context = subspace
session . is_context_tracker = is_issue_tracker
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-05-10 18:46:55 +00:00
if is_issue_tracker :
if len ( post . title ) == 0 :
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-05-10 18:46:55 +00:00
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-05-11 18:44:22 +00:00
page + = f ' => { link } /omit-feed { session . CHECKS [ nonzero ( post . flags & Post . OMIT_FROM_FEED_FLAG ) ] } Omit { post_type . lower ( ) } from Gemini feed \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 ( )
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 '
page + = f " => { session . path } move-segment/ { opt . id } ↕︎ Move/remove \n "
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-07 21:04:14 +00:00
f " \n => { seg_link if len ( segment . content ) < 850 else titan_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-05-07 21:04:14 +00:00
page + = f " => { session . path } move-segment/ { segment . id } ↕︎ Move/remove \n "
2023-05-06 13:29:21 +00:00
if len ( segments ) :
page + = f ' \n '
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-06 13:29:21 +00:00
page + = f ' => { link } /add-poll Add poll option \n '
2023-05-07 06:09:10 +00:00
page + = f ' \n => { link } /delete/ { user_token } ❌ Delete { kind . lower ( ) } \n '
2023-05-06 13:29:21 +00:00
return page
def make_comment ( session ) :
if not session . user :
return 60 , ' Login required '
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 '
com_text = clean_query ( req )
if len ( com_text ) == 0 :
return 10 , ' Enter comment: '
com_id = db . create_post ( session . user , post . subspace , parent = post . id )
com = db . get_post ( com_id , draft = True )
db . create_segment ( com , Segment . TEXT , content = com_text )
db . update_post_summary ( com )
db . publish_post ( com )
db . notify_commenters ( com )
return 30 , post . page_url ( )
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-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 :
page = ' # Post Tags \n \n '
page + = ' Editing tags on: \n '
page + = session . gemini_feed_entry ( post )
2023-05-09 03:36:42 +00:00
tags = list ( filter ( lambda tag : tag != Post . TAG_POLL , db . get_tags ( post ) ) )
2023-05-06 13:29:21 +00:00
popular_tags = db . get_popular_tags ( )
if len ( tags ) :
page + = ' \n \u200b ' + ' ' . join ( map ( lambda t : ' # ' + t , tags ) ) + ' \n '
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
2023-05-09 03:36:42 +00:00
if tags :
page + = ' \n ## Remove Tag \n '
for tag in tags :
page + = f ' => { edit_link } /remove? { tag } ❌ { tag } \n '
2023-05-06 13:29:21 +00:00
return page
return 30 , post . page_url ( )