Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon

main
Bob Mottram 2021-04-26 14:01:05 +01:00
commit a17b72b63b
38 changed files with 1496 additions and 185 deletions

12
blog.py
View File

@ -619,7 +619,9 @@ def _noOfBlogAccounts(baseDir: str) -> int:
for acct in dirs:
if '@' not in acct:
continue
if 'inbox@' in acct:
if acct.startswith('inbox@'):
continue
elif acct.startswith('news@'):
continue
accountDir = os.path.join(baseDir + '/accounts', acct)
blogsIndex = accountDir + '/tlblogs.index'
@ -636,7 +638,9 @@ def _singleBlogAccountNickname(baseDir: str) -> str:
for acct in dirs:
if '@' not in acct:
continue
if 'inbox@' in acct:
if acct.startswith('inbox@'):
continue
elif acct.startswith('news@'):
continue
accountDir = os.path.join(baseDir + '/accounts', acct)
blogsIndex = accountDir + '/tlblogs.index'
@ -676,7 +680,9 @@ def htmlBlogView(authorized: bool,
for acct in dirs:
if '@' not in acct:
continue
if 'inbox@' in acct:
if acct.startswith('inbox@'):
continue
elif acct.startswith('news@'):
continue
accountDir = os.path.join(baseDir + '/accounts', acct)
blogsIndex = accountDir + '/tlblogs.index'

View File

@ -26,6 +26,12 @@ No stalking, unwanted personal attention, or unwelcome revealing or speculating
In cases of sincere, good-faith curiosity about someones experience or identity, ask politely in a manner such that they will feel free to decline the request.
## No non-consenting research
People contributing to, or maintaining, this project should not be treated as research subjects in academic studies without their prior written consent. If anthropological, security, or other types of research are being conducted upon contributors then they must be made aware of this and formally agree to it taking place.
Publishing software under an AGPL license does not imply consent to become a research subject.
## No hostile communication
No insults, harassment (sexual or otherwise), condescension, ad hominem, threats, or other intimidation. Claims that such communications were intended as "ironic" or humerous will also be considered a code of conduct violation.

468
daemon.py
View File

@ -134,6 +134,8 @@ from webapp_utils import getBlogAddress
from webapp_calendar import htmlCalendarDeleteConfirm
from webapp_calendar import htmlCalendar
from webapp_about import htmlAbout
from webapp_accesskeys import htmlAccessKeys
from webapp_accesskeys import loadAccessKeysForAccounts
from webapp_confirm import htmlConfirmDelete
from webapp_confirm import htmlConfirmRemoveSharedItem
from webapp_confirm import htmlConfirmUnblock
@ -1800,6 +1802,93 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy = False
return
def _keyShortcuts(self, path: str,
callingDomain: str, cookie: str,
baseDir: str, httpPrefix: str, nickname: str,
domain: str, domainFull: str, port: int,
onionDomain: str, i2pDomain: str,
debug: bool, accessKeys: {},
defaultTimeline: str) -> None:
"""Receive POST from webapp_accesskeys
"""
usersPath = '/users/' + nickname
originPathStr = \
httpPrefix + '://' + domainFull + usersPath + '/' + defaultTimeline
length = int(self.headers['Content-length'])
try:
accessKeysParams = self.rfile.read(length).decode('utf-8')
except SocketError as e:
if e.errno == errno.ECONNRESET:
print('WARN: POST accessKeysParams ' +
'connection reset by peer')
else:
print('WARN: POST accessKeysParams socket error')
self.send_response(400)
self.end_headers()
self.server.POSTbusy = False
return
except ValueError as e:
print('ERROR: POST accessKeysParams rfile.read failed')
print(e)
self.send_response(400)
self.end_headers()
self.server.POSTbusy = False
return
accessKeysParams = \
urllib.parse.unquote_plus(accessKeysParams)
# key shortcuts screen, back button
# See htmlAccessKeys
if 'submitAccessKeysCancel=' in accessKeysParams or \
'submitAccessKeys=' not in accessKeysParams:
if callingDomain.endswith('.onion') and onionDomain:
originPathStr = \
'http://' + onionDomain + usersPath + '/' + defaultTimeline
elif callingDomain.endswith('.i2p') and i2pDomain:
originPathStr = \
'http://' + i2pDomain + usersPath + '/' + defaultTimeline
self._redirect_headers(originPathStr, cookie, callingDomain)
self.server.POSTbusy = False
return
saveKeys = False
accessKeysTemplate = self.server.accessKeys
for variableName, key in accessKeysTemplate.items():
if not accessKeys.get(variableName):
accessKeys[variableName] = accessKeysTemplate[variableName]
variableName2 = variableName.replace(' ', '_')
if variableName2 + '=' in accessKeysParams:
newKey = accessKeysParams.split(variableName2 + '=')[1]
if '&' in newKey:
newKey = newKey.split('&')[0]
if newKey:
if len(newKey) > 1:
newKey = newKey[0]
if newKey != accessKeys[variableName]:
accessKeys[variableName] = newKey
saveKeys = True
if saveKeys:
accessKeysFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + \
'/accessKeys.json'
saveJson(accessKeys, accessKeysFilename)
if not self.server.keyShortcuts.get(nickname):
self.server.keyShortcuts[nickname] = accessKeys.copy()
# redirect back from key shortcuts screen
if callingDomain.endswith('.onion') and onionDomain:
originPathStr = \
'http://' + onionDomain + usersPath + '/' + defaultTimeline
elif callingDomain.endswith('.i2p') and i2pDomain:
originPathStr = \
'http://' + i2pDomain + usersPath + '/' + defaultTimeline
self._redirect_headers(originPathStr, cookie, callingDomain)
self.server.POSTbusy = False
return
def _personOptions(self, path: str,
callingDomain: str, cookie: str,
baseDir: str, httpPrefix: str,
@ -2168,6 +2257,15 @@ class PubServer(BaseHTTPRequestHandler):
if debug:
print('Sending DM to ' + optionsActor)
reportPath = path.replace('/personoptions', '') + '/newdm'
accessKeys = self.server.accessKeys
if '/users/' in path:
nickname = path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
msg = htmlNewPost(self.server.cssCache,
False, self.server.translate,
baseDir,
@ -2181,7 +2279,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.defaultTimeline,
self.server.newswire,
self.server.themeName,
True).encode('utf-8')
True, accessKeys).encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
cookie, callingDomain)
@ -2268,6 +2366,15 @@ class PubServer(BaseHTTPRequestHandler):
print('Reporting ' + optionsActor)
reportPath = \
path.replace('/personoptions', '') + '/newreport'
accessKeys = self.server.accessKeys
if '/users/' in path:
nickname = path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
msg = htmlNewPost(self.server.cssCache,
False, self.server.translate,
baseDir,
@ -2280,7 +2387,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.defaultTimeline,
self.server.newswire,
self.server.themeName,
True).encode('utf-8')
True, accessKeys).encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
cookie, callingDomain)
@ -2843,6 +2950,11 @@ class PubServer(BaseHTTPRequestHandler):
showPublishedDateOnly = self.server.showPublishedDateOnly
allowLocalNetworkAccess = \
self.server.allowLocalNetworkAccess
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
profileStr = \
htmlProfileAfterSearch(self.server.cssCache,
self.server.recentPostsCache,
@ -2865,7 +2977,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.defaultTimeline,
self.server.peertubeInstances,
allowLocalNetworkAccess,
self.server.themeName)
self.server.themeName,
accessKeys)
if profileStr:
msg = profileStr.encode('utf-8')
msglen = len(msg)
@ -5019,11 +5132,24 @@ class PubServer(BaseHTTPRequestHandler):
if os.path.isfile(blockedFilename):
os.remove(blockedFilename)
# Save DM allowed instances list.
# The allow list for incoming DMs,
# if the .followDMs flag file exists
dmAllowedInstancesFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/dmAllowedinstances.txt'
if fields.get('dmAllowedInstances'):
with open(dmAllowedInstancesFilename, 'w+') as aFile:
aFile.write(fields['dmAllowedInstances'])
else:
if os.path.isfile(dmAllowedInstancesFilename):
os.remove(dmAllowedInstancesFilename)
# save allowed instances list
# This is the account level allow list
allowedInstancesFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + \
'/allowedinstances.txt'
nickname + '@' + domain + '/allowedinstances.txt'
if fields.get('allowedInstances'):
with open(allowedInstancesFilename, 'w+') as aFile:
aFile.write(fields['allowedInstances'])
@ -5703,6 +5829,13 @@ class PubServer(BaseHTTPRequestHandler):
optionsActor, optionsProfileUrl,
self.server.personCache, 5)
accessKeys = self.server.accessKeys
if '/users/' in path:
nickname = path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
msg = htmlPersonOptions(self.server.defaultTimeline,
self.server.cssCache,
self.server.translate,
@ -5725,7 +5858,8 @@ class PubServer(BaseHTTPRequestHandler):
movedTo, alsoKnownAs,
self.server.textModeBanner,
self.server.newsInstance,
authorized).encode('utf-8')
authorized,
accessKeys).encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
cookie, callingDomain)
@ -7329,6 +7463,11 @@ class PubServer(BaseHTTPRequestHandler):
self.server.YTReplacementDomain
iconsAsButtons = \
self.server.iconsAsButtons
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
msg = \
htmlProfile(self.server.rssIconAtTop,
self.server.cssCache,
@ -7352,6 +7491,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess,
self.server.textModeBanner,
self.server.debug,
accessKeys,
actorJson['roles'],
None, None)
msg = msg.encode('utf-8')
@ -7418,6 +7558,10 @@ class PubServer(BaseHTTPRequestHandler):
self.server.iconsAsButtons
allowLocalNetworkAccess = \
self.server.allowLocalNetworkAccess
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlProfile(self.server.rssIconAtTop,
self.server.cssCache,
@ -7441,6 +7585,7 @@ class PubServer(BaseHTTPRequestHandler):
allowLocalNetworkAccess,
self.server.textModeBanner,
self.server.debug,
accessKeys,
actorJson['skills'],
None, None)
msg = msg.encode('utf-8')
@ -7808,6 +7953,12 @@ class PubServer(BaseHTTPRequestHandler):
fullWidthTimelineButtonHeader = \
self.server.fullWidthTimelineButtonHeader
minimalNick = self._isMinimal(nickname)
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = htmlInbox(self.server.cssCache,
defaultTimeline,
recentPostsCache,
@ -7839,7 +7990,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
self.server.textModeBanner)
self.server.textModeBanner,
accessKeys)
if GETstartTime:
self._benchmarkGETtimings(GETstartTime, GETtimings,
'show status done',
@ -7937,6 +8089,12 @@ class PubServer(BaseHTTPRequestHandler):
fullWidthTimelineButtonHeader = \
self.server.fullWidthTimelineButtonHeader
minimalNick = self._isMinimal(nickname)
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlInboxDMs(self.server.cssCache,
self.server.defaultTimeline,
@ -7968,7 +8126,8 @@ class PubServer(BaseHTTPRequestHandler):
authorized, self.server.themeName,
self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
self.server.textModeBanner)
self.server.textModeBanner,
accessKeys)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -8059,6 +8218,12 @@ class PubServer(BaseHTTPRequestHandler):
fullWidthTimelineButtonHeader = \
self.server.fullWidthTimelineButtonHeader
minimalNick = self._isMinimal(nickname)
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlInboxReplies(self.server.cssCache,
self.server.defaultTimeline,
@ -8090,7 +8255,8 @@ class PubServer(BaseHTTPRequestHandler):
authorized, self.server.themeName,
self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
self.server.textModeBanner)
self.server.textModeBanner,
accessKeys)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -8181,6 +8347,12 @@ class PubServer(BaseHTTPRequestHandler):
fullWidthTimelineButtonHeader = \
self.server.fullWidthTimelineButtonHeader
minimalNick = self._isMinimal(nickname)
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlInboxMedia(self.server.cssCache,
self.server.defaultTimeline,
@ -8213,7 +8385,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
self.server.textModeBanner)
self.server.textModeBanner,
accessKeys)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -8304,6 +8477,12 @@ class PubServer(BaseHTTPRequestHandler):
fullWidthTimelineButtonHeader = \
self.server.fullWidthTimelineButtonHeader
minimalNick = self._isMinimal(nickname)
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlInboxBlogs(self.server.cssCache,
self.server.defaultTimeline,
@ -8336,7 +8515,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
self.server.textModeBanner)
self.server.textModeBanner,
accessKeys)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -8435,6 +8615,12 @@ class PubServer(BaseHTTPRequestHandler):
fullWidthTimelineButtonHeader = \
self.server.fullWidthTimelineButtonHeader
minimalNick = self._isMinimal(nickname)
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlInboxNews(self.server.cssCache,
self.server.defaultTimeline,
@ -8468,7 +8654,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
self.server.textModeBanner)
self.server.textModeBanner,
accessKeys)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -8564,6 +8751,12 @@ class PubServer(BaseHTTPRequestHandler):
fullWidthTimelineButtonHeader = \
self.server.fullWidthTimelineButtonHeader
minimalNick = self._isMinimal(nickname)
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlInboxFeatures(self.server.cssCache,
self.server.defaultTimeline,
@ -8596,7 +8789,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
self.server.textModeBanner)
self.server.textModeBanner,
accessKeys)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -8656,6 +8850,12 @@ class PubServer(BaseHTTPRequestHandler):
pageNumber = int(pageNumber)
else:
pageNumber = 1
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlShares(self.server.cssCache,
self.server.defaultTimeline,
@ -8685,7 +8885,8 @@ class PubServer(BaseHTTPRequestHandler):
authorized, self.server.themeName,
self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
self.server.textModeBanner)
self.server.textModeBanner,
accessKeys)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -8759,6 +8960,12 @@ class PubServer(BaseHTTPRequestHandler):
fullWidthTimelineButtonHeader = \
self.server.fullWidthTimelineButtonHeader
minimalNick = self._isMinimal(nickname)
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlBookmarks(self.server.cssCache,
self.server.defaultTimeline,
@ -8791,7 +8998,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
self.server.textModeBanner)
self.server.textModeBanner,
accessKeys)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -8885,6 +9093,12 @@ class PubServer(BaseHTTPRequestHandler):
fullWidthTimelineButtonHeader = \
self.server.fullWidthTimelineButtonHeader
minimalNick = self._isMinimal(nickname)
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlEvents(self.server.cssCache,
self.server.defaultTimeline,
@ -8917,7 +9131,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
self.server.textModeBanner)
self.server.textModeBanner,
accessKeys)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -9003,6 +9218,12 @@ class PubServer(BaseHTTPRequestHandler):
fullWidthTimelineButtonHeader = \
self.server.fullWidthTimelineButtonHeader
minimalNick = self._isMinimal(nickname)
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlOutbox(self.server.cssCache,
self.server.defaultTimeline,
@ -9035,7 +9256,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
self.server.textModeBanner)
self.server.textModeBanner,
accessKeys)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -9112,6 +9334,12 @@ class PubServer(BaseHTTPRequestHandler):
fullWidthTimelineButtonHeader = \
self.server.fullWidthTimelineButtonHeader
moderationActionStr = ''
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlModeration(self.server.cssCache,
self.server.defaultTimeline,
@ -9143,7 +9371,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
self.server.textModeBanner)
self.server.textModeBanner,
accessKeys)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -9222,6 +9451,16 @@ class PubServer(BaseHTTPRequestHandler):
self._404()
self.server.GETbusy = False
return True
accessKeys = self.server.accessKeys
if '/users/' in path:
nickname = path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlProfile(self.server.rssIconAtTop,
self.server.cssCache,
@ -9246,6 +9485,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess,
self.server.textModeBanner,
self.server.debug,
accessKeys,
shares,
pageNumber, sharesPerPage)
msg = msg.encode('utf-8')
@ -9322,6 +9562,15 @@ class PubServer(BaseHTTPRequestHandler):
self.server.GETbusy = False
return True
accessKeys = self.server.accessKeys
if '/users/' in path:
nickname = path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlProfile(self.server.rssIconAtTop,
self.server.cssCache,
@ -9346,6 +9595,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess,
self.server.textModeBanner,
self.server.debug,
accessKeys,
following,
pageNumber,
followsPerPage).encode('utf-8')
@ -9420,6 +9670,16 @@ class PubServer(BaseHTTPRequestHandler):
self._404()
self.server.GETbusy = False
return True
accessKeys = self.server.accessKeys
if '/users/' in path:
nickname = path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlProfile(self.server.rssIconAtTop,
self.server.cssCache,
@ -9445,6 +9705,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess,
self.server.textModeBanner,
self.server.debug,
accessKeys,
followers,
pageNumber,
followsPerPage).encode('utf-8')
@ -9542,6 +9803,16 @@ class PubServer(BaseHTTPRequestHandler):
self._404()
self.server.GETbusy = False
return True
accessKeys = self.server.accessKeys
if '/users/' in path:
nickname = path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlProfile(self.server.rssIconAtTop,
self.server.cssCache,
@ -9567,6 +9838,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess,
self.server.textModeBanner,
self.server.debug,
accessKeys,
None, None).encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -10091,6 +10363,11 @@ class PubServer(BaseHTTPRequestHandler):
break
if isNewPostEndpoint:
nickname = getNicknameFromActor(path)
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
msg = htmlNewPost(self.server.cssCache,
mediaInstance,
translate,
@ -10105,7 +10382,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.defaultTimeline,
self.server.newswire,
self.server.themeName,
noDropDown).encode('utf-8')
noDropDown, accessKeys).encode('utf-8')
if not msg:
print('Error replying to ' + inReplyToUrl)
self._404()
@ -10158,6 +10435,14 @@ class PubServer(BaseHTTPRequestHandler):
"""Show the links from the left column
"""
if '/users/' in path and path.endswith('/editlinks'):
nickname = path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
msg = htmlEditLinks(self.server.cssCache,
translate,
baseDir,
@ -10165,7 +10450,7 @@ class PubServer(BaseHTTPRequestHandler):
port,
httpPrefix,
self.server.defaultTimeline,
theme).encode('utf-8')
theme, accessKeys).encode('utf-8')
if msg:
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -10184,6 +10469,14 @@ class PubServer(BaseHTTPRequestHandler):
"""Show the newswire from the right column
"""
if '/users/' in path and path.endswith('/editnewswire'):
nickname = path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
msg = htmlEditNewswire(self.server.cssCache,
translate,
baseDir,
@ -10191,7 +10484,8 @@ class PubServer(BaseHTTPRequestHandler):
port,
httpPrefix,
self.server.defaultTimeline,
self.server.themeName).encode('utf-8')
self.server.themeName,
accessKeys).encode('utf-8')
if msg:
msglen = len(msg)
self._set_headers('text/html', msglen,
@ -10984,6 +11278,34 @@ class PubServer(BaseHTTPRequestHandler):
'show about screen')
return
if htmlGET and usersInPath and authorized and \
self.path.endswith('/accesskeys'):
nickname = self.path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = \
self.server.keyShortcuts[nickname]
msg = \
htmlAccessKeys(self.server.cssCache,
self.server.baseDir,
nickname, self.server.domain,
self.server.translate,
accessKeys,
self.server.accessKeys,
self.server.defaultTimeline)
msg = msg.encode('utf-8')
msglen = len(msg)
self._login_headers('text/html', msglen, callingDomain)
self._write(msg)
self._benchmarkGETtimings(GETstartTime, GETtimings,
'following accounts done',
'show accesskeys screen')
return
self._benchmarkGETtimings(GETstartTime, GETtimings,
'following accounts done',
'show about screen done')
@ -11465,6 +11787,9 @@ class PubServer(BaseHTTPRequestHandler):
rssIconAtTop = self.server.rssIconAtTop
iconsAsButtons = self.server.iconsAsButtons
defaultTimeline = self.server.defaultTimeline
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
msg = htmlNewswireMobile(self.server.cssCache,
self.server.baseDir,
nickname,
@ -11480,7 +11805,8 @@ class PubServer(BaseHTTPRequestHandler):
rssIconAtTop,
iconsAsButtons,
defaultTimeline,
self.server.themeName).encode('utf-8')
self.server.themeName,
accessKeys).encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
cookie, callingDomain)
@ -11499,6 +11825,9 @@ class PubServer(BaseHTTPRequestHandler):
self._404()
self.server.GETbusy = False
return
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
timelinePath = \
'/users/' + nickname + '/' + self.server.defaultTimeline
iconsAsButtons = self.server.iconsAsButtons
@ -11513,7 +11842,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.rssIconAtTop,
iconsAsButtons,
defaultTimeline,
self.server.themeName).encode('utf-8')
self.server.themeName,
accessKeys).encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen, cookie, callingDomain)
self._write(msg)
@ -11577,6 +11907,15 @@ class PubServer(BaseHTTPRequestHandler):
'/search?' in self.path:
if '?' in self.path:
self.path = self.path.split('?')[0]
nickname = self.path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
# show the search screen
msg = htmlSearch(self.server.cssCache,
self.server.translate,
@ -11584,7 +11923,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.domain,
self.server.defaultTimeline,
self.server.themeName,
self.server.textModeBanner).encode('utf-8')
self.server.textModeBanner,
accessKeys).encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen, cookie, callingDomain)
self._write(msg)
@ -11619,6 +11959,14 @@ class PubServer(BaseHTTPRequestHandler):
# Show the calendar for a user
if htmlGET and usersInPath:
if '/calendar' in self.path:
nickname = self.path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
# show the calendar screen
msg = htmlCalendar(self.server.personCache,
self.server.cssCache,
@ -11626,7 +11974,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.baseDir, self.path,
self.server.httpPrefix,
self.server.domainFull,
self.server.textModeBanner).encode('utf-8')
self.server.textModeBanner,
accessKeys).encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen, cookie, callingDomain)
self._write(msg)
@ -13987,6 +14336,33 @@ class PubServer(BaseHTTPRequestHandler):
self.server.debug)
return
# Change the key shortcuts
if usersInPath and \
self.path.endswith('/changeAccessKeys'):
nickname = self.path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
if not self.server.keyShortcuts.get(nickname):
accessKeys = self.server.accessKeys
self.server.keyShortcuts[nickname] = accessKeys.copy()
accessKeys = self.server.keyShortcuts[nickname]
self._keyShortcuts(self.path,
callingDomain, cookie,
self.server.baseDir,
self.server.httpPrefix,
nickname,
self.server.domain,
self.server.domainFull,
self.server.port,
self.server.onionDomain,
self.server.i2pDomain,
self.server.debug,
accessKeys,
self.server.defaultTimeline)
return
self._benchmarkPOSTtimings(POSTstartTime, POSTtimings, 14)
# receive different types of post created by htmlNewPost
@ -14464,6 +14840,48 @@ def runDaemon(brochMode: bool,
# ASCII/ANSI text banner used in shell browsers, such as Lynx
httpd.textModeBanner = getTextModeBanner(baseDir)
# key shortcuts SHIFT + ALT + [key]
httpd.accessKeys = {
'Page up': ',',
'Page down': '.',
'submitButton': 'y',
'followButton': 'f',
'blockButton': 'b',
'infoButton': 'i',
'snoozeButton': 's',
'reportButton': '[',
'viewButton': 'v',
'enterPetname': 'p',
'enterNotes': 'n',
'menuTimeline': 't',
'menuEdit': 'e',
'menuProfile': 'p',
'menuInbox': 'i',
'menuSearch': '/',
'menuNewPost': 'n',
'menuCalendar': 'c',
'menuDM': 'd',
'menuReplies': 'r',
'menuOutbox': 's',
'menuBookmarks': 'q',
'menuShares': 'h',
'menuBlogs': 'b',
'menuNewswire': 'w',
'menuLinks': 'l',
'menuMedia': 'm',
'menuModeration': 'o',
'menuFollowing': 'f',
'menuFollowers': 'g',
'menuRoles': 'o',
'menuSkills': 'a',
'menuLogout': 'x',
'menuKeys': 'k',
'Public': 'p',
'Reminder': 'r'
}
httpd.keyShortcuts = {}
loadAccessKeysForAccounts(baseDir, httpd.keyShortcuts, httpd.accessKeys)
httpd.unitTest = unitTest
httpd.allowLocalNetworkAccess = allowLocalNetworkAccess
if unitTest:

View File

@ -197,6 +197,17 @@ figure {
height: auto;
}
.accesskeys {
border: 0;
width: 100%;
}
.accesskeys-left {
width: 90%;
}
.accesskeys-right {
width: 10%;
}
.cw {
font-style: var(--cw-style);
font-weight: var(--cw-weight);

View File

@ -11,6 +11,7 @@ import os
import datetime
import time
from linked_data_sig import verifyJsonSignature
from utils import dmAllowedFromDomain
from utils import isRecentPost
from utils import getConfigParam
from utils import hasUsersPath
@ -2466,28 +2467,32 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
if not isFollowingActor(baseDir,
nickname, domain,
sendH):
# send back a bounce DM
if postJsonObject.get('id') and \
postJsonObject.get('object'):
# don't send bounces back to
# replies to bounce messages
obj = postJsonObject['object']
if isinstance(obj, dict):
if not obj.get('inReplyTo'):
senderPostId = \
postJsonObject['id']
_bounceDM(senderPostId,
session, httpPrefix,
baseDir,
nickname, domain,
port, sendH,
federationList,
sendThreads, postLog,
cachedWebfingers,
personCache,
translate, debug,
lastBounceMessage)
return False
# DMs may always be allowed from some domains
if not dmAllowedFromDomain(baseDir,
nickname, domain,
sendingActorDomain):
# send back a bounce DM
if postJsonObject.get('id') and \
postJsonObject.get('object'):
# don't send bounces back to
# replies to bounce messages
obj = postJsonObject['object']
if isinstance(obj, dict):
if not obj.get('inReplyTo'):
senderPostId = \
postJsonObject['id']
_bounceDM(senderPostId,
session, httpPrefix,
baseDir,
nickname, domain,
port, sendH,
federationList,
sendThreads, postLog,
cachedWebfingers,
personCache,
translate, debug,
lastBounceMessage)
return False
# dm index will be updated
updateIndexList.append('dm')

View File

@ -551,7 +551,9 @@ def _setThemeImages(baseDir: str, name: str) -> None:
for acct in dirs:
if '@' not in acct:
continue
if 'inbox@' in acct:
if acct.startswith('inbox@'):
continue
elif acct.startswith('news@'):
continue
accountDir = \
os.path.join(baseDir + '/accounts', acct)

View File

@ -401,5 +401,41 @@
"counselor": "مستشار",
"Counselors": "المستشارين",
"shocked": "صدمت",
"Encrypted": "مشفر"
"Encrypted": "مشفر",
"Direct Message permitted instances": "الرسالة المباشرة المسموح بها",
"Direct messages are always allowed from these instances.": "الرسائل المباشرة مسموح بها دائما من هذه المثيلات.",
"Key Shortcuts": "الاختصارات الرئيسية",
"menuTimeline": "عرض الجدول الزمني",
"menuEdit": "يحرر",
"menuProfile": "عرض الملف الشخصي",
"menuInbox": "صندوق الوارد",
"menuSearch": "البحث / المتتالية",
"menuNewPost": "منشور جديد",
"menuCalendar": "تقويم",
"menuDM": "رسالة مباشرة",
"menuReplies": "الردود",
"menuOutbox": "مرسل",
"menuBookmarks": "إشارات مرجعية",
"menuShares": "البنود المشتركة",
"menuBlogs": "المدونات",
"menuNewswire": "Newswire",
"menuLinks": "روابط انترنت",
"menuModeration": "الاعتدال",
"menuFollowing": "التالية",
"menuFollowers": "متابعون",
"menuRoles": "أدوار",
"menuSkills": "مهارات",
"menuLogout": "تسجيل خروج",
"menuKeys": "الاختصارات الرئيسية",
"submitButton": "زر الإرسال",
"menuMedia": "وسائط",
"followButton": "زر متابعة / متابعة",
"blockButton": "زر كتلة",
"infoButton": "زر المعلومات",
"snoozeButton": "زر الغفوة",
"reportButton": "زر تقرير",
"viewButton": "عرض زر",
"enterPetname": "أدخل PETNAME",
"enterNotes": "أدخل الملاحظات",
"These access keys may be used": "قد يتم استخدام مفاتيح الوصول هذه، عادة مع مفتاح ALT + SHIFT + مفتاح ALT +"
}

View File

@ -401,5 +401,41 @@
"counselor": "conseller",
"Counselors": "Consellers",
"shocked": "sorprès",
"Encrypted": "Xifrat"
"Encrypted": "Xifrat",
"Direct Message permitted instances": "Instàncies permeses del missatge directe",
"Direct messages are always allowed from these instances.": "Els missatges directes sempre estan permesos d'aquests casos.",
"Key Shortcuts": "Dreceres clau",
"menuTimeline": "Vista de la línia de temps",
"menuEdit": "Preprarar una edició",
"menuProfile": "Vista de perfil",
"menuInbox": "Capa inferior",
"menuSearch": "Cerca / Segueix",
"menuNewPost": "Nou missatge",
"menuCalendar": "Calendari",
"menuDM": "Missatges directes",
"menuReplies": "Resum",
"menuOutbox": "Present",
"menuBookmarks": "Adreces d'interès",
"menuShares": "Articles compartits",
"menuBlogs": "Blocs",
"menuNewswire": "Newswire",
"menuLinks": "Enllaços web",
"menuModeration": "Moderació",
"menuFollowing": "Proper",
"menuFollowers": "Seguidors",
"menuRoles": "Rols",
"menuSkills": "Habilitats",
"menuLogout": "Tancar sessió",
"menuKeys": "Dreceres clau",
"submitButton": "Envia el botó",
"menuMedia": "Medis de comunicació",
"followButton": "Seguiu / no seguit",
"blockButton": "Botó de bloc",
"infoButton": "Botó d'informació",
"snoozeButton": "Botó de snooze",
"reportButton": "Botó d'informe",
"viewButton": "Botó Veure",
"enterPetname": "Introduïu PETNAME",
"enterNotes": "Introduïu notes",
"These access keys may be used": "Es poden utilitzar aquestes tecles d'accés, típicament amb Alt + Maj + tecla o Alt + clau"
}

