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

merge-requests/30/head
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')
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:
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 follow import getFollowingFeed
from follow import sendFollowRequest
from follow import unfollowPerson
from auth import authorize
from auth import createPassword
from auth import createBasicAuthHeader
@ -112,6 +113,7 @@ from blog import htmlBlogView
from blog import htmlBlogPage
from blog import htmlBlogPost
from blog import htmlEditBlog
from webinterface import htmlCitations
from webinterface import htmlFollowingList
from webinterface import getBlogAddress
from webinterface import setBlogAddress
@ -1799,8 +1801,7 @@ class PubServer(BaseHTTPRequestHandler):
# person options screen, unfollow button
# See htmlPersonOptions
if '&submitUnfollow=' in optionsConfirmParams:
if debug:
print('Unfollowing ' + optionsActor)
print('Unfollowing ' + optionsActor)
msg = \
htmlUnfollowConfirm(self.server.cssCache,
self.server.translate,
@ -1830,7 +1831,8 @@ class PubServer(BaseHTTPRequestHandler):
chooserNickname,
domain,
domainFull,
self.server.defaultTimeline).encode('utf-8')
self.server.defaultTimeline,
self.server.newswire).encode('utf-8')
self._set_headers('text/html', len(msg),
cookie, callingDomain)
self._write(msg)
@ -1897,7 +1899,8 @@ class PubServer(BaseHTTPRequestHandler):
chooserNickname,
domain,
domainFull,
self.server.defaultTimeline).encode('utf-8')
self.server.defaultTimeline,
self.server.newswire).encode('utf-8')
self._set_headers('text/html', len(msg),
cookie, callingDomain)
self._write(msg)
@ -1956,6 +1959,11 @@ class PubServer(BaseHTTPRequestHandler):
followingNickname = getNicknameFromActor(followingActor)
followingDomain, followingPort = \
getDomainFromActor(followingActor)
followingDomainFull = followingDomain
if followingPort:
if followingPort != 80 and followingPort != 443:
followingDomainFull = \
followingDomain + ':' + str(followingPort)
if followerNickname == followingNickname and \
followingDomain == domain and \
followingPort == port:
@ -1984,6 +1992,9 @@ class PubServer(BaseHTTPRequestHandler):
}
pathUsersSection = path.split('/users/')[1]
self.postToNickname = pathUsersSection.split('/')[0]
unfollowPerson(self.server.baseDir, self.postToNickname,
self.server.domain,
followingNickname, followingDomainFull)
self._postToOutboxThread(unfollowJson)
if callingDomain.endswith('.onion') and onionDomain:
@ -3045,6 +3056,109 @@ class PubServer(BaseHTTPRequestHandler):
cookie, callingDomain)
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,
authorized: bool, path: str,
baseDir: str, httpPrefix: str,
@ -8494,7 +8608,8 @@ class PubServer(BaseHTTPRequestHandler):
replyPageNumber,
nickname, domain,
domainFull,
self.server.defaultTimeline).encode('utf-8')
self.server.defaultTimeline,
self.server.newswire).encode('utf-8')
if not msg:
print('Error replying to ' + inReplyToUrl)
self._404()
@ -10756,9 +10871,8 @@ class PubServer(BaseHTTPRequestHandler):
filename.endswith('.webp') or \
filename.endswith('.avif') or \
filename.endswith('.gif'):
if self.server.debug:
print('DEBUG: POST media removing metadata')
postImageFilename = filename.replace('.temp', '')
print('Removing metadata from ' + postImageFilename)
removeMetaData(filename, postImageFilename)
if os.path.isfile(postImageFilename):
print('POST media saved to ' + postImageFilename)
@ -10779,15 +10893,24 @@ class PubServer(BaseHTTPRequestHandler):
else:
print('WARN: no text fields could be extracted from POST')
# process the received text fields from the POST
if not fields.get('message') and \
not fields.get('imageDescription'):
return -1
if fields.get('submitPost'):
if fields['submitPost'] != 'Submit':
# was the citations button pressed on the newblog screen?
citationsButtonPress = False
if postType == 'newblog' and fields.get('submitCitations'):
if fields['submitCitations'] == \
self.server.translate['Citations']:
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
else:
return 2
if fields.get('submitPost'):
if fields['submitPost'] != \
self.server.translate['Submit']:
return -1
else:
return 2
if not fields.get('imageDescription'):
fields['imageDescription'] = None
@ -10809,19 +10932,20 @@ class PubServer(BaseHTTPRequestHandler):
if not fields.get('location'):
fields['location'] = None
# Store a file which contains the time in seconds
# since epoch when an attempt to post something was made.
# This is then used for active monthly users counts
lastUsedFilename = \
self.server.baseDir + '/accounts/' + \
nickname + '@' + self.server.domain + '/.lastUsed'
try:
lastUsedFile = open(lastUsedFilename, 'w+')
if lastUsedFile:
lastUsedFile.write(str(int(time.time())))
lastUsedFile.close()
except BaseException:
pass
if not citationsButtonPress:
# Store a file which contains the time in seconds
# since epoch when an attempt to post something was made.
# This is then used for active monthly users counts
lastUsedFilename = \
self.server.baseDir + '/accounts/' + \
nickname + '@' + self.server.domain + '/.lastUsed'
try:
lastUsedFile = open(lastUsedFilename, 'w+')
if lastUsedFile:
lastUsedFile.write(str(int(time.time())))
lastUsedFile.close()
except BaseException:
pass
mentionsStr = ''
if fields.get('mentions'):
@ -10866,6 +10990,31 @@ class PubServer(BaseHTTPRequestHandler):
else:
return -1
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 = \
createBlogPost(self.server.baseDir, nickname,
self.server.domain, self.server.port,
@ -10876,8 +11025,10 @@ class PubServer(BaseHTTPRequestHandler):
fields['imageDescription'],
self.server.useBlurHash,
fields['replyTo'], fields['replyTo'],
fields['subject'], fields['schedulePost'],
fields['eventDate'], fields['eventTime'],
fields['subject'],
fields['schedulePost'],
fields['eventDate'],
fields['eventTime'],
fields['location'])
if messageJson:
if fields['schedulePost']:
@ -11637,6 +11788,17 @@ class PubServer(BaseHTTPRequestHandler):
self.server.defaultTimeline)
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'):
self._newsPostEdit(callingDomain, cookie, authorized, self.path,
self.server.baseDir, self.server.httpPrefix,

View File

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

View File

@ -30,6 +30,7 @@
--font-size-publish-button: 18px;
--font-size-newswire: 18px;
--font-size-newswire-mobile: 40px;
--font-size-dropdown-header: 40px;
--font-size: 30px;
--font-size2: 24px;
--font-size3: 38px;
@ -82,10 +83,15 @@
--column-left-width: 10vw;
--column-center-width: 80vw;
--column-right-width: 10vw;
--column-left-header-style: uppercase;
--column-left-header-background: #555;
--column-left-header-color: #fff;
--column-left-header-size: 20px;
--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-mobile: 10%;
--column-left-image-width-mobile: 40vw;
@ -105,6 +111,7 @@
--tab-border-color: grey;
--icon-brightness-change: 150%;
--container-button-padding: 20px;
--header-button-padding: 20px;
--container-padding: 2%;
--container-padding-bottom: 1%;
--container-padding-bottom-mobile: 0%;
@ -118,6 +125,11 @@
--publish-button-vertical-offset: 10px;
--banner-height: 15vh;
--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 {
@ -146,6 +158,27 @@ body, html {
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 {
border-left: 10px;
margin: 1.5em 10px;
@ -399,14 +432,6 @@ a:focus {
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 {
width: 80%;
border-radius: 5px;
@ -670,23 +695,6 @@ input[type=submit]:hover {
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 {
margin: 10px auto;
padding: 0px 0px;
@ -711,39 +719,102 @@ input[type=submit]:hover {
padding: 0px 30px;
}
/* Dropdown Content (Hidden by Default) */
.dropdown-content {
display: none;
position: absolute;
background-color: var(--dropdown-bg-color);
min-width: 600px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
/* new post dropdown */
.newPostDropdown {
position: relative;
}
/* Links inside the dropdown */
.dropdown-content a {
background-color: var(--dropdown-bg-color);
color: var(--dropdown-fg-color);
padding: 12px 16px;
text-decoration: none;
display: block;
.newPostDropdown img {
width: var(--font-size-dropdown-header);
}
.dropdown-content img {
width: 32px;
height: 32px;
padding: 0px 0px;
.newPostDropdown > input[type="checkbox"] {
position: absolute;
left: -100vw;
}
/* Change color of dropdown links on hover */
.dropdown-content a:hover {
color: var(--dropdown-fg-color-hover);
background-color: var(--dropdown-bg-color-hover);
.newPostDropdown > label,
.newPostDropdown > a[role="button"] {
display: inline-block;
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 */
.show {display: block;}
.newPostDropdown > label:hover,
.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 {
-webkit-appearance: none;
@ -808,8 +879,6 @@ div.gallery img {
}
li { list-style:none;}
.msgscope-collapse { position: relative; }
.nav { width: 150px; }
/***********BUTTON CODE ******************************************************/
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;
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 {
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 {
overflow: auto;
}
@ -1026,6 +912,14 @@ div.container {
font-size: var(--font-size);
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 {
border: var(--border-width) solid var(--border-color);
background-color: var(--main-bg-color);
@ -1040,11 +934,13 @@ div.container {
background-color: var(--column-left-header-background);
color: var(--column-left-header-color);
font-size: var(--column-left-header-size);
text-transform: uppercase;
text-transform: var(--column-left-header-style);
padding: 4px;
border: none;
}
.newswireItem {
padding-top: 0;
margin-top: 0;
font-size: var(--font-size-newswire);
color: var(--column-right-fg-color);
line-height: var(--line-spacing-newswire);
@ -1102,6 +998,7 @@ div.container {
width: var(--column-left-width);
}
.col-left {
border: var(--column-left-border-width) solid var(--column-left-border-color);
color: var(--column-left-fg-color);
font-size: var(--font-size-links);
float: left;
@ -1109,7 +1006,6 @@ div.container {
}
.col-left img.leftColEdit {
background: var(--column-left-color);
margin: 40px 0;
width: var(--column-left-icon-size);
}
.col-left img.leftColEditImage {
@ -1142,6 +1038,7 @@ div.container {
overflow: hidden;
}
.col-right {
border: var(--column-right-border-width) solid var(--column-left-border-color);
background-color: var(--column-left-color);
color: var(--column-left-fg-color);
font-size: var(--font-size-links);
@ -1150,7 +1047,6 @@ div.container {
}
.col-right img.rightColEdit {
background: var(--column-left-color);
margin: 40px 0;
width: var(--column-right-icon-size);
}
.col-right img.rightColEditImage {
@ -1605,40 +1501,6 @@ div.container {
border-radius: 1%;
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]
{
-ms-transform: scale(2);
@ -1695,6 +1557,14 @@ div.container {
font-size: var(--font-size);
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 {
border: var(--border-width) solid var(--border-color);
background-color: var(--main-bg-color);
@ -1709,7 +1579,7 @@ div.container {
background-color: var(--column-left-header-background);
color: var(--column-left-header-color);
font-size: var(--column-left-header-size-mobile);
text-transform: uppercase;
text-transform: var(--column-left-header-style);
padding: 4px;
border: none;
}
@ -1740,6 +1610,8 @@ div.container {
padding: 0 0;
}
.newswireItem {
padding-top: 0;
margin-top: 0;
font-size: var(--font-size-newswire-mobile);
color: var(--column-right-fg-color);
line-height: var(--line-spacing-newswire);
@ -2262,41 +2134,6 @@ div.container {
border-radius: 1%;
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]
{
-ms-transform: scale(4);

View File

@ -205,10 +205,11 @@ def unfollowPerson(baseDir: str, nickname: str, domain: str,
return
with open(filename, "r") as f:
lines = f.readlines()
with open(filename, 'w+') as f:
for line in lines:
if line.strip("\n").strip("\r").lower() != handleToUnfollowLower:
f.write(line)
with open(filename, 'w+') as f:
for line in lines:
if line.strip("\n").strip("\r").lower() != \
handleToUnfollowLower:
f.write(line)
# write to an unfollowed file so that if a follow accept
# 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 handleToUnfollowLower not in \
open(unfollowedFilename).read().lower():
with open(filename, "a+") as f:
with open(unfollowedFilename, "a+") as f:
f.write(handleToUnfollow + '\n')
else:
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)
httpd.newswire = newNewswire
saveJson(httpd.newswire, newswireStateFilename)
print('Newswire updated')
if newNewswire:
saveJson(httpd.newswire, newswireStateFilename)
print('Newswire updated')
convertRSStoActivityPub(baseDir,
httpPrefix, domain, port,

View File

@ -1228,6 +1228,31 @@ def createBlogPost(baseDir: str,
schedulePost,
eventDate, eventTime, location)
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

View File

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

View File

@ -313,7 +313,8 @@ def setThemeDefault(baseDir: str):
}
themeParams = {
"banner-height": "20vh",
"banner-height-mobile": "10vh"
"banner-height-mobile": "10vh",
"search-banner-height-mobile": "15vh"
}
setThemeFromDict(baseDir, name, themeParams, bgParams)
@ -462,6 +463,11 @@ def setThemeNight(baseDir: str):
fontStrItalic = \
"url('./fonts/solidaric-italic.woff2') format('woff2')"
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",
"banner-height": "15vh",
"banner-height-mobile": "10vh",
@ -476,7 +482,7 @@ def setThemeNight(baseDir: str):
"column-left-color": "#0f0d10",
"text-entry-background": "#0f0d10",
"link-bg-color": "#0f0d10",
"main-link-color": "ff9900",
"main-link-color": "#6481f5",
"main-link-color-hover": "#d09338",
"main-fg-color": "#0481f5",
"column-left-fg-color": "#0481f5",
@ -600,6 +606,7 @@ def setThemeHenge(baseDir: str):
setRssIconAtTop(baseDir, True)
setPublishButtonAtTop(baseDir, False)
themeParams = {
"banner-height": "25vh",
"column-left-image-width-mobile": "40vw",
"column-right-image-width-mobile": "40vw",
"font-size-button-mobile": "26px",
@ -1019,14 +1026,49 @@ def setThemeIndymediaModern(baseDir: str):
fontStrItalic = \
"url('./fonts/NimbusSanL-italic.otf') format('opentype')"
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-mobile": "10vh",
"publish-button-vertical-offset": "10px",
"container-button-padding": "0px",
"container-button-margin": "0px",
"column-right-icon-size": "11%",
"column-left-icon-size": "15%",
"column-right-icon-size": "15%",
"button-height-padding": "5px",
"icon-brightness-change": "70%",
"border-width": "0px",
"border-width-header": "0px",
"tab-border-width": "3px",
"tab-border-color": "grey",
@ -1034,8 +1076,8 @@ def setThemeIndymediaModern(baseDir: str):
"login-button-color": "#25408f",
"login-button-fg-color": "white",
"column-left-width": "10vw",
"column-center-width": "70vw",
"column-right-width": "20vw",
"column-center-width": "80vw",
"column-right-width": "10vw",
"column-right-fg-color": "#25408f",
"column-right-fg-color-voted-on": "red",
"newswire-item-moderated-color": "red",
@ -1054,12 +1096,7 @@ def setThemeIndymediaModern(baseDir: str):
"hashtag-background-color": "#b2b2b2",
"focus-color": "grey",
"font-size-button-mobile": "26px",
"font-size-publish-button": "26px",
"font-size": "32px",
"font-size2": "26px",
"font-size3": "40px",
"font-size4": "24px",
"font-size5": "22px",
"font-size-publish-button": "14px",
"rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)",
"column-left-color": "white",
"main-bg-color": "white",
@ -1108,7 +1145,7 @@ def setThemeIndymediaModern(baseDir: str):
}
setThemeFromDict(baseDir, name, themeParams, bgParams)
setNewswirePublishAsIcon(baseDir, False)
setFullWidthTimelineButtonHeader(baseDir, True)
setFullWidthTimelineButtonHeader(baseDir, False)
setIconsAsButtons(baseDir, True)
setRssIconAtTop(baseDir, False)
setPublishButtonAtTop(baseDir, True)
@ -1117,8 +1154,15 @@ def setThemeIndymediaModern(baseDir: str):
def setThemeSolidaric(baseDir: str):
name = 'solidaric'
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-mobile": "15vh",
"search-banner-height-mobile": "15vh",
"time-vertical-align": "-4px",
"time-vertical-align-mobile": "15px",
"hashtag-background-color": "lightred",

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "إزالة مشاركات Twitter",
"Sensitive": "حساس",
"Word Replacements": "استبدال الكلمات",
"Happening Today": "يحدث اليوم",
"Happening This Week": "يحدث هذا الاسبوع",
"Happening Today": "اليوم",
"Happening This Week": "هكذا",
"Blog": "مدونة",
"Blogs": "المدونات",
"Title": "عنوان",
@ -325,5 +325,7 @@
"Features" : "ميزات",
"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",
"Sensitive": "Sensible",
"Word Replacements": "Substitucions de paraula",
"Happening Today": "Passant avui",
"Happening This Week": "Passa aquesta setmana",
"Happening Today": "Avui",
"Happening This Week": "Aviat",
"Blog": "Bloc",
"Blogs": "Blocs",
"Title": "Títol",
@ -325,5 +325,7 @@
"Features" : "Article",
"Article": "Reportatge",
"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",
"Sensitive": "Sensitif",
"Word Replacements": "Amnewidiadau Geiriau",
"Happening Today": "Digwydd Heddiw",
"Happening This Week": "Yn Digwydd Yr Wythnos Hon",
"Happening Today": "Heddiw",
"Happening This Week": "Yn fuan",
"Blog": "Blog",
"Blogs": "Blogs",
"Title": "Teitl",
@ -325,5 +325,7 @@
"Features" : "Nodweddion",
"Article": "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",
"Sensitive": "Empfindlich",
"Word Replacements": "Wortersetzungen",
"Happening Today": "Heute passiert",
"Happening This Week": "Diese Woche passiert",
"Happening Today": "Heute",
"Happening This Week": "Demnächst",
"Blog": "Blog",
"Blogs": "Blogs",
"Title": "Titel",
@ -325,5 +325,7 @@
"Features" : "Eigenschaften",
"Article": "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",
"Word Replacements": "Word Replacements",
"Happening Today": "Today",
"Happening This Week": "This Week",
"Happening This Week": "Soon",
"Blog": "Blog",
"Blogs": "Blogs",
"Title": "Title",
@ -325,5 +325,7 @@
"Features" : "Features",
"Article": "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",
"Sensitive": "Sensible",
"Word Replacements": "Reemplazos de palabras",
"Happening Today": "Sucediendo hoy",
"Happening This Week": "Sucediendo esta semana",
"Happening Today": "Hoy",
"Happening This Week": "Pronto",
"Blog": "Blog",
"Blogs": "Blogs",
"Title": "Título",
@ -325,5 +325,7 @@
"Features" : "Caracteristicas",
"Article": "Artículo",
"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",
"Sensitive": "Sensible",
"Word Replacements": "Remplacements de mots",
"Happening Today": "Se passe aujourd'hui",
"Happening This Week": "Se passe cette semaine",
"Happening Today": "Aujourd'hui",
"Happening This Week": "Bientôt",
"Blog": "Blog",
"Blogs": "Blogs",
"Title": "Titre",
@ -325,5 +325,7 @@
"Features" : "Traits",
"Article": "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",
"Sensitive": "Íogair",
"Word Replacements": "Athchur Focal",
"Happening Today": "Ag tarlú inniu",
"Happening This Week": "Ag tarlú an tseachtain seo",
"Happening Today": "Inniu",
"Happening This Week": "Go gairid",
"Blog": "Blag",
"Blogs": "Blaganna",
"Title": "Teideal",
@ -325,5 +325,7 @@
"Features" : "Gnéithe",
"Article": "Airteagal",
"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": "ट्विटर पोस्ट हटाएं",
"Sensitive": "संवेदनशील",
"Word Replacements": "शब्द प्रतिस्थापन",
"Happening Today": "आज हो रहा है",
"Happening This Week": "इस सप्ताह हो रहा है",
"Happening Today": "आज",
"Happening This Week": "जल्द ही",
"Blog": "ब्लॉग",
"Blogs": "ब्लॉग",
"Title": "शीर्षक",
@ -325,5 +325,7 @@
"Features" : "विशेषताएं",
"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",
"Sensitive": "Sensibile",
"Word Replacements": "Sostituzioni di parole",
"Happening Today": "Succede oggi",
"Happening This Week": "Succede questa settimana",
"Happening Today": "Oggi",
"Happening This Week": "Presto",
"Blog": "Blog",
"Blogs": "Blog",
"Title": "Titolo",
@ -325,5 +325,7 @@
"Features" : "Caratteristiche",
"Article": "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の投稿を削除する",
"Sensitive": "敏感",
"Word Replacements": "単語の置換",
"Happening Today": "今日の出来事",
"Happening This Week": "今週の出来事",
"Happening Today": "今日",
"Happening This Week": "すぐに",
"Blog": "ブログ",
"Blogs": "ブログ",
"Title": "題名",
@ -325,5 +325,7 @@
"Features" : "特徴",
"Article": "論文",
"Create an article": "記事を作成する",
"Settings": "設定"
"Settings": "設定",
"Citations": "引用",
"Choose newswire items referenced in your article": "あなたの記事で参照されているニュースワイヤーアイテムを選択してください"
}

View File

@ -209,7 +209,7 @@
"Sensitive": "Sensitive",
"Word Replacements": "Word Replacements",
"Happening Today": "Happening Today",
"Happening This Week": "Happening This Week",
"Happening This Week": "Soon",
"Blog": "Blog",
"Blogs": "Blogs",
"Title": "Title",
@ -321,5 +321,7 @@
"Features" : "Features",
"Article": "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",
"Sensitive": "Sensível",
"Word Replacements": "Substituições do Word",
"Happening Today": "Acontecendo hoje",
"Happening This Week": "Acontecendo Esta Semana",
"Happening Today": "Hoje",
"Happening This Week": "Em breve",
"Blog": "Blog",
"Blogs": "Blogs",
"Title": "Título",
@ -325,5 +325,7 @@
"Features" : "Recursos",
"Article": "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": "Удалить сообщения из Твиттера",
"Sensitive": "чувствительный",
"Word Replacements": "Замены слов",
"Happening Today": "Происходит сегодня",
"Happening This Week": "Происходит на этой неделе",
"Happening Today": "Cегодня",
"Happening This Week": "Скоро",
"Blog": "Блог",
"Blogs": "Блоги",
"Title": "заглавие",
@ -325,5 +325,7 @@
"Features" : "особенности",
"Article": "Статья",
"Create an article": "Создать статью",
"Settings": "Настройки"
"Settings": "Настройки",
"Citations": "Цитаты",
"Choose newswire items referenced in your article": "Выберите элементы ленты новостей, на которые есть ссылки в вашей статье"
}

View File

@ -212,8 +212,8 @@
"Remove Twitter posts": "删除Twitter帖子",
"Sensitive": "敏感",
"Word Replacements": "单词替换",
"Happening Today": "今天发生",
"Happening This Week": "本周发生",
"Happening Today": "今天",
"Happening This Week": "不久",
"Blog": "博客",
"Blogs": "网志",
"Title": "标题",
@ -325,5 +325,7 @@
"Features" : "特征",
"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
iconsDir = getIconsDir(baseDir)
separatorStr = htmlPostSeparator(baseDir, None)
# check that the directory for the nickname exists
if nickname:
@ -829,7 +830,7 @@ def htmlHashtagSearch(cssCache: {},
if nickname:
showIndividualPostIcons = True
allowDeletion = False
hashtagSearchForm += \
hashtagSearchForm += separatorStr + \
individualPostAsHtml(True, recentPostsCache,
maxRecentPosts,
iconsDir, translate, None,
@ -1165,6 +1166,7 @@ def htmlHistorySearch(cssCache: {}, translate: {}, baseDir: str,
return historySearchForm
iconsDir = getIconsDir(baseDir)
separatorStr = htmlPostSeparator(baseDir, None)
# ensure that the page number is in bounds
if not pageNumber:
@ -1191,7 +1193,7 @@ def htmlHistorySearch(cssCache: {}, translate: {}, baseDir: str,
continue
showIndividualPostIcons = True
allowDeletion = False
historySearchForm += \
historySearchForm += separatorStr + \
individualPostAsHtml(True, recentPostsCache,
maxRecentPosts,
iconsDir, translate, None,
@ -1282,7 +1284,7 @@ def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
' <center>\n' + \
' <input type="submit" name="submitLinks" value="' + \
translate['Submit'] + '">\n' + \
' <center>\n'
' </center>\n'
editLinksForm += \
' </div>\n'
@ -1365,7 +1367,7 @@ def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str,
' <center>\n' + \
' <input type="submit" name="submitNewswire" value="' + \
translate['Submit'] + '">\n' + \
' <center>\n'
' </center>\n'
editNewswireForm += \
' </div>\n'
@ -2413,6 +2415,111 @@ def htmlSuspended(cssCache: {}, baseDir: str) -> str:
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: {},
baseDir: str, httpPrefix: str,
path: str, inReplyTo: str,
@ -2420,7 +2527,7 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
reportUrl: str, pageNumber: int,
nickname: str, domain: str,
domainFull: str,
defaultTimeline: str) -> str:
defaultTimeline: str, newswire: {}) -> str:
"""New post screen
"""
iconsDir = getIconsDir(baseDir)
@ -2643,6 +2750,33 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
extraFields += '<input type="text" name="location">\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 = ''
if endpoint != 'newshare' and \
endpoint != 'newreport' and \
@ -2791,21 +2925,6 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
newPostForm += '<img loading="lazy" class="timeline-banner" src="' + \
'/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 = ''
for m in mentions:
mentionNickname = getNicknameFromActor(m)
@ -2858,89 +2977,22 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
dropDownContent = ''
if not reportUrl:
dropDownContent += "<div class='msgscope-collapse collapse "
dropDownContent += "right desktoponly' id='msgscope'>\n"
dropDownContent += " <ul class='nav msgscope-nav msgscope-right'>\n"
dropDownContent += " <li class=' ' style='position: relative;'>\n"
dropDownContent += " <div class='toggle-msgScope button-msgScope'>\n"
dropDownContent += " <input id='toggleMsgScope' "
dropDownContent += "name='toggleMsgScope' type='checkbox'/>\n"
dropDownContent += " <label for='toggleMsgScope'>\n"
dropDownContent += " <div class='lined-thin'>\n"
dropDownContent += ' <img loading="lazy" alt="" title="" src="/'
dropDownContent += iconsDir + '/' + scopeIcon
dropDownContent += '"/><b class="scope-desc">'
dropDownContent += scopeDescription + '</b>\n'
dropDownContent += " <span class='caret'/>\n"
dropDownContent += " </div>\n"
dropDownContent += " </label>\n"
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'
dropDownContent = \
htmlNewPostDropDown(scopeIcon, scopeDescription,
replyStr,
translate,
iconsDir,
showPublicOnDropdown,
defaultTimeline,
pathBase,
dropdownNewPostSuffix,
dropdownNewBlogSuffix,
dropdownUnlistedSuffix,
dropdownFollowersSuffix,
dropdownDMSuffix,
dropdownReminderSuffix,
dropdownEventSuffix,
dropdownReportSuffix)
else:
mentionsStr = 'Re: ' + reportUrl + '\n\n' + mentionsStr
@ -2966,13 +3018,22 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
newPostForm += ' </div>\n'
newPostForm += ' <div class="containerSubmitNewPost"><center>\n'
# newPostForm += \
# ' <a href="' + pathBase + \
# '/inbox"><button class="cancelbtn">' + \
# 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 += \
' <input type="submit" name="submitPost" value="' + \
translate['Submit'] + '">\n'
newPostForm += ' </center></div>\n'
newPostForm += replyStr
@ -3018,7 +3079,7 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
newPostForm += \
' <textarea id="message" name="message" style="height:' + \
str(messageBoxHeight) + 'px"' + selectedStr + '></textarea>\n'
newPostForm += extraFields+dateAndLocation
newPostForm += extraFields + citationsStr + dateAndLocation
if not mediaInstance or replyStr:
newPostForm += newPostImageSection
newPostForm += ' </div>\n'
@ -3079,6 +3140,7 @@ def htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
These should only be public posts
"""
iconsDir = getIconsDir(baseDir)
separatorStr = htmlPostSeparator(baseDir, None)
profileStr = ''
maxItems = 4
ctr = 0
@ -3111,7 +3173,7 @@ def htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
showPublishedDateOnly,
False, False, False, True, False)
if postStr:
profileStr += postStr
profileStr += separatorStr + postStr
ctr += 1
if ctr >= maxItems:
break
@ -3329,6 +3391,7 @@ def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
'" alt="' + translate['Page up'] + '"></a>\n' + \
' </center>\n'
separatorStr = htmlPostSeparator(baseDir, None)
for published, item in sharesJson.items():
showContactButton = False
if item['actor'] != actor:
@ -3336,7 +3399,7 @@ def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
showRemoveButton = False
if item['actor'] == actor:
showRemoveButton = True
timelineStr += \
timelineStr += separatorStr + \
htmlIndividualShare(actor, item, translate,
showContactButton, showRemoveButton)
@ -5524,6 +5587,29 @@ def individualPostAsHtml(allowDownloads: bool,
'<div class="gitpatch"><pre><code>' + contentStr + \
'</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 = ''
if boxName != 'tlmedia':
postHtml = ' <div id="' + timelinePostBookmark + \
@ -5532,7 +5618,7 @@ def individualPostAsHtml(allowDownloads: bool,
postHtml += ' <div class="post-title">\n' + \
' ' + titleStr + \
replyAvatarImageInPost + ' </div>\n'
postHtml += contentStr + footerStr + '\n'
postHtml += contentStr + citationsStr + footerStr + '\n'
postHtml += ' </div>\n'
else:
postHtml = galleryStr
@ -5599,6 +5685,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
"""
htmlStr = ''
separatorStr = htmlPostSeparator(baseDir, 'left')
domain = domainFull
if ':' in domain:
domain = domain.split(':')
@ -5648,6 +5735,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
if editImageClass == 'leftColEdit':
htmlStr += '\n <center>\n'
htmlStr += ' <div class="leftColIcons">\n'
if editor:
# show the edit icon
htmlStr += \
@ -5676,6 +5764,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
'" src="/' + iconsDir + '/logorss.png" /></a>\n'
if rssIconAtTop:
htmlStr += rssIconStr
htmlStr += ' </div>\n'
if editImageClass == 'leftColEdit':
htmlStr += ' </center>\n'
@ -5683,8 +5772,8 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
if (editor or rssIconAtTop) and not showHeaderImage:
htmlStr += '</div><br>'
if showHeaderImage:
htmlStr += '<br>'
# if showHeaderImage:
# htmlStr += '<br>'
linksFilename = baseDir + '/accounts/links.txt'
linksFileContainsEntries = False
@ -5725,6 +5814,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
else:
if lineStr.startswith('#') or lineStr.startswith('*'):
lineStr = lineStr[1:].strip()
htmlStr += separatorStr
htmlStr += \
' <h3 class="linksHeader">' + \
lineStr + '</h3>\n'
@ -5752,10 +5842,11 @@ def votesIndicator(totalVotes: int, positiveVoting: bool) -> str:
return totalVotesStr
def htmlNewswire(newswire: {}, nickname: str, moderator: bool,
def htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
translate: {}, positiveVoting: bool, iconsDir: str) -> str:
"""Converts a newswire dict into html
"""
separatorStr = htmlPostSeparator(baseDir, 'right')
htmlStr = ''
for dateStr, item in newswire.items():
publishedDate = \
@ -5765,6 +5856,7 @@ def htmlNewswire(newswire: {}, nickname: str, moderator: bool,
dateStrLink = dateStr.replace('T', ' ')
dateStrLink = dateStrLink.replace('Z', '')
moderatedItem = item[5]
htmlStr += separatorStr
if moderatedItem and 'vote:' + nickname in item[2]:
totalVotesStr = ''
totalVotes = 0
@ -5821,6 +5913,118 @@ def htmlNewswire(newswire: {}, nickname: str, moderator: bool,
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,
httpPrefix: str, translate: {},
iconsDir: str, moderator: bool, editor: bool,
@ -5968,7 +6172,7 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
# show the newswire lines
newswireContentStr = \
htmlNewswire(newswire, nickname, moderator, translate,
htmlNewswire(baseDir, newswire, nickname, moderator, translate,
positiveVoting, iconsDir)
htmlStr += newswireContentStr
@ -6348,6 +6552,54 @@ def headerButtonsTimeline(defaultTimeline: str,
inboxButton + '"><span>' + translate['Inbox'] + \
'</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:
# button for the outbox
tlStr += \
@ -6359,66 +6611,20 @@ def headerButtonsTimeline(defaultTimeline: str,
# add other buttons
tlStr += \
sharesButtonStr + bookmarksButtonStr + eventsButtonStr + \
moderationButtonStr + newPostButtonStr
# show todays events buttons on the first inbox page
if boxName == 'inbox' and pageNumber == 1:
if todaysEventsCheck(baseDir, nickname, domain):
now = datetime.now()
# happening today button
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>'
moderationButtonStr + happeningStr + newPostButtonStr
if not newsHeader:
if not iconsAsButtons:
# the search button
# the search icon
tlStr += \
' <a class="imageAnchor" href="' + usersPath + \
'<a class="imageAnchor" href="' + usersPath + \
'/search"><img loading="lazy" src="/' + \
iconsDir + '/search.png" title="' + \
translate['Search and follow'] + '" alt="| ' + \
translate['Search and follow'] + \
'" class="timelineicon"/></a>'
else:
# the search button
tlStr += \
'<a href="' + usersPath + \
'/search"><button class="button">' + \
@ -6501,6 +6707,8 @@ def headerButtonsTimeline(defaultTimeline: str,
'/links.png" title="' + translate['Edit Links'] + \
'" alt="| ' + translate['Edit Links'] + \
'" class="timelineicon"/></a>'
# end of headericons div
tlStr += '</div>'
else:
# NOTE: deliberately no \n at end of line
tlStr += \
@ -6523,6 +6731,23 @@ def headerButtonsTimeline(defaultTimeline: str,
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,
recentPostsCache: {}, maxRecentPosts: int,
translate: {}, pageNumber: int,
@ -6598,6 +6823,10 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
# This changes depending upon theme
iconsDir = getIconsDir(baseDir)
separatorStr = ''
if boxName != 'tlmedia':
separatorStr = htmlPostSeparator(baseDir, None)
# the css filename
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
@ -6760,10 +6989,21 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
if timeDiff > 100:
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
if boxName == 'dm':
if not iconsAsButtons:
newPostButtonStr = \
newPostButtonStr += \
'<a class="imageAnchor" href="' + usersPath + \
'/newdm"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \
@ -6771,13 +7011,13 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
'" alt="| ' + translate['Create a new DM'] + \
'" class="timelineicon"/></a>\n'
else:
newPostButtonStr = \
newPostButtonStr += \
'<a href="' + usersPath + '/newdm">' + \
'<button class="button"><span>' + \
translate['Post'] + ' </span></button></a>'
elif boxName == 'tlblogs' or boxName == 'tlnews':
if not iconsAsButtons:
newPostButtonStr = \
newPostButtonStr += \
'<a class="imageAnchor" href="' + usersPath + \
'/newblog"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \
@ -6785,13 +7025,13 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
translate['Create a new post'] + \
'" class="timelineicon"/></a>\n'
else:
newPostButtonStr = \
newPostButtonStr += \
'<a href="' + usersPath + '/newblog">' + \
'<button class="button"><span>' + \
translate['Post'] + '</span></button></a>'
elif boxName == 'tlevents':
if not iconsAsButtons:
newPostButtonStr = \
newPostButtonStr += \
'<a class="imageAnchor" href="' + usersPath + \
'/newevent"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \
@ -6799,14 +7039,14 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
translate['Create a new event'] + \
'" class="timelineicon"/></a>\n'
else:
newPostButtonStr = \
newPostButtonStr += \
'<a href="' + usersPath + '/newevent">' + \
'<button class="button"><span>' + \
translate['Post'] + '</span></button></a>'
else:
if not manuallyApproveFollowers:
if not iconsAsButtons:
newPostButtonStr = \
newPostButtonStr += \
'<a class="imageAnchor" href="' + usersPath + \
'/newpost"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \
@ -6814,13 +7054,13 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
translate['Create a new post'] + \
'" class="timelineicon"/></a>\n'
else:
newPostButtonStr = \
newPostButtonStr += \
'<a href="' + usersPath + '/newpost">' + \
'<button class="button"><span>' + \
translate['Post'] + '</span></button></a>'
else:
if not iconsAsButtons:
newPostButtonStr = \
newPostButtonStr += \
'<a class="imageAnchor" href="' + usersPath + \
'/newfollowers"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \
@ -6828,10 +7068,11 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
'" alt="| ' + translate['Create a new post'] + \
'" class="timelineicon"/></a>\n'
else:
newPostButtonStr = \
newPostButtonStr += \
'<a href="' + usersPath + '/newfollowers">' + \
'<button class="button"><span>' + \
translate['Post'] + '</span></button></a>'
# This creates a link to the profile page when viewed
# in lynx, but should be invisible in a graphical web browser
tlStr += \
@ -7056,6 +7297,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
if currTlStr:
itemCtr += 1
if separatorStr:
tlStr += separatorStr
tlStr += currTlStr
if boxName == 'tlmedia':
tlStr += '</div>\n'
@ -8499,7 +8742,8 @@ def htmlCalendar(cssCache: {}, translate: {},
' <img loading="lazy" alt="' + translate['Previous month'] + \
'" title="' + translate['Previous month'] + '" src="/' + iconsDir + \
'/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 += \
' <a href="' + calActor + '/calendar?year=' + str(nextYear) + \