__filename__ = "webapp_create_post.py" __author__ = "Bob Mottram" __license__ = "AGPL3+" __version__ = "1.2.0" __maintainer__ = "Bob Mottram" __email__ = "bob@freedombone.net" __status__ = "Production" __module_group__ = "Web Interface" import os from utils import isPublicPostFromUrl from utils import getNicknameFromActor from utils import getDomainFromActor from utils import getImageFormats from utils import getMediaFormats from utils import getConfigParam from utils import acctDir from webapp_utils import getBannerFile from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlFooter def _htmlFollowingDataList(baseDir: str, nickname: str, domain: str, domainFull: str) -> str: """Returns a datalist of handles being followed """ listStr = '<datalist id="followingHandles">\n' followingFilename = \ acctDir(baseDir, nickname, domain) + '/following.txt' msg = None if os.path.isfile(followingFilename): with open(followingFilename, 'r') as followingFile: msg = followingFile.read() # add your own handle, so that you can send DMs # to yourself as reminders msg += nickname + '@' + domainFull + '\n' if msg: # include petnames petnamesFilename = \ acctDir(baseDir, nickname, domain) + '/petnames.txt' if os.path.isfile(petnamesFilename): followingList = [] with open(petnamesFilename, 'r') as petnamesFile: petStr = petnamesFile.read() # extract each petname and append it petnamesList = petStr.split('\n') for pet in petnamesList: followingList.append(pet.split(' ')[0]) # add the following.txt entries followingList += msg.split('\n') else: # no petnames list exists - just use following.txt followingList = msg.split('\n') followingList.sort() if followingList: for followingAddress in followingList: if followingAddress: listStr += '<option>@' + followingAddress + '</option>\n' listStr += '</datalist>\n' return listStr def _htmlNewPostDropDown(scopeIcon: str, scopeDescription: str, replyStr: str, translate: {}, showPublicOnDropdown: bool, defaultTimeline: str, pathBase: str, dropdownNewPostSuffix: str, dropdownNewBlogSuffix: str, dropdownUnlistedSuffix: str, dropdownFollowersSuffix: str, dropdownDMSuffix: str, dropdownReminderSuffix: str, dropdownEventSuffix: str, dropdownReportSuffix: str, noDropDown: bool, accessKeys: {}) -> str: """Returns the html for a drop down list of new post types """ dropDownContent = '<nav><div class="newPostDropdown">\n' if not noDropDown: dropDownContent += ' <input type="checkbox" ' + \ 'id="my-newPostDropdown" value="" name="my-checkbox">\n' dropDownContent += ' <label for="my-newPostDropdown"\n' dropDownContent += ' data-toggle="newPostDropdown">\n' dropDownContent += ' <img loading="lazy" alt="" title="" src="/' + \ 'icons/' + scopeIcon + '"/><b>' + scopeDescription + '</b></label>\n' if noDropDown: dropDownContent += '</div></nav>\n' return dropDownContent dropDownContent += ' <ul>\n' if showPublicOnDropdown: dropDownContent += \ '<li><a href="' + pathBase + dropdownNewPostSuffix + \ '" accesskey="' + accessKeys['Public'] + '">' + \ '<img loading="lazy" alt="" title="" src="/' + \ 'icons/scope_public.png"/><b>' + \ translate['Public'] + '</b><br>' + \ translate['Visible to anyone'] + '</a></li>\n' if defaultTimeline == 'tlfeatures': dropDownContent += \ '<li><a href="' + pathBase + dropdownNewBlogSuffix + \ '" accesskey="' + accessKeys['menuBlogs'] + '">' + \ '<img loading="lazy" alt="" title="" src="/' + \ 'icons/scope_blog.png"/><b>' + \ translate['Article'] + '</b><br>' + \ translate['Create an article'] + '</a></li>\n' else: dropDownContent += \ '<li><a href="' + pathBase + dropdownNewBlogSuffix + \ '" accesskey="' + accessKeys['menuBlogs'] + '">' + \ '<img loading="lazy" alt="" title="" src="/' + \ 'icons/scope_blog.png"/><b>' + \ translate['Blog'] + '</b><br>' + \ translate['Publicly visible post'] + '</a></li>\n' dropDownContent += \ '<li><a href="' + pathBase + dropdownUnlistedSuffix + \ '"><img loading="lazy" alt="" title="" src="/' + \ 'icons/scope_unlisted.png"/><b>' + \ translate['Unlisted'] + '</b><br>' + \ translate['Not on public timeline'] + '</a></li>\n' dropDownContent += \ '<li><a href="' + pathBase + dropdownFollowersSuffix + \ '" accesskey="' + accessKeys['menuFollowers'] + '">' + \ '<img loading="lazy" alt="" title="" src="/' + \ 'icons/scope_followers.png"/><b>' + \ translate['Followers'] + '</b><br>' + \ translate['Only to followers'] + '</a></li>\n' dropDownContent += \ '<li><a href="' + pathBase + dropdownDMSuffix + \ '" accesskey="' + accessKeys['menuDM'] + '">' + \ '<img loading="lazy" alt="" title="" src="/' + \ 'icons/scope_dm.png"/><b>' + \ translate['DM'] + '</b><br>' + \ translate['Only to mentioned people'] + '</a></li>\n' dropDownContent += \ '<li><a href="' + pathBase + dropdownReminderSuffix + \ '" accesskey="' + accessKeys['Reminder'] + '">' + \ '<img loading="lazy" alt="" title="" src="/' + \ 'icons/scope_reminder.png"/><b>' + \ translate['Reminder'] + '</b><br>' + \ translate['Scheduled note to yourself'] + '</a></li>\n' dropDownContent += \ '<li><a href="' + pathBase + dropdownReportSuffix + \ '" accesskey="' + accessKeys['reportButton'] + '">' + \ '<img loading="lazy" alt="" title="" src="/' + \ 'icons/scope_report.png"/><b>' + \ translate['Report'] + '</b><br>' + \ translate['Send to moderators'] + '</a></li>\n' if not replyStr: dropDownContent += \ '<li><a href="' + pathBase + \ '/newshare" accesskey="' + accessKeys['menuShares'] + '">' + \ '<img loading="lazy" alt="" title="" src="/' + \ 'icons/scope_share.png"/><b>' + \ translate['Shares'] + '</b><br>' + \ translate['Describe a shared item'] + '</a></li>\n' dropDownContent += \ '<li><a href="' + pathBase + \ '/newquestion"><img loading="lazy" alt="" title="" src="/' + \ 'icons/scope_question.png"/><b>' + \ translate['Question'] + '</b><br>' + \ translate['Ask a question'] + '</a></li>\n' dropDownContent += ' </ul>\n' dropDownContent += '</div></nav>\n' return dropDownContent def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {}, baseDir: str, httpPrefix: str, path: str, inReplyTo: str, mentions: [], shareDescription: str, reportUrl: str, pageNumber: int, nickname: str, domain: str, domainFull: str, defaultTimeline: str, newswire: {}, theme: str, noDropDown: bool, accessKeys: {}, customSubmitText: str) -> str: """New post screen """ replyStr = '' showPublicOnDropdown = True messageBoxHeight = 400 # filename of the banner shown at the top bannerFile, bannerFilename = \ getBannerFile(baseDir, nickname, domain, theme) if not path.endswith('/newshare'): if not path.endswith('/newreport'): if not inReplyTo or path.endswith('/newreminder'): newPostText = '<h1>' + \ translate['Write your post text below.'] + '</h1>\n' else: newPostText = \ '<p class="new-post-text">' + \ translate['Write your reply to'] + \ ' <a href="' + inReplyTo + \ '" rel="nofollow noopener noreferrer" ' + \ 'target="_blank">' + \ translate['this post'] + '</a></p>\n' replyStr = '<input type="hidden" ' + \ 'name="replyTo" value="' + inReplyTo + '">\n' # if replying to a non-public post then also make # this post non-public if not isPublicPostFromUrl(baseDir, nickname, domain, inReplyTo): newPostPath = path if '?' in newPostPath: newPostPath = newPostPath.split('?')[0] if newPostPath.endswith('/newpost'): path = path.replace('/newpost', '/newfollowers') elif newPostPath.endswith('/newunlisted'): path = path.replace('/newunlisted', '/newfollowers') showPublicOnDropdown = False else: newPostText = \ '<h1>' + translate['Write your report below.'] + '</h1>\n' # custom report header with any additional instructions if os.path.isfile(baseDir + '/accounts/report.txt'): with open(baseDir + '/accounts/report.txt', 'r') as file: customReportText = file.read() if '</p>' not in customReportText: customReportText = \ '<p class="login-subtext">' + \ customReportText + '</p>\n' repStr = '<p class="login-subtext">' customReportText = \ customReportText.replace('<p>', repStr) newPostText += customReportText idx = 'This message only goes to moderators, even if it ' + \ 'mentions other fediverse addresses.' newPostText += \ '<p class="new-post-subtext">' + translate[idx] + '</p>\n' + \ '<p class="new-post-subtext">' + translate['Also see'] + \ ' <a href="/terms">' + \ translate['Terms of Service'] + '</a></p>\n' else: newPostText = \ '<h1>' + \ translate['Enter the details for your shared item below.'] + \ '</h1>\n' if path.endswith('/newquestion'): newPostText = \ '<h1>' + \ translate['Enter the choices for your question below.'] + \ '</h1>\n' if os.path.isfile(baseDir + '/accounts/newpost.txt'): with open(baseDir + '/accounts/newpost.txt', 'r') as file: newPostText = \ '<p>' + file.read() + '</p>\n' cssFilename = baseDir + '/epicyon-profile.css' if os.path.isfile(baseDir + '/epicyon.css'): cssFilename = baseDir + '/epicyon.css' if '?' in path: path = path.split('?')[0] pathBase = path.replace('/newreport', '').replace('/newpost', '') pathBase = pathBase.replace('/newblog', '').replace('/newshare', '') pathBase = pathBase.replace('/newunlisted', '') pathBase = pathBase.replace('/newevent', '') pathBase = pathBase.replace('/newreminder', '') pathBase = pathBase.replace('/newfollowers', '').replace('/newdm', '') newPostImageSection = ' <div class="container">' if not path.endswith('/newevent'): newPostImageSection += \ ' <label class="labels">' + \ translate['Image description'] + '</label>\n' else: newPostImageSection += \ ' <label class="labels">' + \ translate['Event banner image description'] + '</label>\n' newPostImageSection += \ ' <input type="text" name="imageDescription">\n' if path.endswith('/newevent'): newPostImageSection += \ ' <label class="labels">' + \ translate['Banner image'] + '</label>\n' newPostImageSection += \ ' <input type="file" id="attachpic" name="attachpic"' newPostImageSection += \ ' accept="' + getImageFormats() + '">\n' else: newPostImageSection += \ ' <input type="file" id="attachpic" name="attachpic"' newPostImageSection += \ ' accept="' + getMediaFormats() + '">\n' newPostImageSection += ' </div>\n' scopeIcon = 'scope_public.png' scopeDescription = translate['Public'] if shareDescription: placeholderSubject = translate['Ask about a shared item.'] + '..' else: placeholderSubject = \ translate['Subject or Content Warning (optional)'] + '...' placeholderMentions = '' if inReplyTo: placeholderMentions = \ translate['Replying to'] + '...' placeholderMessage = translate['Write something'] + '...' extraFields = '' endpoint = 'newpost' if path.endswith('/newblog'): placeholderSubject = translate['Title'] scopeIcon = 'scope_blog.png' if defaultTimeline != 'tlfeatures': scopeDescription = translate['Blog'] else: scopeDescription = translate['Article'] endpoint = 'newblog' elif path.endswith('/newunlisted'): scopeIcon = 'scope_unlisted.png' scopeDescription = translate['Unlisted'] endpoint = 'newunlisted' elif path.endswith('/newfollowers'): scopeIcon = 'scope_followers.png' scopeDescription = translate['Followers'] endpoint = 'newfollowers' elif path.endswith('/newdm'): scopeIcon = 'scope_dm.png' scopeDescription = translate['DM'] endpoint = 'newdm' elif path.endswith('/newreminder'): scopeIcon = 'scope_reminder.png' scopeDescription = translate['Reminder'] endpoint = 'newreminder' elif path.endswith('/newevent'): scopeIcon = 'scope_event.png' scopeDescription = translate['Event'] endpoint = 'newevent' placeholderSubject = translate['Event name'] placeholderMessage = translate['Describe the event'] + '...' elif path.endswith('/newreport'): scopeIcon = 'scope_report.png' scopeDescription = translate['Report'] endpoint = 'newreport' elif path.endswith('/newquestion'): scopeIcon = 'scope_question.png' scopeDescription = translate['Question'] placeholderMessage = translate['Enter your question'] + '...' endpoint = 'newquestion' extraFields = '<div class="container">\n' extraFields += ' <label class="labels">' + \ translate['Possible answers'] + ':</label><br>\n' for questionCtr in range(8): extraFields += \ ' <input type="text" class="questionOption" placeholder="' + \ str(questionCtr + 1) + \ '" name="questionOption' + str(questionCtr) + '"><br>\n' extraFields += \ ' <label class="labels">' + \ translate['Duration of listing in days'] + \ ':</label> <input type="number" name="duration" ' + \ 'min="1" max="365" step="1" value="14"><br>\n' extraFields += '</div>' elif path.endswith('/newshare'): scopeIcon = 'scope_share.png' scopeDescription = translate['Shared Item'] placeholderSubject = translate['Name of the shared item'] + '...' placeholderMessage = \ translate['Description of the item being shared'] + '...' endpoint = 'newshare' extraFields = '<div class="container">\n' extraFields += \ ' <label class="labels">' + \ translate['Type of shared item. eg. hat'] + ':</label>\n' extraFields += \ ' <input type="text" class="itemType" name="itemType">\n' extraFields += \ ' <br><label class="labels">' + \ translate['Category of shared item. eg. clothing'] + ':</label>\n' extraFields += \ ' <input type="text" class="category" name="category">\n' extraFields += \ ' <br><label class="labels">' + \ translate['Duration of listing in days'] + ':</label>\n' extraFields += ' <input type="number" name="duration" ' + \ 'min="1" max="365" step="1" value="14">\n' extraFields += '</div>\n' extraFields += '<div class="container">\n' extraFields += \ '<label class="labels">' + \ translate['City or location of the shared item'] + ':</label>\n' extraFields += '<input type="text" name="location">\n' extraFields += '</div>\n' citationsStr = '' if endpoint == 'newblog': citationsFilename = \ acctDir(baseDir, nickname, domain) + '/.citations.txt' if os.path.isfile(citationsFilename): citationsStr = '<div class="container">\n' citationsStr += '<p><label class="labels">' + \ translate['Citations'] + ':</label></p>\n' citationsStr += ' <ul>\n' citationsSeparator = '#####' with open(citationsFilename, 'r') as f: citations = f.readlines() for line in citations: if citationsSeparator not in line: continue sections = line.strip().split(citationsSeparator) if len(sections) != 3: continue title = sections[1] link = sections[2] citationsStr += \ ' <li><a href="' + link + '"><cite>' + \ title + '</cite></a></li>' citationsStr += ' </ul>\n' citationsStr += '</div>\n' dateAndLocation = '' if endpoint != 'newshare' and \ endpoint != 'newreport' and \ endpoint != 'newquestion': dateAndLocation = '<div class="container">\n' if endpoint == 'newevent': # event status dateAndLocation += '<label class="labels">' + \ translate['Status of the event'] + ':</label><br>\n' dateAndLocation += '<input type="radio" id="tentative" ' + \ 'name="eventStatus" value="tentative">\n' dateAndLocation += '<label class="labels" for="tentative">' + \ translate['Tentative'] + '</label><br>\n' dateAndLocation += '<input type="radio" id="confirmed" ' + \ 'name="eventStatus" value="confirmed" checked>\n' dateAndLocation += '<label class="labels" for="confirmed">' + \ translate['Confirmed'] + '</label><br>\n' dateAndLocation += '<input type="radio" id="cancelled" ' + \ 'name="eventStatus" value="cancelled">\n' dateAndLocation += '<label class="labels" for="cancelled">' + \ translate['Cancelled'] + '</label><br>\n' dateAndLocation += '</div>\n' dateAndLocation += '<div class="container">\n' # maximum attendees dateAndLocation += '<label class="labels" ' + \ 'for="maximumAttendeeCapacity">' + \ translate['Maximum attendees'] + ':</label>\n' dateAndLocation += '<input type="number" ' + \ 'id="maximumAttendeeCapacity" ' + \ 'name="maximumAttendeeCapacity" min="1" max="999999" ' + \ 'value="100">\n' dateAndLocation += '</div>\n' dateAndLocation += '<div class="container">\n' # event joining options dateAndLocation += '<label class="labels">' + \ translate['Joining'] + ':</label><br>\n' dateAndLocation += '<input type="radio" id="free" ' + \ 'name="joinMode" value="free" checked>\n' dateAndLocation += '<label class="labels" for="free">' + \ translate['Anyone can join'] + '</label><br>\n' dateAndLocation += '<input type="radio" id="restricted" ' + \ 'name="joinMode" value="restricted">\n' dateAndLocation += '<label class="labels" for="female">' + \ translate['Apply to join'] + '</label><br>\n' dateAndLocation += '<input type="radio" id="invite" ' + \ 'name="joinMode" value="invite">\n' dateAndLocation += '<label class="labels" for="other">' + \ translate['Invitation only'] + '</label>\n' dateAndLocation += '</div>\n' dateAndLocation += '<div class="container">\n' # Event posts don't allow replies - they're just an announcement. # They also have a few more checkboxes dateAndLocation += \ '<p><input type="checkbox" class="profilecheckbox" ' + \ 'name="privateEvent"><label class="labels"> ' + \ translate['This is a private event.'] + '</label></p>\n' dateAndLocation += \ '<p><input type="checkbox" class="profilecheckbox" ' + \ 'name="anonymousParticipationEnabled">' + \ '<label class="labels"> ' + \ translate['Allow anonymous participation.'] + '</label></p>\n' else: dateAndLocation += \ '<p><input type="checkbox" class="profilecheckbox" ' + \ 'name="commentsEnabled" checked><label class="labels"> ' + \ translate['Allow replies.'] + '</label></p>\n' if endpoint == 'newpost': dateAndLocation += \ '<p><input type="checkbox" class="profilecheckbox" ' + \ 'name="pinToProfile"><label class="labels"> ' + \ translate['Pin this post to your profile.'] + '</label></p>\n' if not inReplyTo and endpoint != 'newevent': dateAndLocation += \ '<p><input type="checkbox" class="profilecheckbox" ' + \ 'name="schedulePost"><label class="labels"> ' + \ translate['This is a scheduled post.'] + '</label></p>\n' if endpoint != 'newevent': dateAndLocation += \ '<p><img loading="lazy" alt="" title="" ' + \ 'class="emojicalendar" src="/' + \ 'icons/calendar.png"/>\n' # select a date and time for this post dateAndLocation += '<label class="labels">' + \ translate['Date'] + ': </label>\n' dateAndLocation += '<input type="date" name="eventDate">\n' dateAndLocation += '<label class="labelsright">' + \ translate['Time'] + ':' dateAndLocation += \ '<input type="time" name="eventTime"></label></p>\n' else: dateAndLocation += '</div>\n' dateAndLocation += '<div class="container">\n' dateAndLocation += \ '<p><img loading="lazy" alt="" title="" ' + \ 'class="emojicalendar" src="/' + \ 'icons/calendar.png"/>\n' # select start time for the event dateAndLocation += '<label class="labels">' + \ translate['Start Date'] + ': </label>\n' dateAndLocation += '<input type="date" name="eventDate">\n' dateAndLocation += '<label class="labelsright">' + \ translate['Time'] + ':' dateAndLocation += \ '<input type="time" name="eventTime"></label></p>\n' # select end time for the event dateAndLocation += \ '<br><img loading="lazy" alt="" title="" ' + \ 'class="emojicalendar" src="/' + \ 'icons/calendar.png"/>\n' dateAndLocation += '<label class="labels">' + \ translate['End Date'] + ': </label>\n' dateAndLocation += '<input type="date" name="endDate">\n' dateAndLocation += '<label class="labelsright">' + \ translate['Time'] + ':' dateAndLocation += \ '<input type="time" name="endTime"></label>\n' if endpoint == 'newevent': dateAndLocation += '</div>\n' dateAndLocation += '<div class="container">\n' dateAndLocation += '<br><label class="labels">' + \ translate['Moderation policy or code of conduct'] + \ ': </label>\n' dateAndLocation += \ ' <textarea id="message" ' + \ 'name="repliesModerationOption" style="height:' + \ str(messageBoxHeight) + 'px" spellcheck="true" ' + \ 'autocomplete="on"></textarea>\n' dateAndLocation += '</div>\n' dateAndLocation += '<div class="container">\n' dateAndLocation += '<label class="labels">' + \ translate['Location'] + ': </label>\n' dateAndLocation += '<input type="text" name="location">\n' if endpoint == 'newevent': dateAndLocation += '<br><label class="labels">' + \ translate['Ticket URL'] + ': </label>\n' dateAndLocation += '<input type="text" name="ticketUrl">\n' dateAndLocation += '<br><label class="labels">' + \ translate['Categories'] + ': </label>\n' dateAndLocation += '<input type="text" name="category">\n' dateAndLocation += '</div>\n' instanceTitle = getConfigParam(baseDir, 'instanceTitle') newPostForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) newPostForm += \ '<header>\n' + \ '<a href="/users/' + nickname + '/' + defaultTimeline + '" title="' + \ translate['Switch to timeline view'] + '" alt="' + \ translate['Switch to timeline view'] + '" ' + \ 'accesskey="' + accessKeys['menuTimeline'] + '">\n' newPostForm += '<img loading="lazy" class="timeline-banner" src="' + \ '/users/' + nickname + '/' + bannerFile + '" alt="" /></a>\n' + \ '</header>\n' mentionsStr = '' for m in mentions: mentionNickname = getNicknameFromActor(m) if not mentionNickname: continue mentionDomain, mentionPort = getDomainFromActor(m) if not mentionDomain: continue if mentionPort: mentionsHandle = \ '@' + mentionNickname + '@' + \ mentionDomain + ':' + str(mentionPort) else: mentionsHandle = '@' + mentionNickname + '@' + mentionDomain if mentionsHandle not in mentionsStr: mentionsStr += mentionsHandle + ' ' # build suffixes so that any replies or mentions are # preserved when switching between scopes dropdownNewPostSuffix = '/newpost' dropdownNewBlogSuffix = '/newblog' dropdownUnlistedSuffix = '/newunlisted' dropdownFollowersSuffix = '/newfollowers' dropdownDMSuffix = '/newdm' dropdownEventSuffix = '/newevent' dropdownReminderSuffix = '/newreminder' dropdownReportSuffix = '/newreport' if inReplyTo or mentions: dropdownNewPostSuffix = '' dropdownNewBlogSuffix = '' dropdownUnlistedSuffix = '' dropdownFollowersSuffix = '' dropdownDMSuffix = '' dropdownEventSuffix = '' dropdownReminderSuffix = '' dropdownReportSuffix = '' if inReplyTo: dropdownNewPostSuffix += '?replyto=' + inReplyTo dropdownNewBlogSuffix += '?replyto=' + inReplyTo dropdownUnlistedSuffix += '?replyto=' + inReplyTo dropdownFollowersSuffix += '?replyfollowers=' + inReplyTo dropdownDMSuffix += '?replydm=' + inReplyTo for mentionedActor in mentions: dropdownNewPostSuffix += '?mention=' + mentionedActor dropdownNewBlogSuffix += '?mention=' + mentionedActor dropdownUnlistedSuffix += '?mention=' + mentionedActor dropdownFollowersSuffix += '?mention=' + mentionedActor dropdownDMSuffix += '?mention=' + mentionedActor dropdownReportSuffix += '?mention=' + mentionedActor dropDownContent = '' if not reportUrl and not shareDescription: dropDownContent = \ _htmlNewPostDropDown(scopeIcon, scopeDescription, replyStr, translate, showPublicOnDropdown, defaultTimeline, pathBase, dropdownNewPostSuffix, dropdownNewBlogSuffix, dropdownUnlistedSuffix, dropdownFollowersSuffix, dropdownDMSuffix, dropdownReminderSuffix, dropdownEventSuffix, dropdownReportSuffix, noDropDown, accessKeys) else: if not shareDescription: # reporting a post to moderator mentionsStr = 'Re: ' + reportUrl + '\n\n' + mentionsStr newPostForm += \ '<form enctype="multipart/form-data" method="POST" ' + \ 'accept-charset="UTF-8" action="' + \ path + '?' + endpoint + '?page=' + str(pageNumber) + '">\n' newPostForm += ' <div class="vertical-center">\n' newPostForm += \ ' <label for="nickname"><b>' + newPostText + '</b></label>\n' newPostForm += ' <div class="containerNewPost">\n' newPostForm += ' <table style="width:100%" border="0">\n' newPostForm += ' <colgroup>\n' newPostForm += ' <col span="1" style="width:70%">\n' newPostForm += ' <col span="1" style="width:10%">\n' if newswire and path.endswith('/newblog'): newPostForm += ' <col span="1" style="width:10%">\n' newPostForm += ' <col span="1" style="width:10%">\n' else: newPostForm += ' <col span="1" style="width:20%">\n' newPostForm += ' </colgroup>\n' newPostForm += '<tr>\n' newPostForm += '<td>' + dropDownContent + '</td>\n' newPostForm += \ ' <td><a href="' + pathBase + \ '/searchemoji"><img loading="lazy" class="emojisearch" ' + \ 'src="/emoji/1F601.png" title="' + \ translate['Search for emoji'] + '" alt="' + \ translate['Search for emoji'] + '"/></a></td>\n' # for a new blog if newswire items exist then add a citations button if newswire and path.endswith('/newblog'): newPostForm += \ ' <td><input type="submit" name="submitCitations" value="' + \ translate['Citations'] + '"></td>\n' submitText = translate['Submit'] if customSubmitText: submitText = customSubmitText newPostForm += \ ' <td><input type="submit" name="submitPost" value="' + \ submitText + '" ' + \ 'accesskey="' + accessKeys['submitButton'] + '"></td>\n' newPostForm += ' </tr>\n</table>\n' newPostForm += ' </div>\n' newPostForm += ' <div class="containerSubmitNewPost"><center>\n' newPostForm += ' </center></div>\n' newPostForm += replyStr if mediaInstance and not replyStr: newPostForm += newPostImageSection newPostForm += \ ' <label class="labels">' + placeholderSubject + '</label><br>' if not shareDescription: shareDescription = '' newPostForm += \ ' <input type="text" name="subject" value="' + \ shareDescription + '">' newPostForm += '' selectedStr = ' selected' if inReplyTo or endpoint == 'newdm': if inReplyTo: newPostForm += \ ' <label class="labels">' + placeholderMentions + \ '</label><br>\n' else: newPostForm += \ ' <a href="/users/' + nickname + \ '/followingaccounts" title="' + \ translate['Show a list of addresses to send to'] + '">' \ '<label class="labels">' + \ translate['Send to'] + ':' + '</label> 📄</a><br>\n' newPostForm += \ ' <input type="text" name="mentions" ' + \ 'list="followingHandles" value="' + mentionsStr + '" selected>\n' newPostForm += \ _htmlFollowingDataList(baseDir, nickname, domain, domainFull) newPostForm += '' selectedStr = '' newPostForm += \ ' <br><label class="labels">' + placeholderMessage + '</label>' if mediaInstance: messageBoxHeight = 200 if endpoint == 'newquestion': messageBoxHeight = 100 elif endpoint == 'newblog': messageBoxHeight = 800 newPostForm += \ ' <textarea id="message" name="message" style="height:' + \ str(messageBoxHeight) + 'px"' + selectedStr + \ ' spellcheck="true" autocomplete="on">' + \ '</textarea>\n' newPostForm += extraFields + citationsStr + dateAndLocation if not mediaInstance or replyStr: newPostForm += newPostImageSection newPostForm += \ ' <div class="container">\n' + \ ' <input type="submit" name="submitPost" value="' + \ submitText + '">\n' + \ ' </div>\n' + \ ' </div>\n' + \ '</form>\n' if not reportUrl: newPostForm = \ newPostForm.replace('<body>', '<body onload="focusOnMessage()">') newPostForm += htmlFooter() return newPostForm