View File

@ -401,5 +401,41 @@
"counselor": "cynghorydd",
"Counselors": "Cynghorwyr",
"shocked": "sioc",
"Encrypted": "Amgryptio"
"Encrypted": "Amgryptio",
"Direct Message permitted instances": "Achosion a ganiateir negeseuon uniongyrchol",
"Direct messages are always allowed from these instances.": "Caniateir negeseuon uniongyrchol bob amser o'r achosion hyn.",
"Key Shortcuts": "Llwybrau byr allweddol",
"menuTimeline": "View View",
"menuEdit": "Golygaf",
"menuProfile": "Gweld Proffil",
"menuInbox": "Mewnflwch",
"menuSearch": "Chwilio / Dilyn",
"menuNewPost": "Swydd newydd",
"menuCalendar": "Galendr",
"menuDM": "Negeseuon Uniongyrchol",
"menuReplies": "Atebion",
"menuOutbox": "Hanfon",
"menuBookmarks": "Nodau tudalen",
"menuShares": "Eitemau a Rennir",
"menuBlogs": "Blogiau",
"menuNewswire": "Newswire",
"menuLinks": "Cysylltiadau",
"menuModeration": "Safoniad",
"menuFollowing": "Ddilynol",
"menuFollowers": "Ddilynwyr",
"menuRoles": "Rolau",
"menuSkills": "Medrau",
"menuLogout": "Allgofnodi",
"menuKeys": "Llwybrau byr allweddol",
"submitButton": "Cyflwyno botwm",
"menuMedia": "Chyfryngau",
"followButton": "Dilynwch / Peidiwch â Dilynwch y botwm",
"blockButton": "Botwm bloc",
"infoButton": "Botwm info",
"snoozeButton": "Botwm Snooze",
"reportButton": "Botwm adroddiadau",
"viewButton": "Gweld y botwm",
"enterPetname": "Rhowch enw PETName",
"enterNotes": "Rhowch nodiadau",
"These access keys may be used": "Gellir defnyddio'r allweddi mynediad hyn, fel arfer gyda ALT + Shift + Allwedd Allwedd neu ALT +"
}

