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

main
Bob Mottram 2020-11-07 23:57:21 +00:00
commit 2577990163
45 changed files with 947 additions and 578 deletions

23
blog.py
View File

@ -233,7 +233,28 @@ def htmlBlogPostContent(authorized: bool,
'content') 'content')
blogStr += '<br>' + contentStr + '\n' blogStr += '<br>' + contentStr + '\n'
blogStr += '<br>\n' citationsStr = ''
if postJsonObject['object'].get('tag'):
for tagJson in postJsonObject['object']['tag']:
if not isinstance(tagJson, dict):
continue
if not tagJson.get('type'):
continue
if tagJson['type'] != 'Article':
continue
if not tagJson.get('name'):
continue
if not tagJson.get('url'):
continue
citationsStr += \
'<li><a href="' + tagJson['url'] + '">' + \
'<cite>' + tagJson['name'] + '</cite></a></li>\n'
if citationsStr:
citationsStr = '<p><b>' + translate['Citations'] + \
':</b></p>' + \
'<ul>\n' + citationsStr + '</ul>\n'
blogStr += '<br>\n' + citationsStr
if not linkedAuthor: if not linkedAuthor:
blogStr += '<p class="about"><a class="about" href="' + \ blogStr += '<p class="about"><a class="about" href="' + \

222
daemon.py
View File

