mirror of https://gitlab.com/bashrc2/epicyon
Split up webapp into smaller modules
parent
4a3a2c2319
commit
2f82d2281c
46
daemon.py
46
daemon.py
|
@ -114,46 +114,46 @@ from blog import htmlBlogPage
|
|||
from blog import htmlBlogPost
|
||||
from blog import htmlEditBlog
|
||||
from webapp_utils import setBlogAddress
|
||||
from webapp_utils import getBlogAddress
|
||||
from webapp_calendar import htmlCalendarDeleteConfirm
|
||||
from webapp_calendar import htmlCalendar
|
||||
from webapp import htmlCitations
|
||||
from webapp import htmlFollowingList
|
||||
from webapp import getBlogAddress
|
||||
from webapp import htmlDeletePost
|
||||
from webapp import htmlAbout
|
||||
from webapp import htmlRemoveSharedItem
|
||||
from webapp import htmlInboxDMs
|
||||
from webapp import htmlInboxReplies
|
||||
from webapp import htmlInboxMedia
|
||||
from webapp import htmlInboxBlogs
|
||||
from webapp import htmlInboxNews
|
||||
from webapp import htmlUnblockConfirm
|
||||
from webapp import htmlPersonOptions
|
||||
from webapp import htmlIndividualPost
|
||||
from webapp import htmlProfile
|
||||
from webapp import htmlInbox
|
||||
from webapp import htmlBookmarks
|
||||
from webapp import htmlEvents
|
||||
from webapp import htmlShares
|
||||
from webapp import htmlOutbox
|
||||
from webapp import htmlModeration
|
||||
from webapp import htmlPostReplies
|
||||
from webapp_person_options import htmlPersonOptions
|
||||
from webapp_timeline import htmlShares
|
||||
from webapp_timeline import htmlInbox
|
||||
from webapp_timeline import htmlBookmarks
|
||||
from webapp_timeline import htmlEvents
|
||||
from webapp_timeline import htmlInboxDMs
|
||||
from webapp_timeline import htmlInboxReplies
|
||||
from webapp_timeline import htmlInboxMedia
|
||||
from webapp_timeline import htmlInboxBlogs
|
||||
from webapp_timeline import htmlInboxNews
|
||||
from webapp_timeline import htmlOutbox
|
||||
from webapp_timeline import htmlModeration
|
||||
from webapp import htmlLogin
|
||||
from webapp import htmlSuspended
|
||||
from webapp import htmlGetLoginCredentials
|
||||
from webapp import htmlNewPost
|
||||
from webapp import htmlFollowConfirm
|
||||
from webapp import htmlNewswireMobile
|
||||
from webapp import htmlLinksMobile
|
||||
from webapp import htmlUnfollowConfirm
|
||||
from webapp import htmlProfileAfterSearch
|
||||
from webapp import htmlEditProfile
|
||||
from webapp import htmlEditLinks
|
||||
from webapp import htmlEditNewswire
|
||||
from webapp import htmlEditNewsPost
|
||||
from webapp import htmlTermsOfService
|
||||
from webapp import htmlModerationInfo
|
||||
from webapp import htmlHashtagBlocked
|
||||
from webapp_post import htmlPostReplies
|
||||
from webapp_post import htmlIndividualPost
|
||||
from webapp_profile import htmlEditProfile
|
||||
from webapp_profile import htmlProfileAfterSearch
|
||||
from webapp_profile import htmlProfile
|
||||
from webapp_column_left import htmlLinksMobile
|
||||
from webapp_column_left import htmlEditLinks
|
||||
from webapp_column_right import htmlNewswireMobile
|
||||
from webapp_column_right import htmlEditNewswire
|
||||
from webapp_column_right import htmlCitations
|
||||
from webapp_search import htmlSkillsSearch
|
||||
from webapp_search import htmlHistorySearch
|
||||
from webapp_search import htmlHashtagSearch
|
||||
|
|
|
@ -0,0 +1,317 @@
|
|||
__filename__ = "webapp_column_left.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
from shutil import copyfile
|
||||
from utils import getConfigParam
|
||||
from utils import getCSS
|
||||
from utils import getNicknameFromActor
|
||||
from posts import isEditor
|
||||
from webapp_utils import htmlPostSeparator
|
||||
from webapp_utils import getLeftImageFile
|
||||
from webapp_utils import getImageFile
|
||||
from webapp_utils import headerButtonsFrontScreen
|
||||
from webapp_utils import getIconsDir
|
||||
from webapp_utils import htmlHeader
|
||||
from webapp_utils import htmlFooter
|
||||
from webapp_utils import getBannerFile
|
||||
|
||||
|
||||
def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
|
||||
httpPrefix: str, translate: {},
|
||||
iconsDir: str, editor: bool,
|
||||
showBackButton: bool, timelinePath: str,
|
||||
rssIconAtTop: bool, showHeaderImage: bool,
|
||||
frontPage: bool) -> str:
|
||||
"""Returns html content for the left column
|
||||
"""
|
||||
htmlStr = ''
|
||||
|
||||
separatorStr = htmlPostSeparator(baseDir, 'left')
|
||||
domain = domainFull
|
||||
if ':' in domain:
|
||||
domain = domain.split(':')
|
||||
|
||||
editImageClass = ''
|
||||
if showHeaderImage:
|
||||
leftImageFile, leftColumnImageFilename = \
|
||||
getLeftImageFile(baseDir, nickname, domain)
|
||||
if not os.path.isfile(leftColumnImageFilename):
|
||||
theme = getConfigParam(baseDir, 'theme').lower()
|
||||
if theme == 'default':
|
||||
theme = ''
|
||||
else:
|
||||
theme = '_' + theme
|
||||
themeLeftImageFile, themeLeftColumnImageFilename = \
|
||||
getImageFile(baseDir, 'left_col_image', baseDir + '/img',
|
||||
nickname, domain)
|
||||
if os.path.isfile(themeLeftColumnImageFilename):
|
||||
leftColumnImageFilename = \
|
||||
baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/' + themeLeftImageFile
|
||||
copyfile(themeLeftColumnImageFilename,
|
||||
leftColumnImageFilename)
|
||||
leftImageFile = themeLeftImageFile
|
||||
|
||||
# show the image at the top of the column
|
||||
editImageClass = 'leftColEdit'
|
||||
if os.path.isfile(leftColumnImageFilename):
|
||||
editImageClass = 'leftColEditImage'
|
||||
htmlStr += \
|
||||
'\n <center>\n' + \
|
||||
' <img class="leftColImg" ' + \
|
||||
'loading="lazy" src="/users/' + \
|
||||
nickname + '/' + leftImageFile + '" />\n' + \
|
||||
' </center>\n'
|
||||
|
||||
if showBackButton:
|
||||
htmlStr += \
|
||||
' <div>' + \
|
||||
' <a href="' + timelinePath + '">' + \
|
||||
'<button class="cancelbtn">' + \
|
||||
translate['Go Back'] + '</button></a>\n'
|
||||
|
||||
if (editor or rssIconAtTop) and not showHeaderImage:
|
||||
htmlStr += '<div class="columnIcons">'
|
||||
|
||||
if editImageClass == 'leftColEdit':
|
||||
htmlStr += '\n <center>\n'
|
||||
|
||||
htmlStr += ' <div class="leftColIcons">\n'
|
||||
if editor:
|
||||
# show the edit icon
|
||||
htmlStr += \
|
||||
' <a href="' + \
|
||||
'/users/' + nickname + '/editlinks">' + \
|
||||
'<img class="' + editImageClass + \
|
||||
'" loading="lazy" alt="' + \
|
||||
translate['Edit Links'] + '" title="' + \
|
||||
translate['Edit Links'] + '" src="/' + \
|
||||
iconsDir + '/edit.png" /></a>\n'
|
||||
|
||||
# RSS icon
|
||||
if nickname != 'news':
|
||||
# rss feed for this account
|
||||
rssUrl = httpPrefix + '://' + domainFull + \
|
||||
'/blog/' + nickname + '/rss.xml'
|
||||
else:
|
||||
# rss feed for all accounts on the instance
|
||||
rssUrl = httpPrefix + '://' + domainFull + '/blog/rss.xml'
|
||||
if not frontPage:
|
||||
rssTitle = translate['RSS feed for your blog']
|
||||
else:
|
||||
rssTitle = translate['RSS feed for this site']
|
||||
rssIconStr = \
|
||||
' <a href="' + rssUrl + '">' + \
|
||||
'<img class="' + editImageClass + \
|
||||
'" loading="lazy" alt="' + rssTitle + \
|
||||
'" title="' + rssTitle + \
|
||||
'" src="/' + iconsDir + '/logorss.png" /></a>\n'
|
||||
if rssIconAtTop:
|
||||
htmlStr += rssIconStr
|
||||
htmlStr += ' </div>\n'
|
||||
|
||||
if editImageClass == 'leftColEdit':
|
||||
htmlStr += ' </center>\n'
|
||||
|
||||
if (editor or rssIconAtTop) and not showHeaderImage:
|
||||
htmlStr += '</div><br>'
|
||||
|
||||
# if showHeaderImage:
|
||||
# htmlStr += '<br>'
|
||||
|
||||
linksFilename = baseDir + '/accounts/links.txt'
|
||||
linksFileContainsEntries = False
|
||||
if os.path.isfile(linksFilename):
|
||||
linksList = None
|
||||
with open(linksFilename, "r") as f:
|
||||
linksList = f.readlines()
|
||||
if linksList:
|
||||
for lineStr in linksList:
|
||||
if ' ' not in lineStr:
|
||||
if '#' not in lineStr:
|
||||
if '*' not in lineStr:
|
||||
continue
|
||||
lineStr = lineStr.strip()
|
||||
words = lineStr.split(' ')
|
||||
# get the link
|
||||
linkStr = None
|
||||
for word in words:
|
||||
if word == '#':
|
||||
continue
|
||||
if word == '*':
|
||||
continue
|
||||
if '://' in word:
|
||||
linkStr = word
|
||||
break
|
||||
if linkStr:
|
||||
lineStr = lineStr.replace(linkStr, '').strip()
|
||||
# avoid any dubious scripts being added
|
||||
if '<' not in lineStr:
|
||||
# remove trailing comma if present
|
||||
if lineStr.endswith(','):
|
||||
lineStr = lineStr[:len(lineStr)-1]
|
||||
# add link to the returned html
|
||||
htmlStr += \
|
||||
' <p><a href="' + linkStr + '">' + \
|
||||
lineStr + '</a></p>\n'
|
||||
linksFileContainsEntries = True
|
||||
else:
|
||||
if lineStr.startswith('#') or lineStr.startswith('*'):
|
||||
lineStr = lineStr[1:].strip()
|
||||
htmlStr += separatorStr
|
||||
htmlStr += \
|
||||
' <h3 class="linksHeader">' + \
|
||||
lineStr + '</h3>\n'
|
||||
else:
|
||||
htmlStr += \
|
||||
' <p>' + lineStr + '</p>\n'
|
||||
linksFileContainsEntries = True
|
||||
|
||||
if linksFileContainsEntries and not rssIconAtTop:
|
||||
htmlStr += '<br><div class="columnIcons">' + rssIconStr + '</div>'
|
||||
return htmlStr
|
||||
|
||||
|
||||
def htmlLinksMobile(cssCache: {}, baseDir: str,
|
||||
nickname: str, domainFull: str,
|
||||
httpPrefix: str, translate,
|
||||
timelinePath: str, authorized: bool,
|
||||
rssIconAtTop: bool,
|
||||
iconsAsButtons: bool,
|
||||
defaultTimeline: str) -> str:
|
||||
"""Show the left column links within mobile view
|
||||
"""
|
||||
htmlStr = ''
|
||||
|
||||
# the css filename
|
||||
cssFilename = baseDir + '/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||
cssFilename = baseDir + '/epicyon.css'
|
||||
|
||||
profileStyle = getCSS(baseDir, cssFilename, cssCache)
|
||||
if profileStyle:
|
||||
# replace any https within the css with whatever prefix is needed
|
||||
if httpPrefix != 'https':
|
||||
profileStyle = \
|
||||
profileStyle.replace('https://', httpPrefix + '://')
|
||||
|
||||
iconsDir = getIconsDir(baseDir)
|
||||
|
||||
# is the user a site editor?
|
||||
if nickname == 'news':
|
||||
editor = False
|
||||
else:
|
||||
editor = isEditor(baseDir, nickname)
|
||||
|
||||
domain = domainFull
|
||||
if ':' in domain:
|
||||
domain = domain.split(':')[0]
|
||||
|
||||
htmlStr = htmlHeader(cssFilename, profileStyle)
|
||||
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
|
||||
htmlStr += \
|
||||
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
|
||||
'<img loading="lazy" class="timeline-banner" ' + \
|
||||
'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n'
|
||||
|
||||
htmlStr += '<center>' + \
|
||||
headerButtonsFrontScreen(translate, nickname,
|
||||
'links', authorized,
|
||||
iconsAsButtons, iconsDir) + '</center>'
|
||||
htmlStr += \
|
||||
getLeftColumnContent(baseDir, nickname, domainFull,
|
||||
httpPrefix, translate,
|
||||
iconsDir, editor,
|
||||
False, timelinePath,
|
||||
rssIconAtTop, False, False)
|
||||
htmlStr += '</div>\n' + htmlFooter()
|
||||
return htmlStr
|
||||
|
||||
|
||||
def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
|
||||
domain: str, port: int, httpPrefix: str,
|
||||
defaultTimeline: str) -> str:
|
||||
"""Shows the edit links screen
|
||||
"""
|
||||
if '/users/' not in path:
|
||||
return ''
|
||||
path = path.replace('/inbox', '').replace('/outbox', '')
|
||||
path = path.replace('/shares', '')
|
||||
|
||||
nickname = getNicknameFromActor(path)
|
||||
if not nickname:
|
||||
return ''
|
||||
|
||||
# is the user a moderator?
|
||||
if not isEditor(baseDir, nickname):
|
||||
return ''
|
||||
|
||||
cssFilename = baseDir + '/epicyon-links.css'
|
||||
if os.path.isfile(baseDir + '/links.css'):
|
||||
cssFilename = baseDir + '/links.css'
|
||||
|
||||
editCSS = getCSS(baseDir, cssFilename, cssCache)
|
||||
if editCSS:
|
||||
if httpPrefix != 'https':
|
||||
editCSS = \
|
||||
editCSS.replace('https://', httpPrefix + '://')
|
||||
|
||||
# filename of the banner shown at the top
|
||||
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
|
||||
|
||||
editLinksForm = htmlHeader(cssFilename, editCSS)
|
||||
|
||||
# top banner
|
||||
editLinksForm += \
|
||||
'<a href="/users/' + nickname + '/' + defaultTimeline + '" title="' + \
|
||||
translate['Switch to timeline view'] + '" alt="' + \
|
||||
translate['Switch to timeline view'] + '">\n'
|
||||
editLinksForm += '<img loading="lazy" class="timeline-banner" src="' + \
|
||||
'/users/' + nickname + '/' + bannerFile + '" /></a>\n'
|
||||
|
||||
editLinksForm += \
|
||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||
'accept-charset="UTF-8" action="' + path + '/linksdata">\n'
|
||||
editLinksForm += \
|
||||
' <div class="vertical-center">\n'
|
||||
editLinksForm += \
|
||||
' <p class="new-post-text">' + translate['Edit Links'] + '</p>'
|
||||
editLinksForm += \
|
||||
' <div class="container">\n'
|
||||
# editLinksForm += \
|
||||
# ' <a href="' + pathOriginal + '"><button class="cancelbtn">' + \
|
||||
# translate['Go Back'] + '</button></a>\n'
|
||||
editLinksForm += \
|
||||
' <center>\n' + \
|
||||
' <input type="submit" name="submitLinks" value="' + \
|
||||
translate['Submit'] + '">\n' + \
|
||||
' </center>\n'
|
||||
editLinksForm += \
|
||||
' </div>\n'
|
||||
|
||||
linksFilename = baseDir + '/accounts/links.txt'
|
||||
linksStr = ''
|
||||
if os.path.isfile(linksFilename):
|
||||
with open(linksFilename, 'r') as fp:
|
||||
linksStr = fp.read()
|
||||
|
||||
editLinksForm += \
|
||||
'<div class="container">'
|
||||
editLinksForm += \
|
||||
' ' + \
|
||||
translate['One link per line. Description followed by the link.'] + \
|
||||
'<br>'
|
||||
editLinksForm += \
|
||||
' <textarea id="message" name="editedLinks" style="height:80vh">' + \
|
||||
linksStr + '</textarea>'
|
||||
editLinksForm += \
|
||||
'</div>'
|
||||
|
||||
editLinksForm += htmlFooter()
|
||||
return editLinksForm
|
|
@ -0,0 +1,567 @@
|
|||
__filename__ = "webapp_column_right.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from shutil import copyfile
|
||||
from content import removeLongWords
|
||||
from utils import getCSS
|
||||
from utils import getConfigParam
|
||||
from utils import votesOnNewswireItem
|
||||
from utils import getNicknameFromActor
|
||||
from posts import isEditor
|
||||
from posts import isModerator
|
||||
from webapp_utils import getRightImageFile
|
||||
from webapp_utils import getImageFile
|
||||
from webapp_utils import htmlHeader
|
||||
from webapp_utils import htmlFooter
|
||||
from webapp_utils import getBannerFile
|
||||
from webapp_utils import htmlPostSeparator
|
||||
from webapp_utils import headerButtonsFrontScreen
|
||||
from webapp_utils import getIconsDir
|
||||
|
||||
|
||||
def votesIndicator(totalVotes: int, positiveVoting: bool) -> str:
|
||||
"""Returns an indicator of the number of votes on a newswire item
|
||||
"""
|
||||
if totalVotes <= 0:
|
||||
return ''
|
||||
totalVotesStr = ' '
|
||||
for v in range(totalVotes):
|
||||
if positiveVoting:
|
||||
totalVotesStr += '✓'
|
||||
else:
|
||||
totalVotesStr += '✗'
|
||||
return totalVotesStr
|
||||
|
||||
|
||||
def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
|
||||
httpPrefix: str, translate: {},
|
||||
iconsDir: str, moderator: bool, editor: bool,
|
||||
newswire: {}, positiveVoting: bool,
|
||||
showBackButton: bool, timelinePath: str,
|
||||
showPublishButton: bool,
|
||||
showPublishAsIcon: bool,
|
||||
rssIconAtTop: bool,
|
||||
publishButtonAtTop: bool,
|
||||
authorized: bool,
|
||||
showHeaderImage: bool) -> str:
|
||||
"""Returns html content for the right column
|
||||
"""
|
||||
htmlStr = ''
|
||||
|
||||
domain = domainFull
|
||||
if ':' in domain:
|
||||
domain = domain.split(':')
|
||||
|
||||
if authorized:
|
||||
# only show the publish button if logged in, otherwise replace it with
|
||||
# a login button
|
||||
publishButtonStr = \
|
||||
' <a href="' + \
|
||||
'/users/' + nickname + '/newblog" ' + \
|
||||
'title="' + translate['Publish a news article'] + '">' + \
|
||||
'<button class="publishbtn">' + \
|
||||
translate['Publish'] + '</button></a>\n'
|
||||
else:
|
||||
# if not logged in then replace the publish button with
|
||||
# a login button
|
||||
publishButtonStr = \
|
||||
' <a href="/login"><button class="publishbtn">' + \
|
||||
translate['Login'] + '</button></a>\n'
|
||||
|
||||
# show publish button at the top if needed
|
||||
if publishButtonAtTop:
|
||||
htmlStr += '<center>' + publishButtonStr + '</center>'
|
||||
|
||||
# show a column header image, eg. title of the theme or newswire banner
|
||||
editImageClass = ''
|
||||
if showHeaderImage:
|
||||
rightImageFile, rightColumnImageFilename = \
|
||||
getRightImageFile(baseDir, nickname, domain)
|
||||
if not os.path.isfile(rightColumnImageFilename):
|
||||
theme = getConfigParam(baseDir, 'theme').lower()
|
||||
if theme == 'default':
|
||||
theme = ''
|
||||
else:
|
||||
theme = '_' + theme
|
||||
themeRightImageFile, themeRightColumnImageFilename = \
|
||||
getImageFile(baseDir, 'right_col_image', baseDir + '/img',
|
||||
nickname, domain)
|
||||
if os.path.isfile(themeRightColumnImageFilename):
|
||||
rightColumnImageFilename = \
|
||||
baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/' + themeRightImageFile
|
||||
copyfile(themeRightColumnImageFilename,
|
||||
rightColumnImageFilename)
|
||||
rightImageFile = themeRightImageFile
|
||||
|
||||
# show the image at the top of the column
|
||||
editImageClass = 'rightColEdit'
|
||||
if os.path.isfile(rightColumnImageFilename):
|
||||
editImageClass = 'rightColEditImage'
|
||||
htmlStr += \
|
||||
'\n <center>\n' + \
|
||||
' <img class="rightColImg" ' + \
|
||||
'loading="lazy" src="/users/' + \
|
||||
nickname + '/' + rightImageFile + '" />\n' + \
|
||||
' </center>\n'
|
||||
|
||||
if (showPublishButton or editor or rssIconAtTop) and not showHeaderImage:
|
||||
htmlStr += '<div class="columnIcons">'
|
||||
|
||||
if editImageClass == 'rightColEdit':
|
||||
htmlStr += '\n <center>\n'
|
||||
|
||||
# whether to show a back icon
|
||||
# This is probably going to be osolete soon
|
||||
if showBackButton:
|
||||
htmlStr += \
|
||||
' <a href="' + timelinePath + '">' + \
|
||||
'<button class="cancelbtn">' + \
|
||||
translate['Go Back'] + '</button></a>\n'
|
||||
|
||||
if showPublishButton and not publishButtonAtTop:
|
||||
if not showPublishAsIcon:
|
||||
htmlStr += publishButtonStr
|
||||
|
||||
# show the edit icon
|
||||
if editor:
|
||||
if os.path.isfile(baseDir + '/accounts/newswiremoderation.txt'):
|
||||
# show the edit icon highlighted
|
||||
htmlStr += \
|
||||
' <a href="' + \
|
||||
'/users/' + nickname + '/editnewswire">' + \
|
||||
'<img class="' + editImageClass + \
|
||||
'" loading="lazy" alt="' + \
|
||||
translate['Edit newswire'] + '" title="' + \
|
||||
translate['Edit newswire'] + '" src="/' + \
|
||||
iconsDir + '/edit_notify.png" /></a>\n'
|
||||
else:
|
||||
# show the edit icon
|
||||
htmlStr += \
|
||||
' <a href="' + \
|
||||
'/users/' + nickname + '/editnewswire">' + \
|
||||
'<img class="' + editImageClass + \
|
||||
'" loading="lazy" alt="' + \
|
||||
translate['Edit newswire'] + '" title="' + \
|
||||
translate['Edit newswire'] + '" src="/' + \
|
||||
iconsDir + '/edit.png" /></a>\n'
|
||||
|
||||
# show the RSS icon
|
||||
rssIconStr = \
|
||||
' <a href="/newswire.xml">' + \
|
||||
'<img class="' + editImageClass + \
|
||||
'" loading="lazy" alt="' + \
|
||||
translate['Newswire RSS Feed'] + '" title="' + \
|
||||
translate['Newswire RSS Feed'] + '" src="/' + \
|
||||
iconsDir + '/logorss.png" /></a>\n'
|
||||
if rssIconAtTop:
|
||||
htmlStr += rssIconStr
|
||||
|
||||
# show publish icon at top
|
||||
if showPublishButton:
|
||||
if showPublishAsIcon:
|
||||
htmlStr += \
|
||||
' <a href="' + \
|
||||
'/users/' + nickname + '/newblog">' + \
|
||||
'<img class="' + editImageClass + \
|
||||
'" loading="lazy" alt="' + \
|
||||
translate['Publish a news article'] + '" title="' + \
|
||||
translate['Publish a news article'] + '" src="/' + \
|
||||
iconsDir + '/publish.png" /></a>\n'
|
||||
|
||||
if editImageClass == 'rightColEdit':
|
||||
htmlStr += ' </center>\n'
|
||||
else:
|
||||
if showHeaderImage:
|
||||
htmlStr += ' <br>\n'
|
||||
|
||||
if (showPublishButton or editor or rssIconAtTop) and not showHeaderImage:
|
||||
htmlStr += '</div><br>'
|
||||
|
||||
# show the newswire lines
|
||||
newswireContentStr = \
|
||||
htmlNewswire(baseDir, newswire, nickname, moderator, translate,
|
||||
positiveVoting, iconsDir)
|
||||
htmlStr += newswireContentStr
|
||||
|
||||
# show the rss icon at the bottom, typically on the right hand side
|
||||
if newswireContentStr and not rssIconAtTop:
|
||||
htmlStr += '<br><div class="columnIcons">' + rssIconStr + '</div>'
|
||||
return htmlStr
|
||||
|
||||
|
||||
def htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
|
||||
translate: {}, positiveVoting: bool, iconsDir: str) -> str:
|
||||
"""Converts a newswire dict into html
|
||||
"""
|
||||
separatorStr = htmlPostSeparator(baseDir, 'right')
|
||||
htmlStr = ''
|
||||
for dateStr, item in newswire.items():
|
||||
publishedDate = \
|
||||
datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S%z")
|
||||
dateShown = publishedDate.strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
dateStrLink = dateStr.replace('T', ' ')
|
||||
dateStrLink = dateStrLink.replace('Z', '')
|
||||
moderatedItem = item[5]
|
||||
htmlStr += separatorStr
|
||||
if moderatedItem and 'vote:' + nickname in item[2]:
|
||||
totalVotesStr = ''
|
||||
totalVotes = 0
|
||||
if moderator:
|
||||
totalVotes = votesOnNewswireItem(item[2])
|
||||
totalVotesStr = \
|
||||
votesIndicator(totalVotes, positiveVoting)
|
||||
|
||||
title = removeLongWords(item[0], 16, []).replace('\n', '<br>')
|
||||
htmlStr += '<p class="newswireItemVotedOn">' + \
|
||||
'<a href="' + item[1] + '">' + \
|
||||
'<span class="newswireItemVotedOn">' + title + \
|
||||
'</span></a>' + totalVotesStr
|
||||
if moderator:
|
||||
htmlStr += \
|
||||
' ' + dateShown + '<a href="/users/' + nickname + \
|
||||
'/newswireunvote=' + dateStrLink + '" ' + \
|
||||
'title="' + translate['Remove Vote'] + '">'
|
||||
htmlStr += '<img loading="lazy" class="voteicon" src="/' + \
|
||||
iconsDir + '/vote.png" /></a></p>\n'
|
||||
else:
|
||||
htmlStr += ' <span class="newswireDateVotedOn">'
|
||||
htmlStr += dateShown + '</span></p>\n'
|
||||
else:
|
||||
totalVotesStr = ''
|
||||
totalVotes = 0
|
||||
if moderator:
|
||||
if moderatedItem:
|
||||
totalVotes = votesOnNewswireItem(item[2])
|
||||
# show a number of ticks or crosses for how many
|
||||
# votes for or against
|
||||
totalVotesStr = \
|
||||
votesIndicator(totalVotes, positiveVoting)
|
||||
|
||||
title = removeLongWords(item[0], 16, []).replace('\n', '<br>')
|
||||
if moderator and moderatedItem:
|
||||
htmlStr += '<p class="newswireItemModerated">' + \
|
||||
'<a href="' + item[1] + '">' + \
|
||||
title + '</a>' + totalVotesStr
|
||||
htmlStr += ' ' + dateShown
|
||||
htmlStr += '<a href="/users/' + nickname + \
|
||||
'/newswirevote=' + dateStrLink + '" ' + \
|
||||
'title="' + translate['Vote'] + '">'
|
||||
htmlStr += '<img class="voteicon" src="/' + \
|
||||
iconsDir + '/vote.png" /></a>'
|
||||
htmlStr += '</p>\n'
|
||||
else:
|
||||
htmlStr += '<p class="newswireItem">' + \
|
||||
'<a href="' + item[1] + '">' + \
|
||||
title + '</a>' + \
|
||||
totalVotesStr
|
||||
htmlStr += ' <span class="newswireDate">'
|
||||
htmlStr += dateShown + '</span></p>\n'
|
||||
return htmlStr
|
||||
|
||||
|
||||
def htmlCitations(baseDir: str, nickname: str, domain: str,
|
||||
httpPrefix: str, defaultTimeline: str,
|
||||
translate: {}, newswire: {}, cssCache: {},
|
||||
blogTitle: str, blogContent: str,
|
||||
blogImageFilename: str,
|
||||
blogImageAttachmentMediaType: str,
|
||||
blogImageDescription: str) -> str:
|
||||
"""Show the citations screen when creating a blog
|
||||
"""
|
||||
htmlStr = ''
|
||||
|
||||
# create a list of dates for citations
|
||||
# these can then be used to re-select checkboxes later
|
||||
citationsFilename = \
|
||||
baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/.citations.txt'
|
||||
citationsSelected = []
|
||||
if os.path.isfile(citationsFilename):
|
||||
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
|
||||
dateStr = sections[0]
|
||||
citationsSelected.append(dateStr)
|
||||
|
||||
# the css filename
|
||||
cssFilename = baseDir + '/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||
cssFilename = baseDir + '/epicyon.css'
|
||||
|
||||
profileStyle = getCSS(baseDir, cssFilename, cssCache)
|
||||
if profileStyle:
|
||||
# replace any https within the css with whatever prefix is needed
|
||||
if httpPrefix != 'https':
|
||||
profileStyle = \
|
||||
profileStyle.replace('https://', httpPrefix + '://')
|
||||
|
||||
# iconsDir = getIconsDir(baseDir)
|
||||
|
||||
htmlStr = htmlHeader(cssFilename, profileStyle)
|
||||
|
||||
# top banner
|
||||
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
|
||||
htmlStr += \
|
||||
'<a href="/users/' + nickname + '/newblog" title="' + \
|
||||
translate['Go Back'] + '" alt="' + \
|
||||
translate['Go Back'] + '">\n'
|
||||
htmlStr += '<img loading="lazy" class="timeline-banner" src="' + \
|
||||
'/users/' + nickname + '/' + bannerFile + '" /></a>\n'
|
||||
|
||||
htmlStr += \
|
||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||
'accept-charset="UTF-8" action="/users/' + nickname + \
|
||||
'/citationsdata">\n'
|
||||
htmlStr += ' <center>\n'
|
||||
htmlStr += translate['Choose newswire items ' +
|
||||
'referenced in your article'] + '<br>'
|
||||
if blogTitle is None:
|
||||
blogTitle = ''
|
||||
htmlStr += \
|
||||
' <input type="hidden" name="blogTitle" value="' + \
|
||||
blogTitle + '">\n'
|
||||
if blogContent is None:
|
||||
blogContent = ''
|
||||
htmlStr += \
|
||||
' <input type="hidden" name="blogContent" value="' + \
|
||||
blogContent + '">\n'
|
||||
# submit button
|
||||
htmlStr += \
|
||||
' <input type="submit" name="submitCitations" value="' + \
|
||||
translate['Submit'] + '">\n'
|
||||
htmlStr += ' </center>\n'
|
||||
|
||||
citationsSeparator = '#####'
|
||||
|
||||
# list of newswire items
|
||||
if newswire:
|
||||
ctr = 0
|
||||
for dateStr, item in newswire.items():
|
||||
# should this checkbox be selected?
|
||||
selectedStr = ''
|
||||
if dateStr in citationsSelected:
|
||||
selectedStr = ' checked'
|
||||
|
||||
publishedDate = \
|
||||
datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S%z")
|
||||
dateShown = publishedDate.strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
title = removeLongWords(item[0], 16, []).replace('\n', '<br>')
|
||||
link = item[1]
|
||||
|
||||
citationValue = \
|
||||
dateStr + citationsSeparator + \
|
||||
title + citationsSeparator + \
|
||||
link
|
||||
htmlStr += \
|
||||
'<input type="checkbox" name="newswire' + str(ctr) + \
|
||||
'" value="' + citationValue + '"' + selectedStr + '/>' + \
|
||||
'<a href="' + link + '"><cite>' + title + '</cite></a> '
|
||||
htmlStr += '<span class="newswireDate">' + \
|
||||
dateShown + '</span><br>\n'
|
||||
ctr += 1
|
||||
|
||||
htmlStr += '</form>\n'
|
||||
return htmlStr + htmlFooter()
|
||||
|
||||
|
||||
def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
|
||||
domain: str, domainFull: str,
|
||||
httpPrefix: str, translate: {},
|
||||
newswire: {},
|
||||
positiveVoting: bool,
|
||||
timelinePath: str,
|
||||
showPublishAsIcon: bool,
|
||||
authorized: bool,
|
||||
rssIconAtTop: bool,
|
||||
iconsAsButtons: bool,
|
||||
defaultTimeline: str) -> str:
|
||||
"""Shows the mobile version of the newswire right column
|
||||
"""
|
||||
htmlStr = ''
|
||||
|
||||
# the css filename
|
||||
cssFilename = baseDir + '/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||
cssFilename = baseDir + '/epicyon.css'
|
||||
|
||||
profileStyle = getCSS(baseDir, cssFilename, cssCache)
|
||||
if profileStyle:
|
||||
# replace any https within the css with whatever prefix is needed
|
||||
if httpPrefix != 'https':
|
||||
profileStyle = \
|
||||
profileStyle.replace('https://',
|
||||
httpPrefix + '://')
|
||||
|
||||
iconsDir = getIconsDir(baseDir)
|
||||
|
||||
if nickname == 'news':
|
||||
editor = False
|
||||
moderator = False
|
||||
else:
|
||||
# is the user a moderator?
|
||||
moderator = isModerator(baseDir, nickname)
|
||||
|
||||
# is the user a site editor?
|
||||
editor = isEditor(baseDir, nickname)
|
||||
|
||||
showPublishButton = editor
|
||||
|
||||
htmlStr = htmlHeader(cssFilename, profileStyle)
|
||||
|
||||
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
|
||||
htmlStr += \
|
||||
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
|
||||
'<img loading="lazy" class="timeline-banner" ' + \
|
||||
'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n'
|
||||
|
||||
htmlStr += '<center>' + \
|
||||
headerButtonsFrontScreen(translate, nickname,
|
||||
'newswire', authorized,
|
||||
iconsAsButtons, iconsDir) + '</center>'
|
||||
htmlStr += \
|
||||
getRightColumnContent(baseDir, nickname, domainFull,
|
||||
httpPrefix, translate,
|
||||
iconsDir, moderator, editor,
|
||||
newswire, positiveVoting,
|
||||
False, timelinePath, showPublishButton,
|
||||
showPublishAsIcon, rssIconAtTop, False,
|
||||
authorized, False)
|
||||
htmlStr += htmlFooter()
|
||||
return htmlStr
|
||||
|
||||
|
||||
def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str,
|
||||
domain: str, port: int, httpPrefix: str,
|
||||
defaultTimeline: str) -> str:
|
||||
"""Shows the edit newswire screen
|
||||
"""
|
||||
if '/users/' not in path:
|
||||
return ''
|
||||
path = path.replace('/inbox', '').replace('/outbox', '')
|
||||
path = path.replace('/shares', '')
|
||||
|
||||
nickname = getNicknameFromActor(path)
|
||||
if not nickname:
|
||||
return ''
|
||||
|
||||
# is the user a moderator?
|
||||
if not isModerator(baseDir, nickname):
|
||||
return ''
|
||||
|
||||
cssFilename = baseDir + '/epicyon-links.css'
|
||||
if os.path.isfile(baseDir + '/links.css'):
|
||||
cssFilename = baseDir + '/links.css'
|
||||
|
||||
editCSS = getCSS(baseDir, cssFilename, cssCache)
|
||||
if editCSS:
|
||||
if httpPrefix != 'https':
|
||||
editCSS = \
|
||||
editCSS.replace('https://', httpPrefix + '://')
|
||||
|
||||
# filename of the banner shown at the top
|
||||
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
|
||||
|
||||
editNewswireForm = htmlHeader(cssFilename, editCSS)
|
||||
|
||||
# top banner
|
||||
editNewswireForm += \
|
||||
'<a href="/users/' + nickname + '/' + defaultTimeline + '" title="' + \
|
||||
translate['Switch to timeline view'] + '" alt="' + \
|
||||
translate['Switch to timeline view'] + '">\n'
|
||||
editNewswireForm += '<img loading="lazy" class="timeline-banner" src="' + \
|
||||
'/users/' + nickname + '/' + bannerFile + '" /></a>\n'
|
||||
|
||||
editNewswireForm += \
|
||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||
'accept-charset="UTF-8" action="' + path + '/newswiredata">\n'
|
||||
editNewswireForm += \
|
||||
' <div class="vertical-center">\n'
|
||||
editNewswireForm += \
|
||||
' <p class="new-post-text">' + translate['Edit newswire'] + '</p>'
|
||||
editNewswireForm += \
|
||||
' <div class="container">\n'
|
||||
# editNewswireForm += \
|
||||
# ' <a href="' + pathOriginal + '"><button class="cancelbtn">' + \
|
||||
# translate['Go Back'] + '</button></a>\n'
|
||||
editNewswireForm += \
|
||||
' <center>\n' + \
|
||||
' <input type="submit" name="submitNewswire" value="' + \
|
||||
translate['Submit'] + '">\n' + \
|
||||
' </center>\n'
|
||||
editNewswireForm += \
|
||||
' </div>\n'
|
||||
|
||||
newswireFilename = baseDir + '/accounts/newswire.txt'
|
||||
newswireStr = ''
|
||||
if os.path.isfile(newswireFilename):
|
||||
with open(newswireFilename, 'r') as fp:
|
||||
newswireStr = fp.read()
|
||||
|
||||
editNewswireForm += \
|
||||
'<div class="container">'
|
||||
|
||||
editNewswireForm += \
|
||||
' ' + \
|
||||
translate['Add RSS feed links below.'] + \
|
||||
'<br>'
|
||||
editNewswireForm += \
|
||||
' <textarea id="message" name="editedNewswire" ' + \
|
||||
'style="height:80vh">' + newswireStr + '</textarea>'
|
||||
|
||||
filterStr = ''
|
||||
filterFilename = \
|
||||
baseDir + '/accounts/news@' + domain + '/filters.txt'
|
||||
if os.path.isfile(filterFilename):
|
||||
with open(filterFilename, 'r') as filterfile:
|
||||
filterStr = filterfile.read()
|
||||
|
||||
editNewswireForm += \
|
||||
' <br><b><label class="labels">' + \
|
||||
translate['Filtered words'] + '</label></b>\n'
|
||||
editNewswireForm += ' <br><label class="labels">' + \
|
||||
translate['One per line'] + '</label>'
|
||||
editNewswireForm += ' <textarea id="message" ' + \
|
||||
'name="filteredWordsNewswire" style="height:50vh">' + \
|
||||
filterStr + '</textarea>\n'
|
||||
|
||||
hashtagRulesStr = ''
|
||||
hashtagRulesFilename = \
|
||||
baseDir + '/accounts/hashtagrules.txt'
|
||||
if os.path.isfile(hashtagRulesFilename):
|
||||
with open(hashtagRulesFilename, 'r') as rulesfile:
|
||||
hashtagRulesStr = rulesfile.read()
|
||||
|
||||
editNewswireForm += \
|
||||
' <br><b><label class="labels">' + \
|
||||
translate['News tagging rules'] + '</label></b>\n'
|
||||
editNewswireForm += ' <br><label class="labels">' + \
|
||||
translate['One per line'] + '.</label>\n'
|
||||
editNewswireForm += \
|
||||
' <a href="' + \
|
||||
'https://gitlab.com/bashrc2/epicyon/-/raw/main/hashtagrules.txt' + \
|
||||
'">' + translate['See instructions'] + '</a>\n'
|
||||
editNewswireForm += ' <textarea id="message" ' + \
|
||||
'name="hashtagRulesList" style="height:80vh">' + \
|
||||
hashtagRulesStr + '</textarea>\n'
|
||||
|
||||
editNewswireForm += \
|
||||
'</div>'
|
||||
|
||||
editNewswireForm += htmlFooter()
|
||||
return editNewswireForm
|
|
@ -0,0 +1,251 @@
|
|||
__filename__ = "webapp_person_options.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.1.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
|
||||
import os
|
||||
from shutil import copyfile
|
||||
from petnames import getPetName
|
||||
from person import isPersonSnoozed
|
||||
from posts import isModerator
|
||||
from utils import getDomainFromActor
|
||||
from utils import getNicknameFromActor
|
||||
from utils import getCSS
|
||||
from blocking import isBlocked
|
||||
from follow import isFollowingActor
|
||||
from followingCalendar import receivingCalendarEvents
|
||||
from webapp_utils import htmlHeader
|
||||
from webapp_utils import htmlFooter
|
||||
|
||||
|
||||
def htmlPersonOptions(cssCache: {}, translate: {}, baseDir: str,
|
||||
domain: str, domainFull: str,
|
||||
originPathStr: str,
|
||||
optionsActor: str,
|
||||
optionsProfileUrl: str,
|
||||
optionsLink: str,
|
||||
pageNumber: int,
|
||||
donateUrl: str,
|
||||
xmppAddress: str,
|
||||
matrixAddress: str,
|
||||
ssbAddress: str,
|
||||
blogAddress: str,
|
||||
toxAddress: str,
|
||||
PGPpubKey: str,
|
||||
PGPfingerprint: str,
|
||||
emailAddress) -> str:
|
||||
"""Show options for a person: view/follow/block/report
|
||||
"""
|
||||
optionsDomain, optionsPort = getDomainFromActor(optionsActor)
|
||||
optionsDomainFull = optionsDomain
|
||||
if optionsPort:
|
||||
if optionsPort != 80 and optionsPort != 443:
|
||||
optionsDomainFull = optionsDomain + ':' + str(optionsPort)
|
||||
|
||||
if os.path.isfile(baseDir + '/accounts/options-background-custom.jpg'):
|
||||
if not os.path.isfile(baseDir + '/accounts/options-background.jpg'):
|
||||
copyfile(baseDir + '/accounts/options-background.jpg',
|
||||
baseDir + '/accounts/options-background.jpg')
|
||||
|
||||
followStr = 'Follow'
|
||||
blockStr = 'Block'
|
||||
nickname = None
|
||||
optionsNickname = None
|
||||
if originPathStr.startswith('/users/'):
|
||||
nickname = originPathStr.split('/users/')[1]
|
||||
if '/' in nickname:
|
||||
nickname = nickname.split('/')[0]
|
||||
if '?' in nickname:
|
||||
nickname = nickname.split('?')[0]
|
||||
followerDomain, followerPort = getDomainFromActor(optionsActor)
|
||||
if isFollowingActor(baseDir, nickname, domain, optionsActor):
|
||||
followStr = 'Unfollow'
|
||||
|
||||
optionsNickname = getNicknameFromActor(optionsActor)
|
||||
optionsDomainFull = optionsDomain
|
||||
if optionsPort:
|
||||
if optionsPort != 80 and optionsPort != 443:
|
||||
optionsDomainFull = optionsDomain + ':' + str(optionsPort)
|
||||
if isBlocked(baseDir, nickname, domain,
|
||||
optionsNickname, optionsDomainFull):
|
||||
blockStr = 'Block'
|
||||
|
||||
optionsLinkStr = ''
|
||||
if optionsLink:
|
||||
optionsLinkStr = \
|
||||
' <input type="hidden" name="postUrl" value="' + \
|
||||
optionsLink + '">\n'
|
||||
cssFilename = baseDir + '/epicyon-options.css'
|
||||
if os.path.isfile(baseDir + '/options.css'):
|
||||
cssFilename = baseDir + '/options.css'
|
||||
|
||||
profileStyle = getCSS(baseDir, cssFilename, cssCache)
|
||||
if profileStyle:
|
||||
profileStyle = \
|
||||
profileStyle.replace('--follow-text-entry-width: 90%;',
|
||||
'--follow-text-entry-width: 20%;')
|
||||
|
||||
if not os.path.isfile(baseDir + '/accounts/' +
|
||||
'options-background.jpg'):
|
||||
profileStyle = \
|
||||
profileStyle.replace('background-image: ' +
|
||||
'url("options-background.jpg");',
|
||||
'background-image: none;')
|
||||
|
||||
# To snooze, or not to snooze? That is the question
|
||||
snoozeButtonStr = 'Snooze'
|
||||
if nickname:
|
||||
if isPersonSnoozed(baseDir, nickname, domain, optionsActor):
|
||||
snoozeButtonStr = 'Unsnooze'
|
||||
|
||||
donateStr = ''
|
||||
if donateUrl:
|
||||
donateStr = \
|
||||
' <a href="' + donateUrl + \
|
||||
'"><button class="button" name="submitDonate">' + \
|
||||
translate['Donate'] + '</button></a>\n'
|
||||
|
||||
optionsStr = htmlHeader(cssFilename, profileStyle)
|
||||
optionsStr += '<br><br>\n'
|
||||
optionsStr += '<div class="options">\n'
|
||||
optionsStr += ' <div class="optionsAvatar">\n'
|
||||
optionsStr += ' <center>\n'
|
||||
optionsStr += ' <a href="' + optionsActor + '">\n'
|
||||
optionsStr += ' <img loading="lazy" src="' + optionsProfileUrl + \
|
||||
'"/></a>\n'
|
||||
handle = getNicknameFromActor(optionsActor) + '@' + optionsDomain
|
||||
optionsStr += \
|
||||
' <p class="optionsText">' + translate['Options for'] + \
|
||||
' @' + handle + '</p>\n'
|
||||
if emailAddress:
|
||||
optionsStr += \
|
||||
'<p class="imText">' + translate['Email'] + \
|
||||
': <a href="mailto:' + \
|
||||
emailAddress + '">' + emailAddress + '</a></p>\n'
|
||||
if xmppAddress:
|
||||
optionsStr += \
|
||||
'<p class="imText">' + translate['XMPP'] + \
|
||||
': <a href="xmpp:' + xmppAddress + '">' + \
|
||||
xmppAddress + '</a></p>\n'
|
||||
if matrixAddress:
|
||||
optionsStr += \
|
||||
'<p class="imText">' + translate['Matrix'] + ': ' + \
|
||||
matrixAddress + '</p>\n'
|
||||
if ssbAddress:
|
||||
optionsStr += \
|
||||
'<p class="imText">SSB: ' + ssbAddress + '</p>\n'
|
||||
if blogAddress:
|
||||
optionsStr += \
|
||||
'<p class="imText">Blog: <a href="' + blogAddress + '">' + \
|
||||
blogAddress + '</a></p>\n'
|
||||
if toxAddress:
|
||||
optionsStr += \
|
||||
'<p class="imText">Tox: ' + toxAddress + '</p>\n'
|
||||
if PGPfingerprint:
|
||||
optionsStr += '<p class="pgp">PGP: ' + \
|
||||
PGPfingerprint.replace('\n', '<br>') + '</p>\n'
|
||||
if PGPpubKey:
|
||||
optionsStr += '<p class="pgp">' + \
|
||||
PGPpubKey.replace('\n', '<br>') + '</p>\n'
|
||||
optionsStr += ' <form method="POST" action="' + \
|
||||
originPathStr + '/personoptions">\n'
|
||||
optionsStr += ' <input type="hidden" name="pageNumber" value="' + \
|
||||
str(pageNumber) + '">\n'
|
||||
optionsStr += ' <input type="hidden" name="actor" value="' + \
|
||||
optionsActor + '">\n'
|
||||
optionsStr += ' <input type="hidden" name="avatarUrl" value="' + \
|
||||
optionsProfileUrl + '">\n'
|
||||
if optionsNickname:
|
||||
handle = optionsNickname + '@' + optionsDomainFull
|
||||
petname = getPetName(baseDir, nickname, domain, handle)
|
||||
optionsStr += \
|
||||
' ' + translate['Petname'] + ': \n' + \
|
||||
' <input type="text" name="optionpetname" value="' + \
|
||||
petname + '">\n' \
|
||||
' <button type="submit" class="buttonsmall" ' + \
|
||||
'name="submitPetname">' + \
|
||||
translate['Submit'] + '</button><br>\n'
|
||||
|
||||
# checkbox for receiving calendar events
|
||||
if isFollowingActor(baseDir, nickname, domain, optionsActor):
|
||||
checkboxStr = \
|
||||
' <input type="checkbox" ' + \
|
||||
'class="profilecheckbox" name="onCalendar" checked> ' + \
|
||||
translate['Receive calendar events from this account'] + \
|
||||
'\n <button type="submit" class="buttonsmall" ' + \
|
||||
'name="submitOnCalendar">' + \
|
||||
translate['Submit'] + '</button><br>\n'
|
||||
if not receivingCalendarEvents(baseDir, nickname, domain,
|
||||
optionsNickname, optionsDomainFull):
|
||||
checkboxStr = checkboxStr.replace(' checked>', '>')
|
||||
optionsStr += checkboxStr
|
||||
|
||||
# checkbox for permission to post to newswire
|
||||
if optionsDomainFull == domainFull:
|
||||
if isModerator(baseDir, nickname) and \
|
||||
not isModerator(baseDir, optionsNickname):
|
||||
newswireBlockedFilename = \
|
||||
baseDir + '/accounts/' + \
|
||||
optionsNickname + '@' + optionsDomain + '/.nonewswire'
|
||||
checkboxStr = \
|
||||
' <input type="checkbox" ' + \
|
||||
'class="profilecheckbox" name="postsToNews" checked> ' + \
|
||||
translate['Allow news posts'] + \
|
||||
'\n <button type="submit" class="buttonsmall" ' + \
|
||||
'name="submitPostToNews">' + \
|
||||
translate['Submit'] + '</button><br>\n'
|
||||
if os.path.isfile(newswireBlockedFilename):
|
||||
checkboxStr = checkboxStr.replace(' checked>', '>')
|
||||
optionsStr += checkboxStr
|
||||
|
||||
optionsStr += optionsLinkStr
|
||||
optionsStr += \
|
||||
' <a href="/"><button type="button" class="buttonIcon" ' + \
|
||||
'name="submitBack">' + translate['Go Back'] + '</button></a>'
|
||||
optionsStr += \
|
||||
' <button type="submit" class="button" name="submitView">' + \
|
||||
translate['View'] + '</button>'
|
||||
optionsStr += donateStr
|
||||
optionsStr += \
|
||||
' <button type="submit" class="button" name="submit' + \
|
||||
followStr + '">' + translate[followStr] + '</button>'
|
||||
optionsStr += \
|
||||
' <button type="submit" class="button" name="submit' + \
|
||||
blockStr + '">' + translate[blockStr] + '</button>'
|
||||
optionsStr += \
|
||||
' <button type="submit" class="button" name="submitDM">' + \
|
||||
translate['DM'] + '</button>'
|
||||
optionsStr += \
|
||||
' <button type="submit" class="button" name="submit' + \
|
||||
snoozeButtonStr + '">' + translate[snoozeButtonStr] + '</button>'
|
||||
optionsStr += \
|
||||
' <button type="submit" class="button" name="submitReport">' + \
|
||||
translate['Report'] + '</button>'
|
||||
|
||||
personNotes = ''
|
||||
personNotesFilename = \
|
||||
baseDir + '/accounts/' + nickname + '@' + domain + \
|
||||
'/notes/' + handle + '.txt'
|
||||
if os.path.isfile(personNotesFilename):
|
||||
with open(personNotesFilename, 'r') as fp:
|
||||
personNotes = fp.read()
|
||||
|
||||
optionsStr += \
|
||||
' <br><br>' + translate['Notes'] + ': \n'
|
||||
optionsStr += ' <button type="submit" class="buttonsmall" ' + \
|
||||
'name="submitPersonNotes">' + \
|
||||
translate['Submit'] + '</button><br>\n'
|
||||
optionsStr += \
|
||||
' <textarea id="message" ' + \
|
||||
'name="optionnotes" style="height:400px">' + \
|
||||
personNotes + '</textarea>\n'
|
||||
|
||||
optionsStr += ' </form>\n'
|
||||
optionsStr += '</center>\n'
|
||||
optionsStr += '</div>\n'
|
||||
optionsStr += '</div>\n'
|
||||
optionsStr += htmlFooter()
|
||||
return optionsStr
|
167
webapp_post.py
167
webapp_post.py
|
@ -16,11 +16,16 @@ from cache import getPersonFromCache
|
|||
from bookmarks import bookmarkedByPerson
|
||||
from like import likedByPerson
|
||||
from like import noOfLikes
|
||||
from follow import isFollowingActor
|
||||
from posts import isEditor
|
||||
from posts import postIsMuted
|
||||
from posts import getPersonBox
|
||||
from posts import isDM
|
||||
from posts import downloadAnnounce
|
||||
from posts import populateRepliesJson
|
||||
from utils import locatePost
|
||||
from utils import loadJson
|
||||
from utils import getCSS
|
||||
from utils import getCachedPostDirectory
|
||||
from utils import getCachedPostFilename
|
||||
from utils import getProtocolPrefixes
|
||||
|
@ -49,6 +54,9 @@ from webapp_utils import addEmojiToDisplayName
|
|||
from webapp_utils import postContainsPublic
|
||||
from webapp_utils import getContentWarningButton
|
||||
from webapp_utils import getPostAttachmentsAsHtml
|
||||
from webapp_utils import getIconsDir
|
||||
from webapp_utils import htmlHeader
|
||||
from webapp_utils import htmlFooter
|
||||
from webapp_media import addEmbeddedElements
|
||||
from webapp_question import insertQuestion
|
||||
from devices import E2EEdecryptMessageFromDevice
|
||||
|
@ -1215,3 +1223,162 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
print('TIMING INDIV ' + boxName + ' 19 = ' + str(timeDiff))
|
||||
|
||||
return postHtml
|
||||
|
||||
|
||||
def htmlIndividualPost(cssCache: {},
|
||||
recentPostsCache: {}, maxRecentPosts: int,
|
||||
translate: {},
|
||||
baseDir: str, session, wfRequest: {}, personCache: {},
|
||||
nickname: str, domain: str, port: int, authorized: bool,
|
||||
postJsonObject: {}, httpPrefix: str,
|
||||
projectVersion: str, likedBy: str,
|
||||
YTReplacementDomain: str,
|
||||
showPublishedDateOnly: bool) -> str:
|
||||
"""Show an individual post as html
|
||||
"""
|
||||
iconsDir = getIconsDir(baseDir)
|
||||
postStr = ''
|
||||
if likedBy:
|
||||
likedByNickname = getNicknameFromActor(likedBy)
|
||||
likedByDomain, likedByPort = getDomainFromActor(likedBy)
|
||||
if likedByPort:
|
||||
if likedByPort != 80 and likedByPort != 443:
|
||||
likedByDomain += ':' + str(likedByPort)
|
||||
likedByHandle = likedByNickname + '@' + likedByDomain
|
||||
postStr += \
|
||||
'<p>' + translate['Liked by'] + \
|
||||
' <a href="' + likedBy + '">@' + \
|
||||
likedByHandle + '</a>\n'
|
||||
|
||||
domainFull = domain
|
||||
if port:
|
||||
if port != 80 and port != 443:
|
||||
domainFull = domain + ':' + str(port)
|
||||
actor = '/users/' + nickname
|
||||
followStr = ' <form method="POST" ' + \
|
||||
'accept-charset="UTF-8" action="' + actor + '/searchhandle">\n'
|
||||
followStr += \
|
||||
' <input type="hidden" name="actor" value="' + actor + '">\n'
|
||||
followStr += \
|
||||
' <input type="hidden" name="searchtext" value="' + \
|
||||
likedByHandle + '">\n'
|
||||
if not isFollowingActor(baseDir, nickname, domainFull, likedBy):
|
||||
followStr += ' <button type="submit" class="button" ' + \
|
||||
'name="submitSearch">' + translate['Follow'] + '</button>\n'
|
||||
followStr += ' <button type="submit" class="button" ' + \
|
||||
'name="submitBack">' + translate['Go Back'] + '</button>\n'
|
||||
followStr += ' </form>\n'
|
||||
postStr += followStr + '</p>\n'
|
||||
|
||||
postStr += \
|
||||
individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
|
||||
iconsDir, translate, None,
|
||||
baseDir, session, wfRequest, personCache,
|
||||
nickname, domain, port, postJsonObject,
|
||||
None, True, False,
|
||||
httpPrefix, projectVersion, 'inbox',
|
||||
YTReplacementDomain,
|
||||
showPublishedDateOnly,
|
||||
False, authorized, False, False, False)
|
||||
messageId = removeIdEnding(postJsonObject['id'])
|
||||
|
||||
# show the previous posts
|
||||
if isinstance(postJsonObject['object'], dict):
|
||||
while postJsonObject['object'].get('inReplyTo'):
|
||||
postFilename = \
|
||||
locatePost(baseDir, nickname, domain,
|
||||
postJsonObject['object']['inReplyTo'])
|
||||
if not postFilename:
|
||||
break
|
||||
postJsonObject = loadJson(postFilename)
|
||||
if postJsonObject:
|
||||
postStr = \
|
||||
individualPostAsHtml(True, recentPostsCache,
|
||||
maxRecentPosts,
|
||||
iconsDir, translate, None,
|
||||
baseDir, session, wfRequest,
|
||||
personCache,
|
||||
nickname, domain, port,
|
||||
postJsonObject,
|
||||
None, True, False,
|
||||
httpPrefix, projectVersion, 'inbox',
|
||||
YTReplacementDomain,
|
||||
showPublishedDateOnly,
|
||||
False, authorized,
|
||||
False, False, False) + postStr
|
||||
|
||||
# show the following posts
|
||||
postFilename = locatePost(baseDir, nickname, domain, messageId)
|
||||
if postFilename:
|
||||
# is there a replies file for this post?
|
||||
repliesFilename = postFilename.replace('.json', '.replies')
|
||||
if os.path.isfile(repliesFilename):
|
||||
# get items from the replies file
|
||||
repliesJson = {
|
||||
'orderedItems': []
|
||||
}
|
||||
populateRepliesJson(baseDir, nickname, domain,
|
||||
repliesFilename, authorized, repliesJson)
|
||||
# add items to the html output
|
||||
for item in repliesJson['orderedItems']:
|
||||
postStr += \
|
||||
individualPostAsHtml(True, recentPostsCache,
|
||||
maxRecentPosts,
|
||||
iconsDir, translate, None,
|
||||
baseDir, session, wfRequest,
|
||||
personCache,
|
||||
nickname, domain, port, item,
|
||||
None, True, False,
|
||||
httpPrefix, projectVersion, 'inbox',
|
||||
YTReplacementDomain,
|
||||
showPublishedDateOnly,
|
||||
False, authorized,
|
||||
False, False, False)
|
||||
cssFilename = baseDir + '/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||
cssFilename = baseDir + '/epicyon.css'
|
||||
|
||||
postsCSS = getCSS(baseDir, cssFilename, cssCache)
|
||||
if postsCSS:
|
||||
if httpPrefix != 'https':
|
||||
postsCSS = postsCSS.replace('https://',
|
||||
httpPrefix + '://')
|
||||
return htmlHeader(cssFilename, postsCSS) + postStr + htmlFooter()
|
||||
|
||||
|
||||
def htmlPostReplies(cssCache: {},
|
||||
recentPostsCache: {}, maxRecentPosts: int,
|
||||
translate: {}, baseDir: str,
|
||||
session, wfRequest: {}, personCache: {},
|
||||
nickname: str, domain: str, port: int, repliesJson: {},
|
||||
httpPrefix: str, projectVersion: str,
|
||||
YTReplacementDomain: str,
|
||||
showPublishedDateOnly: bool) -> str:
|
||||
"""Show the replies to an individual post as html
|
||||
"""
|
||||
iconsDir = getIconsDir(baseDir)
|
||||
repliesStr = ''
|
||||
if repliesJson.get('orderedItems'):
|
||||
for item in repliesJson['orderedItems']:
|
||||
repliesStr += \
|
||||
individualPostAsHtml(True, recentPostsCache,
|
||||
maxRecentPosts,
|
||||
iconsDir, translate, None,
|
||||
baseDir, session, wfRequest, personCache,
|
||||
nickname, domain, port, item,
|
||||
None, True, False,
|
||||
httpPrefix, projectVersion, 'inbox',
|
||||
YTReplacementDomain,
|
||||
showPublishedDateOnly,
|
||||
False, False, False, False, False)
|
||||
|
||||
cssFilename = baseDir + '/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||
cssFilename = baseDir + '/epicyon.css'
|
||||
|
||||
postsCSS = getCSS(baseDir, cssFilename, cssCache)
|
||||
if postsCSS:
|
||||
if httpPrefix != 'https':
|
||||
postsCSS = postsCSS.replace('https://',
|
||||
httpPrefix + '://')
|
||||
return htmlHeader(cssFilename, postsCSS) + repliesStr + htmlFooter()
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -740,3 +740,73 @@ def htmlPostSeparator(baseDir: str, column: str) -> str:
|
|||
'<img src="/' + iconsDir + '/' + filename + '"/>' + \
|
||||
'</center></div>\n'
|
||||
return separatorStr
|
||||
|
||||
|
||||
def headerButtonsFrontScreen(translate: {},
|
||||
nickname: str, boxName: str,
|
||||
authorized: bool,
|
||||
iconsAsButtons: bool,
|
||||
iconsDir: bool) -> str:
|
||||
"""Returns the header buttons for the front page of a news instance
|
||||
"""
|
||||
headerStr = ''
|
||||
if nickname == 'news':
|
||||
buttonFeatures = 'buttonMobile'
|
||||
buttonNewswire = 'buttonMobile'
|
||||
buttonLinks = 'buttonMobile'
|
||||
if boxName == 'features':
|
||||
buttonFeatures = 'buttonselected'
|
||||
elif boxName == 'newswire':
|
||||
buttonNewswire = 'buttonselected'
|
||||
elif boxName == 'links':
|
||||
buttonLinks = 'buttonselected'
|
||||
|
||||
headerStr += \
|
||||
' <a href="/">' + \
|
||||
'<button class="' + buttonFeatures + '">' + \
|
||||
'<span>' + translate['Features'] + \
|
||||
'</span></button></a>'
|
||||
if not authorized:
|
||||
headerStr += \
|
||||
' <a href="/login">' + \
|
||||
'<button class="buttonMobile">' + \
|
||||
'<span>' + translate['Login'] + \
|
||||
'</span></button></a>'
|
||||
if iconsAsButtons:
|
||||
headerStr += \
|
||||
' <a href="/users/news/newswiremobile">' + \
|
||||
'<button class="' + buttonNewswire + '">' + \
|
||||
'<span>' + translate['Newswire'] + \
|
||||
'</span></button></a>'
|
||||
headerStr += \
|
||||
' <a href="/users/news/linksmobile">' + \
|
||||
'<button class="' + buttonLinks + '">' + \
|
||||
'<span>' + translate['Links'] + \
|
||||
'</span></button></a>'
|
||||
else:
|
||||
headerStr += \
|
||||
' <a href="' + \
|
||||
'/users/news/newswiremobile">' + \
|
||||
'<img loading="lazy" src="/' + iconsDir + \
|
||||
'/newswire.png" title="' + translate['Newswire'] + \
|
||||
'" alt="| ' + translate['Newswire'] + '"/></a>\n'
|
||||
headerStr += \
|
||||
' <a href="' + \
|
||||
'/users/news/linksmobile">' + \
|
||||
'<img loading="lazy" src="/' + iconsDir + \
|
||||
'/links.png" title="' + translate['Links'] + \
|
||||
'" alt="| ' + translate['Links'] + '"/></a>\n'
|
||||
else:
|
||||
if not authorized:
|
||||
headerStr += \
|
||||
' <a href="/login">' + \
|
||||
'<button class="buttonMobile">' + \
|
||||
'<span>' + translate['Login'] + \
|
||||
'</span></button></a>'
|
||||
|
||||
if headerStr:
|
||||
headerStr = \
|
||||
'\n <div class="frontPageMobileButtons">\n' + \
|
||||
headerStr + \
|
||||
' </div>\n'
|
||||
return headerStr
|
||||
|
|
Loading…
Reference in New Issue