View File

@ -401,5 +401,41 @@
"counselor": "Beraterin",
"Counselors": "Berater",
"shocked": "schockiert",
"Encrypted": "Verschlüsselt"
"Encrypted": "Verschlüsselt",
"Direct Message permitted instances": "Direktnachricht erlaubte Instanzen",
"Direct messages are always allowed from these instances.": "Direkte Nachrichten sind in diesen Fällen immer zulässig.",
"Key Shortcuts": "Schlüsselverknüpfungen",
"menuTimeline": "Timeline-Ansicht",
"menuEdit": "Bearbeiten",
"menuProfile": "Profilansicht",
"menuInbox": "Inbox",
"menuSearch": "Suche / Folgen",
"menuNewPost": "Neuer Beitrag",
"menuCalendar": "Kalender",
"menuDM": "Direkte Nachrichten",
"menuReplies": "Antworten",
"menuOutbox": "Geschickt",
"menuBookmarks": "Lesezeichen",
"menuShares": "Gemeinsame Artikel",
"menuBlogs": "Blogs",
"menuNewswire": "Newswire",
"menuLinks": "Web-Links",
"menuModeration": "Mäßigung",
"menuFollowing": "Folgen",
"menuFollowers": "Anhänger",
"menuRoles": "Rollen",
"menuSkills": "Kompetenzen",
"menuLogout": "Ausloggen",
"menuKeys": "Schlüsselverknüpfungen",
"submitButton": "Button einreichen",
"menuMedia": "Medien",
"followButton": "Folgen / folgen Sie nicht der Taste",
"blockButton": "Blockknopf",
"infoButton": "Info-Taste",
"snoozeButton": "Schlummertaste",
"reportButton": "Berichtsknopf",
"viewButton": "Schaltfläche anzeigen",
"enterPetname": "Petname eingeben",
"enterNotes": "Notizen eingeben",
"These access keys may be used": "Diese Zugriffstasten können verwendet werden, typischerweise mit ALT + SHIFT + -Taste oder ALT + -Taste"
}

View File

@ -401,5 +401,41 @@
"counselor": "counselor",
"Counselors": "Counselors",
"shocked": "shocked",
"Encrypted": "Encrypted"
"Encrypted": "Encrypted",
"Direct Message permitted instances": "Direct Message permitted instances",
"Direct messages are always allowed from these instances.": "Direct messages are always allowed from these instances.",
"Key Shortcuts": "Key Shortcuts",
"menuTimeline": "Timeline view",
"menuEdit": "Edit",
"menuProfile": "Profile view",
"menuInbox": "Inbox",
"menuSearch": "Search/follow",
"menuNewPost": "New post",
"menuCalendar": "Calendar",
"menuDM": "Direct Messages",
"menuReplies": "Replies",
"menuOutbox": "Sent",
"menuBookmarks": "Bookmarks",
"menuShares": "Shared items",
"menuBlogs": "Blogs",
"menuNewswire": "Newswire",
"menuLinks": "Links",
"menuModeration": "Moderation",
"menuFollowing": "Following",
"menuFollowers": "Followers",
"menuRoles": "Roles",
"menuSkills": "Skills",
"menuLogout": "Logout",
"menuKeys": "Key Shortcuts",
"submitButton": "Submit button",
"menuMedia": "Media",
"followButton": "Follow/unfollow button",
"blockButton": "Block button",
"infoButton": "Info button",
"snoozeButton": "Snooze button",
"reportButton": "Report button",
"viewButton": "View button",
"enterPetname": "Enter petname",
"enterNotes": "Enter notes",
"These access keys may be used": "These access keys may be used, typically with ALT + SHIFT + key or ALT + key"
}

View File

@ -401,5 +401,41 @@
"counselor": "Consejera",
"Counselors": "Consejeras",
"shocked": "conmocionada",
"Encrypted": "Cifrada"
"Encrypted": "Cifrada",
"Direct Message permitted instances": "Mensaje directo permitido instancias",
"Direct messages are always allowed from these instances.": "Los mensajes directos siempre están permitidos de estas instancias.",
"Key Shortcuts": "Atajos clave",
"menuTimeline": "Vista de la línea de tiempo",
"menuEdit": "Editar",
"menuProfile": "Vista del perfil",
"menuInbox": "Bandeja de entrada",
"menuSearch": "Búsqueda / Seguir",
"menuNewPost": "Nueva publicación",
"menuCalendar": "Calendario",
"menuDM": "Mensajes directos",
"menuReplies": "Respuestas",
"menuOutbox": "Enviada",
"menuBookmarks": "Marcadores",
"menuShares": "Artículos compartidos",
"menuBlogs": "Blogs",
"menuNewswire": "Newswire",
"menuLinks": "Enlaces web",
"menuModeration": "Moderación",
"menuFollowing": "Siguiente",
"menuFollowers": "De seguidores",
"menuRoles": "Roles",
"menuSkills": "Habilidades",
"menuLogout": "Cerrar sesión",
"menuKeys": "Atajos clave",
"submitButton": "Botón de enviar",
"menuMedia": "Medios de comunicación",
"followButton": "Botón de seguimiento / dejo",
"blockButton": "Botón de bloqueo",
"infoButton": "Botón de información",
"snoozeButton": "El botón de dormitar",
"reportButton": "Botón de informe",
"viewButton": "Botón de vista",
"enterPetname": "Entrar en nombre de pettname",
"enterNotes": "Ingresar notas",
"These access keys may be used": "Se pueden usar estas teclas de acceso, típicamente con teclas ALT + MAYÚS + teclas o ALT +"
}

View File