@ -87,6 +87,7 @@ from inbox import populateReplies
from inbox import getPersonPubKey from inbox import getPersonPubKey
from follow import getFollowingFeed from follow import getFollowingFeed
from follow import sendFollowRequest from follow import sendFollowRequest
from follow import unfollowPerson
from auth import authorize from auth import authorize
from auth import createPassword from auth import createPassword
from auth import createBasicAuthHeader from auth import createBasicAuthHeader
@ -112,6 +113,7 @@ from blog import htmlBlogView
from blog import htmlBlogPage from blog import htmlBlogPage
from blog import htmlBlogPost from blog import htmlBlogPost
from blog import htmlEditBlog from blog import htmlEditBlog
from webinterface import htmlCitations
from webinterface import htmlFollowingList from webinterface import htmlFollowingList
from webinterface import getBlogAddress from webinterface import getBlogAddress
from webinterface import setBlogAddress from webinterface import setBlogAddress
@ -1799,8 +1801,7 @@ class PubServer(BaseHTTPRequestHandler):
# person options screen, unfollow button # person options screen, unfollow button
# See htmlPersonOptions # See htmlPersonOptions
if '&submitUnfollow=' in optionsConfirmParams: if '&submitUnfollow=' in optionsConfirmParams:
if debug: print('Unfollowing ' + optionsActor)
print('Unfollowing ' + optionsActor)
msg = \ msg = \
htmlUnfollowConfirm(self.server.cssCache, htmlUnfollowConfirm(self.server.cssCache,
self.server.translate, self.server.translate,
@ -1830,7 +1831,8 @@ class PubServer(BaseHTTPRequestHandler):
chooserNickname, chooserNickname,
domain, domain,
domainFull, domainFull,
self.server.defaultTimeline).encode('utf-8') self.server.defaultTimeline,
self.server.newswire).encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
self._write(msg) self._write(msg)
@ -1897,7 +1899,8 @@ class PubServer(BaseHTTPRequestHandler):
chooserNickname, chooserNickname,
domain, domain,
domainFull, domainFull,
self.server.defaultTimeline).encode('utf-8') self.server.defaultTimeline,
self.server.newswire).encode('utf-8')
self._set_headers('text/html', len(msg), self._set_headers('text/html', len(msg),
cookie, callingDomain) cookie, callingDomain)
self._write(msg) self._write(msg)
@ -1956,6 +1959,11 @@ class PubServer(BaseHTTPRequestHandler):
followingNickname = getNicknameFromActor(followingActor) followingNickname = getNicknameFromActor(followingActor)
followingDomain, followingPort = \ followingDomain, followingPort = \
getDomainFromActor(followingActor) getDomainFromActor(followingActor)
followingDomainFull = followingDomain
if followingPort:
if followingPort != 80 and followingPort != 443:
followingDomainFull = \
followingDomain + ':' + str(followingPort)
if followerNickname == followingNickname and \ if followerNickname == followingNickname and \
followingDomain == domain and \ followingDomain == domain and \
followingPort == port: followingPort == port:
@ -1984,6 +1992,9 @@ class PubServer(BaseHTTPRequestHandler):
} }
pathUsersSection = path.split('/users/')[1] pathUsersSection = path.split('/users/')[1]
self.postToNickname = pathUsersSection.split('/')[0] self.postToNickname = pathUsersSection.split('/')[0]
unfollowPerson(self.server.baseDir, self.postToNickname,
self.server.domain,
followingNickname, followingDomainFull)
self._postToOutboxThread(unfollowJson) self._postToOutboxThread(unfollowJson)
if callingDomain.endswith('.onion') and onionDomain: if callingDomain.endswith('.onion') and onionDomain:
@ -3045,6 +3056,109 @@ class PubServer(BaseHTTPRequestHandler):
cookie, callingDomain) cookie, callingDomain)
self.server.POSTbusy = False self.server.POSTbusy = False
def _citationsUpdate(self, callingDomain: str, cookie: str,
authorized: bool, path: str,
baseDir: str, httpPrefix: str,
domain: str, domainFull: str,
onionDomain: str, i2pDomain: str, debug: bool,
defaultTimeline: str,
newswire: {}) -> None:
"""Updates the citations for a blog post after hitting
update button on the citations screen
"""
usersPath = path.replace('/citationsdata', '')
actorStr = httpPrefix + '://' + domainFull + usersPath
nickname = getNicknameFromActor(actorStr)
citationsFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/.citations.txt'
# remove any existing citations file
if os.path.isfile(citationsFilename):
os.remove(citationsFilename)
if newswire and \
' boundary=' in self.headers['Content-type']:
boundary = self.headers['Content-type'].split('boundary=')[1]
if ';' in boundary:
boundary = boundary.split(';')[0]
length = int(self.headers['Content-length'])
# check that the POST isn't too large
if length > self.server.maxPostLength:
if callingDomain.endswith('.onion') and \
onionDomain:
actorStr = \
'http://' + onionDomain + usersPath
elif (callingDomain.endswith('.i2p') and
i2pDomain):
actorStr = \
'http://' + i2pDomain + usersPath
print('Maximum citations data length exceeded ' + str(length))
self._redirect_headers(actorStr, cookie, callingDomain)
self.server.POSTbusy = False
return
try:
# read the bytes of the http form POST
postBytes = self.rfile.read(length)
except SocketError as e:
if e.errno == errno.ECONNRESET:
print('WARN: connection was reset while ' +
'reading bytes from http form ' +
'citation screen POST')
else:
print('WARN: error while reading bytes ' +
'from http form citations screen POST')
self.send_response(400)
self.end_headers()
self.server.POSTbusy = False
return
except ValueError as e:
print('ERROR: failed to read bytes for ' +
'citations screen POST')
print(e)
self.send_response(400)
self.end_headers()
self.server.POSTbusy = False
return
# extract all of the text fields into a dict
fields = \
extractTextFieldsInPOST(postBytes, boundary, debug)
print('citationstest: ' + str(fields))
citations = []
for ctr in range(0, 128):
fieldName = 'newswire' + str(ctr)
if not fields.get(fieldName):
continue
citations.append(fields[fieldName])
if citations:
citationsStr = ''
for citationDate in citations:
citationsStr += citationDate + '\n'
# save citations dates, so that they can be added when
# reloading the newblog screen
citationsFile = open(citationsFilename, "w+")
if citationsFile:
citationsFile.write(citationsStr)
citationsFile.close()
# redirect back to the default timeline
if callingDomain.endswith('.onion') and \
onionDomain:
actorStr = \
'http://' + onionDomain + usersPath
elif (callingDomain.endswith('.i2p') and
i2pDomain):
actorStr = \
'http://' + i2pDomain + usersPath
self._redirect_headers(actorStr + '/newblog',
cookie, callingDomain)
self.server.POSTbusy = False
def _newsPostEdit(self, callingDomain: str, cookie: str, def _newsPostEdit(self, callingDomain: str, cookie: str,
authorized: bool, path: str, authorized: bool, path: str,
baseDir: str, httpPrefix: str, baseDir: str, httpPrefix: str,
@ -8494,7 +8608,8 @@ class PubServer(BaseHTTPRequestHandler):
replyPageNumber, replyPageNumber,
nickname, domain, nickname, domain,
domainFull, domainFull,
self.server.defaultTimeline).encode('utf-8') self.server.defaultTimeline,
self.server.newswire).encode('utf-8')
if not msg: if not msg:
print('Error replying to ' + inReplyToUrl) print('Error replying to ' + inReplyToUrl)
self._404() self._404()
@ -10756,9 +10871,8 @@ class PubServer(BaseHTTPRequestHandler):
filename.endswith('.webp') or \ filename.endswith('.webp') or \
filename.endswith('.avif') or \ filename.endswith('.avif') or \
filename.endswith('.gif'): filename.endswith('.gif'):
if self.server.debug:
print('DEBUG: POST media removing metadata')
postImageFilename = filename.replace('.temp', '') postImageFilename = filename.replace('.temp', '')
print('Removing metadata from ' + postImageFilename)
removeMetaData(filename, postImageFilename) removeMetaData(filename, postImageFilename)
if os.path.isfile(postImageFilename): if os.path.isfile(postImageFilename):
print('POST media saved to ' + postImageFilename) print('POST media saved to ' + postImageFilename)
@ -10779,15 +10893,24 @@ class PubServer(BaseHTTPRequestHandler):
else: else:
print('WARN: no text fields could be extracted from POST') print('WARN: no text fields could be extracted from POST')
# process the received text fields from the POST # was the citations button pressed on the newblog screen?
if not fields.get('message') and \ citationsButtonPress = False
not fields.get('imageDescription'): if postType == 'newblog' and fields.get('submitCitations'):
return -1 if fields['submitCitations'] == \
if fields.get('submitPost'): self.server.translate['Citations']:
if fields['submitPost'] != 'Submit': citationsButtonPress = True
if not citationsButtonPress:
# process the received text fields from the POST
if not fields.get('message') and \
not fields.get('imageDescription'):
return -1 return -1
else: if fields.get('submitPost'):
return 2 if fields['submitPost'] != \
self.server.translate['Submit']:
return -1
else:
return 2
if not fields.get('imageDescription'): if not fields.get('imageDescription'):
fields['imageDescription'] = None fields['imageDescription'] = None
@ -10809,19 +10932,20 @@ class PubServer(BaseHTTPRequestHandler):
if not fields.get('location'): if not fields.get('location'):
fields['location'] = None fields['location'] = None
# Store a file which contains the time in seconds if not citationsButtonPress:
# since epoch when an attempt to post something was made. # Store a file which contains the time in seconds
# This is then used for active monthly users counts # since epoch when an attempt to post something was made.
lastUsedFilename = \ # This is then used for active monthly users counts
self.server.baseDir + '/accounts/' + \ lastUsedFilename = \
nickname + '@' + self.server.domain + '/.lastUsed' self.server.baseDir + '/accounts/' + \
try: nickname + '@' + self.server.domain + '/.lastUsed'
lastUsedFile = open(lastUsedFilename, 'w+') try:
if lastUsedFile: lastUsedFile = open(lastUsedFilename, 'w+')
lastUsedFile.write(str(int(time.time()))) if lastUsedFile:
lastUsedFile.close() lastUsedFile.write(str(int(time.time())))
except BaseException: lastUsedFile.close()
pass except BaseException:
pass
mentionsStr = '' mentionsStr = ''
if fields.get('mentions'): if fields.get('mentions'):
@ -10866,6 +10990,31 @@ class PubServer(BaseHTTPRequestHandler):
else: else:
return -1 return -1
elif postType == 'newblog': elif postType == 'newblog':
# citations button on newblog screen
if citationsButtonPress:
messageJson = \
htmlCitations(self.server.baseDir,
nickname,
self.server.domain,
self.server.httpPrefix,
self.server.defaultTimeline,
self.server.translate,
self.server.newswire,
self.server.cssCache,
fields['subject'],
fields['message'],
filename, attachmentMediaType,
fields['imageDescription'])
if messageJson:
messageJson = messageJson.encode('utf-8')
self._set_headers('text/html',
len(messageJson),
cookie, callingDomain)
self._write(messageJson)
return 1
else:
return -1
# submit button on newblog screen
messageJson = \ messageJson = \
createBlogPost(self.server.baseDir, nickname, createBlogPost(self.server.baseDir, nickname,
self.server.domain, self.server.port, self.server.domain, self.server.port,
@ -10876,8 +11025,10 @@ class PubServer(BaseHTTPRequestHandler):
fields['imageDescription'], fields['imageDescription'],
self.server.useBlurHash, self.server.useBlurHash,
fields['replyTo'], fields['replyTo'], fields['replyTo'], fields['replyTo'],
fields['subject'], fields['schedulePost'], fields['subject'],
fields['eventDate'], fields['eventTime'], fields['schedulePost'],
fields['eventDate'],
fields['eventTime'],
fields['location']) fields['location'])
if messageJson: if messageJson:
if fields['schedulePost']: if fields['schedulePost']:
@ -11637,6 +11788,17 @@ class PubServer(BaseHTTPRequestHandler):
self.server.defaultTimeline) self.server.defaultTimeline)
return return
if authorized and self.path.endswith('/citationsdata'):
self._citationsUpdate(callingDomain, cookie, authorized, self.path,
self.server.baseDir, self.server.httpPrefix,
self.server.domain,
self.server.domainFull,
self.server.onionDomain,
self.server.i2pDomain, self.server.debug,
self.server.defaultTimeline,
self.server.newswire)
return
if authorized and self.path.endswith('/newseditdata'): if authorized and self.path.endswith('/newseditdata'):
self._newsPostEdit(callingDomain, cookie, authorized, self.path, self._newsPostEdit(callingDomain, cookie, authorized, self.path,
self.server.baseDir, self.server.httpPrefix, self.server.baseDir, self.server.httpPrefix,

View File

@ -14,6 +14,10 @@
--title-text: #282c37; --title-text: #282c37;
--title-background: #ccc; --title-background: #ccc;
--focus-color: white; --focus-color: white;
--calendar-horizontal-padding: 0;
--font-size-calendar-header: 3rem;
--font-size-calendar-day: 1rem;
--font-size-calendar-cell: 4rem;
} }
@font-face { @font-face {
@ -54,6 +58,7 @@ main {
.calendar { .calendar {
table-display: fixed; table-display: fixed;
width: 100%; width: 100%;
padding: 0 var(--calendar-horizontal-padding);
} }
a:visited{ a:visited{
@ -105,7 +110,7 @@ a:focus {
background-color: var(--title-background); background-color: var(--title-background);
color: var(--title-text); color: var(--title-text);
display: inline-block; display: inline-block;
font-size: 3rem; font-size: var(--font-size-calendar-header);
font-weight: 400; font-weight: 400;
letter-spacing: 0.1em; letter-spacing: 0.1em;
padding: .5rem 2rem; padding: .5rem 2rem;
@ -113,13 +118,13 @@ a:focus {
} }
.calendar__day__header { .calendar__day__header {
font-size: 1rem; font-size: var(--font-size-calendar-day);
letter-spacing: 0.1em; letter-spacing: 0.1em;
text-transform: uppercase; text-transform: uppercase;
} }
.calendar__day__cell { .calendar__day__cell {
font-size: 4rem; font-size: var(--font-size-calendar-cell);
position: relative; position: relative;
} }

View File

@ -30,6 +30,7 @@
--font-size-publish-button: 18px; --font-size-publish-button: 18px;
--font-size-newswire: 18px; --font-size-newswire: 18px;
--font-size-newswire-mobile: 40px; --font-size-newswire-mobile: 40px;
--font-size-dropdown-header: 40px;
--font-size: 30px; --font-size: 30px;
--font-size2: 24px; --font-size2: 24px;
--font-size3: 38px; --font-size3: 38px;
@ -82,10 +83,15 @@
--column-left-width: 10vw; --column-left-width: 10vw;
--column-center-width: 80vw; --column-center-width: 80vw;
--column-right-width: 10vw; --column-right-width: 10vw;
--column-left-header-style: uppercase;
--column-left-header-background: #555; --column-left-header-background: #555;
--column-left-header-color: #fff; --column-left-header-color: #fff;
--column-left-header-size: 20px; --column-left-header-size: 20px;
--column-left-header-size-mobile: 50px; --column-left-header-size-mobile: 50px;
--column-left-border-width: 0;
--column-left-icons-margin: 0;
--column-right-border-width: 0;
--column-left-border-color: black;
--column-left-icon-size: 20%; --column-left-icon-size: 20%;
--column-left-icon-size-mobile: 10%; --column-left-icon-size-mobile: 10%;
--column-left-image-width-mobile: 40vw; --column-left-image-width-mobile: 40vw;
@ -105,6 +111,7 @@
--tab-border-color: grey; --tab-border-color: grey;
--icon-brightness-change: 150%; --icon-brightness-change: 150%;
--container-button-padding: 20px; --container-button-padding: 20px;
--header-button-padding: 20px;
--container-padding: 2%; --container-padding: 2%;
--container-padding-bottom: 1%; --container-padding-bottom: 1%;
--container-padding-bottom-mobile: 0%; --container-padding-bottom-mobile: 0%;
@ -118,6 +125,11 @@
--publish-button-vertical-offset: 10px; --publish-button-vertical-offset: 10px;
--banner-height: 15vh; --banner-height: 15vh;
--banner-height-mobile: 10vh; --banner-height-mobile: 10vh;
--post-separator-margin-top: 0;
--post-separator-margin-bottom: 0;
--post-separator-width: 95%;
--post-separator-height: 1px;
--header-vertical-offset: 0;
} }
@font-face { @font-face {
@ -146,6 +158,27 @@ body, html {
line-height: var(--line-spacing); line-height: var(--line-spacing);
} }
.leftColIcons {
width: 100%;
background-color: var(--main-bg-color);
float: right;
display: block;
padding-bottom: var(--column-left-icons-margin);
}
.postSeparatorImage img {
padding-top: var(--post-separator-margin-top);
padding-bottom: var(--post-separator-margin-bottom);
width: var(--post-separator-width);
height: var(--post-separator-height);
display: block;
}
.headericons {
display: inline-block;
float: right;
}
blockquote { blockquote {
border-left: 10px; border-left: 10px;
margin: 1.5em 10px; margin: 1.5em 10px;
@ -399,14 +432,6 @@ a:focus {
margin: var(--vertical-between-posts); margin: var(--vertical-between-posts);
} }
.containerHeader {
border: var(--border-width-header) solid var(--border-color);
background-color: var(--main-bg-color);
border-radius: var(--timeline-border-radius);
padding: var(--container-button-padding);
margin: var(--vertical-between-posts-header);
}
.media { .media {
width: 80%; width: 80%;
border-radius: 5px; border-radius: 5px;
@ -670,23 +695,6 @@ input[type=submit]:hover {
margin: 0 auto; margin: 0 auto;
} }
/* The container <div> - needed to position the dropdown content */
.dropdown {
margin: 10px auto;
padding: 0px 14px;
position: relative;
display: inline-block;
}
.dropdown img {
opacity: 1.0;
width: 32px;
height: 32px;
padding: 0px 16px;
-ms-transform: translateY(-10%);
transform: translateY(-10%);
}
.timeline-avatar { .timeline-avatar {
margin: 10px auto; margin: 10px auto;
padding: 0px 0px; padding: 0px 0px;
@ -711,39 +719,102 @@ input[type=submit]:hover {
padding: 0px 30px; padding: 0px 30px;
} }
/* Dropdown Content (Hidden by Default) */ /* new post dropdown */
.dropdown-content {
display: none; .newPostDropdown {
position: absolute; position: relative;
background-color: var(--dropdown-bg-color);
min-width: 600px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
} }
/* Links inside the dropdown */ .newPostDropdown img {
.dropdown-content a { width: var(--font-size-dropdown-header);
background-color: var(--dropdown-bg-color);
color: var(--dropdown-fg-color);
padding: 12px 16px;
text-decoration: none;
display: block;
} }
.dropdown-content img { .newPostDropdown > input[type="checkbox"] {
width: 32px; position: absolute;
height: 32px; left: -100vw;
padding: 0px 0px;
} }
/* Change color of dropdown links on hover */ .newPostDropdown > label,
.dropdown-content a:hover { .newPostDropdown > a[role="button"] {
color: var(--dropdown-fg-color-hover); display: inline-block;
background-color: var(--dropdown-bg-color-hover); padding: 6px 0px;
color: var(--dropdown-fg-color);
font-size: var(--font-size-dropdown-header);
line-height: 1.5em;
text-decoration: none;
border: 0;
cursor: pointer;
} }
/* Show the dropdown menu on hover */ .newPostDropdown > label:hover,
.show {display: block;} .newPostDropdown > a[role="button"]:hover,
.newPostDropdown > a[role="button"]:focus {
border-color: var(--dropdown-fg-color-hover);
}
.newPostDropdown > label:after,
.newPostDropdown > a[role="button"]:after {
content: "";
font-family: 'Bedstead';
display: inline-block;
margin-left: 6px;
}
.newPostDropdown > ul {
position: absolute;
z-index: 999;
display: block;
left: -100vw;
top: calc(1.5em + 14px);
border: 0;
background: var(--dropdown-bg-color);
padding: 6px 0;
margin: 0;
list-style: none;
width: 100%;
}
.newPostDropdown > ul a {
display: block;
padding: 6px 15px;
text-decoration: none;
color: var(--dropdown-fg-color);
}
.newPostDropdown > ul a:hover,
.newPostDropdown > ul a:focus {
color: var(--dropdown-fg-color-hover);
background: var(--dropdown-bg-color-hover);
}
.newPostDropdown > input[type="checkbox"]:checked ~ ul,
.newPostDropdown > ul:target {
left: 0;
}
.newPostDropdown > [type="checkbox"]:checked + label:after,
.newPostDropdown > ul:target ~ a:after {
content: "↴";
}
.newPostDropdown a.close {
display: none;
}
.newPostDropdown > ul:target ~ a.close {
display: block;
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
text-indent: -100vw;
z-index: 1000;
}
.newPostDropdown + h2 {
margin-top: 60px;
}
.slider { .slider {
-webkit-appearance: none; -webkit-appearance: none;
@ -808,8 +879,6 @@ div.gallery img {
} }
li { list-style:none;} li { list-style:none;}
.msgscope-collapse { position: relative; }
.nav { width: 150px; }
/***********BUTTON CODE ******************************************************/ /***********BUTTON CODE ******************************************************/
a, button, input:focus, input[type='button'], input[type='reset'], input[type='submit'], textarea:focus, .button { a, button, input:focus, input[type='button'], input[type='reset'], input[type='submit'], textarea:focus, .button {
@ -820,193 +889,10 @@ a, button, input:focus, input[type='button'], input[type='reset'], input[type='s
transition: all 0.1s ease-in-out; transition: all 0.1s ease-in-out;
text-decoration: none; text-decoration: none;
} }
.button-msgScope {
display: flex;
flex-direction: row;
justify-content: center;
width: 100%;
min-height: 100%;
}
.button-msgScope button, .button-msgScope div.lined-thin {
align-self: center;
background: transparent;
padding: 1rem 1rem;
margin: 0 1rem;
transition: all .5s ease;
color: var(--dropdown-fg-color);
font-size: 2rem;
letter-spacing: 1px;
outline: none;
}
.btn { .btn {
margin: -3px 0 0 0; margin: -3px 0 0 0;
} }
aside .toggle-msgScope input[type='checkbox'] {
float: right;
}
.toggle-msgScope {
position: relative;
overflow: hidden;
transition: margin 300ms cubic-bezier(0.17, 0.04, 0.03, 0.94);
line-height: 2rem;
font-size: 2.5rem;
}
.toggle-msgScope div[class*='toggle-inside'] {
overflow: hidden;
box-sizing: border-box;
display: none;
}
aside .toggle-msgScope input[type='checkbox'] {
float:right;
}
aside .toggle-inside li {
padding-left: 20px;
width: 100%;
margin-left: -15px;
overflow: hidden;
}
.nav li:hover {
color: var(--dropdown-fg-color-hover);
background-color: var(--dropdown-bg-color-hover);
}
.nav .toggle-msgScope {
overflow: visible;
}
#msgscope label div {
-webkit-transition: all 0.1s ease-in-out;
-moz-transition: all 0.1s ease-in-out;
-ms-transition: all 0.1s ease-in-out;
-o-transition: all 0.1s ease-in-out;
transition: all 0.1s ease-in-out;
margin: 0 auto;
font-size: 1.5rem;
text-decoration: none;
display: inline-block;
font-weight: bold;
background-color: var(--dropdown-bg-color);
color: var(--dropdown-fg-color);
display: inline-block;
margin-bottom: 0;
text-align: center;
vertical-align: middle;
cursor: pointer;
background-image: none;
white-space: nowrap;
font-size: 0px;
line-height: 1.42857143;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
margin-top: 10px;
}
[id*='toggle'] .container, [class*='toggle'] .container {
transition: margin 300ms cubic-bezier(0.17, 0.04, 0.03, 0.94);
}
[id*='toggle'] {
visibility: hidden;
appearance:none;
cursor:pointer;
left:-100%;
top:-100%;
}
[id*='toggle'] + label {
cursor:pointer;
text-align: left;
-webkit-font-smoothing: antialiased;
cursor: pointer;
transition:all 500ms ease;
}
[id*='toggle'toggle'toggle'] + label div {
transition:all 500ms ease;
}
[id*='toggle'toggle'toggle'] + label div:after {
content:&#39;\002b&#39;; /* open */
text-align: left;
float: left;
}
[id*='toggle'toggle'toggle']:checked + label div:after {
content:&#39;\2212&#39;; /* close */
text-align: left;
float: left;
}
#msgscope label div {
-webkit-transition: all 0.1s ease-in-out;
-moz-transition: all 0.1s ease-in-out;
-ms-transition: all 0.1s ease-in-out;
-o-transition: all 0.1s ease-in-out;
transition: all 0.1s ease-in-out;
margin: 0 auto;
text-decoration: none;
display: inline-block;
font-weight: bold;
display: inline-block;
margin-bottom: 0;
text-align: left;
vertical-align: middle;
cursor: pointer;
background-image: none;
white-space: nowrap;
font-size: 0px;
line-height: 1.42857143;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
[id*='toggle'] .container, [class*='toggle'] .container {
transition: margin 300ms cubic-bezier(0.17, 0.04, 0.03, 0.94);
}
[id*='toggle'] {
visibility: hidden;
appearance:none;
cursor:pointer;
left:-100%;
top:-100%;
}
[id*='toggle'] + label {
cursor:pointer;
text-align: left;
-webkit-font-smoothing: antialiased;
cursor: pointer;
transition:all 500ms ease;
}
[id*='toggle'] + label div {
transition:all 500ms ease;
}
[id*='toggle'] + label div:after {
content:'\002b'; /* open */
text-align: left;
float: left;
}
[id*='toggle']:checked + label div:after {
content:'\2212'; /* close */
text-align: left;
float: left;
}
.nav [id*='toggle'] + label div:after {
content:&#39; &#39;; /* open */
}
.nav [id*='toggle']:checked + label div:after {
content:&#39; &#39;; /* close */
}
[id*='toggle']:checked ~ .container {
display: none;
}
[id*='toggle']:checked ~ .toggle-inside {
display: block;
}
.toggle-msgScope div[class*='toggle-inside'] {
overflow: hidden;
box-sizing: border-box;
display: none;
}
div.containerHeader { div.containerHeader {
overflow: auto; overflow: auto;
} }
@ -1026,6 +912,14 @@ div.container {
font-size: var(--font-size); font-size: var(--font-size);
line-height: var(--line-spacing); line-height: var(--line-spacing);
} }
.containerHeader {
border: var(--border-width-header) solid var(--border-color);
background-color: var(--main-bg-color);
border-radius: var(--timeline-border-radius);
padding: var(--header-button-padding);
margin: var(--vertical-between-posts-header);
transform: translateY(var(--header-vertical-offset));
}
.container { .container {
border: var(--border-width) solid var(--border-color); border: var(--border-width) solid var(--border-color);
background-color: var(--main-bg-color); background-color: var(--main-bg-color);
@ -1040,11 +934,13 @@ div.container {
background-color: var(--column-left-header-background); background-color: var(--column-left-header-background);
color: var(--column-left-header-color); color: var(--column-left-header-color);
font-size: var(--column-left-header-size); font-size: var(--column-left-header-size);
text-transform: uppercase; text-transform: var(--column-left-header-style);
padding: 4px; padding: 4px;
border: none; border: none;
} }
.newswireItem { .newswireItem {
padding-top: 0;
margin-top: 0;
font-size: var(--font-size-newswire); font-size: var(--font-size-newswire);
color: var(--column-right-fg-color); color: var(--column-right-fg-color);
line-height: var(--line-spacing-newswire); line-height: var(--line-spacing-newswire);
@ -1102,6 +998,7 @@ div.container {
width: var(--column-left-width); width: var(--column-left-width);
} }
.col-left { .col-left {
border: var(--column-left-border-width) solid var(--column-left-border-color);
color: var(--column-left-fg-color); color: var(--column-left-fg-color);
font-size: var(--font-size-links); font-size: var(--font-size-links);
float: left; float: left;
@ -1109,7 +1006,6 @@ div.container {
} }
.col-left img.leftColEdit { .col-left img.leftColEdit {
background: var(--column-left-color); background: var(--column-left-color);
margin: 40px 0;
width: var(--column-left-icon-size); width: var(--column-left-icon-size);
} }
.col-left img.leftColEditImage { .col-left img.leftColEditImage {
@ -1142,6 +1038,7 @@ div.container {
overflow: hidden; overflow: hidden;
} }
.col-right { .col-right {
border: var(--column-right-border-width) solid var(--column-left-border-color);
background-color: var(--column-left-color); background-color: var(--column-left-color);
color: var(--column-left-fg-color); color: var(--column-left-fg-color);
font-size: var(--font-size-links); font-size: var(--font-size-links);
@ -1150,7 +1047,6 @@ div.container {
} }
.col-right img.rightColEdit { .col-right img.rightColEdit {
background: var(--column-left-color); background: var(--column-left-color);
margin: 40px 0;
width: var(--column-right-icon-size); width: var(--column-right-icon-size);
} }
.col-right img.rightColEditImage { .col-right img.rightColEditImage {
@ -1605,40 +1501,6 @@ div.container {
border-radius: 1%; border-radius: 1%;
width: 15%; width: 15%;
} }
#msgscope label img {
width: 46px;
height: 46px;
padding: 0px 0px;
}
.toggle-msgScope img {
width: 32px;
height: 32px;
padding: 0px 0px;
}
.dropdown-menutoggle {
-webkit-margin-start: 0px;
-webkit-margin-end: 0px;
-webkit-padding-start: 40px;
border-top-left-radius: 0;
border-top-right-radius: 0;
position: absolute;
top: 100%;
left: 21px;
width: 300%;
min-width: 100%;
z-index: 1000;
display: block;
float: left;
padding: 0 17px !important;
margin: 2px 0 0 !important;
font-size: var(--font-size2);
text-align: left;
list-style: none;
color: var(--dropdown-fg-color);
background-color: var(--dropdown-bg-color);
-webkit-background-clip: padding-box;
background-clip: padding-box;
}
input[type=checkbox] input[type=checkbox]
{ {
-ms-transform: scale(2); -ms-transform: scale(2);
@ -1695,6 +1557,14 @@ div.container {
font-size: var(--font-size); font-size: var(--font-size);
line-height: var(--line-spacing); line-height: var(--line-spacing);
} }
.containerHeader {
border: var(--border-width-header) solid var(--border-color);
background-color: var(--main-bg-color);
border-radius: var(--timeline-border-radius);
padding: var(--header-button-padding);
margin: var(--vertical-between-posts-header);
transform: translateY(0%);
}
.container { .container {
border: var(--border-width) solid var(--border-color); border: var(--border-width) solid var(--border-color);
background-color: var(--main-bg-color); background-color: var(--main-bg-color);
@ -1709,7 +1579,7 @@ div.container {
background-color: var(--column-left-header-background); background-color: var(--column-left-header-background);
color: var(--column-left-header-color); color: var(--column-left-header-color);
font-size: var(--column-left-header-size-mobile); font-size: var(--column-left-header-size-mobile);
text-transform: uppercase; text-transform: var(--column-left-header-style);
padding: 4px; padding: 4px;
border: none; border: none;
} }
@ -1740,6 +1610,8 @@ div.container {
padding: 0 0; padding: 0 0;
} }
.newswireItem { .newswireItem {
padding-top: 0;
margin-top: 0;
font-size: var(--font-size-newswire-mobile); font-size: var(--font-size-newswire-mobile);
color: var(--column-right-fg-color); color: var(--column-right-fg-color);
line-height: var(--line-spacing-newswire); line-height: var(--line-spacing-newswire);
@ -2262,41 +2134,6 @@ div.container {
border-radius: 1%; border-radius: 1%;
width: 25%; width: 25%;
} }
#msgscope label img {
width: 64px;
height: 64px;
padding: 0px 0px;
}
.toggle-msgScope img {
width: 64px;
height: 64px;
margin: -15px 0px;
padding: 0px 20px;
}
.dropdown-menutoggle {
-webkit-margin-start: 0px;
-webkit-margin-end: 0px;
-webkit-padding-start: 40px;
border-top-left-radius: 0;
border-top-right-radius: 0;
position: absolute;
top: 100%;
left: 21px;
width: 460%;
min-width: 100%;
z-index: 1000;
display: block;
float: left;
padding: 0 17px !important;
margin: 2px 0 0 !important;
font-size: var(--font-size3);
text-align: left;
list-style: none;
color: var(--dropdown-fg-color);
background-color: var(--dropdown-bg-color);
-webkit-background-clip: padding-box;
background-clip: padding-box;
}
input[type=checkbox] input[type=checkbox]
{ {
-ms-transform: scale(4); -ms-transform: scale(4);

View File

@ -205,10 +205,11 @@ def unfollowPerson(baseDir: str, nickname: str, domain: str,
return return
with open(filename, "r") as f: with open(filename, "r") as f:
lines = f.readlines() lines = f.readlines()
with open(filename, 'w+') as f: with open(filename, 'w+') as f:
for line in lines: for line in lines:
if line.strip("\n").strip("\r").lower() != handleToUnfollowLower: if line.strip("\n").strip("\r").lower() != \
f.write(line) handleToUnfollowLower:
f.write(line)
# write to an unfollowed file so that if a follow accept # write to an unfollowed file so that if a follow accept
# later arrives then it can be ignored # later arrives then it can be ignored
@ -216,7 +217,7 @@ def unfollowPerson(baseDir: str, nickname: str, domain: str,
if os.path.isfile(unfollowedFilename): if os.path.isfile(unfollowedFilename):
if handleToUnfollowLower not in \ if handleToUnfollowLower not in \
open(unfollowedFilename).read().lower(): open(unfollowedFilename).read().lower():
with open(filename, "a+") as f: with open(unfollowedFilename, "a+") as f:
f.write(handleToUnfollow + '\n') f.write(handleToUnfollow + '\n')
else: else:
with open(unfollowedFilename, "w+") as f: with open(unfollowedFilename, "w+") as f:

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1019 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -725,8 +725,9 @@ def runNewswireDaemon(baseDir: str, httpd,
mergeWithPreviousNewswire(httpd.newswire, newNewswire) mergeWithPreviousNewswire(httpd.newswire, newNewswire)
httpd.newswire = newNewswire httpd.newswire = newNewswire
saveJson(httpd.newswire, newswireStateFilename) if newNewswire:
print('Newswire updated') saveJson(httpd.newswire, newswireStateFilename)
print('Newswire updated')
convertRSStoActivityPub(baseDir, convertRSStoActivityPub(baseDir,
httpPrefix, domain, port, httpPrefix, domain, port,

View File

@ -1228,6 +1228,31 @@ def createBlogPost(baseDir: str,
schedulePost, schedulePost,
eventDate, eventTime, location) eventDate, eventTime, location)
blog['object']['type'] = 'Article' blog['object']['type'] = 'Article'
# append citations tags, stored in a file
citationsFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/.citations.txt'
if os.path.isfile(citationsFilename):
citationsSeparator = '#####'
with open(citationsFilename, "r") as f:
citations = f.readlines()
for line in citations:
if citationsSeparator not in line:
continue
sections = line.strip().split(citationsSeparator)
if len(sections) != 3:
continue
# dateStr = sections[0]
title = sections[1]
link = sections[2]
tagJson = {
"type": "Article",
"name": title,
"url": link
}
blog['object']['tag'].append(tagJson)
return blog return blog

View File

@ -1678,7 +1678,6 @@ def testWebLinks():
'some.incredibly.long.and.annoying.word.which.should.be.removed: ' + \ 'some.incredibly.long.and.annoying.word.which.should.be.removed: ' + \
'The remaining text' 'The remaining text'
resultText = removeLongWords(exampleText, 40, []) resultText = removeLongWords(exampleText, 40, [])
print('resultText: ' + resultText)
assert resultText == \ assert resultText == \
'some.incredibly.long.and.annoying.word.w\n' + \ 'some.incredibly.long.and.annoying.word.w\n' + \
'hich.should.be.removed: The remaining text' 'hich.should.be.removed: The remaining text'

View File

@ -313,7 +313,8 @@ def setThemeDefault(baseDir: str):
} }
themeParams = { themeParams = {
"banner-height": "20vh", "banner-height": "20vh",
"banner-height-mobile": "10vh" "banner-height-mobile": "10vh",
"search-banner-height-mobile": "15vh"
} }
setThemeFromDict(baseDir, name, themeParams, bgParams) setThemeFromDict(baseDir, name, themeParams, bgParams)
@ -462,6 +463,11 @@ def setThemeNight(baseDir: str):
fontStrItalic = \ fontStrItalic = \
"url('./fonts/solidaric-italic.woff2') format('woff2')" "url('./fonts/solidaric-italic.woff2') format('woff2')"
themeParams = { themeParams = {
"main-visited-color": "#0481f5",
"post-separator-margin-top": "9%",
"post-separator-margin-bottom": "9%",
"post-separator-width": "80%",
"post-separator-height": "10%",
"column-left-header-background": "#07447c", "column-left-header-background": "#07447c",
"banner-height": "15vh", "banner-height": "15vh",
"banner-height-mobile": "10vh", "banner-height-mobile": "10vh",
@ -476,7 +482,7 @@ def setThemeNight(baseDir: str):
"column-left-color": "#0f0d10", "column-left-color": "#0f0d10",
"text-entry-background": "#0f0d10", "text-entry-background": "#0f0d10",
"link-bg-color": "#0f0d10", "link-bg-color": "#0f0d10",
"main-link-color": "ff9900", "main-link-color": "#6481f5",
"main-link-color-hover": "#d09338", "main-link-color-hover": "#d09338",
"main-fg-color": "#0481f5", "main-fg-color": "#0481f5",
"column-left-fg-color": "#0481f5", "column-left-fg-color": "#0481f5",
@ -600,6 +606,7 @@ def setThemeHenge(baseDir: str):
setRssIconAtTop(baseDir, True) setRssIconAtTop(baseDir, True)
setPublishButtonAtTop(baseDir, False) setPublishButtonAtTop(baseDir, False)
themeParams = { themeParams = {
"banner-height": "25vh",
"column-left-image-width-mobile": "40vw", "column-left-image-width-mobile": "40vw",
"column-right-image-width-mobile": "40vw", "column-right-image-width-mobile": "40vw",
"font-size-button-mobile": "26px", "font-size-button-mobile": "26px",
@ -1019,14 +1026,49 @@ def setThemeIndymediaModern(baseDir: str):
fontStrItalic = \ fontStrItalic = \
"url('./fonts/NimbusSanL-italic.otf') format('opentype')" "url('./fonts/NimbusSanL-italic.otf') format('opentype')"
themeParams = { themeParams = {
"follow-text-size1": "14px",
"follow-text-size2": "30px",
"hashtag-size1": "20px",
"hashtag-size2": "30px",
"font-size-calendar-header": "2rem",
"font-size-calendar-cell": "2rem",
"calendar-horizontal-padding": "20%",
"time-vertical-align": "10px",
"header-vertical-offset": "-10%",
"publish-button-vertical-offset": "0",
"vertical-between-posts-header": "0 0",
"header-button-padding": "0 0",
"containericons-horizontal-spacing": "0%",
"font-size-header": "14px",
"font-size": "22px",
"font-size2": "16px",
"font-size3": "30px",
"font-size4": "14px",
"font-size5": "12px",
"font-size-likes": "10px",
"font-size-links": "12px",
"font-size-newswire": "12px",
"font-size-newswire-mobile": "30px",
"font-size-dropdown-header": "30px",
"post-separator-margin-top": "1%",
"post-separator-margin-bottom": "1%",
"post-separator-width": "95%",
"post-separator-height": "1px",
"column-left-border-width": "1px",
"column-right-border-width": "0px",
"column-left-border-color": "black",
"column-left-header-color": "black",
"column-left-header-background": "white",
"column-left-header-style": "none",
"search-banner-height": "15vh", "search-banner-height": "15vh",
"search-banner-height-mobile": "10vh", "search-banner-height-mobile": "10vh",
"publish-button-vertical-offset": "10px",
"container-button-padding": "0px", "container-button-padding": "0px",
"container-button-margin": "0px", "container-button-margin": "0px",
"column-right-icon-size": "11%", "column-left-icon-size": "15%",
"column-right-icon-size": "15%",
"button-height-padding": "5px", "button-height-padding": "5px",
"icon-brightness-change": "70%", "icon-brightness-change": "70%",
"border-width": "0px",
"border-width-header": "0px", "border-width-header": "0px",
"tab-border-width": "3px", "tab-border-width": "3px",
"tab-border-color": "grey", "tab-border-color": "grey",
@ -1034,8 +1076,8 @@ def setThemeIndymediaModern(baseDir: str):
"login-button-color": "#25408f", "login-button-color": "#25408f",
"login-button-fg-color": "white", "login-button-fg-color": "white",
"column-left-width": "10vw", "column-left-width": "10vw",
"column-center-width": "70vw", "column-center-width": "80vw",
"column-right-width": "20vw", "column-right-width": "10vw",
"column-right-fg-color": "#25408f", "column-right-fg-color": "#25408f",
"column-right-fg-color-voted-on": "red", "column-right-fg-color-voted-on": "red",
"newswire-item-moderated-color": "red", "newswire-item-moderated-color": "red",
@ -1054,12 +1096,7 @@ def setThemeIndymediaModern(baseDir: str):
"hashtag-background-color": "#b2b2b2", "hashtag-background-color": "#b2b2b2",
"focus-color": "grey", "focus-color": "grey",
"font-size-button-mobile": "26px", "font-size-button-mobile": "26px",
"font-size-publish-button": "26px", "font-size-publish-button": "14px",
"font-size": "32px",
"font-size2": "26px",
"font-size3": "40px",
"font-size4": "24px",
"font-size5": "22px",
"rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)", "rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)",
"column-left-color": "white", "column-left-color": "white",
"main-bg-color": "white", "main-bg-color": "white",
@ -1108,7 +1145,7 @@ def setThemeIndymediaModern(baseDir: str):
} }
setThemeFromDict(baseDir, name, themeParams, bgParams) setThemeFromDict(baseDir, name, themeParams, bgParams)
setNewswirePublishAsIcon(baseDir, False) setNewswirePublishAsIcon(baseDir, False)
setFullWidthTimelineButtonHeader(baseDir, True) setFullWidthTimelineButtonHeader(baseDir, False)
setIconsAsButtons(baseDir, True) setIconsAsButtons(baseDir, True)
setRssIconAtTop(baseDir, False) setRssIconAtTop(baseDir, False)
setPublishButtonAtTop(baseDir, True) setPublishButtonAtTop(baseDir, True)
@ -1117,8 +1154,15 @@ def setThemeIndymediaModern(baseDir: str):
def setThemeSolidaric(baseDir: str): def setThemeSolidaric(baseDir: str):
name = 'solidaric' name = 'solidaric'
themeParams = { themeParams = {
"button-corner-radius": "5px",
"column-left-icons-margin": "15px",
"post-separator-width": "96.5%",
"post-separator-height": "40px",
"border-width-header": "0",
"border-width": "0",
"banner-height": "35vh", "banner-height": "35vh",
"banner-height-mobile": "15vh", "banner-height-mobile": "15vh",
"search-banner-height-mobile": "15vh",
"time-vertical-align": "-4px", "time-vertical-align": "-4px",
"time-vertical-align-mobile": "15px", "time-vertical-align-mobile": "15px",
"hashtag-background-color": "lightred", "hashtag-background-color": "lightred",

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "إزالة مشاركات Twitter", "Remove Twitter posts": "إزالة مشاركات Twitter",
"Sensitive": "حساس", "Sensitive": "حساس",
"Word Replacements": "استبدال الكلمات", "Word Replacements": "استبدال الكلمات",
"Happening Today": "يحدث اليوم", "Happening Today": "اليوم",
"Happening This Week": "يحدث هذا الاسبوع", "Happening This Week": "هكذا",
"Blog": "مدونة", "Blog": "مدونة",
"Blogs": "المدونات", "Blogs": "المدونات",
"Title": "عنوان", "Title": "عنوان",
@ -325,5 +325,7 @@
"Features" : "ميزات", "Features" : "ميزات",
"Article": "مقال إخباري", "Article": "مقال إخباري",
"Create an article": "قم بإنشاء مقال", "Create an article": "قم بإنشاء مقال",
"Settings": "إعدادات" "Settings": "إعدادات",
"Citations": "اقتباسات",
"Choose newswire items referenced in your article": "اختر العناصر الإخبارية المشار إليها في مقالتك"
} }

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "Elimina les publicacions de Twitter", "Remove Twitter posts": "Elimina les publicacions de Twitter",
"Sensitive": "Sensible", "Sensitive": "Sensible",
"Word Replacements": "Substitucions de paraula", "Word Replacements": "Substitucions de paraula",
"Happening Today": "Passant avui", "Happening Today": "Avui",
"Happening This Week": "Passa aquesta setmana", "Happening This Week": "Aviat",
"Blog": "Bloc", "Blog": "Bloc",
"Blogs": "Blocs", "Blogs": "Blocs",
"Title": "Títol", "Title": "Títol",
@ -325,5 +325,7 @@
"Features" : "Article", "Features" : "Article",
"Article": "Reportatge", "Article": "Reportatge",
"Create an article": "Creeu un article", "Create an article": "Creeu un article",
"Settings": "Configuració" "Settings": "Configuració",
"Citations": "Cites",
"Choose newswire items referenced in your article": "Trieu articles de newswire als quals faci referència el vostre article"
} }

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "Dileu postiadau Twitter", "Remove Twitter posts": "Dileu postiadau Twitter",
"Sensitive": "Sensitif", "Sensitive": "Sensitif",
"Word Replacements": "Amnewidiadau Geiriau", "Word Replacements": "Amnewidiadau Geiriau",
"Happening Today": "Digwydd Heddiw", "Happening Today": "Heddiw",
"Happening This Week": "Yn Digwydd Yr Wythnos Hon", "Happening This Week": "Yn fuan",
"Blog": "Blog", "Blog": "Blog",
"Blogs": "Blogs", "Blogs": "Blogs",
"Title": "Teitl", "Title": "Teitl",
@ -325,5 +325,7 @@
"Features" : "Nodweddion", "Features" : "Nodweddion",
"Article": "Erthygl", "Article": "Erthygl",
"Create an article": "Creu erthygl", "Create an article": "Creu erthygl",
"Settings": "Gosodiadau" "Settings": "Gosodiadau",
"Citations": "Dyfyniadau",
"Choose newswire items referenced in your article": "Dewiswch eitemau newyddion y cyfeirir atynt yn eich erthygl"
} }

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "Entfernen Sie Twitter-Posts", "Remove Twitter posts": "Entfernen Sie Twitter-Posts",
"Sensitive": "Empfindlich", "Sensitive": "Empfindlich",
"Word Replacements": "Wortersetzungen", "Word Replacements": "Wortersetzungen",
"Happening Today": "Heute passiert", "Happening Today": "Heute",
"Happening This Week": "Diese Woche passiert", "Happening This Week": "Demnächst",
"Blog": "Blog", "Blog": "Blog",
"Blogs": "Blogs", "Blogs": "Blogs",
"Title": "Titel", "Title": "Titel",
@ -325,5 +325,7 @@
"Features" : "Eigenschaften", "Features" : "Eigenschaften",
"Article": "Artikel", "Article": "Artikel",
"Create an article": "Erstellen Sie einen Artikel", "Create an article": "Erstellen Sie einen Artikel",
"Settings": "Einstellungen" "Settings": "Einstellungen",
"Citations": "Zitate",
"Choose newswire items referenced in your article": "Wählen Sie Newswire-Artikel aus, auf die in Ihrem Artikel verwiesen wird"
} }

View File

@ -213,7 +213,7 @@
"Sensitive": "Sensitive", "Sensitive": "Sensitive",
"Word Replacements": "Word Replacements", "Word Replacements": "Word Replacements",
"Happening Today": "Today", "Happening Today": "Today",
"Happening This Week": "This Week", "Happening This Week": "Soon",
"Blog": "Blog", "Blog": "Blog",
"Blogs": "Blogs", "Blogs": "Blogs",
"Title": "Title", "Title": "Title",
@ -325,5 +325,7 @@
"Features" : "Features", "Features" : "Features",
"Article": "Article", "Article": "Article",
"Create an article": "Create an article", "Create an article": "Create an article",
"Settings": "Settings" "Settings": "Settings",
"Citations": "Citations",
"Choose newswire items referenced in your article": "Choose newswire items referenced in your article"
} }

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "Eliminar publicaciones de Twitter", "Remove Twitter posts": "Eliminar publicaciones de Twitter",
"Sensitive": "Sensible", "Sensitive": "Sensible",
"Word Replacements": "Reemplazos de palabras", "Word Replacements": "Reemplazos de palabras",
"Happening Today": "Sucediendo hoy", "Happening Today": "Hoy",
"Happening This Week": "Sucediendo esta semana", "Happening This Week": "Pronto",
"Blog": "Blog", "Blog": "Blog",
"Blogs": "Blogs", "Blogs": "Blogs",
"Title": "Título", "Title": "Título",
@ -325,5 +325,7 @@
"Features" : "Caracteristicas", "Features" : "Caracteristicas",
"Article": "Artículo", "Article": "Artículo",
"Create an article": "Crea un articulo", "Create an article": "Crea un articulo",
"Settings": "Configuraciones" "Settings": "Configuraciones",
"Citations": "Citas",
"Choose newswire items referenced in your article": "Elija elementos de Newswire a los que se hace referencia en su artículo"
} }

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "Supprimer les messages Twitter", "Remove Twitter posts": "Supprimer les messages Twitter",
"Sensitive": "Sensible", "Sensitive": "Sensible",
"Word Replacements": "Remplacements de mots", "Word Replacements": "Remplacements de mots",
"Happening Today": "Se passe aujourd'hui", "Happening Today": "Aujourd'hui",
"Happening This Week": "Se passe cette semaine", "Happening This Week": "Bientôt",
"Blog": "Blog", "Blog": "Blog",
"Blogs": "Blogs", "Blogs": "Blogs",
"Title": "Titre", "Title": "Titre",
@ -325,5 +325,7 @@
"Features" : "Traits", "Features" : "Traits",
"Article": "Article", "Article": "Article",
"Create an article": "Créer un article", "Create an article": "Créer un article",
"Settings": "Réglages" "Settings": "Réglages",
"Citations": "Citations",
"Choose newswire items referenced in your article": "Choisissez les éléments de fil d'actualité référencés dans votre article"
} }

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "Bain poist Twitter", "Remove Twitter posts": "Bain poist Twitter",
"Sensitive": "Íogair", "Sensitive": "Íogair",
"Word Replacements": "Athchur Focal", "Word Replacements": "Athchur Focal",
"Happening Today": "Ag tarlú inniu", "Happening Today": "Inniu",
"Happening This Week": "Ag tarlú an tseachtain seo", "Happening This Week": "Go gairid",
"Blog": "Blag", "Blog": "Blag",
"Blogs": "Blaganna", "Blogs": "Blaganna",
"Title": "Teideal", "Title": "Teideal",
@ -325,5 +325,7 @@
"Features" : "Gnéithe", "Features" : "Gnéithe",
"Article": "Airteagal", "Article": "Airteagal",
"Create an article": "Cruthaigh alt", "Create an article": "Cruthaigh alt",
"Settings": "Socruithe" "Settings": "Socruithe",
"Citations": "Citations",
"Choose newswire items referenced in your article": "Roghnaigh míreanna sreanga nuachta dá dtagraítear i dalt"
} }

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "ट्विटर पोस्ट हटाएं", "Remove Twitter posts": "ट्विटर पोस्ट हटाएं",
"Sensitive": "संवेदनशील", "Sensitive": "संवेदनशील",
"Word Replacements": "शब्द प्रतिस्थापन", "Word Replacements": "शब्द प्रतिस्थापन",
"Happening Today": "आज हो रहा है", "Happening Today": "आज",
"Happening This Week": "इस सप्ताह हो रहा है", "Happening This Week": "जल्द ही",
"Blog": "ब्लॉग", "Blog": "ब्लॉग",
"Blogs": "ब्लॉग", "Blogs": "ब्लॉग",
"Title": "शीर्षक", "Title": "शीर्षक",
@ -325,5 +325,7 @@
"Features" : "विशेषताएं", "Features" : "विशेषताएं",
"Article": "लेख", "Article": "लेख",
"Create an article": "एक लेख बनाएँ", "Create an article": "एक लेख बनाएँ",
"Settings": "समायोजन" "Settings": "समायोजन",
"Citations": "उद्धरण",
"Choose newswire items referenced in your article": "अपने लेख में संदर्भित newswire आइटम चुनें"
} }

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "Rimuovi i post di Twitter", "Remove Twitter posts": "Rimuovi i post di Twitter",
"Sensitive": "Sensibile", "Sensitive": "Sensibile",
"Word Replacements": "Sostituzioni di parole", "Word Replacements": "Sostituzioni di parole",
"Happening Today": "Succede oggi", "Happening Today": "Oggi",
"Happening This Week": "Succede questa settimana", "Happening This Week": "Presto",
"Blog": "Blog", "Blog": "Blog",
"Blogs": "Blog", "Blogs": "Blog",
"Title": "Titolo", "Title": "Titolo",
@ -325,5 +325,7 @@
"Features" : "Caratteristiche", "Features" : "Caratteristiche",
"Article": "Articolo", "Article": "Articolo",
"Create an article": "Crea un articolo", "Create an article": "Crea un articolo",
"Settings": "impostazioni" "Settings": "impostazioni",
"Citations": "Citazioni",
"Choose newswire items referenced in your article": "Scegli gli articoli del newswire a cui fa riferimento il tuo articolo"
} }

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "Twitterの投稿を削除する", "Remove Twitter posts": "Twitterの投稿を削除する",
"Sensitive": "敏感", "Sensitive": "敏感",
"Word Replacements": "単語の置換", "Word Replacements": "単語の置換",
"Happening Today": "今日の出来事", "Happening Today": "今日",
"Happening This Week": "今週の出来事", "Happening This Week": "すぐに",
"Blog": "ブログ", "Blog": "ブログ",
"Blogs": "ブログ", "Blogs": "ブログ",
"Title": "題名", "Title": "題名",
@ -325,5 +325,7 @@
"Features" : "特徴", "Features" : "特徴",
"Article": "論文", "Article": "論文",
"Create an article": "記事を作成する", "Create an article": "記事を作成する",
"Settings": "設定" "Settings": "設定",
"Citations": "引用",
"Choose newswire items referenced in your article": "あなたの記事で参照されているニュースワイヤーアイテムを選択してください"
} }

View File

@ -209,7 +209,7 @@
"Sensitive": "Sensitive", "Sensitive": "Sensitive",
"Word Replacements": "Word Replacements", "Word Replacements": "Word Replacements",
"Happening Today": "Happening Today", "Happening Today": "Happening Today",
"Happening This Week": "Happening This Week", "Happening This Week": "Soon",
"Blog": "Blog", "Blog": "Blog",
"Blogs": "Blogs", "Blogs": "Blogs",
"Title": "Title", "Title": "Title",
@ -321,5 +321,7 @@
"Features" : "Features", "Features" : "Features",
"Article": "Article", "Article": "Article",
"Create an article": "Create an article", "Create an article": "Create an article",
"Settings": "Settings" "Settings": "Settings",
"Citations": "Citations",
"Choose newswire items referenced in your article": "Choose newswire items referenced in your article"
} }

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "Remover postagens do Twitter", "Remove Twitter posts": "Remover postagens do Twitter",
"Sensitive": "Sensível", "Sensitive": "Sensível",
"Word Replacements": "Substituições do Word", "Word Replacements": "Substituições do Word",
"Happening Today": "Acontecendo hoje", "Happening Today": "Hoje",
"Happening This Week": "Acontecendo Esta Semana", "Happening This Week": "Em breve",
"Blog": "Blog", "Blog": "Blog",
"Blogs": "Blogs", "Blogs": "Blogs",
"Title": "Título", "Title": "Título",
@ -325,5 +325,7 @@
"Features" : "Recursos", "Features" : "Recursos",
"Article": "Artigo", "Article": "Artigo",
"Create an article": "Crie um artigo", "Create an article": "Crie um artigo",
"Settings": "Definições" "Settings": "Definições",
"Citations": "Citações",
"Choose newswire items referenced in your article": "Escolha os itens de notícias mencionados em seu artigo"
} }

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "Удалить сообщения из Твиттера", "Remove Twitter posts": "Удалить сообщения из Твиттера",
"Sensitive": "чувствительный", "Sensitive": "чувствительный",
"Word Replacements": "Замены слов", "Word Replacements": "Замены слов",
"Happening Today": "Происходит сегодня", "Happening Today": "Cегодня",
"Happening This Week": "Происходит на этой неделе", "Happening This Week": "Скоро",
"Blog": "Блог", "Blog": "Блог",
"Blogs": "Блоги", "Blogs": "Блоги",
"Title": "заглавие", "Title": "заглавие",
@ -325,5 +325,7 @@
"Features" : "особенности", "Features" : "особенности",
"Article": "Статья", "Article": "Статья",
"Create an article": "Создать статью", "Create an article": "Создать статью",
"Settings": "Настройки" "Settings": "Настройки",
"Citations": "Цитаты",
"Choose newswire items referenced in your article": "Выберите элементы ленты новостей, на которые есть ссылки в вашей статье"
} }

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "删除Twitter帖子", "Remove Twitter posts": "删除Twitter帖子",
"Sensitive": "敏感", "Sensitive": "敏感",
"Word Replacements": "单词替换", "Word Replacements": "单词替换",
"Happening Today": "今天发生", "Happening Today": "今天",
"Happening This Week": "本周发生", "Happening This Week": "不久",
"Blog": "博客", "Blog": "博客",
"Blogs": "网志", "Blogs": "网志",
"Title": "标题", "Title": "标题",
@ -325,5 +325,7 @@
"Features" : "特征", "Features" : "特征",
"Article": "文章", "Article": "文章",
"Create an article": "建立文章", "Create an article": "建立文章",
"Settings": "设定值" "Settings": "设定值",
"Citations": "引文",
"Choose newswire items referenced in your article": "选择文章中引用的新闻专栏文章"
} }

