epicyon/webapp_column_left.py

527 lines
19 KiB
Python
Raw Normal View History

2020-11-09 22:44:03 +00:00
__filename__ = "webapp_column_left.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
2021-01-26 10:07:42 +00:00
__version__ = "1.2.0"
2020-11-09 22:44:03 +00:00
__maintainer__ = "Bob Mottram"
2021-09-10 16:14:50 +00:00
__email__ = "bob@libreserver.org"
2020-11-09 22:44:03 +00:00
__status__ = "Production"
2021-06-26 11:27:14 +00:00
__module_group__ = "Web Interface Columns"
2020-11-09 22:44:03 +00:00
import os
from utils import getConfigParam
from utils import getNicknameFromActor
2020-12-01 21:44:27 +00:00
from utils import isEditor
2021-06-26 14:21:24 +00:00
from utils import removeDomainPort
2021-08-14 11:13:39 +00:00
from utils import localActorUrl
from webapp_utils import sharesTimelineJson
2020-11-09 22:44:03 +00:00
from webapp_utils import htmlPostSeparator
from webapp_utils import getLeftImageFile
from webapp_utils import headerButtonsFrontScreen
2020-11-10 16:54:35 +00:00
from webapp_utils import htmlHeaderWithExternalStyle
2020-11-09 22:44:03 +00:00
from webapp_utils import htmlFooter
from webapp_utils import getBannerFile
2021-09-19 13:59:31 +00:00
from shares import shareCategoryIcon
2020-11-09 22:44:03 +00:00
def _linksExist(baseDir: str) -> bool:
2020-11-29 11:31:16 +00:00
"""Returns true if links have been created
"""
linksFilename = baseDir + '/accounts/links.txt'
return os.path.isfile(linksFilename)
def _getLeftColumnShares(baseDir: str,
httpPrefix: str, domain: str, domainFull: str,
nickname: str,
maxSharesInLeftColumn: int,
translate: {},
sharedItemsFederatedDomains: []) -> []:
"""get any shares and turn them into the left column links format
"""
pageNumber = 1
2021-08-14 11:13:39 +00:00
actor = localActorUrl(httpPrefix, nickname, domainFull)
# NOTE: this could potentially be slow if the number of federated
# shared items is large
sharesJson, lastPage = \
sharesTimelineJson(actor, pageNumber, maxSharesInLeftColumn,
baseDir, domain, nickname, maxSharesInLeftColumn,
sharedItemsFederatedDomains, 'shares')
if not sharesJson:
return []
linksList = []
ctr = 0
for published, item in sharesJson.items():
sharedesc = item['displayName']
2021-09-21 12:44:55 +00:00
if '<' in sharedesc or '?' in sharedesc:
continue
shareId = item['shareId']
# selecting this link calls htmlShowShare
shareLink = actor + '?showshare=' + shareId
2021-09-19 13:59:31 +00:00
if item.get('category'):
2021-09-21 12:44:55 +00:00
shareLink += '?category=' + item['category']
2021-09-19 13:59:31 +00:00
shareCategory = shareCategoryIcon(item['category'])
linksList.append(shareCategory + sharedesc + ' ' + shareLink)
ctr += 1
if ctr >= maxSharesInLeftColumn:
break
if linksList:
linksList = ['* ' + translate['Shares']] + linksList
return linksList
2021-08-09 19:16:19 +00:00
def _getLeftColumnWanted(baseDir: str,
httpPrefix: str, domain: str, domainFull: str,
nickname: str,
maxSharesInLeftColumn: int,
translate: {},
sharedItemsFederatedDomains: []) -> []:
"""get any wanted items and turn them into the left column links format
"""
pageNumber = 1
2021-08-14 11:13:39 +00:00
actor = localActorUrl(httpPrefix, nickname, domainFull)
2021-08-09 19:16:19 +00:00
# NOTE: this could potentially be slow if the number of federated
# wanted items is large
sharesJson, lastPage = \
sharesTimelineJson(actor, pageNumber, maxSharesInLeftColumn,
baseDir, domain, nickname, maxSharesInLeftColumn,
sharedItemsFederatedDomains, 'wanted')
if not sharesJson:
return []
linksList = []
ctr = 0
for published, item in sharesJson.items():
sharedesc = item['displayName']
2021-09-19 16:20:12 +00:00
if '<' in sharedesc or ';' in sharedesc:
2021-08-09 19:16:19 +00:00
continue
shareId = item['shareId']
# selecting this link calls htmlShowShare
shareLink = actor + '?showwanted=' + shareId
linksList.append(sharedesc + ' ' + shareLink)
ctr += 1
if ctr >= maxSharesInLeftColumn:
break
if linksList:
linksList = ['* ' + translate['Wanted']] + linksList
return linksList
2020-11-09 22:44:03 +00:00
def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
httpPrefix: str, translate: {},
2020-12-09 13:31:54 +00:00
editor: bool,
2020-11-09 22:44:03 +00:00
showBackButton: bool, timelinePath: str,
rssIconAtTop: bool, showHeaderImage: bool,
2021-04-23 16:29:03 +00:00
frontPage: bool, theme: str,
accessKeys: {},
sharedItemsFederatedDomains: []) -> str:
2020-11-09 22:44:03 +00:00
"""Returns html content for the left column
"""
htmlStr = ''
separatorStr = htmlPostSeparator(baseDir, 'left')
domain = removeDomainPort(domainFull)
2020-11-09 22:44:03 +00:00
editImageClass = ''
if showHeaderImage:
leftImageFile, leftColumnImageFilename = \
2020-12-20 17:26:38 +00:00
getLeftImageFile(baseDir, nickname, domain, theme)
2020-11-09 22:44:03 +00:00
# show the image at the top of the column
editImageClass = 'leftColEdit'
if os.path.isfile(leftColumnImageFilename):
editImageClass = 'leftColEditImage'
htmlStr += \
2021-07-05 19:46:55 +00:00
'\n <center>\n <img class="leftColImg" ' + \
2021-02-01 19:48:46 +00:00
'alt="" loading="lazy" src="/users/' + \
2020-11-09 22:44:03 +00:00
nickname + '/' + leftImageFile + '" />\n' + \
' </center>\n'
if showBackButton:
htmlStr += \
2021-07-05 19:46:55 +00:00
' <div> <a href="' + timelinePath + '">' + \
2020-11-09 22:44:03 +00:00
'<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 += \
2021-07-05 19:46:55 +00:00
' <a href="/users/' + nickname + '/editlinks" ' + \
2021-04-23 17:49:09 +00:00
'accesskey="' + accessKeys['menuEdit'] + '">' + \
2021-07-05 19:46:55 +00:00
'<img class="' + editImageClass + '" loading="lazy" alt="' + \
2021-02-01 19:28:07 +00:00
translate['Edit Links'] + ' | " title="' + \
2021-07-05 19:46:55 +00:00
translate['Edit Links'] + '" src="/icons/edit.png" /></a>\n'
2020-11-09 22:44:03 +00:00
# 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 = \
2021-07-05 19:46:55 +00:00
' <a href="' + rssUrl + '"><img class="' + editImageClass + \
'" loading="lazy" alt="' + rssTitle + '" title="' + rssTitle + \
2020-12-09 13:08:26 +00:00
'" src="/icons/logorss.png" /></a>\n'
2020-11-09 22:44:03 +00:00
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>'
# flag used not to show the first separator
firstSeparatorAdded = False
2020-11-09 22:44:03 +00:00
linksFilename = baseDir + '/accounts/links.txt'
linksFileContainsEntries = False
linksList = None
2020-11-09 22:44:03 +00:00
if os.path.isfile(linksFilename):
2021-07-13 14:40:49 +00:00
with open(linksFilename, 'r') as f:
2020-11-09 22:44:03 +00:00
linksList = f.readlines()
2020-12-06 22:29:03 +00:00
if not frontPage:
# show a number of shares
maxSharesInLeftColumn = 3
sharesList = \
_getLeftColumnShares(baseDir,
httpPrefix, domain, domainFull, nickname,
maxSharesInLeftColumn, translate,
sharedItemsFederatedDomains)
2020-12-06 22:29:03 +00:00
if linksList and sharesList:
2020-12-06 22:35:25 +00:00
linksList = sharesList + linksList
2021-08-09 19:16:19 +00:00
wantedList = \
_getLeftColumnWanted(baseDir,
httpPrefix, domain, domainFull, nickname,
maxSharesInLeftColumn, translate,
sharedItemsFederatedDomains)
if linksList and wantedList:
linksList = wantedList + linksList
newTabStr = ' target="_blank" rel="nofollow noopener noreferrer"'
if linksList:
2020-12-27 15:22:14 +00:00
htmlStr += '<nav>\n'
for lineStr in linksList:
if ' ' not in lineStr:
if '#' not in lineStr:
if '*' not in lineStr:
2021-02-24 14:41:46 +00:00
if not lineStr.startswith('['):
if not lineStr.startswith('=> '):
continue
lineStr = lineStr.strip()
linkStr = None
2021-02-24 14:41:46 +00:00
if not lineStr.startswith('['):
words = lineStr.split(' ')
# get the link
for word in words:
if word == '#':
continue
if word == '*':
continue
if word == '=>':
continue
if '://' in word:
linkStr = word
break
else:
# markdown link
if ']' not in lineStr:
continue
2021-02-24 14:41:46 +00:00
if '(' not in lineStr:
continue
2021-02-24 14:41:46 +00:00
if ')' not in lineStr:
2021-02-24 14:31:04 +00:00
continue
2021-02-24 14:41:46 +00:00
linkStr = lineStr.split('(')[1]
if ')' not in linkStr:
continue
linkStr = linkStr.split(')')[0]
if '://' not in linkStr:
continue
lineStr = lineStr.split('[')[1]
if ']' not in lineStr:
continue
lineStr = lineStr.split(']')[0]
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
2021-08-09 19:16:19 +00:00
if '?showshare=' not in linkStr and \
'?showwarning=' not in linkStr:
htmlStr += \
' <p><a href="' + linkStr + \
'"' + newTabStr + '>' + \
lineStr + '</a></p>\n'
else:
htmlStr += \
' <p><a href="' + linkStr + \
'">' + lineStr + '</a></p>\n'
2020-11-09 22:44:03 +00:00
linksFileContainsEntries = True
2021-02-24 14:31:04 +00:00
elif lineStr.startswith('=> '):
# gemini style link
lineStr = lineStr.replace('=> ', '')
lineStr = lineStr.replace(linkStr, '')
# add link to the returned html
2021-08-09 19:16:19 +00:00
if '?showshare=' not in linkStr and \
'?showwarning=' not in linkStr:
htmlStr += \
' <p><a href="' + linkStr + \
'"' + newTabStr + '>' + \
lineStr.strip() + '</a></p>\n'
else:
htmlStr += \
' <p><a href="' + linkStr + \
'">' + lineStr.strip() + '</a></p>\n'
2021-02-24 14:31:04 +00:00
linksFileContainsEntries = True
else:
if lineStr.startswith('#') or lineStr.startswith('*'):
lineStr = lineStr[1:].strip()
if firstSeparatorAdded:
htmlStr += separatorStr
firstSeparatorAdded = True
htmlStr += \
' <h3 class="linksHeader">' + \
lineStr + '</h3>\n'
else:
htmlStr += \
' <p>' + lineStr + '</p>\n'
linksFileContainsEntries = True
2020-12-27 15:22:14 +00:00
htmlStr += '</nav>\n'
2020-11-09 22:44:03 +00:00
if firstSeparatorAdded:
htmlStr += separatorStr
2021-07-27 21:28:20 +00:00
htmlStr += \
'<p class="login-text"><a href="/users/' + nickname + \
'/catalog.csv">' + translate['Shares Catalog'] + '</a></p>'
2021-04-23 16:07:26 +00:00
htmlStr += \
'<p class="login-text"><a href="/users/' + \
2021-04-23 16:45:46 +00:00
nickname + '/accesskeys" accesskey="' + \
2021-04-23 16:29:03 +00:00
accessKeys['menuKeys'] + '">' + \
2021-04-23 16:07:26 +00:00
translate['Key Shortcuts'] + '</a></p>'
2020-11-15 16:48:55 +00:00
htmlStr += \
'<p class="login-text"><a href="/about">' + \
translate['About this Instance'] + '</a></p>'
htmlStr += \
'<p class="login-text"><a href="/terms">' + \
translate['Terms of Service'] + '</a></p>'
2020-11-09 22:44:03 +00:00
if linksFileContainsEntries and not rssIconAtTop:
htmlStr += '<br><div class="columnIcons">' + rssIconStr + '</div>'
2020-11-15 16:48:55 +00:00
2020-11-09 22:44:03 +00:00
return htmlStr
def htmlLinksMobile(cssCache: {}, baseDir: str,
nickname: str, domainFull: str,
httpPrefix: str, translate,
timelinePath: str, authorized: bool,
rssIconAtTop: bool,
iconsAsButtons: bool,
2020-12-20 17:26:38 +00:00
defaultTimeline: str,
theme: str, accessKeys: {},
sharedItemsFederatedDomains: []) -> str:
2020-11-09 22:44:03 +00:00
"""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'
# is the user a site editor?
if nickname == 'news':
editor = False
else:
editor = isEditor(baseDir, nickname)
domain = removeDomainPort(domainFull)
2020-11-09 22:44:03 +00:00
2021-01-11 19:46:21 +00:00
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
htmlStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle, None)
2020-12-20 17:26:38 +00:00
bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
2020-11-09 22:44:03 +00:00
htmlStr += \
2021-04-23 17:46:57 +00:00
'<a href="/users/' + nickname + '/' + defaultTimeline + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">' + \
2020-11-09 22:44:03 +00:00
'<img loading="lazy" class="timeline-banner" ' + \
2021-02-01 19:48:46 +00:00
'alt="' + translate['Switch to timeline view'] + '" ' + \
2020-11-09 22:44:03 +00:00
'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n'
htmlStr += '<div class="col-left-mobile">\n'
2020-11-09 22:44:03 +00:00
htmlStr += '<center>' + \
headerButtonsFrontScreen(translate, nickname,
'links', authorized,
2020-12-09 13:31:54 +00:00
iconsAsButtons) + '</center>'
if _linksExist(baseDir):
2020-11-29 11:31:16 +00:00
htmlStr += \
getLeftColumnContent(baseDir, nickname, domainFull,
httpPrefix, translate,
2020-12-09 13:31:54 +00:00
editor,
2020-11-29 11:31:16 +00:00
False, timelinePath,
2020-12-20 17:26:38 +00:00
rssIconAtTop, False, False,
theme, accessKeys,
sharedItemsFederatedDomains)
2020-11-29 11:31:16 +00:00
else:
if editor:
2021-07-05 19:46:55 +00:00
htmlStr += '<br><br><br>\n<center>\n '
2020-11-29 11:31:16 +00:00
htmlStr += translate['Select the edit icon to add web links']
htmlStr += '\n</center>\n'
# end of col-left-mobile
htmlStr += '</div>\n'
2020-11-09 22:44:03 +00:00
htmlStr += '</div>\n' + htmlFooter()
return htmlStr
def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
domain: str, port: int, httpPrefix: str,
2021-04-23 18:00:11 +00:00
defaultTimeline: str, theme: str,
accessKeys: {}) -> str:
2020-11-09 22:44:03 +00:00
"""Shows the edit links screen
"""
if '/users/' not in path:
return ''
path = path.replace('/inbox', '').replace('/outbox', '')
2021-08-09 19:37:18 +00:00
path = path.replace('/shares', '').replace('/wanted', '')
2020-11-09 22:44:03 +00:00
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'
# filename of the banner shown at the top
2020-12-20 17:26:38 +00:00
bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
2020-11-09 22:44:03 +00:00
2021-01-11 19:46:21 +00:00
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
editLinksForm = \
htmlHeaderWithExternalStyle(cssFilename, instanceTitle, None)
2020-11-09 22:44:03 +00:00
# top banner
editLinksForm += \
2020-12-27 16:57:15 +00:00
'<header>\n' + \
2020-11-09 22:44:03 +00:00
'<a href="/users/' + nickname + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '" alt="' + \
2021-04-23 18:00:11 +00:00
translate['Switch to timeline view'] + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">\n'
2021-07-05 19:46:55 +00:00
editLinksForm += \
'<img loading="lazy" class="timeline-banner" ' + \
2021-02-01 19:48:46 +00:00
'alt = "" src="' + \
2020-12-27 16:57:15 +00:00
'/users/' + nickname + '/' + bannerFile + '" /></a>\n' + \
'</header>\n'
2020-11-09 22:44:03 +00:00
editLinksForm += \
'<form enctype="multipart/form-data" method="POST" ' + \
'accept-charset="UTF-8" action="' + path + '/linksdata">\n'
editLinksForm += \
' <div class="vertical-center">\n'
editLinksForm += \
2020-12-21 16:07:51 +00:00
' <div class="containerSubmitNewPost">\n'
2020-11-09 22:44:03 +00:00
editLinksForm += \
2020-12-21 16:24:13 +00:00
' <h1>' + translate['Edit Links'] + '</h1>'
2020-12-21 16:01:05 +00:00
editLinksForm += \
2020-12-21 16:24:13 +00:00
' <input type="submit" name="submitLinks" value="' + \
2021-04-23 18:00:11 +00:00
translate['Submit'] + '" ' + \
'accesskey="' + accessKeys['submitButton'] + '">\n'
2020-11-09 22:44:03 +00:00
editLinksForm += \
' </div>\n'
linksFilename = baseDir + '/accounts/links.txt'
linksStr = ''
if os.path.isfile(linksFilename):
with open(linksFilename, 'r') as fp:
linksStr = fp.read()
2020-11-09 22:44:03 +00:00
editLinksForm += \
'<div class="container">'
editLinksForm += \
' ' + \
translate['One link per line. Description followed by the link.'] + \
'<br>'
editLinksForm += \
2021-02-28 14:26:04 +00:00
' <textarea id="message" name="editedLinks" ' + \
2021-07-05 19:46:55 +00:00
'style="height:80vh" spellcheck="false">' + linksStr + '</textarea>'
2020-11-09 22:44:03 +00:00
editLinksForm += \
'</div>'
# the admin can edit terms of service and about text
adminNickname = getConfigParam(baseDir, 'admin')
if adminNickname:
if nickname == adminNickname:
2021-03-01 11:40:49 +00:00
aboutFilename = baseDir + '/accounts/about.md'
aboutStr = ''
if os.path.isfile(aboutFilename):
with open(aboutFilename, 'r') as fp:
aboutStr = fp.read()
editLinksForm += \
'<div class="container">'
editLinksForm += \
' ' + \
translate['About this Instance'] + \
'<br>'
editLinksForm += \
' <textarea id="message" name="editedAbout" ' + \
2021-02-28 14:48:41 +00:00
'style="height:100vh" spellcheck="true" autocomplete="on">' + \
2021-02-28 14:26:04 +00:00
aboutStr + '</textarea>'
editLinksForm += \
'</div>'
2021-03-01 11:13:31 +00:00
TOSFilename = baseDir + '/accounts/tos.md'
TOSStr = ''
if os.path.isfile(TOSFilename):
with open(TOSFilename, 'r') as fp:
TOSStr = fp.read()
editLinksForm += \
'<div class="container">'
editLinksForm += \
' ' + \
translate['Terms of Service'] + \
'<br>'
editLinksForm += \
' <textarea id="message" name="editedTOS" ' + \
2021-02-28 14:48:41 +00:00
'style="height:100vh" spellcheck="true" autocomplete="on">' + \
2021-02-28 14:26:04 +00:00
TOSStr + '</textarea>'
editLinksForm += \
'</div>'
2020-11-09 22:44:03 +00:00
editLinksForm += htmlFooter()
return editLinksForm