@ -401,5 +401,41 @@
"counselor": "Conseillère",
"Counselors": "Conseillères",
"shocked": "sous le choc",
"Encrypted": "Crypté"
"Encrypted": "Crypté",
"Direct Message permitted instances": "Message direct des instances autorisées",
"Direct messages are always allowed from these instances.": "Les messages directs sont toujours autorisés dans ces instances.",
"Key Shortcuts": "Raccourcis clés",
"menuTimeline": "Vue de la chronologie",
"menuEdit": "Éditer",
"menuProfile": "Voir",
"menuInbox": "Boîte de réception",
"menuSearch": "Rechercher / suivre",
"menuNewPost": "Nouveau poste",
"menuCalendar": "Calendrier",
"menuDM": "Messages directs",
"menuReplies": "réponses",
"menuOutbox": "Envoyée",
"menuBookmarks": "Favoris",
"menuShares": "Articles partagés",
"menuBlogs": "Blogs",
"menuNewswire": "Newswire",
"menuLinks": "Liens web",
"menuModeration": "Modération",
"menuFollowing": "Suivante",
"menuFollowers": "Suiveuses",
"menuRoles": "Les rôles",
"menuSkills": "Compétences",
"menuLogout": "Se déconnecter",
"menuKeys": "Raccourcis clés",
"submitButton": "Bouton de soumission",
"menuMedia": "Médias",
"followButton": "Suivez / Bouton Suivi",
"blockButton": "Bouton de bloc",
"infoButton": "Bouton info",
"snoozeButton": "Le bouton de la sieste",
"reportButton": "Bouton de rapport",
"viewButton": "Bouton d'affichage",
"enterPetname": "Entrez PETNAME",
"enterNotes": "Faire entrer des notes",
"These access keys may be used": "Ces touches d'accès peuvent être utilisées typiquement avec une touche Alt + Maj + ou Alt +"
}

View File

@ -401,5 +401,41 @@
"counselor": "Comhairleoir",
"Counselors": "Comhairleoirí",
"shocked": "ionadh",
"Encrypted": "Criptithe"
"Encrypted": "Criptithe",
"Direct Message permitted instances": "Ceadaíonn teachtaireacht dhíreach cásanna",
"Direct messages are always allowed from these instances.": "Ceadaítear teachtaireachtaí díreacha i gcónaí ó na cásanna seo.",
"Key Shortcuts": "Príomh-aicearraí",
"menuTimeline": "Amlíne View",
"menuEdit": "Eagarthóireacht a dhéanamh ar",
"menuProfile": "Dearcadh próifíle",
"menuInbox": "Bosca isteach",
"menuSearch": "Cuardaigh / Lean",
"menuNewPost": "Post nua",
"menuCalendar": "Caileandar",
"menuDM": "Teachtaireachtaí díreacha",
"menuReplies": "Freagraí",
"menuOutbox": "Seoltar",
"menuBookmarks": "Leabharmharcanna",
"menuShares": "Míreanna Comhroinnte",
"menuBlogs": "Blaganna",
"menuNewswire": "Newswire",
"menuLinks": "Naisc Ghréasáin",
"menuModeration": "Modhnóireacht a dhéanamh ar",
"menuFollowing": "Lucht tacaíochta",
"menuFollowers": "Leanúna",
"menuRoles": "Róil",
"menuSkills": "Scileanna",
"menuLogout": "Logáil Amach",
"menuKeys": "Príomh-aicearraí",
"submitButton": "Cuir an cnaipe isteach",
"menuMedia": "Na meáin",
"followButton": "Lean / Cnaipe Unurollow",
"blockButton": "Cnaipe bloc",
"infoButton": "Cnaipe Info",
"snoozeButton": "Cnaipe snooze",
"reportButton": "Cnaipe Tuairisce",
"viewButton": "Féach an cnaipe",
"enterPetname": "Cuir isteach PetName",
"enterNotes": "Cuir nótaí isteach",
"These access keys may be used": "Is féidir na heochracha rochtana seo a úsáid, de ghnáth le Alt + Shift + Eochair nó Alt + Eochair"
}

View File

@ -401,5 +401,41 @@
"counselor": "काउंसलर",
"Counselors": "सलाहकार",
"shocked": "हैरान",
"Encrypted": "को गोपित"
"Encrypted": "को गोपित",
"Direct Message permitted instances": "प्रत्यक्ष संदेश अनुमत उदाहरण",
"Direct messages are always allowed from these instances.": "इन उदाहरणों से प्रत्यक्ष संदेश हमेशा अनुमति देते हैं।",
"Key Shortcuts": "कुंजी शॉर्टकट",
"menuTimeline": "समयरेखा दृश्य",
"menuEdit": "संपादित करें",
"menuProfile": "प्रोफ़ाइल दृश्य",
"menuInbox": "इनबॉक्स",
"menuSearch": "खोज / अनुसरण करें",
"menuNewPost": "नई पोस्ट",
"menuCalendar": "पंचांग",
"menuDM": "सीधे संदेश",
"menuReplies": "जवाब",
"menuOutbox": "भेज दिया",
"menuBookmarks": "बुकमार्क",
"menuShares": "साझा आइटम",
"menuBlogs": "ब्लॉग",
"menuNewswire": "न्यूज़वायर",
"menuLinks": "वेब लिंक",
"menuModeration": "संयम",
"menuFollowing": "निम्नलिखित",
"menuFollowers": "समर्थक",
"menuRoles": "भूमिकाएँ",
"menuSkills": "कौशल",
"menuLogout": "लॉग आउट",
"menuKeys": "कुंजी शॉर्टकट",
"submitButton": "जमा करने वाला बटन",
"menuMedia": "मीडिया",
"followButton": "फॉलो / अनफ़ॉलो बटन",
"blockButton": "ब्लॉक बटन",
"infoButton": "जानकारी बटन",
"snoozeButton": "अलार्म को थोड़ी देर बंद करने वाला बटन",
"reportButton": "रिपोर्ट बटन",
"viewButton": "देखें बटन",
"enterPetname": "PETNAME दर्ज करें",
"enterNotes": "नोट्स दर्ज करें",
"These access keys may be used": "इन एक्सेस कुंजियों का उपयोग किया जा सकता है, आमतौर पर Alt + Shift + कुंजी या Alt + कुंजी के साथ"
}

View File

@ -401,5 +401,41 @@
"counselor": "Consulente",
"Counselors": "Consiglieri",
"shocked": "scioccata",
"Encrypted": "Crittografato"
"Encrypted": "Crittografato",
"Direct Message permitted instances": "Messaggio diretto istanze consentite",
"Direct messages are always allowed from these instances.": "I messaggi diretti sono sempre ammessi da questi casi.",
"Key Shortcuts": "Scorciatoie chiave",
"menuTimeline": "Vista della cronologia",
"menuEdit": "Modificare",
"menuProfile": "Visualizzazione del profilo",
"menuInbox": "Posta in arrivo",
"menuSearch": "Cerca / Segui",
"menuNewPost": "Nuovo post.",
"menuCalendar": "Calendario",
"menuDM": "Messaggi diretti",
"menuReplies": "Risposte",
"menuOutbox": "Inviata",
"menuBookmarks": "Segnalibri",
"menuShares": "Articoli condivisi",
"menuBlogs": "Blog",
"menuNewswire": "Newswire",
"menuLinks": "Link internet",
"menuModeration": "Moderazione",
"menuFollowing": "A seguire",
"menuFollowers": "Seguaci",
"menuRoles": "Ruoli",
"menuSkills": "Competenze",
"menuLogout": "Disconnettersi",
"menuKeys": "Scorciatoie chiave",
"submitButton": "Invia il pulsante",
"menuMedia": "Media",
"followButton": "Segui il pulsante Segui / Unfollow",
"blockButton": "Blocco pulsante",
"infoButton": "Pulsante info",
"snoozeButton": "Pulsante snooze.",
"reportButton": "Pulsante report.",
"viewButton": "Visualizza il pulsante",
"enterPetname": "Inserisci PetName",
"enterNotes": "Inserisci le note",
"These access keys may be used": "Questi tasti di accesso possono essere utilizzati, in genere con tasto ALT + MAIUSC + o ALT + Key"
}

View File

@ -401,5 +401,41 @@
"counselor": "カウンセラー",
"Counselors": "カウンセラー",
"shocked": "ショックを受けた",
"Encrypted": "暗号化"
"Encrypted": "暗号化",
"Direct Message permitted instances": "直接メッセージ許可インスタンス",
"Direct messages are always allowed from these instances.": "直接メッセージは常にこれらのインスタンスから許可されています。",
"Key Shortcuts": "キーショートカット",
"menuTimeline": "タイムラインビュー",
"menuEdit": "編集する",
"menuProfile": "プロファイルビュー",
"menuInbox": "受信箱",
"menuSearch": "検索/フォロー",
"menuNewPost": "新しい投稿",
"menuCalendar": "カレンダー",
"menuDM": "ダイレクトメッセージ",
"menuReplies": "返信",
"menuOutbox": "送り返した",
"menuBookmarks": "ブックマーク",
"menuShares": "共有アイテム",
"menuBlogs": "ブログ",
"menuNewswire": "ニューワイヤー",
"menuLinks": "Webリンク",
"menuModeration": "節度",
"menuFollowing": "以下",
"menuFollowers": "フォロワー",
"menuRoles": "役割",
"menuSkills": "スキル",
"menuLogout": "ログアウト",
"menuKeys": "キーショートカット",
"submitButton": "送信ボタン",
"menuMedia": "メディア",
"followButton": "フォロー/フォローダウンボタン",
"blockButton": "ブロックボタン",
"infoButton": "情報ボタン",
"snoozeButton": "スヌーズボタン",
"reportButton": "レポートボタン",
"viewButton": "ボタンを見る",
"enterPetname": "PetNameを入力してください",
"enterNotes": "ノートを入力してください",
"These access keys may be used": "これらのアクセスキーは、通常はAlt + Shift +キーまたはAlt +キーを使用して使用できます。"
}

View File

@ -401,5 +401,41 @@
"counselor": "Pêşnîyarvan",
"Counselors": "Selêwirmendan",
"shocked": "şok kirin",
"Encrypted": "Encîfre kirin"
"Encrypted": "Encîfre kirin",
"Direct Message permitted instances": "Peyama rasterast destûrê",
"Direct messages are always allowed from these instances.": "Peyamên rasterast her gav ji van deman têne destûr kirin.",
"Key Shortcuts": "Kurteyên Key",
"menuTimeline": "Dîtina Timeline",
"menuEdit": "Weşandin",
"menuProfile": "View Profîl",
"menuInbox": "Inbott",
"menuSearch": "Lêgerîn / bişopîne",
"menuNewPost": "Peyama nû",
"menuCalendar": "Salname",
"menuDM": "Peyamên rasterast",
"menuReplies": "Bersiv",
"menuOutbox": "Şandin",
"menuBookmarks": "Emîrê",
"menuShares": "Tiştên parvekirî",
"menuBlogs": "Blogs",
"menuNewswire": "Newswire",
"menuLinks": "Girêdanên malperê",
"menuModeration": "Nermbûn",
"menuFollowing": "Pêketînî",
"menuFollowers": "Followers",
"menuRoles": "Roles",
"menuSkills": "Şarezayên",
"menuLogout": "Derkeve",
"menuKeys": "Kurteyên Key",
"submitButton": "Bişkojka bişînin",
"menuMedia": "Medya",
"followButton": "Bişkojka bişopînin / Nexşe",
"blockButton": "Bişkojka Block",
"infoButton": "Bişkoja INFO",
"snoozeButton": "Bişkojka Snooze",
"reportButton": "Bişkoja Report",
"viewButton": "Bişkoja View",
"enterPetname": "Porê binivîse",
"enterNotes": "Nîşan binivîse",
"These access keys may be used": "Dibe ku ev keysên gihîştinê bikar bînin, bi gelemperî bi alt + shift + key an alt + key"
}

View File

@ -397,5 +397,41 @@
"counselor": "Counselors",
"Counselors": "Counselors",
"shocked": "shocked",
"Encrypted": "Encrypted"
"Encrypted": "Encrypted",
"Direct Message permitted instances": "Direct Message permitted instances",
"Direct messages are always allowed from these instances.": "Direct messages are always allowed from these instances.",
"Key Shortcuts": "Key Shortcuts",
"menuTimeline": "Timeline view",
"menuEdit": "Edit",
"menuProfile": "Profile view",
"menuInbox": "Inbox",
"menuSearch": "Search/follow",
"menuNewPost": "New post",
"menuCalendar": "Calendar",
"menuDM": "Direct Messages",
"menuReplies": "Replies",
"menuOutbox": "Sent",
"menuBookmarks": "Bookmarks",
"menuShares": "Shared items",
"menuBlogs": "Blogs",
"menuNewswire": "Newswire",
"menuLinks": "Links",
"menuModeration": "Moderation",
"menuFollowing": "Following",
"menuFollowers": "Followers",
"menuRoles": "Roles",
"menuSkills": "Skills",
"menuLogout": "Logout",
"menuKeys": "Key Shortcuts",
"submitButton": "Submit button",
"menuMedia": "Media",
"followButton": "Follow/unfollow button",
"blockButton": "Block button",
"infoButton": "Info button",
"snoozeButton": "Snooze button",
"reportButton": "Report button",
"viewButton": "View button",
"enterPetname": "Enter petname",
"enterNotes": "Enter notes",
"These access keys may be used": "These access keys may be used, typically with ALT + SHIFT + key or ALT + key"
}

View File

