Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon into main
|
|
@ -66,7 +66,7 @@ def createReject(baseDir: str, federationList: [],
|
||||||
return createAcceptReject(baseDir, federationList,
|
return createAcceptReject(baseDir, federationList,
|
||||||
nickname, domain, port,
|
nickname, domain, port,
|
||||||
toUrl, ccUrl,
|
toUrl, ccUrl,
|
||||||
httpPrefix, objectJson, None, 'Reject')
|
httpPrefix, objectJson, 'Reject')
|
||||||
|
|
||||||
|
|
||||||
def acceptFollow(baseDir: str, domain: str, messageJson: {},
|
def acceptFollow(baseDir: str, domain: str, messageJson: {},
|
||||||
|
|
|
||||||
49
blog.py
|
|
@ -20,6 +20,8 @@ from utils import getDomainFromActor
|
||||||
from utils import locatePost
|
from utils import locatePost
|
||||||
from utils import loadJson
|
from utils import loadJson
|
||||||
from posts import createBlogsTimeline
|
from posts import createBlogsTimeline
|
||||||
|
from newswire import rss2Header
|
||||||
|
from newswire import rss2Footer
|
||||||
|
|
||||||
|
|
||||||
def noOfBlogReplies(baseDir: str, httpPrefix: str, translate: {},
|
def noOfBlogReplies(baseDir: str, httpPrefix: str, translate: {},
|
||||||
|
|
@ -365,12 +367,12 @@ def htmlBlogPost(authorized: bool,
|
||||||
'title="RSS 2.0" src="/' + \
|
'title="RSS 2.0" src="/' + \
|
||||||
iconsDir + '/rss.png" /></a>'
|
iconsDir + '/rss.png" /></a>'
|
||||||
|
|
||||||
blogStr += '<a href="' + httpPrefix + '://' + \
|
# blogStr += '<a href="' + httpPrefix + '://' + \
|
||||||
domainFull + '/blog/' + nickname + '/rss.txt">'
|
# domainFull + '/blog/' + nickname + '/rss.txt">'
|
||||||
blogStr += '<img style="width:3%;min-width:50px" ' + \
|
# blogStr += '<img style="width:3%;min-width:50px" ' + \
|
||||||
'loading="lazy" alt="RSS 3.0" ' + \
|
# 'loading="lazy" alt="RSS 3.0" ' + \
|
||||||
'title="RSS 3.0" src="/' + \
|
# 'title="RSS 3.0" src="/' + \
|
||||||
iconsDir + '/rss3.png" /></a>'
|
# iconsDir + '/rss3.png" /></a>'
|
||||||
|
|
||||||
blogStr += '</p>'
|
blogStr += '</p>'
|
||||||
|
|
||||||
|
|
@ -405,7 +407,7 @@ def htmlBlogPage(authorized: bool, session,
|
||||||
timelineJson = createBlogsTimeline(session, baseDir,
|
timelineJson = createBlogsTimeline(session, baseDir,
|
||||||
nickname, domain, port,
|
nickname, domain, port,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
noOfItems, False, False,
|
noOfItems, False,
|
||||||
pageNumber)
|
pageNumber)
|
||||||
|
|
||||||
if not timelineJson:
|
if not timelineJson:
|
||||||
|
|
@ -461,11 +463,11 @@ def htmlBlogPage(authorized: bool, session,
|
||||||
'title="RSS 2.0" src="/' + \
|
'title="RSS 2.0" src="/' + \
|
||||||
iconsDir + '/rss.png" /></a>'
|
iconsDir + '/rss.png" /></a>'
|
||||||
|
|
||||||
blogStr += '<a href="' + httpPrefix + '://' + \
|
# blogStr += '<a href="' + httpPrefix + '://' + \
|
||||||
domainFull + '/blog/' + nickname + '/rss.txt">'
|
# domainFull + '/blog/' + nickname + '/rss.txt">'
|
||||||
blogStr += '<img loading="lazy" alt="RSS 3.0" ' + \
|
# blogStr += '<img loading="lazy" alt="RSS 3.0" ' + \
|
||||||
'title="RSS 3.0" src="/' + \
|
# 'title="RSS 3.0" src="/' + \
|
||||||
iconsDir + '/rss3.png" /></a>'
|
# iconsDir + '/rss3.png" /></a>'
|
||||||
|
|
||||||
blogStr += '</p>'
|
blogStr += '</p>'
|
||||||
|
|
||||||
|
|
@ -473,23 +475,6 @@ def htmlBlogPage(authorized: bool, session,
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def rss2Header(httpPrefix: str,
|
|
||||||
nickname: str, domainFull: str, translate: {}) -> str:
|
|
||||||
rssStr = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
|
|
||||||
rssStr += "<rss version=\"2.0\">"
|
|
||||||
rssStr += '<channel>'
|
|
||||||
rssStr += ' <title>' + translate['Blog'] + '</title>'
|
|
||||||
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
|
|
||||||
'/users/' + nickname + '/rss.xml' + '</link>'
|
|
||||||
return rssStr
|
|
||||||
|
|
||||||
|
|
||||||
def rss2Footer() -> str:
|
|
||||||
rssStr = '</channel>'
|
|
||||||
rssStr += '</rss>'
|
|
||||||
return rssStr
|
|
||||||
|
|
||||||
|
|
||||||
def htmlBlogPageRSS2(authorized: bool, session,
|
def htmlBlogPageRSS2(authorized: bool, session,
|
||||||
baseDir: str, httpPrefix: str, translate: {},
|
baseDir: str, httpPrefix: str, translate: {},
|
||||||
nickname: str, domain: str, port: int,
|
nickname: str, domain: str, port: int,
|
||||||
|
|
@ -505,7 +490,7 @@ def htmlBlogPageRSS2(authorized: bool, session,
|
||||||
if port != 80 and port != 443:
|
if port != 80 and port != 443:
|
||||||
domainFull = domain + ':' + str(port)
|
domainFull = domain + ':' + str(port)
|
||||||
|
|
||||||
blogRSS2 = rss2Header(httpPrefix, nickname, domainFull, translate)
|
blogRSS2 = rss2Header(httpPrefix, nickname, domainFull, 'Blog', translate)
|
||||||
|
|
||||||
blogsIndex = baseDir + '/accounts/' + \
|
blogsIndex = baseDir + '/accounts/' + \
|
||||||
nickname + '@' + domain + '/tlblogs.index'
|
nickname + '@' + domain + '/tlblogs.index'
|
||||||
|
|
@ -515,7 +500,7 @@ def htmlBlogPageRSS2(authorized: bool, session,
|
||||||
timelineJson = createBlogsTimeline(session, baseDir,
|
timelineJson = createBlogsTimeline(session, baseDir,
|
||||||
nickname, domain, port,
|
nickname, domain, port,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
noOfItems, False, False,
|
noOfItems, False,
|
||||||
pageNumber)
|
pageNumber)
|
||||||
|
|
||||||
if not timelineJson:
|
if not timelineJson:
|
||||||
|
|
@ -561,7 +546,7 @@ def htmlBlogPageRSS3(authorized: bool, session,
|
||||||
timelineJson = createBlogsTimeline(session, baseDir,
|
timelineJson = createBlogsTimeline(session, baseDir,
|
||||||
nickname, domain, port,
|
nickname, domain, port,
|
||||||
httpPrefix,
|
httpPrefix,
|
||||||
noOfItems, False, False,
|
noOfItems, False,
|
||||||
pageNumber)
|
pageNumber)
|
||||||
|
|
||||||
if not timelineJson:
|
if not timelineJson:
|
||||||
|
|
|
||||||
31
content.py
|
|
@ -14,6 +14,32 @@ from utils import fileLastModified
|
||||||
from utils import getLinkPrefixes
|
from utils import getLinkPrefixes
|
||||||
|
|
||||||
|
|
||||||
|
def removeQuotesWithinQuotes(content: str) -> str:
|
||||||
|
"""Removes any blockquote inside blockquote
|
||||||
|
"""
|
||||||
|
if '<blockquote>' not in content:
|
||||||
|
return content
|
||||||
|
if '</blockquote>' not in content:
|
||||||
|
return content
|
||||||
|
ctr = 1
|
||||||
|
found = True
|
||||||
|
while found:
|
||||||
|
prefix = content.split('<blockquote>', ctr)[0] + '<blockquote>'
|
||||||
|
quotedStr = content.split('<blockquote>', ctr)[1]
|
||||||
|
if '</blockquote>' not in quotedStr:
|
||||||
|
found = False
|
||||||
|
else:
|
||||||
|
endStr = quotedStr.split('</blockquote>')[1]
|
||||||
|
quotedStr = quotedStr.split('</blockquote>')[0]
|
||||||
|
if '<blockquote>' not in endStr:
|
||||||
|
found = False
|
||||||
|
if '<blockquote>' in quotedStr:
|
||||||
|
quotedStr = quotedStr.replace('<blockquote>', '')
|
||||||
|
content = prefix + quotedStr + '</blockquote>' + endStr
|
||||||
|
ctr += 1
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
def htmlReplaceEmailQuote(content: str) -> str:
|
def htmlReplaceEmailQuote(content: str) -> str:
|
||||||
"""Replaces an email style quote "> Some quote" with html blockquote
|
"""Replaces an email style quote "> Some quote" with html blockquote
|
||||||
"""
|
"""
|
||||||
|
|
@ -44,9 +70,12 @@ def htmlReplaceEmailQuote(content: str) -> str:
|
||||||
newContent += '<p>' + lineStr + '</p>'
|
newContent += '<p>' + lineStr + '</p>'
|
||||||
else:
|
else:
|
||||||
lineStr = lineStr.replace('>> ', '><blockquote>')
|
lineStr = lineStr.replace('>> ', '><blockquote>')
|
||||||
|
if lineStr.startswith('>'):
|
||||||
|
lineStr = lineStr.replace('>', '<blockquote>', 1)
|
||||||
|
else:
|
||||||
lineStr = lineStr.replace('>', '<br>')
|
lineStr = lineStr.replace('>', '<br>')
|
||||||
newContent += '<p>' + lineStr + '</blockquote></p>'
|
newContent += '<p>' + lineStr + '</blockquote></p>'
|
||||||
return newContent
|
return removeQuotesWithinQuotes(newContent)
|
||||||
|
|
||||||
|
|
||||||
def htmlReplaceQuoteMarks(content: str) -> str:
|
def htmlReplaceQuoteMarks(content: str) -> str:
|
||||||
|
|
|
||||||
424
daemon.py
|
|
@ -61,6 +61,7 @@ from person import removeAccount
|
||||||
from person import canRemovePost
|
from person import canRemovePost
|
||||||
from person import personSnooze
|
from person import personSnooze
|
||||||
from person import personUnsnooze
|
from person import personUnsnooze
|
||||||
|
from posts import isModerator
|
||||||
from posts import mutePost
|
from posts import mutePost
|
||||||
from posts import unmutePost
|
from posts import unmutePost
|
||||||
from posts import createQuestionPost
|
from posts import createQuestionPost
|
||||||
|
|
@ -144,6 +145,8 @@ from webinterface import htmlSearchEmojiTextEntry
|
||||||
from webinterface import htmlUnfollowConfirm
|
from webinterface import htmlUnfollowConfirm
|
||||||
from webinterface import htmlProfileAfterSearch
|
from webinterface import htmlProfileAfterSearch
|
||||||
from webinterface import htmlEditProfile
|
from webinterface import htmlEditProfile
|
||||||
|
from webinterface import htmlEditLinks
|
||||||
|
from webinterface import htmlEditNewswire
|
||||||
from webinterface import htmlTermsOfService
|
from webinterface import htmlTermsOfService
|
||||||
from webinterface import htmlSkillsSearch
|
from webinterface import htmlSkillsSearch
|
||||||
from webinterface import htmlHistorySearch
|
from webinterface import htmlHistorySearch
|
||||||
|
|
@ -200,6 +203,7 @@ from followingCalendar import removePersonFromCalendar
|
||||||
from devices import E2EEdevicesCollection
|
from devices import E2EEdevicesCollection
|
||||||
from devices import E2EEvalidDevice
|
from devices import E2EEvalidDevice
|
||||||
from devices import E2EEaddDevice
|
from devices import E2EEaddDevice
|
||||||
|
from newswire import getRSSfromDict
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2689,6 +2693,216 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
cookie, callingDomain)
|
cookie, callingDomain)
|
||||||
self.server.POSTbusy = False
|
self.server.POSTbusy = False
|
||||||
|
|
||||||
|
def _linksUpdate(self, callingDomain: str, cookie: str,
|
||||||
|
authorized: bool, path: str,
|
||||||
|
baseDir: str, httpPrefix: str,
|
||||||
|
domain: str, domainFull: str,
|
||||||
|
onionDomain: str, i2pDomain: str, debug: bool,
|
||||||
|
defaultTimeline: str):
|
||||||
|
"""Updates the left links column of the timeline
|
||||||
|
"""
|
||||||
|
usersPath = path.replace('/linksdata', '')
|
||||||
|
usersPath = usersPath.replace('/editlinks', '')
|
||||||
|
actorStr = httpPrefix + '://' + domainFull + usersPath
|
||||||
|
if ' boundary=' in self.headers['Content-type']:
|
||||||
|
boundary = self.headers['Content-type'].split('boundary=')[1]
|
||||||
|
if ';' in boundary:
|
||||||
|
boundary = boundary.split(';')[0]
|
||||||
|
|
||||||
|
# get the nickname
|
||||||
|
nickname = getNicknameFromActor(actorStr)
|
||||||
|
moderator = None
|
||||||
|
if nickname:
|
||||||
|
moderator = isModerator(baseDir, nickname)
|
||||||
|
if not nickname or not moderator:
|
||||||
|
if callingDomain.endswith('.onion') and \
|
||||||
|
onionDomain:
|
||||||
|
actorStr = \
|
||||||
|
'http://' + onionDomain + usersPath
|
||||||
|
elif (callingDomain.endswith('.i2p') and
|
||||||
|
i2pDomain):
|
||||||
|
actorStr = \
|
||||||
|
'http://' + i2pDomain + usersPath
|
||||||
|
if not nickname:
|
||||||
|
print('WARN: nickname not found in ' + actorStr)
|
||||||
|
else:
|
||||||
|
print('WARN: nickname is not a moderator' + actorStr)
|
||||||
|
self._redirect_headers(actorStr, cookie, callingDomain)
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
return
|
||||||
|
|
||||||
|
length = int(self.headers['Content-length'])
|
||||||
|
|
||||||
|
# check that the POST isn't too large
|
||||||
|
if length > self.server.maxPostLength:
|
||||||
|
if callingDomain.endswith('.onion') and \
|
||||||
|
onionDomain:
|
||||||
|
actorStr = \
|
||||||
|
'http://' + onionDomain + usersPath
|
||||||
|
elif (callingDomain.endswith('.i2p') and
|
||||||
|
i2pDomain):
|
||||||
|
actorStr = \
|
||||||
|
'http://' + i2pDomain + usersPath
|
||||||
|
print('Maximum links data length exceeded ' + str(length))
|
||||||
|
self._redirect_headers(actorStr, cookie, callingDomain)
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# read the bytes of the http form POST
|
||||||
|
postBytes = self.rfile.read(length)
|
||||||
|
except SocketError as e:
|
||||||
|
if e.errno == errno.ECONNRESET:
|
||||||
|
print('WARN: connection was reset while ' +
|
||||||
|
'reading bytes from http form POST')
|
||||||
|
else:
|
||||||
|
print('WARN: error while reading bytes ' +
|
||||||
|
'from http form POST')
|
||||||
|
self.send_response(400)
|
||||||
|
self.end_headers()
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
return
|
||||||
|
except ValueError as e:
|
||||||
|
print('ERROR: failed to read bytes for POST')
|
||||||
|
print(e)
|
||||||
|
self.send_response(400)
|
||||||
|
self.end_headers()
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
return
|
||||||
|
|
||||||
|
linksFilename = baseDir + '/accounts/links.txt'
|
||||||
|
|
||||||
|
# extract all of the text fields into a dict
|
||||||
|
fields = \
|
||||||
|
extractTextFieldsInPOST(postBytes, boundary, debug)
|
||||||
|
if fields.get('editedLinks'):
|
||||||
|
linksStr = fields['editedLinks']
|
||||||
|
linksFile = open(linksFilename, "w+")
|
||||||
|
if linksFile:
|
||||||
|
linksFile.write(linksStr)
|
||||||
|
linksFile.close()
|
||||||
|
else:
|
||||||
|
if os.path.isfile(linksFilename):
|
||||||
|
os.remove(linksFilename)
|
||||||
|
|
||||||
|
# redirect back to the default timeline
|
||||||
|
if callingDomain.endswith('.onion') and \
|
||||||
|
onionDomain:
|
||||||
|
actorStr = \
|
||||||
|
'http://' + onionDomain + usersPath
|
||||||
|
elif (callingDomain.endswith('.i2p') and
|
||||||
|
i2pDomain):
|
||||||
|
actorStr = \
|
||||||
|
'http://' + i2pDomain + usersPath
|
||||||
|
self._redirect_headers(actorStr + '/' + defaultTimeline,
|
||||||
|
cookie, callingDomain)
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
|
||||||
|
def _newswireUpdate(self, callingDomain: str, cookie: str,
|
||||||
|
authorized: bool, path: str,
|
||||||
|
baseDir: str, httpPrefix: str,
|
||||||
|
domain: str, domainFull: str,
|
||||||
|
onionDomain: str, i2pDomain: str, debug: bool,
|
||||||
|
defaultTimeline: str):
|
||||||
|
"""Updates the right newswire column of the timeline
|
||||||
|
"""
|
||||||
|
usersPath = path.replace('/newswiredata', '')
|
||||||
|
usersPath = usersPath.replace('/editnewswire', '')
|
||||||
|
actorStr = httpPrefix + '://' + domainFull + usersPath
|
||||||
|
if ' boundary=' in self.headers['Content-type']:
|
||||||
|
boundary = self.headers['Content-type'].split('boundary=')[1]
|
||||||
|
if ';' in boundary:
|
||||||
|
boundary = boundary.split(';')[0]
|
||||||
|
|
||||||
|
# get the nickname
|
||||||
|
nickname = getNicknameFromActor(actorStr)
|
||||||
|
moderator = None
|
||||||
|
if nickname:
|
||||||
|
moderator = isModerator(baseDir, nickname)
|
||||||
|
if not nickname or not moderator:
|
||||||
|
if callingDomain.endswith('.onion') and \
|
||||||
|
onionDomain:
|
||||||
|
actorStr = \
|
||||||
|
'http://' + onionDomain + usersPath
|
||||||
|
elif (callingDomain.endswith('.i2p') and
|
||||||
|
i2pDomain):
|
||||||
|
actorStr = \
|
||||||
|
'http://' + i2pDomain + usersPath
|
||||||
|
if not nickname:
|
||||||
|
print('WARN: nickname not found in ' + actorStr)
|
||||||
|
else:
|
||||||
|
print('WARN: nickname is not a moderator' + actorStr)
|
||||||
|
self._redirect_headers(actorStr, cookie, callingDomain)
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
return
|
||||||
|
|
||||||
|
length = int(self.headers['Content-length'])
|
||||||
|
|
||||||
|
# check that the POST isn't too large
|
||||||
|
if length > self.server.maxPostLength:
|
||||||
|
if callingDomain.endswith('.onion') and \
|
||||||
|
onionDomain:
|
||||||
|
actorStr = \
|
||||||
|
'http://' + onionDomain + usersPath
|
||||||
|
elif (callingDomain.endswith('.i2p') and
|
||||||
|
i2pDomain):
|
||||||
|
actorStr = \
|
||||||
|
'http://' + i2pDomain + usersPath
|
||||||
|
print('Maximum newswire data length exceeded ' + str(length))
|
||||||
|
self._redirect_headers(actorStr, cookie, callingDomain)
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# read the bytes of the http form POST
|
||||||
|
postBytes = self.rfile.read(length)
|
||||||
|
except SocketError as e:
|
||||||
|
if e.errno == errno.ECONNRESET:
|
||||||
|
print('WARN: connection was reset while ' +
|
||||||
|
'reading bytes from http form POST')
|
||||||
|
else:
|
||||||
|
print('WARN: error while reading bytes ' +
|
||||||
|
'from http form POST')
|
||||||
|
self.send_response(400)
|
||||||
|
self.end_headers()
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
return
|
||||||
|
except ValueError as e:
|
||||||
|
print('ERROR: failed to read bytes for POST')
|
||||||
|
print(e)
|
||||||
|
self.send_response(400)
|
||||||
|
self.end_headers()
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
return
|
||||||
|
|
||||||
|
newswireFilename = baseDir + '/accounts/newswire.txt'
|
||||||
|
|
||||||
|
# extract all of the text fields into a dict
|
||||||
|
fields = \
|
||||||
|
extractTextFieldsInPOST(postBytes, boundary, debug)
|
||||||
|
if fields.get('editedNewswire'):
|
||||||
|
newswireStr = fields['editedNewswire']
|
||||||
|
newswireFile = open(newswireFilename, "w+")
|
||||||
|
if newswireFile:
|
||||||
|
newswireFile.write(newswireStr)
|
||||||
|
newswireFile.close()
|
||||||
|
else:
|
||||||
|
if os.path.isfile(newswireFilename):
|
||||||
|
os.remove(newswireFilename)
|
||||||
|
|
||||||
|
# redirect back to the default timeline
|
||||||
|
if callingDomain.endswith('.onion') and \
|
||||||
|
onionDomain:
|
||||||
|
actorStr = \
|
||||||
|
'http://' + onionDomain + usersPath
|
||||||
|
elif (callingDomain.endswith('.i2p') and
|
||||||
|
i2pDomain):
|
||||||
|
actorStr = \
|
||||||
|
'http://' + i2pDomain + usersPath
|
||||||
|
self._redirect_headers(actorStr + '/' + defaultTimeline,
|
||||||
|
cookie, callingDomain)
|
||||||
|
self.server.POSTbusy = False
|
||||||
|
|
||||||
def _profileUpdate(self, callingDomain: str, cookie: str,
|
def _profileUpdate(self, callingDomain: str, cookie: str,
|
||||||
authorized: bool, path: str,
|
authorized: bool, path: str,
|
||||||
baseDir: str, httpPrefix: str,
|
baseDir: str, httpPrefix: str,
|
||||||
|
|
@ -2765,7 +2979,8 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
actorChanged = True
|
actorChanged = True
|
||||||
profileMediaTypes = ('avatar', 'image',
|
profileMediaTypes = ('avatar', 'image',
|
||||||
'banner', 'search_banner',
|
'banner', 'search_banner',
|
||||||
'instanceLogo')
|
'instanceLogo',
|
||||||
|
'left_col_image', 'right_col_image')
|
||||||
profileMediaTypesUploaded = {}
|
profileMediaTypesUploaded = {}
|
||||||
for mType in profileMediaTypes:
|
for mType in profileMediaTypes:
|
||||||
if debug:
|
if debug:
|
||||||
|
|
@ -2834,8 +3049,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
# extract all of the text fields into a dict
|
# extract all of the text fields into a dict
|
||||||
fields = \
|
fields = \
|
||||||
extractTextFieldsInPOST(postBytes, boundary,
|
extractTextFieldsInPOST(postBytes, boundary, debug)
|
||||||
debug)
|
|
||||||
if debug:
|
if debug:
|
||||||
if fields:
|
if fields:
|
||||||
print('DEBUG: profile update text ' +
|
print('DEBUG: profile update text ' +
|
||||||
|
|
@ -3205,10 +3419,14 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.defaultTimeline = 'inbox'
|
self.server.defaultTimeline = 'inbox'
|
||||||
if fields['mediaInstance'] == 'on':
|
if fields['mediaInstance'] == 'on':
|
||||||
self.server.mediaInstance = True
|
self.server.mediaInstance = True
|
||||||
|
self.server.blogsInstance = False
|
||||||
self.server.defaultTimeline = 'tlmedia'
|
self.server.defaultTimeline = 'tlmedia'
|
||||||
setConfigParam(baseDir,
|
setConfigParam(baseDir,
|
||||||
"mediaInstance",
|
"mediaInstance",
|
||||||
self.server.mediaInstance)
|
self.server.mediaInstance)
|
||||||
|
setConfigParam(baseDir,
|
||||||
|
"blogsInstance",
|
||||||
|
self.server.blogsInstance)
|
||||||
else:
|
else:
|
||||||
if self.server.mediaInstance:
|
if self.server.mediaInstance:
|
||||||
self.server.mediaInstance = False
|
self.server.mediaInstance = False
|
||||||
|
|
@ -3223,10 +3441,14 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.defaultTimeline = 'inbox'
|
self.server.defaultTimeline = 'inbox'
|
||||||
if fields['blogsInstance'] == 'on':
|
if fields['blogsInstance'] == 'on':
|
||||||
self.server.blogsInstance = True
|
self.server.blogsInstance = True
|
||||||
|
self.server.mediaInstance = False
|
||||||
self.server.defaultTimeline = 'tlblogs'
|
self.server.defaultTimeline = 'tlblogs'
|
||||||
setConfigParam(baseDir,
|
setConfigParam(baseDir,
|
||||||
"blogsInstance",
|
"blogsInstance",
|
||||||
self.server.blogsInstance)
|
self.server.blogsInstance)
|
||||||
|
setConfigParam(baseDir,
|
||||||
|
"mediaInstance",
|
||||||
|
self.server.mediaInstance)
|
||||||
else:
|
else:
|
||||||
if self.server.blogsInstance:
|
if self.server.blogsInstance:
|
||||||
self.server.blogsInstance = False
|
self.server.blogsInstance = False
|
||||||
|
|
@ -3733,6 +3955,42 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
path + ' ' + callingDomain)
|
path + ' ' + callingDomain)
|
||||||
self._404()
|
self._404()
|
||||||
|
|
||||||
|
def _getNewswireFeed(self, authorized: bool,
|
||||||
|
callingDomain: str, path: str,
|
||||||
|
baseDir: str, httpPrefix: str,
|
||||||
|
domain: str, port: int, proxyType: str,
|
||||||
|
GETstartTime, GETtimings: {},
|
||||||
|
debug: bool):
|
||||||
|
"""Returns the newswire feed
|
||||||
|
"""
|
||||||
|
if not self.server.session:
|
||||||
|
print('Starting new session during RSS request')
|
||||||
|
self.server.session = \
|
||||||
|
createSession(proxyType)
|
||||||
|
if not self.server.session:
|
||||||
|
print('ERROR: GET failed to create session ' +
|
||||||
|
'during RSS request')
|
||||||
|
self._404()
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = getRSSfromDict(self.server.baseDir, self.server.newswire,
|
||||||
|
self.server.httpPrefix,
|
||||||
|
self.server.domainFull,
|
||||||
|
'Newswire', self.server.translate)
|
||||||
|
if msg:
|
||||||
|
msg = msg.encode('utf-8')
|
||||||
|
self._set_headers('text/xml', len(msg),
|
||||||
|
None, callingDomain)
|
||||||
|
self._write(msg)
|
||||||
|
if debug:
|
||||||
|
print('Sent rss2 newswire feed: ' +
|
||||||
|
path + ' ' + callingDomain)
|
||||||
|
return
|
||||||
|
if debug:
|
||||||
|
print('Failed to get rss2 newswire feed: ' +
|
||||||
|
path + ' ' + callingDomain)
|
||||||
|
self._404()
|
||||||
|
|
||||||
def _getRSS3feed(self, authorized: bool,
|
def _getRSS3feed(self, authorized: bool,
|
||||||
callingDomain: str, path: str,
|
callingDomain: str, path: str,
|
||||||
baseDir: str, httpPrefix: str,
|
baseDir: str, httpPrefix: str,
|
||||||
|
|
@ -7047,6 +7305,47 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self._404()
|
self._404()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _columImage(self, side: str, callingDomain: str, path: str,
|
||||||
|
baseDir: str, domain: str, port: int,
|
||||||
|
GETstartTime, GETtimings: {}) -> bool:
|
||||||
|
"""Shows an image at the top of the left/right column
|
||||||
|
"""
|
||||||
|
nickname = getNicknameFromActor(path)
|
||||||
|
if not nickname:
|
||||||
|
self._404()
|
||||||
|
return True
|
||||||
|
bannerFilename = \
|
||||||
|
baseDir + '/accounts/' + \
|
||||||
|
nickname + '@' + domain + '/' + side + '_col_image.png'
|
||||||
|
if os.path.isfile(bannerFilename):
|
||||||
|
if self._etag_exists(bannerFilename):
|
||||||
|
# The file has not changed
|
||||||
|
self._304()
|
||||||
|
return True
|
||||||
|
|
||||||
|
tries = 0
|
||||||
|
mediaBinary = None
|
||||||
|
while tries < 5:
|
||||||
|
try:
|
||||||
|
with open(bannerFilename, 'rb') as avFile:
|
||||||
|
mediaBinary = avFile.read()
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
time.sleep(1)
|
||||||
|
tries += 1
|
||||||
|
if mediaBinary:
|
||||||
|
self._set_headers_etag(bannerFilename, 'image/png',
|
||||||
|
mediaBinary, None,
|
||||||
|
callingDomain)
|
||||||
|
self._write(mediaBinary)
|
||||||
|
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||||
|
'account qrcode done',
|
||||||
|
side + ' col image')
|
||||||
|
return True
|
||||||
|
self._404()
|
||||||
|
return True
|
||||||
|
|
||||||
def _showBackgroundImage(self, callingDomain: str, path: str,
|
def _showBackgroundImage(self, callingDomain: str, path: str,
|
||||||
baseDir: str,
|
baseDir: str,
|
||||||
GETstartTime, GETtimings: {}) -> bool:
|
GETstartTime, GETtimings: {}) -> bool:
|
||||||
|
|
@ -7307,6 +7606,50 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _editLinks(self, callingDomain: str, path: str,
|
||||||
|
translate: {}, baseDir: str,
|
||||||
|
httpPrefix: str, domain: str, port: int,
|
||||||
|
cookie: str) -> bool:
|
||||||
|
"""Show the links from the left column
|
||||||
|
"""
|
||||||
|
if '/users/' in path and path.endswith('/editlinks'):
|
||||||
|
msg = htmlEditLinks(translate,
|
||||||
|
baseDir,
|
||||||
|
path, domain,
|
||||||
|
port,
|
||||||
|
httpPrefix).encode('utf-8')
|
||||||
|
if msg:
|
||||||
|
self._set_headers('text/html', len(msg),
|
||||||
|
cookie, callingDomain)
|
||||||
|
self._write(msg)
|
||||||
|
else:
|
||||||
|
self._404()
|
||||||
|
self.server.GETbusy = False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _editNewswire(self, callingDomain: str, path: str,
|
||||||
|
translate: {}, baseDir: str,
|
||||||
|
httpPrefix: str, domain: str, port: int,
|
||||||
|
cookie: str) -> bool:
|
||||||
|
"""Show the newswire from the right column
|
||||||
|
"""
|
||||||
|
if '/users/' in path and path.endswith('/editnewswire'):
|
||||||
|
msg = htmlEditNewswire(translate,
|
||||||
|
baseDir,
|
||||||
|
path, domain,
|
||||||
|
port,
|
||||||
|
httpPrefix).encode('utf-8')
|
||||||
|
if msg:
|
||||||
|
self._set_headers('text/html', len(msg),
|
||||||
|
cookie, callingDomain)
|
||||||
|
self._write(msg)
|
||||||
|
else:
|
||||||
|
self._404()
|
||||||
|
self.server.GETbusy = False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def _editEvent(self, callingDomain: str, path: str,
|
def _editEvent(self, callingDomain: str, path: str,
|
||||||
httpPrefix: str, domain: str, domainFull: str,
|
httpPrefix: str, domain: str, domainFull: str,
|
||||||
baseDir: str, translate: {},
|
baseDir: str, translate: {},
|
||||||
|
|
@ -7507,6 +7850,18 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||||
'fonts', 'sharedInbox enabled')
|
'fonts', 'sharedInbox enabled')
|
||||||
|
|
||||||
|
if self.path == '/newswire.xml':
|
||||||
|
self._getNewswireFeed(authorized,
|
||||||
|
callingDomain, self.path,
|
||||||
|
self.server.baseDir,
|
||||||
|
self.server.httpPrefix,
|
||||||
|
self.server.domain,
|
||||||
|
self.server.port,
|
||||||
|
self.server.proxyType,
|
||||||
|
GETstartTime, GETtimings,
|
||||||
|
self.server.debug)
|
||||||
|
return
|
||||||
|
|
||||||
# RSS 2.0
|
# RSS 2.0
|
||||||
if self.path.startswith('/blog/') and \
|
if self.path.startswith('/blog/') and \
|
||||||
self.path.endswith('/rss.xml'):
|
self.path.endswith('/rss.xml'):
|
||||||
|
|
@ -7965,8 +8320,8 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
'account qrcode done')
|
'account qrcode done')
|
||||||
|
|
||||||
# search screen banner image
|
# search screen banner image
|
||||||
if '/users/' in self.path and \
|
if '/users/' in self.path:
|
||||||
self.path.endswith('/search_banner.png'):
|
if self.path.endswith('/search_banner.png'):
|
||||||
if self._searchScreenBanner(callingDomain, self.path,
|
if self._searchScreenBanner(callingDomain, self.path,
|
||||||
self.server.baseDir,
|
self.server.baseDir,
|
||||||
self.server.domain,
|
self.server.domain,
|
||||||
|
|
@ -7974,6 +8329,22 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
GETstartTime, GETtimings):
|
GETstartTime, GETtimings):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.path.endswith('/left_col_image.png'):
|
||||||
|
if self._columImage('left', callingDomain, self.path,
|
||||||
|
self.server.baseDir,
|
||||||
|
self.server.domain,
|
||||||
|
self.server.port,
|
||||||
|
GETstartTime, GETtimings):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.path.endswith('/right_col_image.png'):
|
||||||
|
if self._columImage('right', callingDomain, self.path,
|
||||||
|
self.server.baseDir,
|
||||||
|
self.server.domain,
|
||||||
|
self.server.port,
|
||||||
|
GETstartTime, GETtimings):
|
||||||
|
return
|
||||||
|
|
||||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||||
'account qrcode done',
|
'account qrcode done',
|
||||||
'search screen banner done')
|
'search screen banner done')
|
||||||
|
|
@ -8601,6 +8972,26 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
cookie):
|
cookie):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# edit links from the left column of the timeline in web interface
|
||||||
|
if self._editLinks(callingDomain, self.path,
|
||||||
|
self.server.translate,
|
||||||
|
self.server.baseDir,
|
||||||
|
self.server.httpPrefix,
|
||||||
|
self.server.domain,
|
||||||
|
self.server.port,
|
||||||
|
cookie):
|
||||||
|
return
|
||||||
|
|
||||||
|
# edit newswire from the right column of the timeline
|
||||||
|
if self._editNewswire(callingDomain, self.path,
|
||||||
|
self.server.translate,
|
||||||
|
self.server.baseDir,
|
||||||
|
self.server.httpPrefix,
|
||||||
|
self.server.domain,
|
||||||
|
self.server.port,
|
||||||
|
cookie):
|
||||||
|
return
|
||||||
|
|
||||||
if self._showNewPost(callingDomain, self.path,
|
if self._showNewPost(callingDomain, self.path,
|
||||||
self.server.mediaInstance,
|
self.server.mediaInstance,
|
||||||
self.server.translate,
|
self.server.translate,
|
||||||
|
|
@ -10049,6 +10440,26 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.i2pDomain, self.server.debug)
|
self.server.i2pDomain, self.server.debug)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if authorized and self.path.endswith('/linksdata'):
|
||||||
|
self._linksUpdate(callingDomain, cookie, authorized, self.path,
|
||||||
|
self.server.baseDir, self.server.httpPrefix,
|
||||||
|
self.server.domain,
|
||||||
|
self.server.domainFull,
|
||||||
|
self.server.onionDomain,
|
||||||
|
self.server.i2pDomain, self.server.debug,
|
||||||
|
self.server.defaultTimeline)
|
||||||
|
return
|
||||||
|
|
||||||
|
if authorized and self.path.endswith('/newswiredata'):
|
||||||
|
self._newswireUpdate(callingDomain, cookie, authorized, self.path,
|
||||||
|
self.server.baseDir, self.server.httpPrefix,
|
||||||
|
self.server.domain,
|
||||||
|
self.server.domainFull,
|
||||||
|
self.server.onionDomain,
|
||||||
|
self.server.i2pDomain, self.server.debug,
|
||||||
|
self.server.defaultTimeline)
|
||||||
|
return
|
||||||
|
|
||||||
self._benchmarkPOSTtimings(POSTstartTime, POSTtimings, 3)
|
self._benchmarkPOSTtimings(POSTstartTime, POSTtimings, 3)
|
||||||
|
|
||||||
# moderator action buttons
|
# moderator action buttons
|
||||||
|
|
@ -10660,6 +11071,9 @@ def runDaemon(blogsInstance: bool, mediaInstance: bool,
|
||||||
httpd.unitTest = unitTest
|
httpd.unitTest = unitTest
|
||||||
httpd.YTReplacementDomain = YTReplacementDomain
|
httpd.YTReplacementDomain = YTReplacementDomain
|
||||||
|
|
||||||
|
# newswire storing rss feeds
|
||||||
|
httpd.newswire = {}
|
||||||
|
|
||||||
# This counter is used to update the list of blocked domains in memory.
|
# This counter is used to update the list of blocked domains in memory.
|
||||||
# It helps to avoid touching the disk and so improves flooding resistance
|
# It helps to avoid touching the disk and so improves flooding resistance
|
||||||
httpd.blocklistUpdateCtr = 0
|
httpd.blocklistUpdateCtr = 0
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--main-bg-color: #282c37;
|
--main-bg-color: #282c37;
|
||||||
|
--column-left-color: #282c37;
|
||||||
--link-bg-color: #282c37;
|
--link-bg-color: #282c37;
|
||||||
--dropdown-fg-color: #dddddd;
|
--dropdown-fg-color: #dddddd;
|
||||||
--dropdown-bg-color: #111;
|
--dropdown-bg-color: #111;
|
||||||
|
|
@ -12,6 +13,7 @@
|
||||||
--main-bg-color-report: #221c27;
|
--main-bg-color-report: #221c27;
|
||||||
--main-header-color-roles: #282237;
|
--main-header-color-roles: #282237;
|
||||||
--main-fg-color: #dddddd;
|
--main-fg-color: #dddddd;
|
||||||
|
--column-left-fg-color: #dddddd;
|
||||||
--main-link-color: #999;
|
--main-link-color: #999;
|
||||||
--main-link-color-hover: #bbb;
|
--main-link-color-hover: #bbb;
|
||||||
--main-visited-color: #888;
|
--main-visited-color: #888;
|
||||||
|
|
@ -21,6 +23,7 @@
|
||||||
--font-size-header-mobile: 32px;
|
--font-size-header-mobile: 32px;
|
||||||
--font-color-header: #ccc;
|
--font-color-header: #ccc;
|
||||||
--font-size-button-mobile: 34px;
|
--font-size-button-mobile: 34px;
|
||||||
|
--font-size-links: 18px;
|
||||||
--font-size: 30px;
|
--font-size: 30px;
|
||||||
--font-size2: 24px;
|
--font-size2: 24px;
|
||||||
--font-size3: 38px;
|
--font-size3: 38px;
|
||||||
|
|
@ -61,6 +64,14 @@
|
||||||
--quote-font-weight: normal;
|
--quote-font-weight: normal;
|
||||||
--quote-font-size: 120%;
|
--quote-font-size: 120%;
|
||||||
--line-spacing: 130%;
|
--line-spacing: 130%;
|
||||||
|
--column-left-width: 10vw;
|
||||||
|
--column-center-width: 80vw;
|
||||||
|
--column-right-width: 10vw;
|
||||||
|
--column-left-header-background: #555;
|
||||||
|
--column-left-header-color: #fff;
|
||||||
|
--column-left-header-size: 20px;
|
||||||
|
--column-left-icon-size: 20%;
|
||||||
|
--column-right-icon-size: 20%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
|
@ -84,9 +95,7 @@ body, html {
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
max-width: 80%;
|
|
||||||
min-width: 950px;
|
min-width: 950px;
|
||||||
margin: 0 auto;
|
|
||||||
font-size: var(--font-size);
|
font-size: var(--font-size);
|
||||||
line-height: var(--line-spacing);
|
line-height: var(--line-spacing);
|
||||||
}
|
}
|
||||||
|
|
@ -126,6 +135,15 @@ h1 {
|
||||||
color: var(--title-color);
|
color: var(--title-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3.linksHeader {
|
||||||
|
background-color: var(--column-left-header-background);
|
||||||
|
color: var(--column-left-header-color);
|
||||||
|
font-size: var(--column-left-header-size);
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 4px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
a, u {
|
a, u {
|
||||||
color: var(--main-fg-color);
|
color: var(--main-fg-color);
|
||||||
}
|
}
|
||||||
|
|
@ -158,15 +176,6 @@ a:focus {
|
||||||
border: 2px solid var(--focus-color);
|
border: 2px solid var(--focus-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-banner {
|
|
||||||
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("banner.png");
|
|
||||||
height: 10%;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-image {
|
.hero-image {
|
||||||
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("image.png");
|
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("image.png");
|
||||||
height: 50%;
|
height: 50%;
|
||||||
|
|
@ -923,6 +932,101 @@ aside .toggle-inside li {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 400px) {
|
@media screen and (min-width: 400px) {
|
||||||
|
body, html {
|
||||||
|
background-color: var(--main-bg-color);
|
||||||
|
color: var(--main-fg-color);
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
min-width: 950px;
|
||||||
|
font-size: var(--font-size);
|
||||||
|
line-height: var(--line-spacing);
|
||||||
|
}
|
||||||
|
.timeline-banner {
|
||||||
|
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("banner.png");
|
||||||
|
height: 15%;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100vw;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.timeline {
|
||||||
|
border: 0;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
.col-left a:link {
|
||||||
|
background: var(--column-left-color);
|
||||||
|
}
|
||||||
|
.col-left a:visited {
|
||||||
|
background: var(--column-left-color);
|
||||||
|
}
|
||||||
|
.column-left {
|
||||||
|
background-color: var(--column-left-color);
|
||||||
|
width: var(--column-left-width);
|
||||||
|
}
|
||||||
|
.col-left {
|
||||||
|
color: var(--column-left-fg-color);
|
||||||
|
padding: 10px 10px;
|
||||||
|
font-size: var(--font-size-links);
|
||||||
|
float: left;
|
||||||
|
width: var(--column-left-width);
|
||||||
|
}
|
||||||
|
.col-left img.leftColEdit {
|
||||||
|
background: var(--column-left-color);
|
||||||
|
margin: 40px 0;
|
||||||
|
width: var(--column-left-icon-size);
|
||||||
|
}
|
||||||
|
.col-left img.leftColEditImage {
|
||||||
|
background: var(--column-left-color);
|
||||||
|
width: var(--column-left-icon-size);
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.col-left img.leftColImg {
|
||||||
|
background: var(--column-left-color);
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0;
|
||||||
|
padding: 0 0;
|
||||||
|
}
|
||||||
|
.column-center {
|
||||||
|
width: var(--column-center-width);
|
||||||
|
}
|
||||||
|
.col-center {
|
||||||
|
width: var(--column-center-width);
|
||||||
|
background-color: var(--main-bg-color);
|
||||||
|
}
|
||||||
|
.col-right a:link {
|
||||||
|
background: var(--column-left-color);
|
||||||
|
}
|
||||||
|
.col-right a:visited {
|
||||||
|
background: var(--column-left-color);
|
||||||
|
}
|
||||||
|
.column-right {
|
||||||
|
background-color: var(--column-left-color);
|
||||||
|
width: var(--column-right-width);
|
||||||
|
}
|
||||||
|
.col-right {
|
||||||
|
background-color: var(--column-left-color);
|
||||||
|
color: var(--column-left-fg-color);
|
||||||
|
padding-right: 30px;
|
||||||
|
font-size: var(--font-size-links);
|
||||||
|
width: var(--column-right-width);
|
||||||
|
}
|
||||||
|
.col-right img.rightColEdit {
|
||||||
|
background: var(--column-left-color);
|
||||||
|
margin: 40px 0;
|
||||||
|
width: var(--column-right-icon-size);
|
||||||
|
}
|
||||||
|
.col-right img.rightColEditImage {
|
||||||
|
background: var(--column-left-color);
|
||||||
|
width: var(--column-right-icon-size);
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.col-right img.rightColImg {
|
||||||
|
background: var(--column-left-color);
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0;
|
||||||
|
padding: 0 0;
|
||||||
|
}
|
||||||
.likesCount {
|
.likesCount {
|
||||||
font-size: var(--font-size-likes);
|
font-size: var(--font-size-likes);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
|
@ -1372,6 +1476,53 @@ aside .toggle-inside li {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1000px) {
|
@media screen and (max-width: 1000px) {
|
||||||
|
body, html {
|
||||||
|
background-color: var(--main-bg-color);
|
||||||
|
color: var(--main-fg-color);
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
min-width: 950px;
|
||||||
|
margin-left: 0;
|
||||||
|
font-size: var(--font-size);
|
||||||
|
line-height: var(--line-spacing);
|
||||||
|
}
|
||||||
|
.timeline-banner {
|
||||||
|
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("banner.png");
|
||||||
|
height: 6%;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 145vw;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.timeline {
|
||||||
|
border: 0;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
.column-left {
|
||||||
|
display: none;
|
||||||
|
width: 0%;
|
||||||
|
}
|
||||||
|
.col-left {
|
||||||
|
float: left;
|
||||||
|
width: 0%;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.col-center {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
.col-right {
|
||||||
|
float: right;
|
||||||
|
width: 0%;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.column-right {
|
||||||
|
display: none;
|
||||||
|
width: 0%;
|
||||||
|
}
|
||||||
|
.column-center {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
.likesCount {
|
.likesCount {
|
||||||
font-size: var(--font-size-likes-mobile);
|
font-size: var(--font-size-likes-mobile);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
|
|
||||||
13
epicyon.py
|
|
@ -28,6 +28,7 @@ from posts import getUserUrl
|
||||||
from posts import checkDomains
|
from posts import checkDomains
|
||||||
from session import createSession
|
from session import createSession
|
||||||
from session import getJson
|
from session import getJson
|
||||||
|
from newswire import getRSS
|
||||||
from filters import addFilter
|
from filters import addFilter
|
||||||
from filters import removeFilter
|
from filters import removeFilter
|
||||||
import os
|
import os
|
||||||
|
|
@ -176,6 +177,8 @@ parser.add_argument('--postsraw', dest='postsraw', type=str,
|
||||||
help='Show raw json of posts for the given handle')
|
help='Show raw json of posts for the given handle')
|
||||||
parser.add_argument('--json', dest='json', type=str, default=None,
|
parser.add_argument('--json', dest='json', type=str, default=None,
|
||||||
help='Show the json for a given activitypub url')
|
help='Show the json for a given activitypub url')
|
||||||
|
parser.add_argument('--rss', dest='rss', type=str, default=None,
|
||||||
|
help='Show an rss feed for a given url')
|
||||||
parser.add_argument('-f', '--federate', nargs='+', dest='federationList',
|
parser.add_argument('-f', '--federate', nargs='+', dest='federationList',
|
||||||
help='Specify federation list separated by spaces')
|
help='Specify federation list separated by spaces')
|
||||||
parser.add_argument("--repliesEnabled", "--commentsEnabled",
|
parser.add_argument("--repliesEnabled", "--commentsEnabled",
|
||||||
|
|
@ -595,6 +598,12 @@ if args.json:
|
||||||
pprint(testJson)
|
pprint(testJson)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
if args.rss:
|
||||||
|
session = createSession(None)
|
||||||
|
testRSS = getRSS(session, args.rss)
|
||||||
|
pprint(testRSS)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
# create cache for actors
|
# create cache for actors
|
||||||
if not os.path.isdir(baseDir + '/cache'):
|
if not os.path.isdir(baseDir + '/cache'):
|
||||||
os.mkdir(baseDir + '/cache')
|
os.mkdir(baseDir + '/cache')
|
||||||
|
|
@ -615,11 +624,15 @@ if not args.mediainstance:
|
||||||
mediaInstance = getConfigParam(baseDir, 'mediaInstance')
|
mediaInstance = getConfigParam(baseDir, 'mediaInstance')
|
||||||
if mediaInstance is not None:
|
if mediaInstance is not None:
|
||||||
args.mediainstance = mediaInstance
|
args.mediainstance = mediaInstance
|
||||||
|
if args.mediainstance:
|
||||||
|
args.blogsinstance = False
|
||||||
|
|
||||||
if not args.blogsinstance:
|
if not args.blogsinstance:
|
||||||
blogsInstance = getConfigParam(baseDir, 'blogsInstance')
|
blogsInstance = getConfigParam(baseDir, 'blogsInstance')
|
||||||
if blogsInstance is not None:
|
if blogsInstance is not None:
|
||||||
args.blogsinstance = blogsInstance
|
args.blogsinstance = blogsInstance
|
||||||
|
if args.blogsinstance:
|
||||||
|
args.mediainstance = False
|
||||||
|
|
||||||
# set the instance title in config.json
|
# set the instance title in config.json
|
||||||
title = getConfigParam(baseDir, 'instanceTitle')
|
title = getConfigParam(baseDir, 'instanceTitle')
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 29 KiB |
|
|
@ -0,0 +1,177 @@
|
||||||
|
__filename__ = "newswire.py"
|
||||||
|
__author__ = "Bob Mottram"
|
||||||
|
__license__ = "AGPL3+"
|
||||||
|
__version__ = "1.1.0"
|
||||||
|
__maintainer__ = "Bob Mottram"
|
||||||
|
__email__ = "bob@freedombone.net"
|
||||||
|
__status__ = "Production"
|
||||||
|
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
from socket import error as SocketError
|
||||||
|
import errno
|
||||||
|
from datetime import datetime
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
|
def rss2Header(httpPrefix: str,
|
||||||
|
nickname: str, domainFull: str,
|
||||||
|
title: str, translate: {}) -> str:
|
||||||
|
rssStr = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
|
||||||
|
rssStr += "<rss version=\"2.0\">"
|
||||||
|
rssStr += '<channel>'
|
||||||
|
if title.startswith('News'):
|
||||||
|
rssStr += ' <title>Newswire</title>'
|
||||||
|
else:
|
||||||
|
rssStr += ' <title>' + translate[title] + '</title>'
|
||||||
|
if title.startswith('News'):
|
||||||
|
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
|
||||||
|
'/newswire.xml' + '</link>'
|
||||||
|
else:
|
||||||
|
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
|
||||||
|
'/users/' + nickname + '/rss.xml' + '</link>'
|
||||||
|
return rssStr
|
||||||
|
|
||||||
|
|
||||||
|
def rss2Footer() -> str:
|
||||||
|
rssStr = '</channel>'
|
||||||
|
rssStr += '</rss>'
|
||||||
|
return rssStr
|
||||||
|
|
||||||
|
|
||||||
|
def xml2StrToDict(xmlStr: str) -> {}:
|
||||||
|
"""Converts an xml 2.0 string to a dictionary
|
||||||
|
"""
|
||||||
|
if '<item>' not in xmlStr:
|
||||||
|
return {}
|
||||||
|
result = {}
|
||||||
|
rssItems = xmlStr.split('<item>')
|
||||||
|
for rssItem in rssItems:
|
||||||
|
if '<title>' not in rssItem:
|
||||||
|
continue
|
||||||
|
if '</title>' not in rssItem:
|
||||||
|
continue
|
||||||
|
if '<link>' not in rssItem:
|
||||||
|
continue
|
||||||
|
if '</link>' not in rssItem:
|
||||||
|
continue
|
||||||
|
if '<pubDate>' not in rssItem:
|
||||||
|
continue
|
||||||
|
if '</pubDate>' not in rssItem:
|
||||||
|
continue
|
||||||
|
title = rssItem.split('<title>')[1]
|
||||||
|
title = title.split('</title>')[0]
|
||||||
|
link = rssItem.split('<link>')[1]
|
||||||
|
link = link.split('</link>')[0]
|
||||||
|
pubDate = rssItem.split('<pubDate>')[1]
|
||||||
|
pubDate = pubDate.split('</pubDate>')[0]
|
||||||
|
parsed = False
|
||||||
|
try:
|
||||||
|
publishedDate = \
|
||||||
|
datetime.strptime(pubDate, "%a, %d %b %Y %H:%M:%S %z")
|
||||||
|
result[str(publishedDate)] = [title, link]
|
||||||
|
parsed = True
|
||||||
|
except BaseException:
|
||||||
|
pass
|
||||||
|
if not parsed:
|
||||||
|
try:
|
||||||
|
publishedDate = \
|
||||||
|
datetime.strptime(pubDate, "%a, %d %b %Y %H:%M:%S UT")
|
||||||
|
result[str(publishedDate) + '+00:00'] = [title, link]
|
||||||
|
parsed = True
|
||||||
|
except BaseException:
|
||||||
|
print('WARN: unrecognized RSS date format: ' + pubDate)
|
||||||
|
pass
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def xmlStrToDict(xmlStr: str) -> {}:
|
||||||
|
"""Converts an xml string to a dictionary
|
||||||
|
"""
|
||||||
|
if 'rss version="2.0"' in xmlStr:
|
||||||
|
return xml2StrToDict(xmlStr)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def getRSS(session, url: str) -> {}:
|
||||||
|
"""Returns an RSS url as a dict
|
||||||
|
"""
|
||||||
|
if not isinstance(url, str):
|
||||||
|
print('url: ' + str(url))
|
||||||
|
print('ERROR: getRSS url should be a string')
|
||||||
|
return None
|
||||||
|
headers = {
|
||||||
|
'Accept': 'text/xml; charset=UTF-8'
|
||||||
|
}
|
||||||
|
params = None
|
||||||
|
sessionParams = {}
|
||||||
|
sessionHeaders = {}
|
||||||
|
if headers:
|
||||||
|
sessionHeaders = headers
|
||||||
|
if params:
|
||||||
|
sessionParams = params
|
||||||
|
sessionHeaders['User-Agent'] = \
|
||||||
|
'Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0'
|
||||||
|
if not session:
|
||||||
|
print('WARN: no session specified for getRSS')
|
||||||
|
try:
|
||||||
|
result = session.get(url, headers=sessionHeaders, params=sessionParams)
|
||||||
|
return xmlStrToDict(result.text)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print('ERROR: getRSS failed\nurl: ' + str(url) + '\n' +
|
||||||
|
'headers: ' + str(sessionHeaders) + '\n' +
|
||||||
|
'params: ' + str(sessionParams) + '\n')
|
||||||
|
print(e)
|
||||||
|
except ValueError as e:
|
||||||
|
print('ERROR: getRSS failed\nurl: ' + str(url) + '\n' +
|
||||||
|
'headers: ' + str(sessionHeaders) + '\n' +
|
||||||
|
'params: ' + str(sessionParams) + '\n')
|
||||||
|
print(e)
|
||||||
|
except SocketError as e:
|
||||||
|
if e.errno == errno.ECONNRESET:
|
||||||
|
print('WARN: connection was reset during getRSS')
|
||||||
|
print(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def getRSSfromDict(baseDir: str, newswire: {},
|
||||||
|
httpPrefix: str, domainFull: str,
|
||||||
|
title: str, translate: {}) -> str:
|
||||||
|
"""Returns an rss feed from the current newswire dict.
|
||||||
|
This allows other instances to subscribe to the same newswire
|
||||||
|
"""
|
||||||
|
rssStr = rss2Header(httpPrefix,
|
||||||
|
None, domainFull,
|
||||||
|
'Newswire', translate)
|
||||||
|
for published, fields in newswire.items():
|
||||||
|
rssStr += '<item>\n'
|
||||||
|
rssStr += ' <title>' + fields[0] + '</title>\n'
|
||||||
|
rssStr += ' <link>' + fields[1] + '</link>\n'
|
||||||
|
pubDate = datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
rssDateStr = pubDate.strftime("%a, %d %b %Y %H:%M:%S UT")
|
||||||
|
rssStr += ' <pubDate>' + rssDateStr + '</pubDate>\n'
|
||||||
|
rssStr += '</item>\n'
|
||||||
|
rssStr += rss2Footer()
|
||||||
|
return rssStr
|
||||||
|
|
||||||
|
|
||||||
|
def getDictFromNewswire(session, baseDir: str) -> {}:
|
||||||
|
"""Gets rss feeds as a dictionary from newswire file
|
||||||
|
"""
|
||||||
|
subscriptionsFilename = baseDir + '/accounts/newswire.txt'
|
||||||
|
if not os.path.isfile(subscriptionsFilename):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
rssFeed = []
|
||||||
|
with open(subscriptionsFilename, 'r') as fp:
|
||||||
|
rssFeed = fp.readlines()
|
||||||
|
result = {}
|
||||||
|
for url in rssFeed:
|
||||||
|
url = url.strip()
|
||||||
|
if '://' not in url:
|
||||||
|
continue
|
||||||
|
if url.startswith('#'):
|
||||||
|
continue
|
||||||
|
result = dict(result.items() + getRSS(session, url).items())
|
||||||
|
sortedResult = OrderedDict(sorted(result.items(), reverse=False))
|
||||||
|
return sortedResult
|
||||||
|
|
@ -54,7 +54,7 @@ def createSession(proxyType: str):
|
||||||
|
|
||||||
|
|
||||||
def getJson(session, url: str, headers: {}, params: {},
|
def getJson(session, url: str, headers: {}, params: {},
|
||||||
version='1.0.0', httpPrefix='https',
|
version='1.1.0', httpPrefix='https',
|
||||||
domain='testdomain') -> {}:
|
domain='testdomain') -> {}:
|
||||||
if not isinstance(url, str):
|
if not isinstance(url, str):
|
||||||
print('url: ' + str(url))
|
print('url: ' + str(url))
|
||||||
|
|
|
||||||
23
tests.py
|
|
@ -2125,7 +2125,7 @@ def testReplaceEmailQuote():
|
||||||
"<p>Some other text.</p>"
|
"<p>Some other text.</p>"
|
||||||
resultStr = htmlReplaceEmailQuote(testStr)
|
resultStr = htmlReplaceEmailQuote(testStr)
|
||||||
if resultStr != expectedStr:
|
if resultStr != expectedStr:
|
||||||
print('Result: ' + resultStr)
|
print('Result: ' + str(resultStr))
|
||||||
print('Expect: ' + expectedStr)
|
print('Expect: ' + expectedStr)
|
||||||
assert resultStr == expectedStr
|
assert resultStr == expectedStr
|
||||||
|
|
||||||
|
|
@ -2135,7 +2135,26 @@ def testReplaceEmailQuote():
|
||||||
"second line</blockquote></p><p>Some question?</p>"
|
"second line</blockquote></p><p>Some question?</p>"
|
||||||
resultStr = htmlReplaceEmailQuote(testStr)
|
resultStr = htmlReplaceEmailQuote(testStr)
|
||||||
if resultStr != expectedStr:
|
if resultStr != expectedStr:
|
||||||
print('Result: ' + resultStr)
|
print('Result: ' + str(resultStr))
|
||||||
|
print('Expect: ' + expectedStr)
|
||||||
|
assert resultStr == expectedStr
|
||||||
|
|
||||||
|
testStr = "<p><span class=\"h-card\">" + \
|
||||||
|
"<a href=\"https://somedomain/@somenick\" " + \
|
||||||
|
"class=\"u-url mention\">@<span>somenick</span>" + \
|
||||||
|
"</a></span> </p><p>> Text1.<br />> <br />" + \
|
||||||
|
"> Text2<br />> <br />> Text3<br />" + \
|
||||||
|
"><br />> Text4<br />> <br />> " + \
|
||||||
|
"Text5<br />> <br />> Text6</p><p>Text7</p>"
|
||||||
|
expectedStr = "<p><span class=\"h-card\">" + \
|
||||||
|
"<a href=\"https://somedomain/@somenick\" " + \
|
||||||
|
"class=\"u-url mention\">@<span>somenick</span></a>" + \
|
||||||
|
"</span> </p><p><blockquote> Text1.<br /><br />" + \
|
||||||
|
"Text2<br /><br />Text3<br />><br />Text4<br />" + \
|
||||||
|
"<br />Text5<br /><br />Text6</blockquote></p><p>Text7</p>"
|
||||||
|
resultStr = htmlReplaceEmailQuote(testStr)
|
||||||
|
if resultStr != expectedStr:
|
||||||
|
print('Result: ' + str(resultStr))
|
||||||
print('Expect: ' + expectedStr)
|
print('Expect: ' + expectedStr)
|
||||||
assert resultStr == expectedStr
|
assert resultStr == expectedStr
|
||||||
|
|
||||||
|
|
|
||||||
60
theme.py
|
|
@ -15,7 +15,7 @@ from shutil import copyfile
|
||||||
def getThemeFiles() -> []:
|
def getThemeFiles() -> []:
|
||||||
return ('epicyon.css', 'login.css', 'follow.css',
|
return ('epicyon.css', 'login.css', 'follow.css',
|
||||||
'suspended.css', 'calendar.css', 'blog.css',
|
'suspended.css', 'calendar.css', 'blog.css',
|
||||||
'options.css', 'search.css')
|
'options.css', 'search.css', 'links.css')
|
||||||
|
|
||||||
|
|
||||||
def getThemesList() -> []:
|
def getThemesList() -> []:
|
||||||
|
|
@ -264,12 +264,17 @@ def setThemeIndymedia(baseDir: str):
|
||||||
"font-size4": "24px",
|
"font-size4": "24px",
|
||||||
"font-size5": "22px",
|
"font-size5": "22px",
|
||||||
"main-bg-color": "black",
|
"main-bg-color": "black",
|
||||||
|
"column-left-header-color": "#fff",
|
||||||
|
"column-left-header-background": "#555",
|
||||||
|
"column-left-header-size": "20px",
|
||||||
|
"column-left-color": "#003366",
|
||||||
"text-entry-background": "#0f0d10",
|
"text-entry-background": "#0f0d10",
|
||||||
"link-bg-color": "black",
|
"link-bg-color": "black",
|
||||||
"main-link-color": "#ff9900",
|
"main-link-color": "#ff9900",
|
||||||
"main-link-color-hover": "#d09338",
|
"main-link-color-hover": "#d09338",
|
||||||
"main-visited-color": "#ffb900",
|
"main-visited-color": "#ffb900",
|
||||||
"main-fg-color": "white",
|
"main-fg-color": "white",
|
||||||
|
"column-left-fg-color": "white",
|
||||||
"main-bg-color-dm": "#0b0a0a",
|
"main-bg-color-dm": "#0b0a0a",
|
||||||
"border-color": "#003366",
|
"border-color": "#003366",
|
||||||
"border-width": "0",
|
"border-width": "0",
|
||||||
|
|
@ -292,6 +297,10 @@ def setThemeIndymedia(baseDir: str):
|
||||||
"title-text": "white",
|
"title-text": "white",
|
||||||
"title-background": "#003366",
|
"title-background": "#003366",
|
||||||
"quote-right-margin": "0.1em",
|
"quote-right-margin": "0.1em",
|
||||||
|
"column-left-width": "10vw",
|
||||||
|
"column-center-width": "70vw",
|
||||||
|
"column-right-width": "20vw",
|
||||||
|
"column-right-icon-size": "11%"
|
||||||
}
|
}
|
||||||
setThemeFromDict(baseDir, name, themeParams, bgParams)
|
setThemeFromDict(baseDir, name, themeParams, bgParams)
|
||||||
|
|
||||||
|
|
@ -311,6 +320,7 @@ def setThemeBlue(baseDir: str):
|
||||||
"gallery-font-size": "35px",
|
"gallery-font-size": "35px",
|
||||||
"gallery-font-size-mobile": "55px",
|
"gallery-font-size-mobile": "55px",
|
||||||
"main-bg-color": "#002365",
|
"main-bg-color": "#002365",
|
||||||
|
"column-left-color": "#002365",
|
||||||
"text-entry-background": "#002365",
|
"text-entry-background": "#002365",
|
||||||
"link-bg-color": "#002365",
|
"link-bg-color": "#002365",
|
||||||
"main-bg-color-reply": "#002365",
|
"main-bg-color-reply": "#002365",
|
||||||
|
|
@ -348,11 +358,13 @@ def setThemeNight(baseDir: str):
|
||||||
"font-size4": "24px",
|
"font-size4": "24px",
|
||||||
"font-size5": "22px",
|
"font-size5": "22px",
|
||||||
"main-bg-color": "#0f0d10",
|
"main-bg-color": "#0f0d10",
|
||||||
|
"column-left-color": "#0f0d10",
|
||||||
"text-entry-background": "#0f0d10",
|
"text-entry-background": "#0f0d10",
|
||||||
"link-bg-color": "#0f0d10",
|
"link-bg-color": "#0f0d10",
|
||||||
"main-link-color": "ff9900",
|
"main-link-color": "ff9900",
|
||||||
"main-link-color-hover": "#d09338",
|
"main-link-color-hover": "#d09338",
|
||||||
"main-fg-color": "#a961ab",
|
"main-fg-color": "#a961ab",
|
||||||
|
"column-left-fg-color": "#a961ab",
|
||||||
"main-bg-color-dm": "#0b0a0a",
|
"main-bg-color-dm": "#0b0a0a",
|
||||||
"border-color": "#606984",
|
"border-color": "#606984",
|
||||||
"main-bg-color-reply": "#0f0d10",
|
"main-bg-color-reply": "#0f0d10",
|
||||||
|
|
@ -398,6 +410,7 @@ def setThemeStarlight(baseDir: str):
|
||||||
"font-size4": "24px",
|
"font-size4": "24px",
|
||||||
"font-size5": "22px",
|
"font-size5": "22px",
|
||||||
"main-bg-color": "#0f0d10",
|
"main-bg-color": "#0f0d10",
|
||||||
|
"column-left-color": "#0f0d10",
|
||||||
"text-entry-background": "#0f0d10",
|
"text-entry-background": "#0f0d10",
|
||||||
"link-bg-color": "#0f0d10",
|
"link-bg-color": "#0f0d10",
|
||||||
"main-link-color": "#ffc4bc",
|
"main-link-color": "#ffc4bc",
|
||||||
|
|
@ -405,6 +418,7 @@ def setThemeStarlight(baseDir: str):
|
||||||
"title-color": "#ffc4bc",
|
"title-color": "#ffc4bc",
|
||||||
"main-visited-color": "#e1c4bc",
|
"main-visited-color": "#e1c4bc",
|
||||||
"main-fg-color": "#ffc4bc",
|
"main-fg-color": "#ffc4bc",
|
||||||
|
"column-left-fg-color": "#ffc4bc",
|
||||||
"main-bg-color-dm": "#0b0a0a",
|
"main-bg-color-dm": "#0b0a0a",
|
||||||
"border-color": "#69282c",
|
"border-color": "#69282c",
|
||||||
"border-width": "3px",
|
"border-width": "3px",
|
||||||
|
|
@ -457,6 +471,7 @@ def setThemeHenge(baseDir: str):
|
||||||
"font-size4": "24px",
|
"font-size4": "24px",
|
||||||
"font-size5": "22px",
|
"font-size5": "22px",
|
||||||
"main-bg-color": "#383335",
|
"main-bg-color": "#383335",
|
||||||
|
"column-left-color": "#383335",
|
||||||
"text-entry-background": "#383335",
|
"text-entry-background": "#383335",
|
||||||
"link-bg-color": "#383335",
|
"link-bg-color": "#383335",
|
||||||
"main-link-color": "white",
|
"main-link-color": "white",
|
||||||
|
|
@ -464,6 +479,7 @@ def setThemeHenge(baseDir: str):
|
||||||
"title-color": "white",
|
"title-color": "white",
|
||||||
"main-visited-color": "#e1c4bc",
|
"main-visited-color": "#e1c4bc",
|
||||||
"main-fg-color": "white",
|
"main-fg-color": "white",
|
||||||
|
"column-left-fg-color": "white",
|
||||||
"main-bg-color-dm": "#343335",
|
"main-bg-color-dm": "#343335",
|
||||||
"border-color": "#222",
|
"border-color": "#222",
|
||||||
"border-width": "5px",
|
"border-width": "5px",
|
||||||
|
|
@ -506,6 +522,7 @@ def setThemeZen(baseDir: str):
|
||||||
setThemeInConfig(baseDir, name)
|
setThemeInConfig(baseDir, name)
|
||||||
themeParams = {
|
themeParams = {
|
||||||
"main-bg-color": "#5c4e41",
|
"main-bg-color": "#5c4e41",
|
||||||
|
"column-left-color": "#5c4e41",
|
||||||
"text-entry-background": "#5c4e41",
|
"text-entry-background": "#5c4e41",
|
||||||
"link-bg-color": "#5c4e41",
|
"link-bg-color": "#5c4e41",
|
||||||
"main-bg-color-reply": "#5c4e41",
|
"main-bg-color-reply": "#5c4e41",
|
||||||
|
|
@ -565,6 +582,7 @@ def setThemeLCD(baseDir: str):
|
||||||
name = 'lcd'
|
name = 'lcd'
|
||||||
themeParams = {
|
themeParams = {
|
||||||
"main-bg-color": "#9fb42b",
|
"main-bg-color": "#9fb42b",
|
||||||
|
"column-left-color": "#9fb42b",
|
||||||
"link-bg-color": "#33390d",
|
"link-bg-color": "#33390d",
|
||||||
"text-entry-foreground": "#33390d",
|
"text-entry-foreground": "#33390d",
|
||||||
"text-entry-background": "#9fb42b",
|
"text-entry-background": "#9fb42b",
|
||||||
|
|
@ -573,6 +591,7 @@ def setThemeLCD(baseDir: str):
|
||||||
"main-bg-color-dm": "#5fb42b",
|
"main-bg-color-dm": "#5fb42b",
|
||||||
"main-header-color-roles": "#9fb42b",
|
"main-header-color-roles": "#9fb42b",
|
||||||
"main-fg-color": "#33390d",
|
"main-fg-color": "#33390d",
|
||||||
|
"column-left-fg-color": "#33390d",
|
||||||
"border-color": "#33390d",
|
"border-color": "#33390d",
|
||||||
"border-width": "5px",
|
"border-width": "5px",
|
||||||
"main-link-color": "#9fb42b",
|
"main-link-color": "#9fb42b",
|
||||||
|
|
@ -641,11 +660,13 @@ def setThemePurple(baseDir: str):
|
||||||
"font-size4": "24px",
|
"font-size4": "24px",
|
||||||
"font-size5": "22px",
|
"font-size5": "22px",
|
||||||
"main-bg-color": "#1f152d",
|
"main-bg-color": "#1f152d",
|
||||||
|
"column-left-color": "#1f152d",
|
||||||
"link-bg-color": "#1f152d",
|
"link-bg-color": "#1f152d",
|
||||||
"main-bg-color-reply": "#1a142d",
|
"main-bg-color-reply": "#1a142d",
|
||||||
"main-bg-color-report": "#12152d",
|
"main-bg-color-report": "#12152d",
|
||||||
"main-header-color-roles": "#1f192d",
|
"main-header-color-roles": "#1f192d",
|
||||||
"main-fg-color": "#f98bb0",
|
"main-fg-color": "#f98bb0",
|
||||||
|
"column-left-fg-color": "#f98bb0",
|
||||||
"border-color": "#3f2145",
|
"border-color": "#3f2145",
|
||||||
"main-link-color": "#ff42a0",
|
"main-link-color": "#ff42a0",
|
||||||
"main-link-color-hover": "white",
|
"main-link-color-hover": "white",
|
||||||
|
|
@ -689,12 +710,14 @@ def setThemeHacker(baseDir: str):
|
||||||
themeParams = {
|
themeParams = {
|
||||||
"focus-color": "green",
|
"focus-color": "green",
|
||||||
"main-bg-color": "black",
|
"main-bg-color": "black",
|
||||||
|
"column-left-color": "black",
|
||||||
"link-bg-color": "black",
|
"link-bg-color": "black",
|
||||||
"main-bg-color-dm": "#0b0a0a",
|
"main-bg-color-dm": "#0b0a0a",
|
||||||
"main-bg-color-reply": "#030202",
|
"main-bg-color-reply": "#030202",
|
||||||
"main-bg-color-report": "#050202",
|
"main-bg-color-report": "#050202",
|
||||||
"main-header-color-roles": "#1f192d",
|
"main-header-color-roles": "#1f192d",
|
||||||
"main-fg-color": "#00ff00",
|
"main-fg-color": "#00ff00",
|
||||||
|
"column-left-fg-color": "#00ff00",
|
||||||
"border-color": "#035103",
|
"border-color": "#035103",
|
||||||
"main-link-color": "#2fff2f",
|
"main-link-color": "#2fff2f",
|
||||||
"main-link-color-hover": "#afff2f",
|
"main-link-color-hover": "#afff2f",
|
||||||
|
|
@ -747,6 +770,7 @@ def setThemeLight(baseDir: str):
|
||||||
"font-size4": "24px",
|
"font-size4": "24px",
|
||||||
"font-size5": "22px",
|
"font-size5": "22px",
|
||||||
"rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)",
|
"rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)",
|
||||||
|
"column-left-color": "#e6ebf0",
|
||||||
"main-bg-color": "#e6ebf0",
|
"main-bg-color": "#e6ebf0",
|
||||||
"main-bg-color-dm": "#e3dbf0",
|
"main-bg-color-dm": "#e3dbf0",
|
||||||
"link-bg-color": "#e6ebf0",
|
"link-bg-color": "#e6ebf0",
|
||||||
|
|
@ -754,6 +778,7 @@ def setThemeLight(baseDir: str):
|
||||||
"main-bg-color-report": "#e3dbf0",
|
"main-bg-color-report": "#e3dbf0",
|
||||||
"main-header-color-roles": "#ebebf0",
|
"main-header-color-roles": "#ebebf0",
|
||||||
"main-fg-color": "#2d2c37",
|
"main-fg-color": "#2d2c37",
|
||||||
|
"column-left-fg-color": "#2d2c37",
|
||||||
"border-color": "#c0cdd9",
|
"border-color": "#c0cdd9",
|
||||||
"main-link-color": "#2a2c37",
|
"main-link-color": "#2a2c37",
|
||||||
"main-link-color-hover": "#aa2c37",
|
"main-link-color-hover": "#aa2c37",
|
||||||
|
|
@ -804,12 +829,14 @@ def setThemeSolidaric(baseDir: str):
|
||||||
"font-size5": "22px",
|
"font-size5": "22px",
|
||||||
"rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)",
|
"rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)",
|
||||||
"main-bg-color": "white",
|
"main-bg-color": "white",
|
||||||
|
"column-left-color": "white",
|
||||||
"main-bg-color-dm": "white",
|
"main-bg-color-dm": "white",
|
||||||
"link-bg-color": "white",
|
"link-bg-color": "white",
|
||||||
"main-bg-color-reply": "white",
|
"main-bg-color-reply": "white",
|
||||||
"main-bg-color-report": "white",
|
"main-bg-color-report": "white",
|
||||||
"main-header-color-roles": "#ebebf0",
|
"main-header-color-roles": "#ebebf0",
|
||||||
"main-fg-color": "#2d2c37",
|
"main-fg-color": "#2d2c37",
|
||||||
|
"column-left-fg-color": "#2d2c37",
|
||||||
"border-color": "#c0cdd9",
|
"border-color": "#c0cdd9",
|
||||||
"main-link-color": "#2a2c37",
|
"main-link-color": "#2a2c37",
|
||||||
"main-link-color-hover": "#aa2c37",
|
"main-link-color-hover": "#aa2c37",
|
||||||
|
|
@ -864,6 +891,10 @@ def setThemeImages(baseDir: str, name: str) -> None:
|
||||||
baseDir + '/img/banner.png'
|
baseDir + '/img/banner.png'
|
||||||
searchBannerFilename = \
|
searchBannerFilename = \
|
||||||
baseDir + '/img/search_banner.png'
|
baseDir + '/img/search_banner.png'
|
||||||
|
leftColImageFilename = \
|
||||||
|
baseDir + '/img/left_col_image.png'
|
||||||
|
rightColImageFilename = \
|
||||||
|
baseDir + '/img/right_col_image.png'
|
||||||
else:
|
else:
|
||||||
profileImageFilename = \
|
profileImageFilename = \
|
||||||
baseDir + '/img/image_' + themeNameLower + '.png'
|
baseDir + '/img/image_' + themeNameLower + '.png'
|
||||||
|
|
@ -871,6 +902,10 @@ def setThemeImages(baseDir: str, name: str) -> None:
|
||||||
baseDir + '/img/banner_' + themeNameLower + '.png'
|
baseDir + '/img/banner_' + themeNameLower + '.png'
|
||||||
searchBannerFilename = \
|
searchBannerFilename = \
|
||||||
baseDir + '/img/search_banner_' + themeNameLower + '.png'
|
baseDir + '/img/search_banner_' + themeNameLower + '.png'
|
||||||
|
leftColImageFilename = \
|
||||||
|
baseDir + '/img/left_col_image_' + themeNameLower + '.png'
|
||||||
|
rightColImageFilename = \
|
||||||
|
baseDir + '/img/right_col_image_' + themeNameLower + '.png'
|
||||||
|
|
||||||
backgroundNames = ('login', 'shares', 'delete', 'follow',
|
backgroundNames = ('login', 'shares', 'delete', 'follow',
|
||||||
'options', 'block', 'search', 'calendar')
|
'options', 'block', 'search', 'calendar')
|
||||||
|
|
@ -937,6 +972,29 @@ def setThemeImages(baseDir: str, name: str) -> None:
|
||||||
except BaseException:
|
except BaseException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.path.isfile(leftColImageFilename):
|
||||||
|
copyfile(leftColImageFilename,
|
||||||
|
accountDir + '/left_col_image.png')
|
||||||
|
else:
|
||||||
|
if os.path.isfile(accountDir +
|
||||||
|
'/left_col_image.png'):
|
||||||
|
os.remove(accountDir + '/left_col_image.png')
|
||||||
|
|
||||||
|
except BaseException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.path.isfile(rightColImageFilename):
|
||||||
|
copyfile(rightColImageFilename,
|
||||||
|
accountDir + '/right_col_image.png')
|
||||||
|
else:
|
||||||
|
if os.path.isfile(accountDir +
|
||||||
|
'/right_col_image.png'):
|
||||||
|
os.remove(accountDir + '/right_col_image.png')
|
||||||
|
except BaseException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def setTheme(baseDir: str, name: str) -> bool:
|
def setTheme(baseDir: str, name: str) -> bool:
|
||||||
result = False
|
result = False
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "علامات التجزئة المُنشأة تلقائيًا",
|
"Autogenerated Hashtags": "علامات التجزئة المُنشأة تلقائيًا",
|
||||||
"Autogenerated Content Warnings": "تحذيرات المحتوى المُنشأ تلقائيًا",
|
"Autogenerated Content Warnings": "تحذيرات المحتوى المُنشأ تلقائيًا",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "Hashtag محظور"
|
"Hashtag Blocked": "Hashtag محظور",
|
||||||
|
"This is a blogging instance": "هذا مثال على المدونات",
|
||||||
|
"Edit Links": "تحرير الارتباطات",
|
||||||
|
"One link per line. Description followed by the link.": "رابط واحد في كل سطر. الوصف متبوع بالرابط.",
|
||||||
|
"Left column image": "صورة العمود الأيسر",
|
||||||
|
"Right column image": "صورة العمود الأيمن",
|
||||||
|
"RSS feed for this site": "تغذية RSS لهذا الموقع",
|
||||||
|
"Edit newswire": "تحرير الأخبار",
|
||||||
|
"Add RSS feed links below.": "إضافة روابط تغذية RSS أدناه.",
|
||||||
|
"Newswire RSS Feed": "Newswire موجز RSS"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "Hashtags autogenerats",
|
"Autogenerated Hashtags": "Hashtags autogenerats",
|
||||||
"Autogenerated Content Warnings": "Advertiments de contingut autogenerats",
|
"Autogenerated Content Warnings": "Advertiments de contingut autogenerats",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "Hashtag bloquejat"
|
"Hashtag Blocked": "Hashtag bloquejat",
|
||||||
|
"This is a blogging instance": "Aquesta és una instància de blocs",
|
||||||
|
"Edit Links": "Edita els enllaços",
|
||||||
|
"One link per line. Description followed by the link.": "Un enllaç per línia. Descripció seguida de l'enllaç.",
|
||||||
|
"Left column image": "Imatge de la columna esquerra",
|
||||||
|
"Right column image": "Imatge de la columna dreta",
|
||||||
|
"RSS feed for this site": "Feed RSS per a aquest lloc",
|
||||||
|
"Edit newswire": "Editeu newswire",
|
||||||
|
"Add RSS feed links below.": "Afegiu enllaços de canals RSS a continuació.",
|
||||||
|
"Newswire RSS Feed": "Feed RSS de Newswire"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "Hashtags awtogeneiddiedig",
|
"Autogenerated Hashtags": "Hashtags awtogeneiddiedig",
|
||||||
"Autogenerated Content Warnings": "Rhybuddion Cynnwys Autogenerated",
|
"Autogenerated Content Warnings": "Rhybuddion Cynnwys Autogenerated",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "Hashtag wedi'i Blocio"
|
"Hashtag Blocked": "Hashtag wedi'i Blocio",
|
||||||
|
"This is a blogging instance": "Dyma enghraifft blogio",
|
||||||
|
"Edit Links": "Golygu Dolenni",
|
||||||
|
"One link per line. Description followed by the link.": "Un dolen y llinell. Disgrifiad wedi'i ddilyn gan y ddolen.",
|
||||||
|
"Left column image": "Delwedd colofn chwith",
|
||||||
|
"Right column image": "Delwedd colofn dde",
|
||||||
|
"RSS feed for this site": "Porthiant RSS ar gyfer y wefan hon",
|
||||||
|
"Edit newswire": "Golygu newyddion",
|
||||||
|
"Add RSS feed links below.": "Ychwanegwch ddolenni porthiant RSS isod.",
|
||||||
|
"Newswire RSS Feed": "Newswire RSS Feed"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "Automatisch generierte Hashtags",
|
"Autogenerated Hashtags": "Automatisch generierte Hashtags",
|
||||||
"Autogenerated Content Warnings": "Warnungen vor automatisch generierten Inhalten",
|
"Autogenerated Content Warnings": "Warnungen vor automatisch generierten Inhalten",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "Hashtag blockiert"
|
"Hashtag Blocked": "Hashtag blockiert",
|
||||||
|
"This is a blogging instance": "Dies ist eine Blogging-Instanz",
|
||||||
|
"Edit Links": "Links bearbeiten",
|
||||||
|
"One link per line. Description followed by the link.": "Ein Link pro Zeile. Beschreibung gefolgt vom Link.",
|
||||||
|
"Left column image": "Bild in der linken Spalte",
|
||||||
|
"Right column image": "Bild in der rechten Spalte",
|
||||||
|
"RSS feed for this site": "RSS-Feed für diese Site",
|
||||||
|
"Edit newswire": "Newswire bearbeiten",
|
||||||
|
"Add RSS feed links below.": "Fügen Sie unten RSS-Feed-Links hinzu.",
|
||||||
|
"Newswire RSS Feed": "Newswire RSS Feed"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "Autogenerated Hashtags",
|
"Autogenerated Hashtags": "Autogenerated Hashtags",
|
||||||
"Autogenerated Content Warnings": "Autogenerated Content Warnings",
|
"Autogenerated Content Warnings": "Autogenerated Content Warnings",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "Hashtag Blocked"
|
"Hashtag Blocked": "Hashtag Blocked",
|
||||||
|
"This is a blogging instance": "This is a blogging instance",
|
||||||
|
"Edit Links": "Edit Links",
|
||||||
|
"One link per line. Description followed by the link.": "One link per line. Description followed by the link. Titles should begin with #",
|
||||||
|
"Left column image": "Left column image",
|
||||||
|
"Right column image": "Right column image",
|
||||||
|
"RSS feed for this site": "RSS feed for this site",
|
||||||
|
"Edit newswire": "Edit newswire",
|
||||||
|
"Add RSS feed links below.": "Add RSS feed links below.",
|
||||||
|
"Newswire RSS Feed": "Newswire RSS Feed"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "Hashtags autogenerados",
|
"Autogenerated Hashtags": "Hashtags autogenerados",
|
||||||
"Autogenerated Content Warnings": "Advertencias de contenido generado automáticamente",
|
"Autogenerated Content Warnings": "Advertencias de contenido generado automáticamente",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "Hashtag bloqueada"
|
"Hashtag Blocked": "Hashtag bloqueada",
|
||||||
|
"This is a blogging instance": "Esta es una instancia de blogs",
|
||||||
|
"Edit Links": "Editar enlaces",
|
||||||
|
"One link per line. Description followed by the link.": "Un enlace por línea. Descripción seguida del enlace.",
|
||||||
|
"Left column image": "Imagen de la columna izquierda",
|
||||||
|
"Right column image": "Imagen de la columna derecha",
|
||||||
|
"RSS feed for this site": "Fuente RSS para este sitio",
|
||||||
|
"Edit newswire": "Editar newswire",
|
||||||
|
"Add RSS feed links below.": "Agregue los enlaces de fuentes RSS a continuación.",
|
||||||
|
"Newswire RSS Feed": "Canal RSS de Newswire"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "Hashtags générés automatiquement",
|
"Autogenerated Hashtags": "Hashtags générés automatiquement",
|
||||||
"Autogenerated Content Warnings": "Avertissements de contenu générés automatiquement",
|
"Autogenerated Content Warnings": "Avertissements de contenu générés automatiquement",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "Hashtag bloqué"
|
"Hashtag Blocked": "Hashtag bloqué",
|
||||||
|
"This is a blogging instance": "Ceci est une instance de blog",
|
||||||
|
"Edit Links": "Modifier les liens",
|
||||||
|
"One link per line. Description followed by the link.": "Un lien par ligne. Description suivie du lien.",
|
||||||
|
"Left column image": "Image de la colonne de gauche",
|
||||||
|
"Right column image": "Image de la colonne de droite",
|
||||||
|
"RSS feed for this site": "Flux RSS de ce site",
|
||||||
|
"Edit newswire": "Modifier le fil d'actualité",
|
||||||
|
"Add RSS feed links below.": "Ajoutez des liens de flux RSS ci-dessous.",
|
||||||
|
"Newswire RSS Feed": "Flux RSS de Newswire"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "Hashtags uathghinte",
|
"Autogenerated Hashtags": "Hashtags uathghinte",
|
||||||
"Autogenerated Content Warnings": "Rabhaidh Ábhar Uathghinte",
|
"Autogenerated Content Warnings": "Rabhaidh Ábhar Uathghinte",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "Hashtag Blocáilte"
|
"Hashtag Blocked": "Hashtag Blocáilte",
|
||||||
|
"This is a blogging instance": "Seo sampla blagála",
|
||||||
|
"Edit Links": "Cuir Naisc in eagar",
|
||||||
|
"One link per line. Description followed by the link.": "Nasc amháin in aghaidh an líne. Cur síos agus an nasc ina dhiaidh sin.",
|
||||||
|
"Left column image": "Íomhá colún ar chlé",
|
||||||
|
"Right column image": "Íomhá colún ar dheis",
|
||||||
|
"RSS feed for this site": "Fotha RSS don láithreán seo",
|
||||||
|
"Edit newswire": "Cuir sreang nuachta in eagar",
|
||||||
|
"Add RSS feed links below.": "Cuir naisc beatha RSS thíos.",
|
||||||
|
"Newswire RSS Feed": "Newswire RSS Feed"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "ऑटोजेनरेटेड हैशटैग",
|
"Autogenerated Hashtags": "ऑटोजेनरेटेड हैशटैग",
|
||||||
"Autogenerated Content Warnings": "स्वतः प्राप्त सामग्री चेतावनी",
|
"Autogenerated Content Warnings": "स्वतः प्राप्त सामग्री चेतावनी",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "हैशटैग अवरुद्ध"
|
"Hashtag Blocked": "हैशटैग अवरुद्ध",
|
||||||
|
"This is a blogging instance": "यह एक ब्लॉगिंग उदाहरण है",
|
||||||
|
"Edit Links": "लिंक संपादित करें",
|
||||||
|
"One link per line. Description followed by the link.": "प्रति पंक्ति एक लिंक। लिंक के बाद विवरण।",
|
||||||
|
"Left column image": "बाएं स्तंभ की छवि",
|
||||||
|
"Right column image": "राइट कॉलम छवि",
|
||||||
|
"RSS feed for this site": "इस साइट के लिए आरएसएस फ़ीड",
|
||||||
|
"Edit newswire": "नवांश संपादित करें",
|
||||||
|
"Add RSS feed links below.": "नीचे आरएसएस फ़ीड लिंक जोड़ें।",
|
||||||
|
"Newswire RSS Feed": "Newswire RSS फ़ीड"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "Hashtag generati automaticamente",
|
"Autogenerated Hashtags": "Hashtag generati automaticamente",
|
||||||
"Autogenerated Content Warnings": "Avvisi sui contenuti generati automaticamente",
|
"Autogenerated Content Warnings": "Avvisi sui contenuti generati automaticamente",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "Hashtag bloccato"
|
"Hashtag Blocked": "Hashtag bloccato",
|
||||||
|
"This is a blogging instance": "Questa è un'istanza di blog",
|
||||||
|
"Edit Links": "Modifica collegamenti",
|
||||||
|
"One link per line. Description followed by the link.": "Un collegamento per riga. Descrizione seguita dal collegamento.",
|
||||||
|
"Left column image": "Immagine della colonna di sinistra",
|
||||||
|
"Right column image": "Immagine della colonna di destra",
|
||||||
|
"RSS feed for this site": "Feed RSS per questo sito",
|
||||||
|
"Edit newswire": "Modifica newswire",
|
||||||
|
"Add RSS feed links below.": "Aggiungi i link ai feed RSS di seguito.",
|
||||||
|
"Newswire RSS Feed": "Feed RSS di Newswire"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "自動生成されたハッシュタグ",
|
"Autogenerated Hashtags": "自動生成されたハッシュタグ",
|
||||||
"Autogenerated Content Warnings": "自動生成されたコンテンツの警告",
|
"Autogenerated Content Warnings": "自動生成されたコンテンツの警告",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "ハッシュタグがブロックされました"
|
"Hashtag Blocked": "ハッシュタグがブロックされました",
|
||||||
|
"This is a blogging instance": "これはブログのインスタンスです",
|
||||||
|
"Edit Links": "リンクの編集",
|
||||||
|
"One link per line. Description followed by the link.": "1行に1つのリンク。 説明の後にリンクが続きます。",
|
||||||
|
"Left column image": "左の列の画像",
|
||||||
|
"Right column image": "右の列の画像",
|
||||||
|
"RSS feed for this site": "このサイトのRSSフィード",
|
||||||
|
"Edit newswire": "ニュースワイヤーを編集",
|
||||||
|
"Add RSS feed links below.": "以下にRSSフィードリンクを追加します。",
|
||||||
|
"Newswire RSS Feed": "NewswireRSSフィード"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -283,5 +283,14 @@
|
||||||
"Autogenerated Hashtags": "Autogenerated Hashtags",
|
"Autogenerated Hashtags": "Autogenerated Hashtags",
|
||||||
"Autogenerated Content Warnings": "Autogenerated Content Warnings",
|
"Autogenerated Content Warnings": "Autogenerated Content Warnings",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "Hashtag Blocked"
|
"Hashtag Blocked": "Hashtag Blocked",
|
||||||
|
"This is a blogging instance": "This is a blogging instance",
|
||||||
|
"Edit Links": "Edit Links",
|
||||||
|
"One link per line. Description followed by the link.": "One link per line. Description followed by the link. Titles should begin with #",
|
||||||
|
"Left column image": "Left column image",
|
||||||
|
"Right column image": "Right column image",
|
||||||
|
"RSS feed for this site": "RSS feed for this site",
|
||||||
|
"Edit newswire": "Edit newswire",
|
||||||
|
"Add RSS feed links below.": "Add RSS feed links below.",
|
||||||
|
"Newswire RSS Feed": "Newswire RSS Feed"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "Hashtags autogeradas",
|
"Autogenerated Hashtags": "Hashtags autogeradas",
|
||||||
"Autogenerated Content Warnings": "Avisos de conteúdo gerado automaticamente",
|
"Autogenerated Content Warnings": "Avisos de conteúdo gerado automaticamente",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "Hashtag bloqueada"
|
"Hashtag Blocked": "Hashtag bloqueada",
|
||||||
|
"This is a blogging instance": "Esta é uma instância de blog",
|
||||||
|
"Edit Links": "Editar Links",
|
||||||
|
"One link per line. Description followed by the link.": "Um link por linha. Descrição seguida pelo link.",
|
||||||
|
"Left column image": "Imagem da coluna esquerda",
|
||||||
|
"Right column image": "Imagem da coluna direita",
|
||||||
|
"RSS feed for this site": "Feed RSS para este site",
|
||||||
|
"Edit newswire": "Editar notícias",
|
||||||
|
"Add RSS feed links below.": "Adicione links de feed RSS abaixo.",
|
||||||
|
"Newswire RSS Feed": "Feed RSS da Newswire"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "Автоматически сгенерированные хештеги",
|
"Autogenerated Hashtags": "Автоматически сгенерированные хештеги",
|
||||||
"Autogenerated Content Warnings": "Автоматические предупреждения о содержании",
|
"Autogenerated Content Warnings": "Автоматические предупреждения о содержании",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "Хештег заблокирован"
|
"Hashtag Blocked": "Хештег заблокирован",
|
||||||
|
"This is a blogging instance": "Это экземпляр блога",
|
||||||
|
"Edit Links": "Редактировать ссылки",
|
||||||
|
"One link per line. Description followed by the link.": "По одной ссылке в строке. Описание с последующей ссылкой.",
|
||||||
|
"Left column image": "Изображение в левом столбце",
|
||||||
|
"Right column image": "Изображение в правом столбце",
|
||||||
|
"RSS feed for this site": "RSS-канал для этого сайта",
|
||||||
|
"Edit newswire": "Редактировать ленту новостей",
|
||||||
|
"Add RSS feed links below.": "Добавьте ссылки на RSS-канал ниже.",
|
||||||
|
"Newswire RSS Feed": "Лента новостей RSS"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,5 +287,14 @@
|
||||||
"Autogenerated Hashtags": "自动生成的标签",
|
"Autogenerated Hashtags": "自动生成的标签",
|
||||||
"Autogenerated Content Warnings": "自动生成的内容警告",
|
"Autogenerated Content Warnings": "自动生成的内容警告",
|
||||||
"Indymedia": "Indymedia",
|
"Indymedia": "Indymedia",
|
||||||
"Hashtag Blocked": "标签被阻止"
|
"Hashtag Blocked": "标签被阻止",
|
||||||
|
"This is a blogging instance": "这是一个博客实例",
|
||||||
|
"Edit Links": "编辑连结",
|
||||||
|
"One link per line. Description followed by the link.": "每行一个链接。 描述,然后是链接。",
|
||||||
|
"Left column image": "左栏图片",
|
||||||
|
"Right column image": "右栏图片",
|
||||||
|
"RSS feed for this site": "该站点的RSS feed",
|
||||||
|
"Edit newswire": "编辑新闻专线",
|
||||||
|
"Add RSS feed links below.": "在下面添加RSS feed链接。",
|
||||||
|
"Newswire RSS Feed": "Newswire RSS提要"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
575
webinterface.py
|
|
@ -104,7 +104,7 @@ def getContentWarningButton(postID: str, translate: {},
|
||||||
return ' <details><summary><b>' + \
|
return ' <details><summary><b>' + \
|
||||||
translate['SHOW MORE'] + '</b></summary>' + \
|
translate['SHOW MORE'] + '</b></summary>' + \
|
||||||
'<div id="' + postID + '">' + content + \
|
'<div id="' + postID + '">' + content + \
|
||||||
'</div></details>'
|
'</div></details>\n'
|
||||||
|
|
||||||
|
|
||||||
def getBlogAddress(actorJson: {}) -> str:
|
def getBlogAddress(actorJson: {}) -> str:
|
||||||
|
|
@ -581,7 +581,8 @@ def htmlSearchSharedItems(translate: {},
|
||||||
'name="searchtext" value="' + \
|
'name="searchtext" value="' + \
|
||||||
searchStrLower + '"><br>\n'
|
searchStrLower + '"><br>\n'
|
||||||
sharedItemsForm += \
|
sharedItemsForm += \
|
||||||
' <center><a href="' + actor + \
|
' <center>\n' + \
|
||||||
|
' <a href="' + actor + \
|
||||||
'" type="submit" name="submitSearch">\n'
|
'" type="submit" name="submitSearch">\n'
|
||||||
sharedItemsForm += \
|
sharedItemsForm += \
|
||||||
' <img loading="lazy" ' + \
|
' <img loading="lazy" ' + \
|
||||||
|
|
@ -614,7 +615,8 @@ def htmlSearchSharedItems(translate: {},
|
||||||
'name="searchtext" value="' + \
|
'name="searchtext" value="' + \
|
||||||
searchStrLower + '"><br>\n'
|
searchStrLower + '"><br>\n'
|
||||||
sharedItemsForm += \
|
sharedItemsForm += \
|
||||||
' <center><a href="' + actor + \
|
' <center>\n' + \
|
||||||
|
' <a href="' + actor + \
|
||||||
'" type="submit" name="submitSearch">\n'
|
'" type="submit" name="submitSearch">\n'
|
||||||
sharedItemsForm += \
|
sharedItemsForm += \
|
||||||
' <img loading="lazy" ' + \
|
' <img loading="lazy" ' + \
|
||||||
|
|
@ -777,13 +779,14 @@ def htmlHashtagSearch(nickname: str, domain: str, port: int,
|
||||||
if startIndex > 0:
|
if startIndex > 0:
|
||||||
# previous page link
|
# previous page link
|
||||||
hashtagSearchForm += \
|
hashtagSearchForm += \
|
||||||
'<center><a href="/tags/' + hashtag + '?page=' + \
|
' <center>\n' + \
|
||||||
|
' <a href="/tags/' + hashtag + '?page=' + \
|
||||||
str(pageNumber - 1) + \
|
str(pageNumber - 1) + \
|
||||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||||
iconsDir + '/pageup.png" title="' + \
|
iconsDir + '/pageup.png" title="' + \
|
||||||
translate['Page up'] + \
|
translate['Page up'] + \
|
||||||
'" alt="' + translate['Page up'] + \
|
'" alt="' + translate['Page up'] + \
|
||||||
'"></a></center>\n'
|
'"></a>\n </center>\n'
|
||||||
index = startIndex
|
index = startIndex
|
||||||
while index <= endIndex:
|
while index <= endIndex:
|
||||||
postId = lines[index].strip('\n').strip('\r')
|
postId = lines[index].strip('\n').strip('\r')
|
||||||
|
|
@ -832,11 +835,13 @@ def htmlHashtagSearch(nickname: str, domain: str, port: int,
|
||||||
if endIndex < noOfLines - 1:
|
if endIndex < noOfLines - 1:
|
||||||
# next page link
|
# next page link
|
||||||
hashtagSearchForm += \
|
hashtagSearchForm += \
|
||||||
'<center><a href="/tags/' + hashtag + \
|
' <center>\n' + \
|
||||||
|
' <a href="/tags/' + hashtag + \
|
||||||
'?page=' + str(pageNumber + 1) + \
|
'?page=' + str(pageNumber + 1) + \
|
||||||
'"><img loading="lazy" class="pageicon" src="/' + iconsDir + \
|
'"><img loading="lazy" class="pageicon" src="/' + iconsDir + \
|
||||||
'/pagedown.png" title="' + translate['Page down'] + \
|
'/pagedown.png" title="' + translate['Page down'] + \
|
||||||
'" alt="' + translate['Page down'] + '"></a></center>'
|
'" alt="' + translate['Page down'] + '"></a>' + \
|
||||||
|
' </center>'
|
||||||
hashtagSearchForm += htmlFooter()
|
hashtagSearchForm += htmlFooter()
|
||||||
return hashtagSearchForm
|
return hashtagSearchForm
|
||||||
|
|
||||||
|
|
@ -1201,6 +1206,142 @@ def scheduledPostsExist(baseDir: str, nickname: str, domain: str) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def htmlEditLinks(translate: {}, baseDir: str, path: str,
|
||||||
|
domain: str, port: int, httpPrefix: str) -> str:
|
||||||
|
"""Shows the edit links screen
|
||||||
|
"""
|
||||||
|
if '/users/' not in path:
|
||||||
|
return ''
|
||||||
|
pathOriginal = path
|
||||||
|
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'
|
||||||
|
with open(cssFilename, 'r') as cssFile:
|
||||||
|
editCSS = cssFile.read()
|
||||||
|
if httpPrefix != 'https':
|
||||||
|
editCSS = \
|
||||||
|
editCSS.replace('https://', httpPrefix + '://')
|
||||||
|
|
||||||
|
editLinksForm = htmlHeader(cssFilename, editCSS)
|
||||||
|
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 += \
|
||||||
|
' <input type="submit" name="submitLinks" value="' + \
|
||||||
|
translate['Submit'] + '">\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:500px">' + \
|
||||||
|
linksStr + '</textarea>'
|
||||||
|
editLinksForm += \
|
||||||
|
'</div>'
|
||||||
|
|
||||||
|
editLinksForm += htmlFooter()
|
||||||
|
return editLinksForm
|
||||||
|
|
||||||
|
|
||||||
|
def htmlEditNewswire(translate: {}, baseDir: str, path: str,
|
||||||
|
domain: str, port: int, httpPrefix: str) -> str:
|
||||||
|
"""Shows the edit newswire screen
|
||||||
|
"""
|
||||||
|
if '/users/' not in path:
|
||||||
|
return ''
|
||||||
|
pathOriginal = path
|
||||||
|
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'
|
||||||
|
with open(cssFilename, 'r') as cssFile:
|
||||||
|
editCSS = cssFile.read()
|
||||||
|
if httpPrefix != 'https':
|
||||||
|
editCSS = \
|
||||||
|
editCSS.replace('https://', httpPrefix + '://')
|
||||||
|
|
||||||
|
editNewswireForm = htmlHeader(cssFilename, editCSS)
|
||||||
|
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 += \
|
||||||
|
' <input type="submit" name="submitNewswire" value="' + \
|
||||||
|
translate['Submit'] + '">\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:500px">' + newswireStr + '</textarea>'
|
||||||
|
editNewswireForm += \
|
||||||
|
'</div>'
|
||||||
|
|
||||||
|
editNewswireForm += htmlFooter()
|
||||||
|
return editNewswireForm
|
||||||
|
|
||||||
|
|
||||||
def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
||||||
domain: str, port: int, httpPrefix: str) -> str:
|
domain: str, port: int, httpPrefix: str) -> str:
|
||||||
"""Shows the edit profile screen
|
"""Shows the edit profile screen
|
||||||
|
|
@ -1230,6 +1371,7 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
||||||
notifyLikes = ''
|
notifyLikes = ''
|
||||||
hideLikeButton = ''
|
hideLikeButton = ''
|
||||||
mediaInstanceStr = ''
|
mediaInstanceStr = ''
|
||||||
|
blogsInstanceStr = ''
|
||||||
displayNickname = nickname
|
displayNickname = nickname
|
||||||
bioStr = ''
|
bioStr = ''
|
||||||
donateUrl = ''
|
donateUrl = ''
|
||||||
|
|
@ -1287,6 +1429,13 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
||||||
if mediaInstance:
|
if mediaInstance:
|
||||||
if mediaInstance is True:
|
if mediaInstance is True:
|
||||||
mediaInstanceStr = 'checked'
|
mediaInstanceStr = 'checked'
|
||||||
|
blogsInstanceStr = ''
|
||||||
|
|
||||||
|
blogsInstance = getConfigParam(baseDir, "blogsInstance")
|
||||||
|
if blogsInstance:
|
||||||
|
if blogsInstance is True:
|
||||||
|
blogsInstanceStr = 'checked'
|
||||||
|
mediaInstanceStr = ''
|
||||||
|
|
||||||
filterStr = ''
|
filterStr = ''
|
||||||
filterFilename = \
|
filterFilename = \
|
||||||
|
|
@ -1594,6 +1743,18 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
||||||
editProfileForm += 'name="search_banner"'
|
editProfileForm += 'name="search_banner"'
|
||||||
editProfileForm += ' accept="' + imageFormats + '">\n'
|
editProfileForm += ' accept="' + imageFormats + '">\n'
|
||||||
|
|
||||||
|
editProfileForm += ' <br><label class="labels">' + \
|
||||||
|
translate['Left column image'] + '</label>\n'
|
||||||
|
editProfileForm += ' <input type="file" id="left_col_image" '
|
||||||
|
editProfileForm += 'name="left_col_image"'
|
||||||
|
editProfileForm += ' accept="' + imageFormats + '">\n'
|
||||||
|
|
||||||
|
editProfileForm += ' <br><label class="labels">' + \
|
||||||
|
translate['Right column image'] + '</label>\n'
|
||||||
|
editProfileForm += ' <input type="file" id="right_col_image" '
|
||||||
|
editProfileForm += 'name="right_col_image"'
|
||||||
|
editProfileForm += ' accept="' + imageFormats + '">\n'
|
||||||
|
|
||||||
editProfileForm += ' </div>\n'
|
editProfileForm += ' </div>\n'
|
||||||
editProfileForm += ' <div class="container">\n'
|
editProfileForm += ' <div class="container">\n'
|
||||||
editProfileForm += \
|
editProfileForm += \
|
||||||
|
|
@ -1607,6 +1768,19 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
||||||
editProfileForm += \
|
editProfileForm += \
|
||||||
' <input type="text" name="passwordconfirm" value="">\n'
|
' <input type="text" name="passwordconfirm" value="">\n'
|
||||||
editProfileForm += ' </div>\n'
|
editProfileForm += ' </div>\n'
|
||||||
|
|
||||||
|
if path.startswith('/users/' + adminNickname + '/'):
|
||||||
|
editProfileForm += ' <div class="container">\n'
|
||||||
|
editProfileForm += \
|
||||||
|
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||||
|
'name="mediaInstance" ' + mediaInstanceStr + '> ' + \
|
||||||
|
translate['This is a media instance'] + '<br>\n'
|
||||||
|
editProfileForm += \
|
||||||
|
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||||
|
'name="blogsInstance" ' + blogsInstanceStr + '> ' + \
|
||||||
|
translate['This is a blogging instance'] + '<br>\n'
|
||||||
|
editProfileForm += ' </div>\n'
|
||||||
|
|
||||||
editProfileForm += ' <div class="container">\n'
|
editProfileForm += ' <div class="container">\n'
|
||||||
editProfileForm += \
|
editProfileForm += \
|
||||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||||
|
|
@ -1628,11 +1802,6 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
||||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||||
'name="removeTwitter" ' + removeTwitter + '> ' + \
|
'name="removeTwitter" ' + removeTwitter + '> ' + \
|
||||||
translate['Remove Twitter posts'] + '<br>\n'
|
translate['Remove Twitter posts'] + '<br>\n'
|
||||||
if path.startswith('/users/' + adminNickname + '/'):
|
|
||||||
editProfileForm += \
|
|
||||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
|
||||||
'name="mediaInstance" ' + mediaInstanceStr + '> ' + \
|
|
||||||
translate['This is a media instance'] + '<br>\n'
|
|
||||||
editProfileForm += \
|
editProfileForm += \
|
||||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||||
'name="notifyLikes" ' + notifyLikes + '> ' + \
|
'name="notifyLikes" ' + notifyLikes + '> ' + \
|
||||||
|
|
@ -2639,6 +2808,7 @@ def htmlHeader(cssFilename: str, css: str, lang='en') -> str:
|
||||||
htmlStr += ' <style>\n' + css + '</style>\n'
|
htmlStr += ' <style>\n' + css + '</style>\n'
|
||||||
htmlStr += ' <link rel="manifest" href="/manifest.json">\n'
|
htmlStr += ' <link rel="manifest" href="/manifest.json">\n'
|
||||||
htmlStr += ' <meta name="theme-color" content="grey">\n'
|
htmlStr += ' <meta name="theme-color" content="grey">\n'
|
||||||
|
htmlStr += ' <title>Epicyon</title>\n'
|
||||||
htmlStr += ' </head>\n'
|
htmlStr += ' </head>\n'
|
||||||
htmlStr += ' <body>\n'
|
htmlStr += ' <body>\n'
|
||||||
return htmlStr
|
return htmlStr
|
||||||
|
|
@ -2719,12 +2889,14 @@ def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str,
|
||||||
if authorized and pageNumber > 1:
|
if authorized and pageNumber > 1:
|
||||||
# page up arrow
|
# page up arrow
|
||||||
profileStr += \
|
profileStr += \
|
||||||
'<center>\n<a href="' + actor + '/' + feedName + \
|
' <center>\n' + \
|
||||||
|
' <a href="' + actor + '/' + feedName + \
|
||||||
'?page=' + str(pageNumber - 1) + \
|
'?page=' + str(pageNumber - 1) + \
|
||||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||||
iconsDir + '/pageup.png" title="' + \
|
iconsDir + '/pageup.png" title="' + \
|
||||||
translate['Page up'] + '" alt="' + \
|
translate['Page up'] + '" alt="' + \
|
||||||
translate['Page up'] + '"></a>\n</center>\n'
|
translate['Page up'] + '"></a>\n' + \
|
||||||
|
' </center>\n'
|
||||||
|
|
||||||
for item in followingJson['orderedItems']:
|
for item in followingJson['orderedItems']:
|
||||||
profileStr += \
|
profileStr += \
|
||||||
|
|
@ -2737,12 +2909,14 @@ def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str,
|
||||||
if len(followingJson['orderedItems']) >= maxItemsPerPage:
|
if len(followingJson['orderedItems']) >= maxItemsPerPage:
|
||||||
# page down arrow
|
# page down arrow
|
||||||
profileStr += \
|
profileStr += \
|
||||||
'<center>\n<a href="' + actor + '/' + feedName + \
|
' <center>\n' + \
|
||||||
|
' <a href="' + actor + '/' + feedName + \
|
||||||
'?page=' + str(pageNumber + 1) + \
|
'?page=' + str(pageNumber + 1) + \
|
||||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||||
iconsDir + '/pagedown.png" title="' + \
|
iconsDir + '/pagedown.png" title="' + \
|
||||||
translate['Page down'] + '" alt="' + \
|
translate['Page down'] + '" alt="' + \
|
||||||
translate['Page down'] + '"></a>\n</center>\n'
|
translate['Page down'] + '"></a>\n' + \
|
||||||
|
' </center>\n'
|
||||||
return profileStr
|
return profileStr
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2899,11 +3073,13 @@ def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
|
||||||
if pageNumber > 1:
|
if pageNumber > 1:
|
||||||
iconsDir = getIconsDir(baseDir)
|
iconsDir = getIconsDir(baseDir)
|
||||||
timelineStr += \
|
timelineStr += \
|
||||||
'<center>\n<a href="' + actor + '/tlshares?page=' + \
|
' <center>\n' + \
|
||||||
|
' <a href="' + actor + '/tlshares?page=' + \
|
||||||
str(pageNumber - 1) + \
|
str(pageNumber - 1) + \
|
||||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||||
iconsDir + '/pageup.png" title="' + translate['Page up'] + \
|
iconsDir + '/pageup.png" title="' + translate['Page up'] + \
|
||||||
'" alt="' + translate['Page up'] + '"></a>\n</center>\n'
|
'" alt="' + translate['Page up'] + '"></a>\n' + \
|
||||||
|
' </center>\n'
|
||||||
|
|
||||||
for published, item in sharesJson.items():
|
for published, item in sharesJson.items():
|
||||||
showContactButton = False
|
showContactButton = False
|
||||||
|
|
@ -2919,11 +3095,13 @@ def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
|
||||||
if not lastPage:
|
if not lastPage:
|
||||||
iconsDir = getIconsDir(baseDir)
|
iconsDir = getIconsDir(baseDir)
|
||||||
timelineStr += \
|
timelineStr += \
|
||||||
'<center>\n<a href="' + actor + '/tlshares?page=' + \
|
' <center>\n' + \
|
||||||
|
' <a href="' + actor + '/tlshares?page=' + \
|
||||||
str(pageNumber + 1) + \
|
str(pageNumber + 1) + \
|
||||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||||
iconsDir + '/pagedown.png" title="' + translate['Page down'] + \
|
iconsDir + '/pagedown.png" title="' + translate['Page down'] + \
|
||||||
'" alt="' + translate['Page down'] + '"></a>\n</center>\n'
|
'" alt="' + translate['Page down'] + '"></a>\n' + \
|
||||||
|
' </center>\n'
|
||||||
|
|
||||||
return timelineStr
|
return timelineStr
|
||||||
|
|
||||||
|
|
@ -4130,7 +4308,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
avatarLink = ' <a class="imageAnchor" href="' + postActor + '">'
|
avatarLink = ' <a class="imageAnchor" href="' + postActor + '">'
|
||||||
avatarLink += \
|
avatarLink += \
|
||||||
' <img loading="lazy" src="' + avatarUrl + '" title="' + \
|
' <img loading="lazy" src="' + avatarUrl + '" title="' + \
|
||||||
translate['Show profile'] + '" alt=" "' + avatarPosition + '/></a>'
|
translate['Show profile'] + '" alt=" "' + avatarPosition + '/></a>\n'
|
||||||
|
|
||||||
if showAvatarOptions and \
|
if showAvatarOptions and \
|
||||||
fullDomain + '/users/' + nickname not in postActor:
|
fullDomain + '/users/' + nickname not in postActor:
|
||||||
|
|
@ -4280,18 +4458,21 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
else:
|
else:
|
||||||
if isDM(postJsonObject):
|
if isDM(postJsonObject):
|
||||||
replyStr += \
|
replyStr += \
|
||||||
|
' ' + \
|
||||||
'<a class="imageAnchor" href="/users/' + nickname + \
|
'<a class="imageAnchor" href="/users/' + nickname + \
|
||||||
'?replydm=' + replyToLink + \
|
'?replydm=' + replyToLink + \
|
||||||
'?actor=' + postJsonObject['actor'] + \
|
'?actor=' + postJsonObject['actor'] + \
|
||||||
'" title="' + translate['Reply to this post'] + '">\n'
|
'" title="' + translate['Reply to this post'] + '">\n'
|
||||||
else:
|
else:
|
||||||
replyStr += \
|
replyStr += \
|
||||||
|
' ' + \
|
||||||
'<a class="imageAnchor" href="/users/' + nickname + \
|
'<a class="imageAnchor" href="/users/' + nickname + \
|
||||||
'?replyfollowers=' + replyToLink + \
|
'?replyfollowers=' + replyToLink + \
|
||||||
'?actor=' + postJsonObject['actor'] + \
|
'?actor=' + postJsonObject['actor'] + \
|
||||||
'" title="' + translate['Reply to this post'] + '">\n'
|
'" title="' + translate['Reply to this post'] + '">\n'
|
||||||
|
|
||||||
replyStr += \
|
replyStr += \
|
||||||
|
' ' + \
|
||||||
'<img loading="lazy" title="' + \
|
'<img loading="lazy" title="' + \
|
||||||
translate['Reply to this post'] + '" alt="' + \
|
translate['Reply to this post'] + '" alt="' + \
|
||||||
translate['Reply to this post'] + \
|
translate['Reply to this post'] + \
|
||||||
|
|
@ -4317,6 +4498,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
if isBlogPost(postJsonObject):
|
if isBlogPost(postJsonObject):
|
||||||
blogPostId = postJsonObject['object']['id']
|
blogPostId = postJsonObject['object']['id']
|
||||||
editStr += \
|
editStr += \
|
||||||
|
' ' + \
|
||||||
'<a class="imageAnchor" href="/users/' + nickname + \
|
'<a class="imageAnchor" href="/users/' + nickname + \
|
||||||
'/tlblogs?editblogpost=' + \
|
'/tlblogs?editblogpost=' + \
|
||||||
blogPostId.split('/statuses/')[1] + \
|
blogPostId.split('/statuses/')[1] + \
|
||||||
|
|
@ -4329,6 +4511,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
elif isEvent:
|
elif isEvent:
|
||||||
eventPostId = postJsonObject['object']['id']
|
eventPostId = postJsonObject['object']['id']
|
||||||
editStr += \
|
editStr += \
|
||||||
|
' ' + \
|
||||||
'<a class="imageAnchor" href="/users/' + nickname + \
|
'<a class="imageAnchor" href="/users/' + nickname + \
|
||||||
'/tlblogs?editeventpost=' + \
|
'/tlblogs?editeventpost=' + \
|
||||||
eventPostId.split('/statuses/')[1] + \
|
eventPostId.split('/statuses/')[1] + \
|
||||||
|
|
@ -4360,6 +4543,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
'?bm=' + timelinePostBookmark + \
|
'?bm=' + timelinePostBookmark + \
|
||||||
'?tl=' + boxName + '" title="' + announceTitle + '">\n'
|
'?tl=' + boxName + '" title="' + announceTitle + '">\n'
|
||||||
announceStr += \
|
announceStr += \
|
||||||
|
' ' + \
|
||||||
'<img loading="lazy" title="' + translate['Repeat this post'] + \
|
'<img loading="lazy" title="' + translate['Repeat this post'] + \
|
||||||
'" alt="' + translate['Repeat this post'] + \
|
'" alt="' + translate['Repeat this post'] + \
|
||||||
' |" src="/' + iconsDir + '/' + announceIcon + '"/></a>\n'
|
' |" src="/' + iconsDir + '/' + announceIcon + '"/></a>\n'
|
||||||
|
|
@ -4425,6 +4609,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
'?tl=' + boxName + '" title="' + \
|
'?tl=' + boxName + '" title="' + \
|
||||||
likeTitle + likeCountStr + '">\n'
|
likeTitle + likeCountStr + '">\n'
|
||||||
likeStr += \
|
likeStr += \
|
||||||
|
' ' + \
|
||||||
'<img loading="lazy" title="' + likeTitle + likeCountStr + \
|
'<img loading="lazy" title="' + likeTitle + likeCountStr + \
|
||||||
'" alt="' + likeTitle + \
|
'" alt="' + likeTitle + \
|
||||||
' |" src="/' + iconsDir + '/' + likeIcon + '"/></a>\n'
|
' |" src="/' + iconsDir + '/' + likeIcon + '"/></a>\n'
|
||||||
|
|
@ -4457,6 +4642,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
'?bm=' + timelinePostBookmark + \
|
'?bm=' + timelinePostBookmark + \
|
||||||
'?tl=' + boxName + '" title="' + bookmarkTitle + '">\n'
|
'?tl=' + boxName + '" title="' + bookmarkTitle + '">\n'
|
||||||
bookmarkStr += \
|
bookmarkStr += \
|
||||||
|
' ' + \
|
||||||
'<img loading="lazy" title="' + bookmarkTitle + '" alt="' + \
|
'<img loading="lazy" title="' + bookmarkTitle + '" alt="' + \
|
||||||
bookmarkTitle + ' |" src="/' + iconsDir + \
|
bookmarkTitle + ' |" src="/' + iconsDir + \
|
||||||
'/' + bookmarkIcon + '"/></a>\n'
|
'/' + bookmarkIcon + '"/></a>\n'
|
||||||
|
|
@ -4486,6 +4672,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
'?delete=' + messageId + pageNumberParam + \
|
'?delete=' + messageId + pageNumberParam + \
|
||||||
'" title="' + translate['Delete this post'] + '">\n'
|
'" title="' + translate['Delete this post'] + '">\n'
|
||||||
deleteStr += \
|
deleteStr += \
|
||||||
|
' ' + \
|
||||||
'<img loading="lazy" alt="' + translate['Delete this post'] + \
|
'<img loading="lazy" alt="' + translate['Delete this post'] + \
|
||||||
' |" title="' + translate['Delete this post'] + \
|
' |" title="' + translate['Delete this post'] + \
|
||||||
'" src="/' + iconsDir + '/delete.png"/></a>\n'
|
'" src="/' + iconsDir + '/delete.png"/></a>\n'
|
||||||
|
|
@ -4497,6 +4684,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
'?bm=' + timelinePostBookmark + \
|
'?bm=' + timelinePostBookmark + \
|
||||||
'" title="' + translate['Mute this post'] + '">\n'
|
'" title="' + translate['Mute this post'] + '">\n'
|
||||||
muteStr += \
|
muteStr += \
|
||||||
|
' ' + \
|
||||||
'<img loading="lazy" alt="' + \
|
'<img loading="lazy" alt="' + \
|
||||||
translate['Mute this post'] + \
|
translate['Mute this post'] + \
|
||||||
' |" title="' + translate['Mute this post'] + \
|
' |" title="' + translate['Mute this post'] + \
|
||||||
|
|
@ -4509,6 +4697,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
timelinePostBookmark + '" title="' + \
|
timelinePostBookmark + '" title="' + \
|
||||||
translate['Undo mute'] + '">\n'
|
translate['Undo mute'] + '">\n'
|
||||||
muteStr += \
|
muteStr += \
|
||||||
|
' ' + \
|
||||||
'<img loading="lazy" alt="' + translate['Undo mute'] + \
|
'<img loading="lazy" alt="' + translate['Undo mute'] + \
|
||||||
' |" title="' + translate['Undo mute'] + \
|
' |" title="' + translate['Undo mute'] + \
|
||||||
'" src="/' + iconsDir+'/unmute.png"/></a>\n'
|
'" src="/' + iconsDir+'/unmute.png"/></a>\n'
|
||||||
|
|
@ -4574,11 +4763,13 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
' 13.3.1 = ' + str(timeDiff))
|
' 13.3.1 = ' + str(timeDiff))
|
||||||
|
|
||||||
titleStr += \
|
titleStr += \
|
||||||
|
' ' + \
|
||||||
'<img loading="lazy" title="' + \
|
'<img loading="lazy" title="' + \
|
||||||
translate['announces'] + '" alt="' + \
|
translate['announces'] + '" alt="' + \
|
||||||
translate['announces'] + '" src="/' + \
|
translate['announces'] + '" src="/' + \
|
||||||
iconsDir + '/repeat_inactive.png" ' + \
|
iconsDir + '/repeat_inactive.png" ' + \
|
||||||
'class="announceOrReply"/> <a href="' + \
|
'class="announceOrReply"/>\n' + \
|
||||||
|
' <a href="' + \
|
||||||
postJsonObject['object']['id'] + '">' + \
|
postJsonObject['object']['id'] + '">' + \
|
||||||
announceDisplayName + '</a>\n'
|
announceDisplayName + '</a>\n'
|
||||||
# show avatar of person replied to
|
# show avatar of person replied to
|
||||||
|
|
@ -4599,6 +4790,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
if announceAvatarUrl:
|
if announceAvatarUrl:
|
||||||
idx = 'Show options for this person'
|
idx = 'Show options for this person'
|
||||||
replyAvatarImageInPost = \
|
replyAvatarImageInPost = \
|
||||||
|
' ' \
|
||||||
'<div class="timeline-avatar-reply">\n' \
|
'<div class="timeline-avatar-reply">\n' \
|
||||||
' <a class="imageAnchor" ' + \
|
' <a class="imageAnchor" ' + \
|
||||||
'href="/users/' + nickname + \
|
'href="/users/' + nickname + \
|
||||||
|
|
@ -4618,7 +4810,8 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
'" alt="' + translate['announces'] + \
|
'" alt="' + translate['announces'] + \
|
||||||
'" src="/' + iconsDir + \
|
'" src="/' + iconsDir + \
|
||||||
'/repeat_inactive.png" ' + \
|
'/repeat_inactive.png" ' + \
|
||||||
'class="announceOrReply"/> <a href="' + \
|
'class="announceOrReply"/>\n' + \
|
||||||
|
' <a href="' + \
|
||||||
postJsonObject['object']['id'] + '">@' + \
|
postJsonObject['object']['id'] + '">@' + \
|
||||||
announceNickname + '@' + \
|
announceNickname + '@' + \
|
||||||
announceDomain + '</a>\n'
|
announceDomain + '</a>\n'
|
||||||
|
|
@ -4628,16 +4821,19 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
translate['announces'] + '" alt="' + \
|
translate['announces'] + '" alt="' + \
|
||||||
translate['announces'] + '" src="/' + iconsDir + \
|
translate['announces'] + '" src="/' + iconsDir + \
|
||||||
'/repeat_inactive.png" ' + \
|
'/repeat_inactive.png" ' + \
|
||||||
'class="announceOrReply"/> <a href="' + \
|
'class="announceOrReply"/>\n' + \
|
||||||
|
' <a href="' + \
|
||||||
postJsonObject['object']['id'] + \
|
postJsonObject['object']['id'] + \
|
||||||
'">@unattributed</a>\n'
|
'">@unattributed</a>\n'
|
||||||
else:
|
else:
|
||||||
titleStr += \
|
titleStr += \
|
||||||
|
' ' + \
|
||||||
'<img loading="lazy" title="' + translate['announces'] + \
|
'<img loading="lazy" title="' + translate['announces'] + \
|
||||||
'" alt="' + translate['announces'] + \
|
'" alt="' + translate['announces'] + \
|
||||||
'" src="/' + iconsDir + \
|
'" src="/' + iconsDir + \
|
||||||
'/repeat_inactive.png" ' + \
|
'/repeat_inactive.png" ' + \
|
||||||
'class="announceOrReply"/> <a href="' + \
|
'class="announceOrReply"/>\n' + \
|
||||||
|
' <a href="' + \
|
||||||
postJsonObject['object']['id'] + '">@unattributed</a>\n'
|
postJsonObject['object']['id'] + '">@unattributed</a>\n'
|
||||||
else:
|
else:
|
||||||
if postJsonObject['object'].get('inReplyTo'):
|
if postJsonObject['object'].get('inReplyTo'):
|
||||||
|
|
@ -4694,13 +4890,15 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
boxName + ' 13.6 = ' +
|
boxName + ' 13.6 = ' +
|
||||||
str(timeDiff))
|
str(timeDiff))
|
||||||
titleStr += \
|
titleStr += \
|
||||||
|
' ' + \
|
||||||
'<img loading="lazy" title="' + \
|
'<img loading="lazy" title="' + \
|
||||||
translate['replying to'] + \
|
translate['replying to'] + \
|
||||||
'" alt="' + \
|
'" alt="' + \
|
||||||
translate['replying to'] + \
|
translate['replying to'] + \
|
||||||
'" src="/' + \
|
'" src="/' + \
|
||||||
iconsDir + '/reply.png" ' + \
|
iconsDir + '/reply.png" ' + \
|
||||||
'class="announceOrReply"/> ' + \
|
'class="announceOrReply"/>\n' + \
|
||||||
|
' ' + \
|
||||||
'<a href="' + inReplyTo + \
|
'<a href="' + inReplyTo + \
|
||||||
'">' + replyDisplayName + '</a>\n'
|
'">' + replyDisplayName + '</a>\n'
|
||||||
|
|
||||||
|
|
@ -4732,6 +4930,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
' <div class=' + \
|
' <div class=' + \
|
||||||
'"timeline-avatar-reply">\n'
|
'"timeline-avatar-reply">\n'
|
||||||
replyAvatarImageInPost += \
|
replyAvatarImageInPost += \
|
||||||
|
' ' + \
|
||||||
'<a class="imageAnchor" ' + \
|
'<a class="imageAnchor" ' + \
|
||||||
'href="/users/' + nickname + \
|
'href="/users/' + nickname + \
|
||||||
'?options=' + replyActor + \
|
'?options=' + replyActor + \
|
||||||
|
|
@ -4739,6 +4938,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
replyAvatarUrl + \
|
replyAvatarUrl + \
|
||||||
messageIdStr + '">\n'
|
messageIdStr + '">\n'
|
||||||
replyAvatarImageInPost += \
|
replyAvatarImageInPost += \
|
||||||
|
' ' + \
|
||||||
'<img loading="lazy" src="' + \
|
'<img loading="lazy" src="' + \
|
||||||
replyAvatarUrl + '" '
|
replyAvatarUrl + '" '
|
||||||
replyAvatarImageInPost += \
|
replyAvatarImageInPost += \
|
||||||
|
|
@ -4746,18 +4946,20 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
translate['Show profile']
|
translate['Show profile']
|
||||||
replyAvatarImageInPost += \
|
replyAvatarImageInPost += \
|
||||||
'" alt=" "' + \
|
'" alt=" "' + \
|
||||||
avatarPosition + '/></a>\n</div>\n'
|
avatarPosition + '/></a>\n' + \
|
||||||
|
' </div>\n'
|
||||||
else:
|
else:
|
||||||
inReplyTo = \
|
inReplyTo = \
|
||||||
postJsonObject['object']['inReplyTo']
|
postJsonObject['object']['inReplyTo']
|
||||||
titleStr += \
|
titleStr += \
|
||||||
|
' ' + \
|
||||||
'<img loading="lazy" title="' + \
|
'<img loading="lazy" title="' + \
|
||||||
translate['replying to'] + \
|
translate['replying to'] + \
|
||||||
'" alt="' + \
|
'" alt="' + \
|
||||||
translate['replying to'] + \
|
translate['replying to'] + \
|
||||||
'" src="/' + \
|
'" src="/' + \
|
||||||
iconsDir + '/reply.png" ' + \
|
iconsDir + '/reply.png" ' + \
|
||||||
'class="announceOrReply"/> ' + \
|
'class="announceOrReply"/>\n' + \
|
||||||
' <a href="' + \
|
' <a href="' + \
|
||||||
inReplyTo + '">@' + \
|
inReplyTo + '">@' + \
|
||||||
replyNickname + '@' + \
|
replyNickname + '@' + \
|
||||||
|
|
@ -4770,7 +4972,7 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
translate['replying to'] + \
|
translate['replying to'] + \
|
||||||
'" src="/' + \
|
'" src="/' + \
|
||||||
iconsDir + \
|
iconsDir + \
|
||||||
'/reply.png" class="announceOrReply"/> ' + \
|
'/reply.png" class="announceOrReply"/>\n' + \
|
||||||
' <a href="' + \
|
' <a href="' + \
|
||||||
postJsonObject['object']['inReplyTo'] + \
|
postJsonObject['object']['inReplyTo'] + \
|
||||||
'">@unknown</a>\n'
|
'">@unknown</a>\n'
|
||||||
|
|
@ -4789,7 +4991,8 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
'" alt="' + translate['replying to'] + \
|
'" alt="' + translate['replying to'] + \
|
||||||
'" src="/' + \
|
'" src="/' + \
|
||||||
iconsDir + '/reply.png" ' + \
|
iconsDir + '/reply.png" ' + \
|
||||||
'class="announceOrReply"/> <a href="' + \
|
'class="announceOrReply"/>\n' + \
|
||||||
|
' <a href="' + \
|
||||||
postJsonObject['object']['inReplyTo'] + \
|
postJsonObject['object']['inReplyTo'] + \
|
||||||
'">' + postDomain + '</a>\n'
|
'">' + postDomain + '</a>\n'
|
||||||
|
|
||||||
|
|
@ -4849,12 +5052,12 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
containerClass = 'container dm'
|
containerClass = 'container dm'
|
||||||
|
|
||||||
if showIcons:
|
if showIcons:
|
||||||
footerStr = '<div class="' + containerClassIcons + '">'
|
footerStr = '\n <div class="' + containerClassIcons + '">\n'
|
||||||
footerStr += replyStr + announceStr + likeStr + bookmarkStr + \
|
footerStr += replyStr + announceStr + likeStr + bookmarkStr + \
|
||||||
deleteStr + muteStr + editStr
|
deleteStr + muteStr + editStr
|
||||||
footerStr += ' <a href="' + publishedLink + '" class="' + \
|
footerStr += ' <a href="' + publishedLink + '" class="' + \
|
||||||
timeClass + '">' + publishedStr + '</a>\n'
|
timeClass + '">' + publishedStr + '</a>\n'
|
||||||
footerStr += '</div>'
|
footerStr += ' </div>\n'
|
||||||
|
|
||||||
postIsSensitive = False
|
postIsSensitive = False
|
||||||
if postJsonObject['object'].get('sensitive'):
|
if postJsonObject['object'].get('sensitive'):
|
||||||
|
|
@ -4948,7 +5151,9 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
contentStr = ''
|
contentStr = ''
|
||||||
else:
|
else:
|
||||||
if not isPatch:
|
if not isPatch:
|
||||||
contentStr = '<div class="message">' + contentStr + '</div>\n'
|
contentStr = ' <div class="message">' + \
|
||||||
|
contentStr + \
|
||||||
|
' </div>\n'
|
||||||
else:
|
else:
|
||||||
contentStr = \
|
contentStr = \
|
||||||
'<div class="gitpatch"><pre><code>' + contentStr + \
|
'<div class="gitpatch"><pre><code>' + contentStr + \
|
||||||
|
|
@ -4959,8 +5164,9 @@ def individualPostAsHtml(allowDownloads: bool,
|
||||||
postHtml = ' <div id="' + timelinePostBookmark + \
|
postHtml = ' <div id="' + timelinePostBookmark + \
|
||||||
'" class="' + containerClass + '">\n'
|
'" class="' + containerClass + '">\n'
|
||||||
postHtml += avatarImageInPost
|
postHtml += avatarImageInPost
|
||||||
postHtml += '<p class="post-title">' + titleStr + \
|
postHtml += ' <div class="post-title">\n' + \
|
||||||
replyAvatarImageInPost + '</p>\n'
|
' ' + titleStr + \
|
||||||
|
replyAvatarImageInPost + ' </div>\n'
|
||||||
postHtml += contentStr + footerStr + '\n'
|
postHtml += contentStr + footerStr + '\n'
|
||||||
postHtml += ' </div>\n'
|
postHtml += ' </div>\n'
|
||||||
else:
|
else:
|
||||||
|
|
@ -5019,6 +5225,184 @@ def htmlHighlightLabel(label: str, highlight: bool) -> str:
|
||||||
return '*' + label + '*'
|
return '*' + label + '*'
|
||||||
|
|
||||||
|
|
||||||
|
def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
|
||||||
|
httpPrefix: str, translate: {},
|
||||||
|
iconsDir: str, moderator: bool) -> str:
|
||||||
|
"""Returns html content for the left column
|
||||||
|
"""
|
||||||
|
htmlStr = ''
|
||||||
|
|
||||||
|
domain = domainFull
|
||||||
|
if ':' in domain:
|
||||||
|
domain = domain.split(':')
|
||||||
|
|
||||||
|
leftColumnImageFilename = \
|
||||||
|
baseDir + '/accounts/' + nickname + '@' + domain + \
|
||||||
|
'/left_col_image.png'
|
||||||
|
if not os.path.isfile(leftColumnImageFilename):
|
||||||
|
theme = getConfigParam(baseDir, 'theme').lower()
|
||||||
|
if theme == 'default':
|
||||||
|
theme = ''
|
||||||
|
else:
|
||||||
|
theme = '_' + theme
|
||||||
|
themeLeftColumnImageFilename = \
|
||||||
|
baseDir + '/img/left_col_image' + theme + '.png'
|
||||||
|
if os.path.isfile(themeLeftColumnImageFilename):
|
||||||
|
copyfile(themeLeftColumnImageFilename, leftColumnImageFilename)
|
||||||
|
|
||||||
|
# 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 + '/left_col_image.png" />\n' + \
|
||||||
|
' </center>\n'
|
||||||
|
|
||||||
|
if editImageClass == 'leftColEdit':
|
||||||
|
htmlStr += '\n <center>\n'
|
||||||
|
|
||||||
|
if moderator:
|
||||||
|
# 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
|
||||||
|
htmlStr += \
|
||||||
|
' <a href="' + \
|
||||||
|
httpPrefix + '://' + domainFull + \
|
||||||
|
'/blog/' + nickname + '/rss.xml">' + \
|
||||||
|
'<img class="' + editImageClass + \
|
||||||
|
'" loading="lazy" alt="' + \
|
||||||
|
translate['RSS feed for this site'] + \
|
||||||
|
'" title="' + translate['RSS feed for this site'] + \
|
||||||
|
'" src="/' + iconsDir + '/rss.png" /></a>\n'
|
||||||
|
|
||||||
|
if editImageClass == 'leftColEdit':
|
||||||
|
htmlStr += ' </center>\n'
|
||||||
|
else:
|
||||||
|
htmlStr += ' <br>\n'
|
||||||
|
|
||||||
|
linksFilename = baseDir + '/accounts/links.txt'
|
||||||
|
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'
|
||||||
|
else:
|
||||||
|
if lineStr.startswith('#') or lineStr.startswith('*'):
|
||||||
|
lineStr = lineStr[1:].strip()
|
||||||
|
htmlStr += \
|
||||||
|
' <h3 class="linksHeader">' + \
|
||||||
|
lineStr + '</h3>\n'
|
||||||
|
else:
|
||||||
|
htmlStr += \
|
||||||
|
' <p>' + lineStr + '</p>\n'
|
||||||
|
|
||||||
|
return htmlStr
|
||||||
|
|
||||||
|
|
||||||
|
def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
|
||||||
|
httpPrefix: str, translate: {},
|
||||||
|
iconsDir: str, moderator: bool) -> str:
|
||||||
|
"""Returns html content for the right column
|
||||||
|
"""
|
||||||
|
htmlStr = ''
|
||||||
|
|
||||||
|
domain = domainFull
|
||||||
|
if ':' in domain:
|
||||||
|
domain = domain.split(':')
|
||||||
|
|
||||||
|
rightColumnImageFilename = \
|
||||||
|
baseDir + '/accounts/' + nickname + '@' + domain + \
|
||||||
|
'/right_col_image.png'
|
||||||
|
if not os.path.isfile(rightColumnImageFilename):
|
||||||
|
theme = getConfigParam(baseDir, 'theme').lower()
|
||||||
|
if theme == 'default':
|
||||||
|
theme = ''
|
||||||
|
else:
|
||||||
|
theme = '_' + theme
|
||||||
|
themeRightColumnImageFilename = \
|
||||||
|
baseDir + '/img/right_col_image' + theme + '.png'
|
||||||
|
if os.path.isfile(themeRightColumnImageFilename):
|
||||||
|
copyfile(themeRightColumnImageFilename, rightColumnImageFilename)
|
||||||
|
|
||||||
|
# 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 + '/right_col_image.png" />\n' + \
|
||||||
|
' </center>\n'
|
||||||
|
|
||||||
|
if editImageClass == 'rightColEdit':
|
||||||
|
htmlStr += '\n <center>\n'
|
||||||
|
|
||||||
|
if moderator:
|
||||||
|
# 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'
|
||||||
|
|
||||||
|
htmlStr += \
|
||||||
|
' <a href="/newswire.xml">' + \
|
||||||
|
'<img class="' + editImageClass + \
|
||||||
|
'" loading="lazy" alt="' + \
|
||||||
|
translate['Newswire RSS Feed'] + '" title="' + \
|
||||||
|
translate['Newswire RSS Feed'] + '" src="/' + \
|
||||||
|
iconsDir + '/rss.png" /></a>\n'
|
||||||
|
|
||||||
|
if editImageClass == 'rightColEdit':
|
||||||
|
htmlStr += ' </center>\n'
|
||||||
|
else:
|
||||||
|
htmlStr += ' <br>\n'
|
||||||
|
|
||||||
|
return htmlStr
|
||||||
|
|
||||||
|
|
||||||
def htmlTimeline(defaultTimeline: str,
|
def htmlTimeline(defaultTimeline: str,
|
||||||
recentPostsCache: {}, maxRecentPosts: int,
|
recentPostsCache: {}, maxRecentPosts: int,
|
||||||
translate: {}, pageNumber: int,
|
translate: {}, pageNumber: int,
|
||||||
|
|
@ -5300,8 +5684,8 @@ def htmlTimeline(defaultTimeline: str,
|
||||||
# This creates a link to the profile page when viewed
|
# This creates a link to the profile page when viewed
|
||||||
# in lynx, but should be invisible in a graphical web browser
|
# in lynx, but should be invisible in a graphical web browser
|
||||||
tlStr += \
|
tlStr += \
|
||||||
'<a href="/users/' + nickname + '"><label class="transparent">' + \
|
'<label class="transparent"><a href="/users/' + nickname + '">' + \
|
||||||
translate['Switch to profile view'] + '</label></a>\n'
|
translate['Switch to profile view'] + '</a></label>\n'
|
||||||
|
|
||||||
# banner and row of buttons
|
# banner and row of buttons
|
||||||
tlStr += \
|
tlStr += \
|
||||||
|
|
@ -5310,8 +5694,34 @@ def htmlTimeline(defaultTimeline: str,
|
||||||
translate['Switch to profile view'] + '">\n'
|
translate['Switch to profile view'] + '">\n'
|
||||||
tlStr += '<div class="timeline-banner">'
|
tlStr += '<div class="timeline-banner">'
|
||||||
tlStr += '</div>\n</a>\n'
|
tlStr += '</div>\n</a>\n'
|
||||||
tlStr += '<div class="container">\n'
|
|
||||||
|
|
||||||
|
# start the timeline
|
||||||
|
tlStr += '<table class="timeline">\n'
|
||||||
|
tlStr += ' <colgroup>\n'
|
||||||
|
tlStr += ' <col span="1" class="column-left">\n'
|
||||||
|
tlStr += ' <col span="1" class="column-center">\n'
|
||||||
|
tlStr += ' <col span="1" class="column-right">\n'
|
||||||
|
tlStr += ' </colgroup>\n'
|
||||||
|
tlStr += ' <tbody>\n'
|
||||||
|
tlStr += ' <tr>\n'
|
||||||
|
|
||||||
|
domainFull = domain
|
||||||
|
if port:
|
||||||
|
if port != 80 and port != 443:
|
||||||
|
domainFull = domain + ':' + str(port)
|
||||||
|
|
||||||
|
# left column
|
||||||
|
leftColumnStr = \
|
||||||
|
getLeftColumnContent(baseDir, nickname, domainFull,
|
||||||
|
httpPrefix, translate, iconsDir,
|
||||||
|
moderator)
|
||||||
|
tlStr += ' <td valign="top" class="col-left">' + \
|
||||||
|
leftColumnStr + ' </td>\n'
|
||||||
|
# center column containing posts
|
||||||
|
tlStr += ' <td valign="top" class="col-center">\n'
|
||||||
|
|
||||||
|
# start of the button header with inbox, outbox, etc
|
||||||
|
tlStr += ' <div class="container">\n'
|
||||||
# first button
|
# first button
|
||||||
if defaultTimeline == 'tlmedia':
|
if defaultTimeline == 'tlmedia':
|
||||||
tlStr += \
|
tlStr += \
|
||||||
|
|
@ -5388,6 +5798,32 @@ def htmlTimeline(defaultTimeline: str,
|
||||||
sharesButtonStr + bookmarksButtonStr + eventsButtonStr + \
|
sharesButtonStr + bookmarksButtonStr + eventsButtonStr + \
|
||||||
moderationButtonStr + newPostButtonStr
|
moderationButtonStr + newPostButtonStr
|
||||||
|
|
||||||
|
# show todays events buttons on the first inbox page
|
||||||
|
if boxName == 'inbox' and pageNumber == 1:
|
||||||
|
if todaysEventsCheck(baseDir, nickname, domain):
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
# happening today button
|
||||||
|
tlStr += \
|
||||||
|
' <a href="' + usersPath + '/calendar?year=' + \
|
||||||
|
str(now.year) + '?month=' + str(now.month) + \
|
||||||
|
'?day=' + str(now.day) + '"><button class="buttonevent">' + \
|
||||||
|
translate['Happening Today'] + '</button></a>\n'
|
||||||
|
|
||||||
|
# happening this week button
|
||||||
|
if thisWeeksEventsCheck(baseDir, nickname, domain):
|
||||||
|
tlStr += \
|
||||||
|
' <a href="' + usersPath + \
|
||||||
|
'/calendar"><button class="buttonevent">' + \
|
||||||
|
translate['Happening This Week'] + '</button></a>\n'
|
||||||
|
else:
|
||||||
|
# happening this week button
|
||||||
|
if thisWeeksEventsCheck(baseDir, nickname, domain):
|
||||||
|
tlStr += \
|
||||||
|
' <a href="' + usersPath + \
|
||||||
|
'/calendar"><button class="buttonevent">' + \
|
||||||
|
translate['Happening This Week'] + '</button></a>\n'
|
||||||
|
|
||||||
# the search button
|
# the search button
|
||||||
tlStr += \
|
tlStr += \
|
||||||
' <a class="imageAnchor" href="' + usersPath + \
|
' <a class="imageAnchor" href="' + usersPath + \
|
||||||
|
|
@ -5420,7 +5856,8 @@ def htmlTimeline(defaultTimeline: str,
|
||||||
'" alt="| ' + translate['Show/Hide Buttons'] + \
|
'" alt="| ' + translate['Show/Hide Buttons'] + \
|
||||||
'" class="timelineicon"/></a>\n'
|
'" class="timelineicon"/></a>\n'
|
||||||
tlStr += followApprovals
|
tlStr += followApprovals
|
||||||
tlStr += '</div>'
|
# end of the button header with inbox, outbox, etc
|
||||||
|
tlStr += ' </div>\n'
|
||||||
|
|
||||||
# second row of buttons for moderator actions
|
# second row of buttons for moderator actions
|
||||||
if moderator and boxName == 'moderation':
|
if moderator and boxName == 'moderation':
|
||||||
|
|
@ -5479,34 +5916,6 @@ def htmlTimeline(defaultTimeline: str,
|
||||||
if timeDiff > 100:
|
if timeDiff > 100:
|
||||||
print('TIMELINE TIMING ' + boxName + ' 7 = ' + str(timeDiff))
|
print('TIMELINE TIMING ' + boxName + ' 7 = ' + str(timeDiff))
|
||||||
|
|
||||||
# show todays events buttons on the first inbox page
|
|
||||||
if boxName == 'inbox' and pageNumber == 1:
|
|
||||||
if todaysEventsCheck(baseDir, nickname, domain):
|
|
||||||
now = datetime.now()
|
|
||||||
|
|
||||||
# happening today button
|
|
||||||
tlStr += \
|
|
||||||
'<center>\n<a href="' + usersPath + '/calendar?year=' + \
|
|
||||||
str(now.year) + '?month=' + str(now.month) + \
|
|
||||||
'?day=' + str(now.day) + '"><button class="buttonevent">' + \
|
|
||||||
translate['Happening Today'] + '</button></a>\n'
|
|
||||||
|
|
||||||
# happening this week button
|
|
||||||
if thisWeeksEventsCheck(baseDir, nickname, domain):
|
|
||||||
tlStr += \
|
|
||||||
'<a href="' + usersPath + \
|
|
||||||
'/calendar"><button class="buttonevent">' + \
|
|
||||||
translate['Happening This Week'] + '</button></a>\n'
|
|
||||||
tlStr += '</center>\n'
|
|
||||||
else:
|
|
||||||
# happening this week button
|
|
||||||
if thisWeeksEventsCheck(baseDir, nickname, domain):
|
|
||||||
tlStr += \
|
|
||||||
'<center>\n<a href="' + usersPath + \
|
|
||||||
'/calendar"><button class="buttonevent">' + \
|
|
||||||
translate['Happening This Week'] + '</button></a>\n' + \
|
|
||||||
'</center>\n'
|
|
||||||
|
|
||||||
# benchmark 8
|
# benchmark 8
|
||||||
timeDiff = int((time.time() - timelineStartTime) * 1000)
|
timeDiff = int((time.time() - timelineStartTime) * 1000)
|
||||||
if timeDiff > 100:
|
if timeDiff > 100:
|
||||||
|
|
@ -5515,12 +5924,14 @@ def htmlTimeline(defaultTimeline: str,
|
||||||
# page up arrow
|
# page up arrow
|
||||||
if pageNumber > 1:
|
if pageNumber > 1:
|
||||||
tlStr += \
|
tlStr += \
|
||||||
'<center>\n<a href="' + usersPath + '/' + boxName + \
|
' <center>\n' + \
|
||||||
|
' <a href="' + usersPath + '/' + boxName + \
|
||||||
'?page=' + str(pageNumber - 1) + \
|
'?page=' + str(pageNumber - 1) + \
|
||||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||||
iconsDir + '/pageup.png" title="' + \
|
iconsDir + '/pageup.png" title="' + \
|
||||||
translate['Page up'] + '" alt="' + \
|
translate['Page up'] + '" alt="' + \
|
||||||
translate['Page up'] + '"></a>\n</center>\n'
|
translate['Page up'] + '"></a>\n' + \
|
||||||
|
' </center>\n'
|
||||||
|
|
||||||
# show the posts
|
# show the posts
|
||||||
itemCtr = 0
|
itemCtr = 0
|
||||||
|
|
@ -5604,6 +6015,17 @@ def htmlTimeline(defaultTimeline: str,
|
||||||
if boxName == 'tlmedia':
|
if boxName == 'tlmedia':
|
||||||
tlStr += '</div>\n'
|
tlStr += '</div>\n'
|
||||||
|
|
||||||
|
# end of column-center
|
||||||
|
tlStr += ' </td>\n'
|
||||||
|
|
||||||
|
# right column
|
||||||
|
rightColumnStr = getRightColumnContent(baseDir, nickname, domainFull,
|
||||||
|
httpPrefix, translate, iconsDir,
|
||||||
|
moderator)
|
||||||
|
tlStr += ' <td valign="top" class="col-right">' + \
|
||||||
|
rightColumnStr + ' </td>\n'
|
||||||
|
tlStr += ' </tr>\n'
|
||||||
|
|
||||||
# benchmark 9
|
# benchmark 9
|
||||||
timeDiff = int((time.time() - timelineStartTime) * 1000)
|
timeDiff = int((time.time() - timelineStartTime) * 1000)
|
||||||
if timeDiff > 100:
|
if timeDiff > 100:
|
||||||
|
|
@ -5612,12 +6034,23 @@ def htmlTimeline(defaultTimeline: str,
|
||||||
# page down arrow
|
# page down arrow
|
||||||
if itemCtr > 2:
|
if itemCtr > 2:
|
||||||
tlStr += \
|
tlStr += \
|
||||||
'<center>\n<a href="' + usersPath + '/' + boxName + '?page=' + \
|
' <tr>\n' + \
|
||||||
|
' <td class="col-left"></td>\n' + \
|
||||||
|
' <td class="col-center">\n' + \
|
||||||
|
' <center>\n' + \
|
||||||
|
' <a href="' + usersPath + '/' + boxName + '?page=' + \
|
||||||
str(pageNumber + 1) + \
|
str(pageNumber + 1) + \
|
||||||
'"><img loading="lazy" class="pageicon" src="/' + \
|
'"><img loading="lazy" class="pageicon" src="/' + \
|
||||||
iconsDir + '/pagedown.png" title="' + \
|
iconsDir + '/pagedown.png" title="' + \
|
||||||
translate['Page down'] + '" alt="' + \
|
translate['Page down'] + '" alt="' + \
|
||||||
translate['Page down'] + '"></a>\n</center>\n'
|
translate['Page down'] + '"></a>\n' + \
|
||||||
|
' </center>\n' + \
|
||||||
|
' </td>\n' + \
|
||||||
|
' <td class="col-right"></td>\n' + \
|
||||||
|
' </tr>\n'
|
||||||
|
|
||||||
|
tlStr += ' </tbody>\n'
|
||||||
|
tlStr += '</table>\n'
|
||||||
tlStr += htmlFooter()
|
tlStr += htmlFooter()
|
||||||
return tlStr
|
return tlStr
|
||||||
|
|
||||||
|
|
|
||||||