View File

@ -736,6 +736,7 @@ def htmlHashtagSearch(cssCache: {},
return None return None
iconsDir = getIconsDir(baseDir) iconsDir = getIconsDir(baseDir)
separatorStr = htmlPostSeparator(baseDir, None)
# check that the directory for the nickname exists # check that the directory for the nickname exists
if nickname: if nickname:
@ -829,7 +830,7 @@ def htmlHashtagSearch(cssCache: {},
if nickname: if nickname:
showIndividualPostIcons = True showIndividualPostIcons = True
allowDeletion = False allowDeletion = False
hashtagSearchForm += \ hashtagSearchForm += separatorStr + \
individualPostAsHtml(True, recentPostsCache, individualPostAsHtml(True, recentPostsCache,
maxRecentPosts, maxRecentPosts,
iconsDir, translate, None, iconsDir, translate, None,
@ -1165,6 +1166,7 @@ def htmlHistorySearch(cssCache: {}, translate: {}, baseDir: str,
return historySearchForm return historySearchForm
iconsDir = getIconsDir(baseDir) iconsDir = getIconsDir(baseDir)
separatorStr = htmlPostSeparator(baseDir, None)
# ensure that the page number is in bounds # ensure that the page number is in bounds
if not pageNumber: if not pageNumber:
@ -1191,7 +1193,7 @@ def htmlHistorySearch(cssCache: {}, translate: {}, baseDir: str,
continue continue
showIndividualPostIcons = True showIndividualPostIcons = True
allowDeletion = False allowDeletion = False
historySearchForm += \ historySearchForm += separatorStr + \
individualPostAsHtml(True, recentPostsCache, individualPostAsHtml(True, recentPostsCache,
maxRecentPosts, maxRecentPosts,
iconsDir, translate, None, iconsDir, translate, None,
@ -1282,7 +1284,7 @@ def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
' <center>\n' + \ ' <center>\n' + \
' <input type="submit" name="submitLinks" value="' + \ ' <input type="submit" name="submitLinks" value="' + \
translate['Submit'] + '">\n' + \ translate['Submit'] + '">\n' + \
' <center>\n' ' </center>\n'
editLinksForm += \ editLinksForm += \
' </div>\n' ' </div>\n'
@ -1365,7 +1367,7 @@ def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str,
' <center>\n' + \ ' <center>\n' + \
' <input type="submit" name="submitNewswire" value="' + \ ' <input type="submit" name="submitNewswire" value="' + \
translate['Submit'] + '">\n' + \ translate['Submit'] + '">\n' + \
' <center>\n' ' </center>\n'
editNewswireForm += \ editNewswireForm += \
' </div>\n' ' </div>\n'
@ -2413,6 +2415,111 @@ def htmlSuspended(cssCache: {}, baseDir: str) -> str:
return suspendedForm return suspendedForm
def htmlNewPostDropDown(scopeIcon: str, scopeDescription: str,
replyStr: str,
translate: {},
iconsDir: str,
showPublicOnDropdown: bool,
defaultTimeline: str,
pathBase: str,
dropdownNewPostSuffix: str,
dropdownNewBlogSuffix: str,
dropdownUnlistedSuffix: str,
dropdownFollowersSuffix: str,
dropdownDMSuffix: str,
dropdownReminderSuffix: str,
dropdownEventSuffix: str,
dropdownReportSuffix: str) -> str:
"""Returns the html for a drop down list of new post types
"""
dropDownContent = '<div class="newPostDropdown">\n'
dropDownContent += ' <input type="checkbox" ' + \
'id="my-newPostDropdown" value="" name="my-checkbox">\n'
dropDownContent += ' <label for="my-newPostDropdown"\n'
dropDownContent += ' data-toggle="newPostDropdown">\n'
dropDownContent += ' <img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/' + scopeIcon + '"/><b>' + \
scopeDescription + '</b></label>\n'
dropDownContent += ' <ul>\n'
if showPublicOnDropdown:
dropDownContent += \
'<li><a href="' + pathBase + dropdownNewPostSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_public.png"/><b>' + \
translate['Public'] + '</b><br>' + \
translate['Visible to anyone'] + '</a></li>\n'
if defaultTimeline == 'tlnews':
dropDownContent += \
'<li><a href="' + pathBase + dropdownNewBlogSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/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="/' + \
iconsDir + '/scope_blog.png"/><b>' + \
translate['Blog'] + '</b><br>' + \
translate['Publicly visible post'] + '</a></li>\n'
dropDownContent += \
'<li><a href="' + pathBase + dropdownUnlistedSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_unlisted.png"/><b>' + \
translate['Unlisted'] + '</b><br>' + \
translate['Not on public timeline'] + '</a></li>\n'
dropDownContent += \
'<li><a href="' + pathBase + dropdownFollowersSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/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="/' + \
iconsDir + '/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="/' + \
iconsDir + '/scope_reminder.png"/><b>' + \
translate['Reminder'] + '</b><br>' + \
translate['Scheduled note to yourself'] + '</a></li>\n'
dropDownContent += \
'<li><a href="' + pathBase + dropdownEventSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_event.png"/><b>' + \
translate['Event'] + '</b><br>' + \
translate['Create an event'] + '</a></li>\n'
dropDownContent += \
'<li><a href="' + pathBase + dropdownReportSuffix + \
'"><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_report.png"/><b>' + \
translate['Report'] + '</b><br>' + \
translate['Send to moderators'] + '</a></li>\n'
if not replyStr:
dropDownContent += \
'<li><a href="' + pathBase + \
'/newshare"><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_share.png"/><b>' + \
translate['Shares'] + '</b><br>' + \
translate['Describe a shared item'] + '</a></li>\n'
dropDownContent += \
'<li><a href="' + pathBase + \
'/newquestion"><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_question.png"/><b>' + \
translate['Question'] + '</b><br>' + \
translate['Ask a question'] + '</a></li>\n'
dropDownContent += ' </ul>\n'
dropDownContent += '</div>\n'
return dropDownContent
def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {}, def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
baseDir: str, httpPrefix: str, baseDir: str, httpPrefix: str,
path: str, inReplyTo: str, path: str, inReplyTo: str,
@ -2420,7 +2527,7 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
reportUrl: str, pageNumber: int, reportUrl: str, pageNumber: int,
nickname: str, domain: str, nickname: str, domain: str,
domainFull: str, domainFull: str,
defaultTimeline: str) -> str: defaultTimeline: str, newswire: {}) -> str:
"""New post screen """New post screen
""" """
iconsDir = getIconsDir(baseDir) iconsDir = getIconsDir(baseDir)
@ -2643,6 +2750,33 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
extraFields += '<input type="text" name="location">\n' extraFields += '<input type="text" name="location">\n'
extraFields += '</div>\n' extraFields += '</div>\n'
citationsStr = ''
if endpoint == 'newblog':
citationsFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/.citations.txt'
if os.path.isfile(citationsFilename):
citationsStr = '<div class="container">\n'
citationsStr += '<p><label class="labels">' + \
translate['Citations'] + ':</label></p>\n'
citationsStr += ' <ul>\n'
citationsSeparator = '#####'
with open(citationsFilename, "r") as f:
citations = f.readlines()
for line in citations:
if citationsSeparator not in line:
continue
sections = line.strip().split(citationsSeparator)
if len(sections) != 3:
continue
title = sections[1]
link = sections[2]
citationsStr += \
' <li><a href="' + link + '"><cite>' + \
title + '</cite></a></li>'
citationsStr += ' </ul>\n'
citationsStr += '</div>\n'
dateAndLocation = '' dateAndLocation = ''
if endpoint != 'newshare' and \ if endpoint != 'newshare' and \
endpoint != 'newreport' and \ endpoint != 'newreport' and \
@ -2791,21 +2925,6 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
newPostForm += '<img loading="lazy" class="timeline-banner" src="' + \ newPostForm += '<img loading="lazy" class="timeline-banner" src="' + \
'/users/' + nickname + '/' + bannerFile + '" /></a>\n' '/users/' + nickname + '/' + bannerFile + '" /></a>\n'
# only show the share option if this is not a reply
shareOptionOnDropdown = ''
questionOptionOnDropdown = ''
if not replyStr:
shareOptionOnDropdown = \
' <a href="' + pathBase + \
'/newshare"><li><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_share.png"/><b>' + translate['Shares'] + \
'</b><br>' + translate['Describe a shared item'] + '</li></a>\n'
questionOptionOnDropdown = \
' <a href="' + pathBase + \
'/newquestion"><li><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_question.png"/><b>' + translate['Question'] + \
'</b><br>' + translate['Ask a question'] + '</li></a>\n'
mentionsStr = '' mentionsStr = ''
for m in mentions: for m in mentions:
mentionNickname = getNicknameFromActor(m) mentionNickname = getNicknameFromActor(m)
@ -2858,89 +2977,22 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
dropDownContent = '' dropDownContent = ''
if not reportUrl: if not reportUrl:
dropDownContent += "<div class='msgscope-collapse collapse " dropDownContent = \
dropDownContent += "right desktoponly' id='msgscope'>\n" htmlNewPostDropDown(scopeIcon, scopeDescription,
dropDownContent += " <ul class='nav msgscope-nav msgscope-right'>\n" replyStr,
dropDownContent += " <li class=' ' style='position: relative;'>\n" translate,
dropDownContent += " <div class='toggle-msgScope button-msgScope'>\n" iconsDir,
dropDownContent += " <input id='toggleMsgScope' " showPublicOnDropdown,
dropDownContent += "name='toggleMsgScope' type='checkbox'/>\n" defaultTimeline,
dropDownContent += " <label for='toggleMsgScope'>\n" pathBase,
dropDownContent += " <div class='lined-thin'>\n" dropdownNewPostSuffix,
dropDownContent += ' <img loading="lazy" alt="" title="" src="/' dropdownNewBlogSuffix,
dropDownContent += iconsDir + '/' + scopeIcon dropdownUnlistedSuffix,
dropDownContent += '"/><b class="scope-desc">' dropdownFollowersSuffix,
dropDownContent += scopeDescription + '</b>\n' dropdownDMSuffix,
dropDownContent += " <span class='caret'/>\n" dropdownReminderSuffix,
dropDownContent += " </div>\n" dropdownEventSuffix,
dropDownContent += " </label>\n" dropdownReportSuffix)
dropDownContent += " <div class='toggle-inside'>\n"
dropDownContent += " <ul aria-labelledby='dropdownMsgScope' "
dropDownContent += "class='dropdown-menutoggle'>\n"
if showPublicOnDropdown:
dropDownContent += " " \
'<a href="' + pathBase + dropdownNewPostSuffix + \
'"><li><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_public.png"/><b>' + \
translate['Public'] + '</b><br>' + \
translate['Visible to anyone'] + '</li></a>\n'
if defaultTimeline == 'tlnews':
dropDownContent += " " \
'<a href="' + pathBase + dropdownNewBlogSuffix + \
'"><li><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_blog.png"/><b>' + \
translate['Article'] + '</b><br>' + \
translate['Create an article'] + '</li></a>\n'
else:
dropDownContent += " " \
'<a href="' + pathBase + dropdownNewBlogSuffix + \
'"><li><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_blog.png"/><b>' + \
translate['Blog'] + '</b><br>' + \
translate['Publicly visible post'] + '</li></a>\n'
dropDownContent += " " \
'<a href="' + pathBase + dropdownUnlistedSuffix + \
'"><li><img loading="lazy" alt="" title="" src="/' + \
iconsDir+'/scope_unlisted.png"/><b>' + \
translate['Unlisted'] + '</b><br>' + \
translate['Not on public timeline'] + '</li></a>\n'
dropDownContent += " " \
'<a href="' + pathBase + dropdownFollowersSuffix + \
'"><li><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_followers.png"/><b>' + \
translate['Followers'] + '</b><br>' + \
translate['Only to followers'] + '</li></a>\n'
dropDownContent += " " \
'<a href="' + pathBase + dropdownDMSuffix + \
'"><li><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_dm.png"/><b>' + translate['DM'] + \
'</b><br>' + translate['Only to mentioned people'] + \
'</li></a>\n'
dropDownContent += " " \
'<a href="' + pathBase + dropdownReminderSuffix + \
'"><li><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_reminder.png"/><b>' + translate['Reminder'] + \
'</b><br>' + translate['Scheduled note to yourself'] + \
'</li></a>\n'
dropDownContent += " " \
'<a href="' + pathBase + dropdownEventSuffix + \
'"><li><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_event.png"/><b>' + translate['Event'] + \
'</b><br>' + translate['Create an event'] + \
'</li></a>\n'
dropDownContent += " " \
'<a href="' + pathBase + dropdownReportSuffix + \
'"><li><img loading="lazy" alt="" title="" src="/' + iconsDir + \
'/scope_report.png"/><b>' + translate['Report'] + \
'</b><br>' + translate['Send to moderators'] + '</li></a>\n'
dropDownContent += questionOptionOnDropdown + shareOptionOnDropdown
dropDownContent += ' </ul>\n'
dropDownContent += ' </div>\n'
dropDownContent += ' </div>\n'
dropDownContent += ' </li>\n'
dropDownContent += ' </ul>\n'
dropDownContent += '</div>\n'
else: else:
mentionsStr = 'Re: ' + reportUrl + '\n\n' + mentionsStr mentionsStr = 'Re: ' + reportUrl + '\n\n' + mentionsStr
@ -2966,13 +3018,22 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
newPostForm += ' </div>\n' newPostForm += ' </div>\n'
newPostForm += ' <div class="containerSubmitNewPost"><center>\n' newPostForm += ' <div class="containerSubmitNewPost"><center>\n'
# newPostForm += \ # newPostForm += \
# ' <a href="' + pathBase + \ # ' <a href="' + pathBase + \
# '/inbox"><button class="cancelbtn">' + \ # '/inbox"><button class="cancelbtn">' + \
# translate['Go Back'] + '</button></a>\n' # translate['Go Back'] + '</button></a>\n'
# for a new blog if newswire items exist then add a citations button
if newswire and path.endswith('/newblog'):
newPostForm += \
' <input type="submit" name="submitCitations" value="' + \
translate['Citations'] + '">\n'
newPostForm += \ newPostForm += \
' <input type="submit" name="submitPost" value="' + \ ' <input type="submit" name="submitPost" value="' + \
translate['Submit'] + '">\n' translate['Submit'] + '">\n'
newPostForm += ' </center></div>\n' newPostForm += ' </center></div>\n'
newPostForm += replyStr newPostForm += replyStr
@ -3018,7 +3079,7 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
newPostForm += \ newPostForm += \
' <textarea id="message" name="message" style="height:' + \ ' <textarea id="message" name="message" style="height:' + \
str(messageBoxHeight) + 'px"' + selectedStr + '></textarea>\n' str(messageBoxHeight) + 'px"' + selectedStr + '></textarea>\n'
newPostForm += extraFields+dateAndLocation newPostForm += extraFields + citationsStr + dateAndLocation
if not mediaInstance or replyStr: if not mediaInstance or replyStr:
newPostForm += newPostImageSection newPostForm += newPostImageSection
newPostForm += ' </div>\n' newPostForm += ' </div>\n'
@ -3079,6 +3140,7 @@ def htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
These should only be public posts These should only be public posts
""" """
iconsDir = getIconsDir(baseDir) iconsDir = getIconsDir(baseDir)
separatorStr = htmlPostSeparator(baseDir, None)
profileStr = '' profileStr = ''
maxItems = 4 maxItems = 4
ctr = 0 ctr = 0
@ -3111,7 +3173,7 @@ def htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
showPublishedDateOnly, showPublishedDateOnly,
False, False, False, True, False) False, False, False, True, False)
if postStr: if postStr:
profileStr += postStr profileStr += separatorStr + postStr
ctr += 1 ctr += 1
if ctr >= maxItems: if ctr >= maxItems:
break break
@ -3329,6 +3391,7 @@ def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
'" alt="' + translate['Page up'] + '"></a>\n' + \ '" alt="' + translate['Page up'] + '"></a>\n' + \
' </center>\n' ' </center>\n'
separatorStr = htmlPostSeparator(baseDir, None)
for published, item in sharesJson.items(): for published, item in sharesJson.items():
showContactButton = False showContactButton = False
if item['actor'] != actor: if item['actor'] != actor:
@ -3336,7 +3399,7 @@ def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
showRemoveButton = False showRemoveButton = False
if item['actor'] == actor: if item['actor'] == actor:
showRemoveButton = True showRemoveButton = True
timelineStr += \ timelineStr += separatorStr + \
htmlIndividualShare(actor, item, translate, htmlIndividualShare(actor, item, translate,
showContactButton, showRemoveButton) showContactButton, showRemoveButton)
@ -5524,6 +5587,29 @@ def individualPostAsHtml(allowDownloads: bool,
'<div class="gitpatch"><pre><code>' + contentStr + \ '<div class="gitpatch"><pre><code>' + contentStr + \
'</code></pre></div>\n' '</code></pre></div>\n'
# show blog citations
citationsStr = ''
if boxName == 'tlblog':
if postJsonObject['object'].get('tag'):
for tagJson in postJsonObject['object']['tag']:
if not isinstance(tagJson, dict):
continue
if not tagJson.get('type'):
continue
if tagJson['type'] != 'Article':
continue
if not tagJson.get('name'):
continue
if not tagJson.get('url'):
continue
citationsStr += \
'<li><a href="' + tagJson['url'] + '">' + \
'<cite>' + tagJson['name'] + '</cite></a></li>\n'
if citationsStr:
citationsStr = '<p><b>' + translate['Citations'] + \
':</b></p>' + \
'<ul>\n' + citationsStr + '</ul>\n'
postHtml = '' postHtml = ''
if boxName != 'tlmedia': if boxName != 'tlmedia':
postHtml = ' <div id="' + timelinePostBookmark + \ postHtml = ' <div id="' + timelinePostBookmark + \
@ -5532,7 +5618,7 @@ def individualPostAsHtml(allowDownloads: bool,
postHtml += ' <div class="post-title">\n' + \ postHtml += ' <div class="post-title">\n' + \
' ' + titleStr + \ ' ' + titleStr + \
replyAvatarImageInPost + ' </div>\n' replyAvatarImageInPost + ' </div>\n'
postHtml += contentStr + footerStr + '\n' postHtml += contentStr + citationsStr + footerStr + '\n'
postHtml += ' </div>\n' postHtml += ' </div>\n'
else: else:
postHtml = galleryStr postHtml = galleryStr
@ -5599,6 +5685,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
""" """
htmlStr = '' htmlStr = ''
separatorStr = htmlPostSeparator(baseDir, 'left')
domain = domainFull domain = domainFull
if ':' in domain: if ':' in domain:
domain = domain.split(':') domain = domain.split(':')
@ -5648,6 +5735,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
if editImageClass == 'leftColEdit': if editImageClass == 'leftColEdit':
htmlStr += '\n <center>\n' htmlStr += '\n <center>\n'
htmlStr += ' <div class="leftColIcons">\n'
if editor: if editor:
# show the edit icon # show the edit icon
htmlStr += \ htmlStr += \
@ -5676,6 +5764,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
'" src="/' + iconsDir + '/logorss.png" /></a>\n' '" src="/' + iconsDir + '/logorss.png" /></a>\n'
if rssIconAtTop: if rssIconAtTop:
htmlStr += rssIconStr htmlStr += rssIconStr
htmlStr += ' </div>\n'
if editImageClass == 'leftColEdit': if editImageClass == 'leftColEdit':
htmlStr += ' </center>\n' htmlStr += ' </center>\n'
@ -5683,8 +5772,8 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
if (editor or rssIconAtTop) and not showHeaderImage: if (editor or rssIconAtTop) and not showHeaderImage:
htmlStr += '</div><br>' htmlStr += '</div><br>'
if showHeaderImage: # if showHeaderImage:
htmlStr += '<br>' # htmlStr += '<br>'
linksFilename = baseDir + '/accounts/links.txt' linksFilename = baseDir + '/accounts/links.txt'
linksFileContainsEntries = False linksFileContainsEntries = False
@ -5725,6 +5814,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
else: else:
if lineStr.startswith('#') or lineStr.startswith('*'): if lineStr.startswith('#') or lineStr.startswith('*'):
lineStr = lineStr[1:].strip() lineStr = lineStr[1:].strip()
htmlStr += separatorStr
htmlStr += \ htmlStr += \
' <h3 class="linksHeader">' + \ ' <h3 class="linksHeader">' + \
lineStr + '</h3>\n' lineStr + '</h3>\n'
@ -5752,10 +5842,11 @@ def votesIndicator(totalVotes: int, positiveVoting: bool) -> str:
return totalVotesStr return totalVotesStr
def htmlNewswire(newswire: {}, nickname: str, moderator: bool, def htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
translate: {}, positiveVoting: bool, iconsDir: str) -> str: translate: {}, positiveVoting: bool, iconsDir: str) -> str:
"""Converts a newswire dict into html """Converts a newswire dict into html
""" """
separatorStr = htmlPostSeparator(baseDir, 'right')
htmlStr = '' htmlStr = ''
for dateStr, item in newswire.items(): for dateStr, item in newswire.items():
publishedDate = \ publishedDate = \
@ -5765,6 +5856,7 @@ def htmlNewswire(newswire: {}, nickname: str, moderator: bool,
dateStrLink = dateStr.replace('T', ' ') dateStrLink = dateStr.replace('T', ' ')
dateStrLink = dateStrLink.replace('Z', '') dateStrLink = dateStrLink.replace('Z', '')
moderatedItem = item[5] moderatedItem = item[5]
htmlStr += separatorStr
if moderatedItem and 'vote:' + nickname in item[2]: if moderatedItem and 'vote:' + nickname in item[2]:
totalVotesStr = '' totalVotesStr = ''
totalVotes = 0 totalVotes = 0
@ -5821,6 +5913,118 @@ def htmlNewswire(newswire: {}, nickname: str, moderator: bool,
return htmlStr return htmlStr
def htmlCitations(baseDir: str, nickname: str, domain: str,
httpPrefix: str, defaultTimeline: str,
translate: {}, newswire: {}, cssCache: {},
blogTitle: str, blogContent: str,
blogImageFilename: str,
blogImageAttachmentMediaType: str,
blogImageDescription: str) -> str:
"""Show the citations screen when creating a blog
"""
htmlStr = ''
# create a list of dates for citations
# these can then be used to re-select checkboxes later
citationsFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/.citations.txt'
citationsSelected = []
if os.path.isfile(citationsFilename):
citationsSeparator = '#####'
with open(citationsFilename, "r") as f:
citations = f.readlines()
for line in citations:
if citationsSeparator not in line:
continue
sections = line.strip().split(citationsSeparator)
if len(sections) != 3:
continue
dateStr = sections[0]
citationsSelected.append(dateStr)
# the css filename
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
profileStyle = getCSS(baseDir, cssFilename, cssCache)
if profileStyle:
# replace any https within the css with whatever prefix is needed
if httpPrefix != 'https':
profileStyle = \
profileStyle.replace('https://', httpPrefix + '://')
# iconsDir = getIconsDir(baseDir)
htmlStr = htmlHeader(cssFilename, profileStyle)
# top banner
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
htmlStr += \
'<a href="/users/' + nickname + '/newblog" title="' + \
translate['Go Back'] + '" alt="' + \
translate['Go Back'] + '">\n'
htmlStr += '<img loading="lazy" class="timeline-banner" src="' + \
'/users/' + nickname + '/' + bannerFile + '" /></a>\n'
htmlStr += \
'<form enctype="multipart/form-data" method="POST" ' + \
'accept-charset="UTF-8" action="/users/' + nickname + \
'/citationsdata">\n'
htmlStr += ' <center>\n'
htmlStr += translate['Choose newswire items ' +
'referenced in your article'] + '<br>'
if blogTitle is None:
blogTitle = ''
htmlStr += \
' <input type="hidden" name="blogTitle" value="' + \
blogTitle + '">\n'
if blogContent is None:
blogContent = ''
htmlStr += \
' <input type="hidden" name="blogContent" value="' + \
blogContent + '">\n'
# submit button
htmlStr += \
' <input type="submit" name="submitCitations" value="' + \
translate['Submit'] + '">\n'
htmlStr += ' </center>\n'
citationsSeparator = '#####'
# list of newswire items
if newswire:
ctr = 0
for dateStr, item in newswire.items():
# should this checkbox be selected?
selectedStr = ''
if dateStr in citationsSelected:
selectedStr = ' checked'
publishedDate = \
datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S%z")
dateShown = publishedDate.strftime("%Y-%m-%d %H:%M")
title = removeLongWords(item[0], 16, []).replace('\n', '<br>')
link = item[1]
citationValue = \
dateStr + citationsSeparator + \
title + citationsSeparator + \
link
htmlStr += \
'<input type="checkbox" name="newswire' + str(ctr) + \
'" value="' + citationValue + '"' + selectedStr + '/>' + \
'<a href="' + link + '"><cite>' + title + '</cite></a> '
htmlStr += '<span class="newswireDate">' + \
dateShown + '</span><br>\n'
ctr += 1
htmlStr += '</form>\n'
return htmlStr + htmlFooter()
def getRightColumnContent(baseDir: str, nickname: str, domainFull: str, def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
httpPrefix: str, translate: {}, httpPrefix: str, translate: {},
iconsDir: str, moderator: bool, editor: bool, iconsDir: str, moderator: bool, editor: bool,
@ -5968,7 +6172,7 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
# show the newswire lines # show the newswire lines
newswireContentStr = \ newswireContentStr = \
htmlNewswire(newswire, nickname, moderator, translate, htmlNewswire(baseDir, newswire, nickname, moderator, translate,
positiveVoting, iconsDir) positiveVoting, iconsDir)
htmlStr += newswireContentStr htmlStr += newswireContentStr
@ -6348,6 +6552,54 @@ def headerButtonsTimeline(defaultTimeline: str,
inboxButton + '"><span>' + translate['Inbox'] + \ inboxButton + '"><span>' + translate['Inbox'] + \
'</span></button></a>' '</span></button></a>'
# show todays events buttons on the first inbox page
happeningStr = ''
if boxName == 'inbox' and pageNumber == 1:
if todaysEventsCheck(baseDir, nickname, domain):
now = datetime.now()
# happening today button
if not iconsAsButtons:
happeningStr += \
'<a href="' + usersPath + '/calendar?year=' + \
str(now.year) + '?month=' + str(now.month) + \
'?day=' + str(now.day) + '">' + \
'<button class="buttonevent">' + \
translate['Happening Today'] + '</button></a>'
else:
happeningStr += \
'<a href="' + usersPath + '/calendar?year=' + \
str(now.year) + '?month=' + str(now.month) + \
'?day=' + str(now.day) + '">' + \
'<button class="button">' + \
translate['Happening Today'] + '</button></a>'
# happening this week button
if thisWeeksEventsCheck(baseDir, nickname, domain):
if not iconsAsButtons:
happeningStr += \
'<a href="' + usersPath + \
'/calendar"><button class="buttonevent">' + \
translate['Happening This Week'] + '</button></a>'
else:
happeningStr += \
'<a href="' + usersPath + \
'/calendar"><button class="button">' + \
translate['Happening This Week'] + '</button></a>'
else:
# happening this week button
if thisWeeksEventsCheck(baseDir, nickname, domain):
if not iconsAsButtons:
happeningStr += \
'<a href="' + usersPath + \
'/calendar"><button class="buttonevent">' + \
translate['Happening This Week'] + '</button></a>'
else:
happeningStr += \
'<a href="' + usersPath + \
'/calendar"><button class="button">' + \
translate['Happening This Week'] + '</button></a>'
if not newsHeader: if not newsHeader:
# button for the outbox # button for the outbox
tlStr += \ tlStr += \
@ -6359,66 +6611,20 @@ def headerButtonsTimeline(defaultTimeline: str,
# add other buttons # add other buttons
tlStr += \ tlStr += \
sharesButtonStr + bookmarksButtonStr + eventsButtonStr + \ sharesButtonStr + bookmarksButtonStr + eventsButtonStr + \
moderationButtonStr + newPostButtonStr moderationButtonStr + happeningStr + newPostButtonStr
# show todays events buttons on the first inbox page
if boxName == 'inbox' and pageNumber == 1:
if todaysEventsCheck(baseDir, nickname, domain):
now = datetime.now()
# happening today button
if not iconsAsButtons:
tlStr += \
'<a href="' + usersPath + '/calendar?year=' + \
str(now.year) + '?month=' + str(now.month) + \
'?day=' + str(now.day) + '">' + \
'<button class="buttonevent">' + \
translate['Happening Today'] + '</button></a>'
else:
tlStr += \
'<a href="' + usersPath + '/calendar?year=' + \
str(now.year) + '?month=' + str(now.month) + \
'?day=' + str(now.day) + '">' + \
'<button class="button">' + \
translate['Happening Today'] + '</button></a>'
# happening this week button
if thisWeeksEventsCheck(baseDir, nickname, domain):
if not iconsAsButtons:
tlStr += \
'<a href="' + usersPath + \
'/calendar"><button class="buttonevent">' + \
translate['Happening This Week'] + '</button></a>'
else:
tlStr += \
'<a href="' + usersPath + \
'/calendar"><button class="button">' + \
translate['Happening This Week'] + '</button></a>'
else:
# happening this week button
if thisWeeksEventsCheck(baseDir, nickname, domain):
if not iconsAsButtons:
tlStr += \
'<a href="' + usersPath + \
'/calendar"><button class="buttonevent">' + \
translate['Happening This Week'] + '</button></a>'
else:
tlStr += \
'<a href="' + usersPath + \
'/calendar"><button class="button">' + \
translate['Happening This Week'] + '</button></a>'
if not newsHeader: if not newsHeader:
if not iconsAsButtons: if not iconsAsButtons:
# the search button # the search icon
tlStr += \ tlStr += \
' <a class="imageAnchor" href="' + usersPath + \ '<a class="imageAnchor" href="' + usersPath + \
'/search"><img loading="lazy" src="/' + \ '/search"><img loading="lazy" src="/' + \
iconsDir + '/search.png" title="' + \ iconsDir + '/search.png" title="' + \
translate['Search and follow'] + '" alt="| ' + \ translate['Search and follow'] + '" alt="| ' + \
translate['Search and follow'] + \ translate['Search and follow'] + \
'" class="timelineicon"/></a>' '" class="timelineicon"/></a>'
else: else:
# the search button
tlStr += \ tlStr += \
'<a href="' + usersPath + \ '<a href="' + usersPath + \
'/search"><button class="button">' + \ '/search"><button class="button">' + \
@ -6501,6 +6707,8 @@ def headerButtonsTimeline(defaultTimeline: str,
'/links.png" title="' + translate['Edit Links'] + \ '/links.png" title="' + translate['Edit Links'] + \
'" alt="| ' + translate['Edit Links'] + \ '" alt="| ' + translate['Edit Links'] + \
'" class="timelineicon"/></a>' '" class="timelineicon"/></a>'
# end of headericons div
tlStr += '</div>'
else: else:
# NOTE: deliberately no \n at end of line # NOTE: deliberately no \n at end of line
tlStr += \ tlStr += \
@ -6523,6 +6731,23 @@ def headerButtonsTimeline(defaultTimeline: str,
return tlStr return tlStr
def htmlPostSeparator(baseDir: str, column: str) -> str:
"""Returns the html for a timeline post separator image
"""
iconsDir = getIconsDir(baseDir)
filename = 'separator.png'
if column:
filename = 'separator_' + column + '.png'
separatorImageFilename = baseDir + '/img/' + iconsDir + '/' + filename
separatorStr = ''
if os.path.isfile(separatorImageFilename):
separatorStr = \
'<div class="postSeparatorImage"><center>' + \
'<img src="/' + iconsDir + '/' + filename + '"/>' + \
'</center></div>\n'
return separatorStr
def htmlTimeline(cssCache: {}, defaultTimeline: str, def htmlTimeline(cssCache: {}, defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int, recentPostsCache: {}, maxRecentPosts: int,
translate: {}, pageNumber: int, translate: {}, pageNumber: int,
@ -6598,6 +6823,10 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
# This changes depending upon theme # This changes depending upon theme
iconsDir = getIconsDir(baseDir) iconsDir = getIconsDir(baseDir)
separatorStr = ''
if boxName != 'tlmedia':
separatorStr = htmlPostSeparator(baseDir, None)
# the css filename # the css filename
cssFilename = baseDir + '/epicyon-profile.css' cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'): if os.path.isfile(baseDir + '/epicyon.css'):
@ -6760,10 +6989,21 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
if timeDiff > 100: if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 4 = ' + str(timeDiff)) print('TIMELINE TIMING ' + boxName + ' 4 = ' + str(timeDiff))
# if this is a news instance and we are viewing the news timeline
newsHeader = False
if defaultTimeline == 'tlnews' and boxName == 'tlnews':
newsHeader = True
newPostButtonStr = ''
# start of headericons div
if not newsHeader:
if not iconsAsButtons:
newPostButtonStr += '<div class="headericons">'
# what screen to go to when a new post is created # what screen to go to when a new post is created
if boxName == 'dm': if boxName == 'dm':
if not iconsAsButtons: if not iconsAsButtons:
newPostButtonStr = \ newPostButtonStr += \
'<a class="imageAnchor" href="' + usersPath + \ '<a class="imageAnchor" href="' + usersPath + \
'/newdm"><img loading="lazy" src="/' + \ '/newdm"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \ iconsDir + '/newpost.png" title="' + \
@ -6771,13 +7011,13 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
'" alt="| ' + translate['Create a new DM'] + \ '" alt="| ' + translate['Create a new DM'] + \
'" class="timelineicon"/></a>\n' '" class="timelineicon"/></a>\n'
else: else:
newPostButtonStr = \ newPostButtonStr += \
'<a href="' + usersPath + '/newdm">' + \ '<a href="' + usersPath + '/newdm">' + \
'<button class="button"><span>' + \ '<button class="button"><span>' + \
translate['Post'] + ' </span></button></a>' translate['Post'] + ' </span></button></a>'
elif boxName == 'tlblogs' or boxName == 'tlnews': elif boxName == 'tlblogs' or boxName == 'tlnews':
if not iconsAsButtons: if not iconsAsButtons:
newPostButtonStr = \ newPostButtonStr += \
'<a class="imageAnchor" href="' + usersPath + \ '<a class="imageAnchor" href="' + usersPath + \
'/newblog"><img loading="lazy" src="/' + \ '/newblog"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \ iconsDir + '/newpost.png" title="' + \
@ -6785,13 +7025,13 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
translate['Create a new post'] + \ translate['Create a new post'] + \
'" class="timelineicon"/></a>\n' '" class="timelineicon"/></a>\n'
else: else:
newPostButtonStr = \ newPostButtonStr += \
'<a href="' + usersPath + '/newblog">' + \ '<a href="' + usersPath + '/newblog">' + \
'<button class="button"><span>' + \ '<button class="button"><span>' + \
translate['Post'] + '</span></button></a>' translate['Post'] + '</span></button></a>'
elif boxName == 'tlevents': elif boxName == 'tlevents':
if not iconsAsButtons: if not iconsAsButtons:
newPostButtonStr = \ newPostButtonStr += \
'<a class="imageAnchor" href="' + usersPath + \ '<a class="imageAnchor" href="' + usersPath + \
'/newevent"><img loading="lazy" src="/' + \ '/newevent"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \ iconsDir + '/newpost.png" title="' + \
@ -6799,14 +7039,14 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
translate['Create a new event'] + \ translate['Create a new event'] + \
'" class="timelineicon"/></a>\n' '" class="timelineicon"/></a>\n'
else: else:
newPostButtonStr = \ newPostButtonStr += \
'<a href="' + usersPath + '/newevent">' + \ '<a href="' + usersPath + '/newevent">' + \
'<button class="button"><span>' + \ '<button class="button"><span>' + \
translate['Post'] + '</span></button></a>' translate['Post'] + '</span></button></a>'
else: else:
if not manuallyApproveFollowers: if not manuallyApproveFollowers:
if not iconsAsButtons: if not iconsAsButtons:
newPostButtonStr = \ newPostButtonStr += \
'<a class="imageAnchor" href="' + usersPath + \ '<a class="imageAnchor" href="' + usersPath + \
'/newpost"><img loading="lazy" src="/' + \ '/newpost"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \ iconsDir + '/newpost.png" title="' + \
@ -6814,13 +7054,13 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
translate['Create a new post'] + \ translate['Create a new post'] + \
'" class="timelineicon"/></a>\n' '" class="timelineicon"/></a>\n'
else: else:
newPostButtonStr = \ newPostButtonStr += \
'<a href="' + usersPath + '/newpost">' + \ '<a href="' + usersPath + '/newpost">' + \
'<button class="button"><span>' + \ '<button class="button"><span>' + \
translate['Post'] + '</span></button></a>' translate['Post'] + '</span></button></a>'
else: else:
if not iconsAsButtons: if not iconsAsButtons:
newPostButtonStr = \ newPostButtonStr += \
'<a class="imageAnchor" href="' + usersPath + \ '<a class="imageAnchor" href="' + usersPath + \
'/newfollowers"><img loading="lazy" src="/' + \ '/newfollowers"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \ iconsDir + '/newpost.png" title="' + \
@ -6828,10 +7068,11 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
'" alt="| ' + translate['Create a new post'] + \ '" alt="| ' + translate['Create a new post'] + \
'" class="timelineicon"/></a>\n' '" class="timelineicon"/></a>\n'
else: else:
newPostButtonStr = \ newPostButtonStr += \
'<a href="' + usersPath + '/newfollowers">' + \ '<a href="' + usersPath + '/newfollowers">' + \
'<button class="button"><span>' + \ '<button class="button"><span>' + \
translate['Post'] + '</span></button></a>' translate['Post'] + '</span></button></a>'
# This creates a link to the profile page when viewed # This creates a link to the profile page when viewed
# in lynx, but should be invisible in a graphical web browser # in lynx, but should be invisible in a graphical web browser
tlStr += \ tlStr += \
@ -7056,6 +7297,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
if currTlStr: if currTlStr:
itemCtr += 1 itemCtr += 1
if separatorStr:
tlStr += separatorStr
tlStr += currTlStr tlStr += currTlStr
if boxName == 'tlmedia': if boxName == 'tlmedia':
tlStr += '</div>\n' tlStr += '</div>\n'
@ -8499,7 +8742,8 @@ def htmlCalendar(cssCache: {}, translate: {},
' <img loading="lazy" alt="' + translate['Previous month'] + \ ' <img loading="lazy" alt="' + translate['Previous month'] + \
'" title="' + translate['Previous month'] + '" src="/' + iconsDir + \ '" title="' + translate['Previous month'] + '" src="/' + iconsDir + \
'/prev.png" class="buttonprev"/></a>\n' '/prev.png" class="buttonprev"/></a>\n'
calendarStr += ' <a href="' + calActor + '/inbox">' calendarStr += ' <a href="' + calActor + '/inbox" title="'
calendarStr += translate['Switch to timeline view'] + '">'
calendarStr += ' <h1>' + monthName + '</h1></a>\n' calendarStr += ' <h1>' + monthName + '</h1></a>\n'
calendarStr += \ calendarStr += \
' <a href="' + calActor + '/calendar?year=' + str(nextYear) + \ ' <a href="' + calActor + '/calendar?year=' + str(nextYear) + \