@ -401,5 +401,41 @@
"counselor": "Conselheira",
"Counselors": "Conselheiras",
"shocked": "chocada",
"Encrypted": "Criptografada"
"Encrypted": "Criptografada",
"Direct Message permitted instances": "Mensagens diretas permitidas instâncias",
"Direct messages are always allowed from these instances.": "Mensagens diretas são sempre permitidas a partir dessas instâncias.",
"Key Shortcuts": "Atalhos-chave",
"menuTimeline": "Vista da linha do tempo",
"menuEdit": "Editar",
"menuProfile": "Vista de perfil",
"menuInbox": "Caixa de entrada",
"menuSearch": "Pesquisa / Siga",
"menuNewPost": "Nova postagem",
"menuCalendar": "Calendário",
"menuDM": "Mensagens diretas",
"menuReplies": "Respostas",
"menuOutbox": "Enviei",
"menuBookmarks": "Favoritas",
"menuShares": "Itens compartilhados",
"menuBlogs": "Blogs",
"menuNewswire": "Newswire",
"menuLinks": "Links da Web",
"menuModeration": "Moderação",
"menuFollowing": "Seguindo",
"menuFollowers": "Seguidoras",
"menuRoles": "Papéis",
"menuSkills": "Habilidades",
"menuLogout": "Sair",
"menuKeys": "Atalhos-chave",
"submitButton": "Botão de envio",
"menuMedia": "meios de comunicação",
"followButton": "Siga / Deixar botão",
"blockButton": "Botão de bloco",
"infoButton": "Botão de informação",
"snoozeButton": "Botão Snooze",
"reportButton": "Botão de relatório",
"viewButton": "Botão de visualização",
"enterPetname": "Digite Petname",
"enterNotes": "Digite notas",
"These access keys may be used": "Essas teclas de acesso podem ser usadas, normalmente com tecla Alt + Shift + Key ou Alt +"
}

View File

@ -401,5 +401,41 @@
"counselor": "Советник",
"Counselors": "Советники",
"shocked": "потрясенный",
"Encrypted": "Зашифрованный"
"Encrypted": "Зашифрованный",
"Direct Message permitted instances": "Прямое сообщение разрешено экземпляры",
"Direct messages are always allowed from these instances.": "Прямые сообщения всегда допускаются из этих экземпляров.",
"Key Shortcuts": "Клавичные ярлыки",
"menuTimeline": "Сроки зрения",
"menuEdit": "Редактировать",
"menuProfile": "вид профиля",
"menuInbox": "Входящие",
"menuSearch": "Поиск / следующее",
"menuNewPost": "Новый пост",
"menuCalendar": "Календарь",
"menuDM": "Прямые сообщения",
"menuReplies": "Отвечает",
"menuOutbox": "Отправил",
"menuBookmarks": "Закладки",
"menuShares": "Общие предметы",
"menuBlogs": "Блоги",
"menuNewswire": "Новобранец",
"menuLinks": "веб ссылки",
"menuModeration": "На модерации",
"menuFollowing": "Следующий",
"menuFollowers": "Подписчики",
"menuRoles": "Роли",
"menuSkills": "Навыки и умения",
"menuLogout": "Выйти",
"menuKeys": "Клавичные ярлыки",
"submitButton": "Отправить кнопку",
"menuMedia": "СМИ",
"followButton": "Следуйте / отписаться кнопка",
"blockButton": "Кнопка блокировки",
"infoButton": "Информация Кнопка",
"snoozeButton": "Кнопка сножения",
"reportButton": "Кнопка отчета",
"viewButton": "Кнопка просмотра",
"enterPetname": "Введите petname",
"enterNotes": "Введите ноты",
"These access keys may be used": "Эти ключевые ключи доступа могут быть использованы, обычно с ALT + Shift + Key или Alt + Key"
}

View File

@ -401,5 +401,41 @@
"counselor": "顾问",
"Counselors": "辅导员",
"shocked": "震惊的",
"Encrypted": "加密的"
"Encrypted": "加密的",
"Direct Message permitted instances": "直接留言允许实例",
"Direct messages are always allowed from these instances.": "这些实例始终允许直接消息。",
"Key Shortcuts": "关键捷径",
"menuTimeline": "时间表视图",
"menuEdit": "编辑",
"menuProfile": "个人资料视图",
"menuInbox": "收件箱",
"menuSearch": "搜索/关注",
"menuNewPost": "最新帖子",
"menuCalendar": "日历",
"menuDM": "直接留言",
"menuReplies": "答案",
"menuOutbox": "发送",
"menuBookmarks": "书签",
"menuShares": "共享项目",
"menuBlogs": "博客",
"menuNewswire": "新闻界.",
"menuLinks": "网页链接",
"menuModeration": "适度",
"menuFollowing": "下列的",
"menuFollowers": "追随者",
"menuRoles": "角色",
"menuSkills": "技能",
"menuLogout": "登出",
"menuKeys": "关键捷径",
"submitButton": "提交按钮",
"menuMedia": "媒体",
"followButton": "关注/取消关注按钮",
"blockButton": "块按钮",
"infoButton": "信息按钮",
"snoozeButton": "贪睡按钮",
"reportButton": "报告按钮",
"viewButton": "查看按钮",
"enterPetname": "进入宠物名",
"enterNotes": "输入笔记",
"These access keys may be used": "可以使用这些访问密钥通常使用Alt + Shift +键或ALT +键"
}

View File

@ -388,7 +388,9 @@ def getFollowersOfPerson(baseDir: str,
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
for account in dirs:
filename = os.path.join(subdir, account) + '/' + followFile
if account == handle or account.startswith('inbox@'):
if account == handle or \
account.startswith('inbox@') or \
account.startswith('news@'):
continue
if not os.path.isfile(filename):
continue
@ -1057,7 +1059,7 @@ def clearFromPostCaches(baseDir: str, recentPostsCache: {},
for acct in dirs:
if '@' not in acct:
continue
if 'inbox@' in acct:
if acct.startswith('inbox@'):
continue
cacheDir = os.path.join(baseDir + '/accounts', acct)
postFilename = cacheDir + filename
@ -1405,8 +1407,11 @@ def noOfAccounts(baseDir: str) -> bool:
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
for account in dirs:
if '@' in account:
if not account.startswith('inbox@'):
accountCtr += 1
if account.startswith('inbox@'):
continue
elif account.startswith('news@'):
continue
accountCtr += 1
break
return accountCtr
@ -1420,7 +1425,8 @@ def noOfActiveAccountsMonthly(baseDir: str, months: int) -> bool:
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
for account in dirs:
if '@' in account:
if not account.startswith('inbox@'):
if not account.startswith('inbox@') and \
not account.startswith('news@'):
lastUsedFilename = \
baseDir + '/accounts/' + account + '/.lastUsed'
if os.path.isfile(lastUsedFilename):
@ -2197,3 +2203,22 @@ def loadTranslationsFromFile(baseDir: str, language: str) -> ({}, str):
translationsFile = baseDir + '/translations/' + \
systemLanguage + '.json'
return loadJson(translationsFile), systemLanguage
def dmAllowedFromDomain(baseDir: str,
nickname: str, domain: str,
sendingActorDomain: str) -> bool:
"""When a DM is received and the .followDMs flag file exists
Then optionally some domains can be specified as allowed,
regardless of individual follows.
i.e. Mostly you only want DMs from followers, but there are
a few particular instances that you trust
"""
dmAllowedInstancesFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/dmAllowedInstances.txt'
if not os.path.isfile(dmAllowedInstancesFilename):
return False
if sendingActorDomain + '\n' in open(dmAllowedInstancesFilename).read():
return True
return False

View File

@ -0,0 +1,115 @@
__filename__ = "webapp_accesskeys.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
import os
from utils import loadJson
from utils import getConfigParam
from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter
def loadAccessKeysForAccounts(baseDir: str, keyShortcuts: {},
accessKeysTemplate: {}) -> None:
"""Loads key shortcuts for each account
"""
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
for acct in dirs:
if '@' not in acct:
continue
if 'inbox@' in acct or 'news@' in acct:
continue
accountDir = os.path.join(baseDir + '/accounts', acct)
accessKeysFilename = accountDir + '/accessKeys.json'
if not os.path.isfile(accessKeysFilename):
continue
nickname = acct.split('@')[0]
accessKeys = loadJson(accessKeysFilename)
if accessKeys:
keyShortcuts[nickname] = accessKeysTemplate.copy()
for variableName, key in accessKeysTemplate.items():
if accessKeys.get(variableName):
keyShortcuts[nickname][variableName] = \
accessKeys[variableName]
break
def htmlAccessKeys(cssCache: {}, baseDir: str,
nickname: str, domain: str,
translate: {}, accessKeys: {},
defaultAccessKeys: {},
defaultTimeline: str) -> str:
"""Show and edit key shortcuts
"""
accessKeysFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + '/accessKeys.json'
if os.path.isfile(accessKeysFilename):
accessKeysFromFile = loadJson(accessKeysFilename)
if accessKeysFromFile:
accessKeys = accessKeysFromFile
accessKeysForm = ''
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
accessKeysForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
accessKeysForm += '<div class="container">\n'
accessKeysForm += \
' <h1>' + translate['Key Shortcuts'] + '</h1>\n'
accessKeysForm += \
'<p>' + translate['These access keys may be used'] + \
'<label class="labels"></label></p>'
accessKeysForm += ' <form method="POST" action="' + \
'/users/' + nickname + '/changeAccessKeys">\n'
timelineKey = accessKeys['menuTimeline']
submitKey = accessKeys['submitButton']
accessKeysForm += \
' <center>\n' + \
' <button type="submit" class="button" ' + \
'name="submitAccessKeysCancel" accesskey="' + timelineKey + '">' + \
translate['Go Back'] + '</button>\n' + \
' <button type="submit" class="button" ' + \
'name="submitAccessKeys" accesskey="' + submitKey + '">' + \
translate['Submit'] + '</button>\n </center>\n'
accessKeysForm += ' <table class="accesskeys">\n'
accessKeysForm += ' <colgroup>\n'
accessKeysForm += ' <col span="1" class="accesskeys-left">\n'
accessKeysForm += ' <col span="1" class="accesskeys-center">\n'
accessKeysForm += ' </colgroup>\n'
accessKeysForm += ' <tbody>\n'
for variableName, key in defaultAccessKeys.items():
if not translate.get(variableName):
continue
keyStr = '<tr>'
keyStr += \
'<td><label class="labels">' + \
translate[variableName] + '</label></td>'
if accessKeys.get(variableName):
key = accessKeys[variableName]
if len(key) > 1:
key = key[0]
keyStr += \
'<td><input type="text" ' + \
'name="' + variableName.replace(' ', '_') + '" ' + \
'value="' + key + '">'
keyStr += '</td></tr>\n'
accessKeysForm += keyStr
accessKeysForm += ' </tbody>\n'
accessKeysForm += ' </table>\n'
accessKeysForm += ' </form>\n'
accessKeysForm += '</div>\n'
accessKeysForm += htmlFooter()
return accessKeysForm

View File

@ -243,7 +243,7 @@ def _htmlCalendarDay(personCache: {}, cssCache: {}, translate: {},
def htmlCalendar(personCache: {}, cssCache: {}, translate: {},
baseDir: str, path: str,
httpPrefix: str, domainFull: str,
textModeBanner: str) -> str:
textModeBanner: str, accessKeys: {}) -> str:
"""Show the calendar for a person
"""
domain = domainFull
@ -348,17 +348,20 @@ def htmlCalendar(personCache: {}, cssCache: {}, translate: {},
calendarStr += '<caption class="calendar__banner--month">\n'
calendarStr += \
' <a href="' + calActor + '/calendar?year=' + str(prevYear) + \
'?month=' + str(prevMonthNumber) + '">'
'?month=' + str(prevMonthNumber) + '" ' + \
'accesskey="' + accessKeys['Page up'] + '">'
calendarStr += \
' <img loading="lazy" alt="' + translate['Previous month'] + \
'" title="' + translate['Previous month'] + '" src="/icons' + \
'/prev.png" class="buttonprev"/></a>\n'
calendarStr += ' <a href="' + calActor + '/inbox" title="'
calendarStr += translate['Switch to timeline view'] + '">'
calendarStr += translate['Switch to timeline view'] + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">'
calendarStr += ' <h1>' + monthName + '</h1></a>\n'
calendarStr += \
' <a href="' + calActor + '/calendar?year=' + str(nextYear) + \
'?month=' + str(nextMonthNumber) + '">'
'?month=' + str(nextMonthNumber) + '" ' + \
'accesskey="' + accessKeys['Page down'] + '">'
calendarStr += \
' <img loading="lazy" alt="' + translate['Next month'] + \
'" title="' + translate['Next month'] + '" src="/icons' + \
@ -456,7 +459,11 @@ def htmlCalendar(personCache: {}, cssCache: {}, translate: {},
htmlHideFromScreenReader('') + ' ' + translate['Previous month']
navLinks[prevMonthStr] = calActor + '/calendar?year=' + str(prevYear) + \
'?month=' + str(prevMonthNumber)
# TODO
navAccessKeys = {
}
screenReaderCal = \
htmlKeyboardNavigation(textModeBanner, navLinks, monthName)
htmlKeyboardNavigation(textModeBanner, navLinks, navAccessKeys,
monthName)
return headerStr + screenReaderCal + calendarStr + htmlFooter()

View File

@ -68,7 +68,8 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
editor: bool,
showBackButton: bool, timelinePath: str,
rssIconAtTop: bool, showHeaderImage: bool,
frontPage: bool, theme: str) -> str:
frontPage: bool, theme: str,
accessKeys: {}) -> str:
"""Returns html content for the left column
"""
htmlStr = ''
@ -112,7 +113,8 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
# show the edit icon
htmlStr += \
' <a href="' + \
'/users/' + nickname + '/editlinks">' + \
'/users/' + nickname + '/editlinks" ' + \
'accesskey="' + accessKeys['menuEdit'] + '">' + \
'<img class="' + editImageClass + \
'" loading="lazy" alt="' + \
translate['Edit Links'] + ' | " title="' + \
@ -254,6 +256,11 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
if firstSeparatorAdded:
htmlStr += separatorStr
htmlStr += \
'<p class="login-text"><a href="/users/' + \
nickname + '/accesskeys" accesskey="' + \
accessKeys['menuKeys'] + '">' + \
translate['Key Shortcuts'] + '</a></p>'
htmlStr += \
'<p class="login-text"><a href="/about">' + \
translate['About this Instance'] + '</a></p>'
@ -274,7 +281,7 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
rssIconAtTop: bool,
iconsAsButtons: bool,
defaultTimeline: str,
theme: str) -> str:
theme: str, accessKeys: {}) -> str:
"""Show the left column links within mobile view
"""
htmlStr = ''
@ -300,7 +307,8 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
htmlStr += \
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
'<a href="/users/' + nickname + '/' + defaultTimeline + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">' + \
'<img loading="lazy" class="timeline-banner" ' + \
'alt="' + translate['Switch to timeline view'] + '" ' + \
'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n'
@ -317,7 +325,7 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
editor,
False, timelinePath,
rssIconAtTop, False, False,
theme)
theme, accessKeys)
else:
if editor:
htmlStr += '<br><br><br>\n'
@ -334,7 +342,8 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
domain: str, port: int, httpPrefix: str,
defaultTimeline: str, theme: str) -> str:
defaultTimeline: str, theme: str,
accessKeys: {}) -> str:
"""Shows the edit links screen
"""
if '/users/' not in path:
@ -367,7 +376,8 @@ def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
'<header>\n' + \
'<a href="/users/' + nickname + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '" alt="' + \
translate['Switch to timeline view'] + '">\n'
translate['Switch to timeline view'] + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">\n'
editLinksForm += '<img loading="lazy" class="timeline-banner" ' + \
'alt = "" src="' + \
'/users/' + nickname + '/' + bannerFile + '" /></a>\n' + \
@ -384,7 +394,8 @@ def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
' <h1>' + translate['Edit Links'] + '</h1>'
editLinksForm += \
' <input type="submit" name="submitLinks" value="' + \
translate['Submit'] + '">\n'
translate['Submit'] + '" ' + \
'accesskey="' + accessKeys['submitButton'] + '">\n'
editLinksForm += \
' </div>\n'

View File

@ -51,7 +51,8 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
authorized: bool,
showHeaderImage: bool,
theme: str,
defaultTimeline: str) -> str:
defaultTimeline: str,
accessKeys: {}) -> str:
"""Returns html content for the right column
"""
htmlStr = ''
@ -69,7 +70,8 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
publishButtonStr = \
' <a href="' + \
'/users/' + nickname + '/newblog?nodropdown" ' + \
'title="' + titleStr + '">' + \
'title="' + titleStr + '" ' + \
'accesskey="' + accessKeys['menuNewPost'] + '">' + \
'<button class="publishbtn">' + \
translate['Publish'] + '</button></a>\n'
else:
@ -124,7 +126,8 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
# show the edit icon highlighted
htmlStr += \
' <a href="' + \
'/users/' + nickname + '/editnewswire">' + \
'/users/' + nickname + '/editnewswire" ' + \
'accesskey="' + accessKeys['menuEdit'] + '">' + \
'<img class="' + editImageClass + \
'" loading="lazy" alt="' + \
translate['Edit newswire'] + ' | " title="' + \
@ -134,7 +137,8 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
# show the edit icon
htmlStr += \
' <a href="' + \
'/users/' + nickname + '/editnewswire">' + \
'/users/' + nickname + '/editnewswire" ' + \
'accesskey="' + accessKeys['menuEdit'] + '">' + \
'<img class="' + editImageClass + \
'" loading="lazy" alt="' + \
translate['Edit newswire'] + ' | " title="' + \
@ -167,7 +171,8 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
titleStr = translate['Publish a news article']
htmlStr += \
' <a href="' + \
'/users/' + nickname + '/newblog?nodropdown">' + \
'/users/' + nickname + '/newblog?nodropdown" ' + \
'accesskey="' + accessKeys['menuNewPost'] + '">' + \
'<img class="' + editImageClass + \
'" loading="lazy" alt="' + \
titleStr + '" title="' + \
@ -444,7 +449,8 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
rssIconAtTop: bool,
iconsAsButtons: bool,
defaultTimeline: str,
theme: str) -> str:
theme: str,
accessKeys: {}) -> str:
"""Shows the mobile version of the newswire right column
"""
htmlStr = ''
@ -473,7 +479,8 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, theme)
htmlStr += \
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
'<a href="/users/' + nickname + '/' + defaultTimeline + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">' + \
'<img loading="lazy" class="timeline-banner" ' + \
'alt="' + translate['Timeline banner image'] + '" ' + \
'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n'
@ -493,7 +500,7 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
False, timelinePath, showPublishButton,
showPublishAsIcon, rssIconAtTop, False,
authorized, False, theme,
defaultTimeline)
defaultTimeline, accessKeys)
else:
if editor:
htmlStr += '<br><br><br>\n'
@ -509,7 +516,8 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str,
domain: str, port: int, httpPrefix: str,
defaultTimeline: str, theme: str) -> str:
defaultTimeline: str, theme: str,
accessKeys: {}) -> str:
"""Shows the edit newswire screen
"""
if '/users/' not in path:
@ -542,7 +550,8 @@ def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str,
'<header>' + \
'<a href="/users/' + nickname + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '" alt="' + \
translate['Switch to timeline view'] + '">\n'
translate['Switch to timeline view'] + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">\n'
editNewswireForm += '<img loading="lazy" class="timeline-banner" src="' + \
'/users/' + nickname + '/' + bannerFile + '" ' + \
'alt="" /></a>\n</header>'
@ -558,7 +567,8 @@ def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str,
' <div class="containerSubmitNewPost">\n'
editNewswireForm += \
' <input type="submit" name="submitNewswire" value="' + \
translate['Submit'] + '">\n'
translate['Submit'] + '" ' + \
'accesskey="' + accessKeys['submitButton'] + '">\n'
editNewswireForm += \
' </div>\n'

View File

@ -72,7 +72,8 @@ def _htmlNewPostDropDown(scopeIcon: str, scopeDescription: str,
dropdownReminderSuffix: str,
dropdownEventSuffix: str,
dropdownReportSuffix: str,
noDropDown: bool) -> str:
noDropDown: bool,
accessKeys: {}) -> str:
"""Returns the html for a drop down list of new post types
"""
dropDownContent = '<nav><div class="newPostDropdown">\n'
@ -93,21 +94,24 @@ def _htmlNewPostDropDown(scopeIcon: str, scopeDescription: str,
if showPublicOnDropdown:
dropDownContent += \
'<li><a href="' + pathBase + dropdownNewPostSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \
'" accesskey="' + accessKeys['Public'] + '">' + \
'<img loading="lazy" alt="" title="" src="/' + \
'icons/scope_public.png"/><b>' + \
translate['Public'] + '</b><br>' + \
translate['Visible to anyone'] + '</a></li>\n'
if defaultTimeline == 'tlfeatures':
dropDownContent += \
'<li><a href="' + pathBase + dropdownNewBlogSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \
'" accesskey="' + accessKeys['menuBlogs'] + '">' + \
'<img loading="lazy" alt="" title="" src="/' + \
'icons/scope_blog.png"/><b>' + \
translate['Article'] + '</b><br>' + \
translate['Create an article'] + '</a></li>\n'
else:
dropDownContent += \
'<li><a href="' + pathBase + dropdownNewBlogSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \
'" accesskey="' + accessKeys['menuBlogs'] + '">' + \
'<img loading="lazy" alt="" title="" src="/' + \
'icons/scope_blog.png"/><b>' + \
translate['Blog'] + '</b><br>' + \
translate['Publicly visible post'] + '</a></li>\n'
@ -119,20 +123,23 @@ def _htmlNewPostDropDown(scopeIcon: str, scopeDescription: str,
translate['Not on public timeline'] + '</a></li>\n'
dropDownContent += \
'<li><a href="' + pathBase + dropdownFollowersSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \
'" accesskey="' + accessKeys['menuFollowers'] + '">' + \
'<img loading="lazy" alt="" title="" src="/' + \
'icons/scope_followers.png"/><b>' + \
translate['Followers'] + '</b><br>' + \
translate['Only to followers'] + '</a></li>\n'
dropDownContent += \
'<li><a href="' + pathBase + dropdownDMSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \
'" accesskey="' + accessKeys['menuDM'] + '">' + \
'<img loading="lazy" alt="" title="" src="/' + \
'icons/scope_dm.png"/><b>' + \
translate['DM'] + '</b><br>' + \
translate['Only to mentioned people'] + '</a></li>\n'
dropDownContent += \
'<li><a href="' + pathBase + dropdownReminderSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \
'" accesskey="' + accessKeys['Reminder'] + '">' + \
'<img loading="lazy" alt="" title="" src="/' + \
'icons/scope_reminder.png"/><b>' + \
translate['Reminder'] + '</b><br>' + \
translate['Scheduled note to yourself'] + '</a></li>\n'
@ -144,7 +151,8 @@ def _htmlNewPostDropDown(scopeIcon: str, scopeDescription: str,
# translate['Create an event'] + '</a></li>\n'
dropDownContent += \
'<li><a href="' + pathBase + dropdownReportSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \
'" accesskey="' + accessKeys['reportButton'] + '">' + \
'<img loading="lazy" alt="" title="" src="/' + \
'icons/scope_report.png"/><b>' + \
translate['Report'] + '</b><br>' + \
translate['Send to moderators'] + '</a></li>\n'
@ -152,7 +160,8 @@ def _htmlNewPostDropDown(scopeIcon: str, scopeDescription: str,
if not replyStr:
dropDownContent += \
'<li><a href="' + pathBase + \
'/newshare"><img loading="lazy" alt="" title="" src="/' + \
'/newshare" accesskey="' + accessKeys['menuShares'] + '">' + \
'<img loading="lazy" alt="" title="" src="/' + \
'icons/scope_share.png"/><b>' + \
translate['Shares'] + '</b><br>' + \
translate['Describe a shared item'] + '</a></li>\n'
@ -177,7 +186,8 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
nickname: str, domain: str,
domainFull: str,
defaultTimeline: str, newswire: {},
theme: str, noDropDown: bool) -> str:
theme: str, noDropDown: bool,
accessKeys: {}) -> str:
"""New post screen
"""
replyStr = ''
@ -579,7 +589,8 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
'<header>\n' + \
'<a href="/users/' + nickname + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '" alt="' + \
translate['Switch to timeline view'] + '">\n'
translate['Switch to timeline view'] + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">\n'
newPostForm += '<img loading="lazy" class="timeline-banner" src="' + \
'/users/' + nickname + '/' + bannerFile + '" alt="" /></a>\n' + \
'</header>\n'
@ -651,7 +662,7 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
dropdownReminderSuffix,
dropdownEventSuffix,
dropdownReportSuffix,
noDropDown)
noDropDown, accessKeys)
else:
if not shareDescription:
# reporting a post to moderator
@ -693,7 +704,8 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
newPostForm += \
' <td><input type="submit" name="submitPost" value="' + \
translate['Submit'] + '"></td>\n'
translate['Submit'] + '" ' + \
'accesskey="' + accessKeys['submitButton'] + '"></td>\n'
newPostForm += ' </tr>\n'
newPostForm += '</table>\n'

View File

@ -96,6 +96,7 @@ def htmlFrontScreen(rssIconAtTop: bool,
newswire: {}, theme: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
accessKeys: {},
extraJson=None,
pageNumber=None, maxItemsPerPage=None) -> str:
"""Show the news instance front screen
@ -138,7 +139,7 @@ def htmlFrontScreen(rssIconAtTop: bool,
getLeftColumnContent(baseDir, 'news', domainFull,
httpPrefix, translate,
False, False, None, rssIconAtTop, True,
True, theme)
True, theme, accessKeys)
profileHeaderStr += ' </td>\n'
profileHeaderStr += ' <td valign="top" class="col-center">\n'
@ -173,7 +174,7 @@ def htmlFrontScreen(rssIconAtTop: bool,
False, False, newswire, False,
False, None, False, False,
False, True, authorized, True, theme,
defaultTimeline)
defaultTimeline, accessKeys)
profileFooterStr += ' </td>\n'
profileFooterStr += ' </tr>\n'
profileFooterStr += ' </tbody>\n'

View File

@ -43,7 +43,8 @@ def headerButtonsTimeline(defaultTimeline: str,
calendarPath: str,
calendarImage: str,
followApprovals: str,
iconsAsButtons: bool) -> str:
iconsAsButtons: bool,
accessKeys: {}) -> str:
"""Returns the header at the top of the timeline, containing
buttons for inbox, outbox, search, calendar, etc
"""
@ -53,7 +54,9 @@ def headerButtonsTimeline(defaultTimeline: str,
if defaultTimeline == 'tlmedia':
tlStr += \
'<a href="' + usersPath + \
'/tlmedia" tabindex="-1"><button class="' + \
'/tlmedia" tabindex="-1" ' + \
'accesskey="' + accessKeys['menuMedia'] + '"' + \
'><button class="' + \
mediaButton + '"><span>' + translate['Media'] + \
'</span></button></a>'
elif defaultTimeline == 'tlblogs':
@ -102,7 +105,9 @@ def headerButtonsTimeline(defaultTimeline: str,
if not minimal and not featuresHeader:
tlStr += \
'<a href="' + usersPath + \
'/tlmedia" tabindex="-1"><button class="' + \
'/tlmedia" tabindex="-1" ' + \
'accesskey="' + accessKeys['menuMedia'] + '">' + \
'<button class="' + \
mediaButton + '"><span>' + translate['Media'] + \
'</span></button></a>'
else:

View File

@ -84,7 +84,7 @@ def htmlLogin(cssCache: {}, translate: {},
copyfile(baseDir + '/img/login.png', loginImageFilename)
textModeLogo = getTextModeLogo(baseDir)
textModeLogoHtml = htmlKeyboardNavigation(textModeLogo, {})
textModeLogoHtml = htmlKeyboardNavigation(textModeLogo, {}, {})
if os.path.isfile(baseDir + '/accounts/login-background-custom.jpg'):
if not os.path.isfile(baseDir + '/accounts/login-background.jpg'):

View File

@ -44,7 +44,8 @@ def htmlModeration(cssCache: {}, defaultTimeline: str,
authorized: bool, moderationActionStr: str,
theme: str, peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str) -> str:
textModeBanner: str,
accessKeys: {}) -> str:
"""Show the moderation feed as html
This is what you see when selecting the "mod" timeline
"""
@ -60,7 +61,7 @@ def htmlModeration(cssCache: {}, defaultTimeline: str,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, moderationActionStr, theme,
peertubeInstances, allowLocalNetworkAccess,
textModeBanner)
textModeBanner, accessKeys)
def htmlAccountInfo(cssCache: {}, translate: {},
@ -270,7 +271,9 @@ def htmlModerationInfo(cssCache: {}, translate: {},
for acct in dirs:
if '@' not in acct:
continue
if 'inbox@' in acct or 'news@' in acct:
if acct.startswith('inbox@'):
continue
elif acct.startswith('news@'):
continue
accounts.append(acct)
break

View File

@ -54,7 +54,8 @@ def htmlPersonOptions(defaultTimeline: str,
alsoKnownAs: [],
textModeBanner: str,
newsInstance: bool,
authorized: bool) -> str:
authorized: bool,
accessKeys: {}) -> str:
"""Show options for a person: view/follow/block/report
"""
optionsDomain, optionsPort = getDomainFromActor(optionsActor)
@ -119,7 +120,7 @@ def htmlPersonOptions(defaultTimeline: str,
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
optionsStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
optionsStr += htmlKeyboardNavigation(textModeBanner, {})
optionsStr += htmlKeyboardNavigation(textModeBanner, {}, {})
optionsStr += '<br><br>\n'
optionsStr += '<div class="options">\n'
optionsStr += ' <div class="optionsAvatar">\n'
@ -234,7 +235,8 @@ def htmlPersonOptions(defaultTimeline: str,
optionsStr += \
' ' + translate['Petname'] + ': \n' + \
' <input type="text" name="optionpetname" value="' + \
petname + '">\n' \
petname + '" ' + \
'accesskey="' + accessKeys['enterPetname'] + '">\n' \
' <button type="submit" class="buttonsmall" ' + \
'name="submitPetname">' + \
translate['Submit'] + '</button><br>\n'
@ -324,40 +326,51 @@ def htmlPersonOptions(defaultTimeline: str,
if authorized and originPathStr == '/users/' + nickname:
optionsStr += \
' <a href="' + backPath + '"><button type="button" ' + \
'class="buttonIcon" name="submitBack">' + translate['Go Back'] + \
'</button></a>\n'
'class="buttonIcon" name="submitBack" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">' + \
translate['Go Back'] + '</button></a>\n'
else:
optionsStr += \
' <a href="' + originPathStr + '"><button type="button" ' + \
'class="buttonIcon" name="submitBack">' + translate['Go Back'] + \
'class="buttonIcon" name="submitBack" accesskey="' + \
accessKeys['menuTimeline'] + '">' + translate['Go Back'] + \
'</button></a>\n'
if authorized:
optionsStr += \
' <button type="submit" class="button" name="submitView">' + \
' <button type="submit" class="button" ' + \
'name="submitView" accesskey="' + \
accessKeys['viewButton'] + '">' + \
translate['View'] + '</button>\n'
optionsStr += donateStr
if authorized:
optionsStr += \
' <button type="submit" class="button" name="submit' + \
followStr + '">' + translate[followStr] + '</button>\n'
followStr + '" accesskey="' + accessKeys['followButton'] + '">' + \
translate[followStr] + '</button>\n'
optionsStr += \
' <button type="submit" class="button" name="submit' + \
blockStr + '">' + translate[blockStr] + '</button>\n'
blockStr + '" accesskey="' + accessKeys['blockButton'] + '">' + \
translate[blockStr] + '</button>\n'
optionsStr += \
' <button type="submit" class="button" name="submitDM">' + \
' <button type="submit" class="button" name="submitDM" ' + \
'accesskey="' + accessKeys['menuDM'] + '">' + \
translate['DM'] + '</button>\n'
optionsStr += \
' <button type="submit" class="button" name="submit' + \
snoozeButtonStr + '">' + translate[snoozeButtonStr] + \
snoozeButtonStr + '" accesskey="' + \
accessKeys['snoozeButton'] + '">' + translate[snoozeButtonStr] + \
'</button>\n'
optionsStr += \
' <button type="submit" class="button" ' + \
'name="submitReport">' + translate['Report'] + '</button>\n'
'name="submitReport" accesskey="' + \
accessKeys['reportButton'] + '">' + \
translate['Report'] + '</button>\n'
if isModerator(baseDir, nickname):
optionsStr += \
' <button type="submit" class="button" ' + \
'name="submitPersonInfo">' + \
'name="submitPersonInfo" accesskey="' + \
accessKeys['infoButton'] + '">' + \
translate['Info'] + '</button>\n'
personNotes = ''
@ -376,7 +389,8 @@ def htmlPersonOptions(defaultTimeline: str,
translate['Submit'] + '</button><br>\n'
optionsStr += \
' <textarea id="message" ' + \
'name="optionnotes" style="height:400px" spellcheck="true">' + \
'name="optionnotes" style="height:400px" spellcheck="true" ' + \
'accesskey="' + accessKeys['enterNotes'] + '">' + \
personNotes + '</textarea>\n'
optionsStr += ' </form>\n'

View File

@ -67,7 +67,8 @@ def htmlProfileAfterSearch(cssCache: {},
defaultTimeline: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
themeName: str) -> str:
themeName: str,
accessKeys: {}) -> str:
"""Show a profile page after a search for a fediverse address
"""
if hasUsersPath(profileHandle) or '/@' in profileHandle:
@ -255,7 +256,7 @@ def htmlProfileAfterSearch(cssCache: {},
profileDescriptionShort,
avatarUrl, imageUrl,
movedTo, profileJson['id'],
alsoKnownAs)
alsoKnownAs, accessKeys)
domainFull = getFullDomain(domain, port)
@ -276,10 +277,12 @@ def htmlProfileAfterSearch(cssCache: {},
' <input type="hidden" name="actor" value="' + \
personUrl + '">\n'
profileStr += \
' <button type="submit" class="button" name="submitYes">' + \
' <button type="submit" class="button" name="submitYes" ' + \
'accesskey="' + accessKeys['followButton'] + '">' + \
translate['Follow'] + '</button>\n'
profileStr += \
' <button type="submit" class="button" name="submitView">' + \
' <button type="submit" class="button" name="submitView" ' + \
'accesskey="' + accessKeys['viewButton'] + '">' + \
translate['View'] + '</button>\n'
profileStr += ' </center>\n'
profileStr += ' </form>\n'
@ -331,14 +334,16 @@ def _getProfileHeader(baseDir: str, httpPrefix: str,
loginButton: str, avatarUrl: str,
theme: str, movedTo: str,
alsoKnownAs: [],
pinnedContent) -> str:
pinnedContent: str,
accessKeys: {}) -> str:
"""The header of the profile screen, containing background
image and avatar
"""
htmlStr = '\n\n <figure class="profileHeader">\n'
htmlStr += ' <a href="/users/' + \
nickname + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '">\n'
translate['Switch to timeline view'] + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">\n'
htmlStr += ' <img class="profileBackground" ' + \
'alt="" ' + \
'src="/users/' + nickname + '/image_' + theme + '.png" /></a>\n'
@ -411,14 +416,16 @@ def _getProfileHeaderAfterSearch(baseDir: str,
profileDescriptionShort: str,
avatarUrl: str, imageUrl: str,
movedTo: str, actor: str,
alsoKnownAs: []) -> str:
alsoKnownAs: [],
accessKeys: {}) -> str:
"""The header of a searched for handle, containing background
image and avatar
"""
htmlStr = '\n\n <figure class="profileHeader">\n'
htmlStr += ' <a href="/users/' + \
nickname + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '">\n'
translate['Switch to timeline view'] + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">\n'
htmlStr += ' <img class="profileBackground" ' + \
'alt="" ' + \
'src="' + imageUrl + '" /></a>\n'
@ -489,7 +496,7 @@ def htmlProfile(rssIconAtTop: bool,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str,
debug: bool,
debug: bool, accessKeys: {},
extraJson=None, pageNumber=None,
maxItemsPerPage=None) -> str:
"""Show the profile page as html
@ -509,7 +516,7 @@ def htmlProfile(rssIconAtTop: bool,
YTReplacementDomain,
showPublishedDateOnly,
newswire, theme, extraJson,
allowLocalNetworkAccess,
allowLocalNetworkAccess, accessKeys,
pageNumber, maxItemsPerPage)
domain, port = getDomainFromActor(profileJson['id'])
@ -723,7 +730,7 @@ def htmlProfile(rssIconAtTop: bool,
profileDescriptionShort,
loginButton, avatarUrl, theme,
movedTo, alsoKnownAs,
pinnedContent)
pinnedContent, accessKeys)
# keyboard navigation
userPathStr = '/users/' + nickname
@ -755,7 +762,14 @@ def htmlProfile(rssIconAtTop: bool,
menuShares: userPathStr + '/shares#timeline',
menuLogout: '/logout'
}
profileStr = htmlKeyboardNavigation(textModeBanner, navLinks)
navAccessKeys = {}
for variableName, key in accessKeys.items():
if not locals().get(variableName):
continue
navAccessKeys[locals()[variableName]] = key
profileStr = htmlKeyboardNavigation(textModeBanner,
navLinks, navAccessKeys)
profileStr += profileHeaderStr + donateSection
profileStr += '<div class="container" id="buttonheader">\n'
@ -1184,6 +1198,14 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
with open(blockedFilename, 'r') as blockedfile:
blockedStr = blockedfile.read()
dmAllowedInstancesStr = ''
dmAllowedInstancesFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/dmAllowedInstances.txt'
if os.path.isfile(dmAllowedInstancesFilename):
with open(dmAllowedInstancesFilename, 'r') as dmAllowedInstancesFile:
dmAllowedInstancesStr = dmAllowedInstancesFile.read()
allowedInstancesStr = ''
allowedInstancesFilename = \
baseDir + '/accounts/' + \
@ -1463,7 +1485,12 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
menuProfile: userPathStr,
menuTimeline: userTimalineStr
}
editProfileForm += htmlKeyboardNavigation(textModeBanner, navLinks)
navAccessKeys = {
menuProfile: 'p',
menuTimeline: 't'
}
editProfileForm += htmlKeyboardNavigation(textModeBanner,
navLinks, navAccessKeys)
# top banner
editProfileForm += \
@ -1740,6 +1767,18 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
' <textarea id="message" name="blocked" style="height:200px" ' + \
'spellcheck="false">' + blockedStr + '</textarea>\n'
editProfileForm += \
' <br><b><label class="labels">' + \
translate['Direct Message permitted instances'] + '</label></b>\n'
idx = 'Direct messages are always allowed from these instances.'
editProfileForm += \
' <br><label class="labels">' + \
translate[idx] + '</label>\n'
editProfileForm += \
' <textarea id="message" name="dmAllowedInstances" ' + \
'style="height:200px" spellcheck="false">' + \
dmAllowedInstancesStr + '</textarea>\n'
editProfileForm += \
' <br><b><label class="labels">' + \
translate['Federation list'] + '</label></b>\n'

View File

@ -319,7 +319,7 @@ def htmlSearchEmojiTextEntry(cssCache: {}, translate: {},
def htmlSearch(cssCache: {}, translate: {},
baseDir: str, path: str, domain: str,
defaultTimeline: str, theme: str,
textModeBanner: str) -> str:
textModeBanner: str, accessKeys: {}) -> str:
"""Search called from the timeline icon
"""
actor = path.replace('/search', '')
@ -342,17 +342,19 @@ def htmlSearch(cssCache: {}, translate: {},
searchBannerFile, searchBannerFilename = \
getSearchBannerFile(baseDir, searchNickname, domain, theme)
textModeBannerStr = htmlKeyboardNavigation(textModeBanner, {})
textModeBannerStr = htmlKeyboardNavigation(textModeBanner, {}, {})
if textModeBannerStr is None:
textModeBannerStr = ''
if os.path.isfile(searchBannerFilename):
timelineKey = accessKeys['menuTimeline']
usersPath = '/users/' + searchNickname
followStr += \
'<header>\n' + textModeBannerStr + \
'<a href="' + usersPath + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '" alt="' + \
translate['Switch to timeline view'] + '">\n'
translate['Switch to timeline view'] + '" ' + \
'accesskey="' + timelineKey + '">\n'
followStr += '<img loading="lazy" class="timeline-banner" src="' + \
usersPath + '/' + searchBannerFile + '" alt="" /></a>\n' + \
'</header>\n'
@ -370,8 +372,10 @@ def htmlSearch(cssCache: {}, translate: {},
followStr += \
' <input type="hidden" name="actor" value="' + actor + '">\n'
followStr += ' <input type="text" name="searchtext" autofocus><br>\n'
submitKey = accessKeys['submitButton']
followStr += ' <button type="submit" class="button" ' + \
'name="submitSearch">' + translate['Submit'] + '</button>\n'
'name="submitSearch" accesskey="' + submitKey + '">' + \
translate['Submit'] + '</button>\n'
followStr += ' </form>\n'
followStr += ' <p class="hashtagswarm">' + \
htmlHashTagSwarm(baseDir, actor, translate) + '</p>\n'
@ -404,6 +408,8 @@ def htmlSkillsSearch(actor: str,
continue
if f.startswith('inbox@'):
continue
elif f.startswith('news@'):
continue
actorFilename = os.path.join(subdir, f)
actorJson = loadJson(actorFilename)
if actorJson:
@ -439,6 +445,8 @@ def htmlSkillsSearch(actor: str,
continue
if f.startswith('inbox@'):
continue
elif f.startswith('news@'):
continue
actorFilename = os.path.join(subdir, f)
cachedActorJson = loadJson(actorFilename)
if cachedActorJson:

View File

@ -116,7 +116,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
theme: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str) -> str:
textModeBanner: str,
accessKeys: {}) -> str:
"""Show the timeline as html
"""
enableTimingLog = False
@ -268,7 +269,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
# show follow approvals icon
followApprovals = \
'<a href="' + usersPath + \
'/followers#buttonheader">' + \
'/followers#buttonheader" ' + \
'accesskey="' + accessKeys['followButton'] + '">' + \
'<img loading="lazy" ' + \
'class="timelineicon" alt="' + \
translate['Approve follow requests'] + \
@ -471,18 +473,23 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
menuCalendar: usersPath + '/calendar',
menuDM: usersPath + '/dm#timelineposts',
menuReplies: usersPath + '/tlreplies#timelineposts',
menuOutbox: usersPath + '/inbox#timelineposts',
menuOutbox: usersPath + '/outbox#timelineposts',
menuBookmarks: usersPath + '/tlbookmarks#timelineposts',
menuShares: usersPath + '/tlshares#timelineposts',
menuBlogs: usersPath + '/tlblogs#timelineposts',
# menuEvents: usersPath + '/tlevents#timelineposts',
menuNewswire: usersPath + '/newswiremobile',
menuLinks: usersPath + '/linksmobile'
}
navAccessKeys = {}
for variableName, key in accessKeys.items():
if not locals().get(variableName):
continue
navAccessKeys[locals()[variableName]] = key
if moderator:
navLinks[menuModeration] = usersPath + '/moderation#modtimeline'
tlStr += htmlKeyboardNavigation(textModeBanner, navLinks, None,
usersPath, translate, followApprovals)
tlStr += htmlKeyboardNavigation(textModeBanner, navLinks, navAccessKeys,
None, usersPath, translate,
followApprovals)
# banner and row of buttons
tlStr += \
@ -509,7 +516,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
domain, timelineStartTime,
newCalendarEvent, calendarPath,
calendarImage, followApprovals,
iconsAsButtons)
iconsAsButtons, accessKeys)
# start the timeline
tlStr += '<table class="timeline">\n'
@ -528,7 +535,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
getLeftColumnContent(baseDir, nickname, domainFull,
httpPrefix, translate,
editor, False, None, rssIconAtTop,
True, False, theme)
True, False, theme, accessKeys)
tlStr += ' <td valign="top" class="col-left" ' + \
'id="links" tabindex="-1">' + \
leftColumnStr + ' </td>\n'
@ -549,7 +556,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
domain, timelineStartTime,
newCalendarEvent, calendarPath,
calendarImage, followApprovals,
iconsAsButtons)
iconsAsButtons, accessKeys)
tlStr += ' <div id="timelineposts" class="timeline-posts">\n'
@ -654,7 +661,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
' <center>\n' + \
' <a href="' + usersPath + '/' + boxName + \
'?page=' + str(pageNumber - 1) + \
'"><img loading="lazy" class="pageicon" src="/' + \
'" accesskey="' + accessKeys['Page up'] + '">' + \
'<img loading="lazy" class="pageicon" src="/' + \
'icons/pageup.png" title="' + \
translate['Page up'] + '" alt="' + \
translate['Page up'] + '"></a>\n' + \
@ -747,7 +755,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
' <center>\n' + \
' <a href="' + usersPath + '/' + boxName + '?page=' + \
str(pageNumber + 1) + \
'"><img loading="lazy" class="pageicon" src="/' + \
'" accesskey="' + accessKeys['Page down'] + '">' + \
'<img loading="lazy" class="pageicon" src="/' + \
'icons/pagedown.png" title="' + \
translate['Page down'] + '" alt="' + \
translate['Page down'] + '"></a>\n' + \
@ -771,7 +780,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
showPublishAsIcon,
rssIconAtTop, publishButtonAtTop,
authorized, True, theme,
defaultTimeline)
defaultTimeline, accessKeys)
tlStr += ' <td valign="top" class="col-right" ' + \
'id="newswire" tabindex="-1">' + \
rightColumnStr + ' </td>\n'
@ -894,7 +903,8 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
authorized: bool, theme: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str) -> str:
textModeBanner: str,
accessKeys: {}) -> str:
"""Show the shares timeline as html
"""
manuallyApproveFollowers = \
@ -915,7 +925,8 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner)
allowLocalNetworkAccess, textModeBanner,
accessKeys)
def htmlInbox(cssCache: {}, defaultTimeline: str,
@ -937,7 +948,8 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
authorized: bool, theme: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str) -> str:
textModeBanner: str,
accessKeys: {}) -> str:
"""Show the inbox as html
"""
manuallyApproveFollowers = \
@ -958,7 +970,8 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner)
allowLocalNetworkAccess, textModeBanner,
accessKeys)
def htmlBookmarks(cssCache: {}, defaultTimeline: str,
@ -980,7 +993,8 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
authorized: bool, theme: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str) -> str:
textModeBanner: str,
accessKeys: {}) -> str:
"""Show the bookmarks as html
"""
manuallyApproveFollowers = \
@ -1001,7 +1015,8 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner)
allowLocalNetworkAccess, textModeBanner,
accessKeys)
def htmlEvents(cssCache: {}, defaultTimeline: str,
@ -1023,7 +1038,8 @@ def htmlEvents(cssCache: {}, defaultTimeline: str,
authorized: bool, theme: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str) -> str:
textModeBanner: str,
accessKeys: {}) -> str:
"""Show the events as html
"""
manuallyApproveFollowers = \
@ -1044,7 +1060,8 @@ def htmlEvents(cssCache: {}, defaultTimeline: str,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner)
allowLocalNetworkAccess, textModeBanner,
accessKeys)
def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
@ -1066,7 +1083,8 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
authorized: bool, theme: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str) -> str:
textModeBanner: str,
accessKeys: {}) -> str:
"""Show the DM timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -1082,7 +1100,8 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner)
allowLocalNetworkAccess, textModeBanner,
accessKeys)
def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
@ -1104,7 +1123,8 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
authorized: bool, theme: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str) -> str:
textModeBanner: str,
accessKeys: {}) -> str:
"""Show the replies timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -1121,7 +1141,8 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner)
allowLocalNetworkAccess, textModeBanner,
accessKeys)
def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
@ -1143,7 +1164,8 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
authorized: bool, theme: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str) -> str:
textModeBanner: str,
accessKeys: {}) -> str:
"""Show the media timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -1160,7 +1182,8 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner)
allowLocalNetworkAccess, textModeBanner,
accessKeys)
def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
@ -1182,7 +1205,8 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
authorized: bool, theme: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str) -> str:
textModeBanner: str,
accessKeys: {}) -> str:
"""Show the blogs timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -1199,7 +1223,8 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner)
allowLocalNetworkAccess, textModeBanner,
accessKeys)
def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
@ -1222,7 +1247,8 @@ def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
theme: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str) -> str:
textModeBanner: str,
accessKeys: {}) -> str:
"""Show the features timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -1239,7 +1265,8 @@ def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner)
allowLocalNetworkAccess, textModeBanner,
accessKeys)
def htmlInboxNews(cssCache: {}, defaultTimeline: str,
@ -1261,7 +1288,8 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
authorized: bool, theme: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str) -> str:
textModeBanner: str,
accessKeys: {}) -> str:
"""Show the news timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -1278,7 +1306,8 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner)
allowLocalNetworkAccess, textModeBanner,
accessKeys)
def htmlOutbox(cssCache: {}, defaultTimeline: str,
@ -1300,7 +1329,8 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
authorized: bool, theme: str,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
textModeBanner: str) -> str:
textModeBanner: str,
accessKeys: {}) -> str:
"""Show the Outbox as html
"""
manuallyApproveFollowers = \
@ -1318,4 +1348,5 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
showPublishAsIcon, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner)
allowLocalNetworkAccess, textModeBanner,
accessKeys)

View File

@ -1114,7 +1114,7 @@ def htmlHideFromScreenReader(htmlStr: str) -> str:
return '<span aria-hidden="true">' + htmlStr + '</span>'
def htmlKeyboardNavigation(banner: str, links: {},
def htmlKeyboardNavigation(banner: str, links: {}, accessKeys: {},
subHeading=None,
usersPath=None, translate=None,
followApprovals=False) -> str:
@ -1138,8 +1138,12 @@ def htmlKeyboardNavigation(banner: str, links: {},
# show the list of links
for title, url in links.items():
accessKeyStr = ''
if accessKeys.get(title):
accessKeyStr = 'accesskey="' + accessKeys[title] + '"'
htmlStr += '<li><label class="transparent">' + \
'<a href="' + str(url) + '">' + \
'<a href="' + str(url) + '" ' + accessKeyStr + '>' + \
str(title) + '</a></label></li>\n'
htmlStr += '</ul></div>\n'
return htmlStr

View File

@ -107,6 +107,8 @@ def htmlWelcomeProfile(baseDir: str, nickname: str, domain: str,
bioStr = \
actorJson['summary'].replace('<p>', '').replace('</p>', '')
if not bioStr:
bioStr = translate['Your bio']
profileForm += ' <label class="labels">' + \
translate['Your bio'] + '</label><br>\n'
profileForm += ' <textarea id="message" name="bio" ' + \