diff --git a/acceptreject.py b/acceptreject.py index 1abb6dd70..1202d1e50 100644 --- a/acceptreject.py +++ b/acceptreject.py @@ -66,7 +66,7 @@ def createReject(baseDir: str, federationList: [], return createAcceptReject(baseDir, federationList, nickname, domain, port, toUrl, ccUrl, - httpPrefix, objectJson, None, 'Reject') + httpPrefix, objectJson, 'Reject') def acceptFollow(baseDir: str, domain: str, messageJson: {}, diff --git a/blog.py b/blog.py index bc76be907..8a1251298 100644 --- a/blog.py +++ b/blog.py @@ -20,6 +20,8 @@ from utils import getDomainFromActor from utils import locatePost from utils import loadJson from posts import createBlogsTimeline +from newswire import rss2Header +from newswire import rss2Footer def noOfBlogReplies(baseDir: str, httpPrefix: str, translate: {}, @@ -365,12 +367,12 @@ def htmlBlogPost(authorized: bool, 'title="RSS 2.0" src="/' + \ iconsDir + '/rss.png" />' - blogStr += '' - blogStr += 'RSS 3.0' + # blogStr += '' + # blogStr += 'RSS 3.0' blogStr += '

' @@ -405,7 +407,7 @@ def htmlBlogPage(authorized: bool, session, timelineJson = createBlogsTimeline(session, baseDir, nickname, domain, port, httpPrefix, - noOfItems, False, False, + noOfItems, False, pageNumber) if not timelineJson: @@ -461,11 +463,11 @@ def htmlBlogPage(authorized: bool, session, 'title="RSS 2.0" src="/' + \ iconsDir + '/rss.png" />' - blogStr += '' - blogStr += 'RSS 3.0' + # blogStr += '' + # blogStr += 'RSS 3.0' blogStr += '

' @@ -473,23 +475,6 @@ def htmlBlogPage(authorized: bool, session, return None -def rss2Header(httpPrefix: str, - nickname: str, domainFull: str, translate: {}) -> str: - rssStr = "" - rssStr += "" - rssStr += '' - rssStr += ' ' + translate['Blog'] + '' - rssStr += ' ' + httpPrefix + '://' + domainFull + \ - '/users/' + nickname + '/rss.xml' + '' - return rssStr - - -def rss2Footer() -> str: - rssStr = '' - rssStr += '' - return rssStr - - def htmlBlogPageRSS2(authorized: bool, session, baseDir: str, httpPrefix: str, translate: {}, nickname: str, domain: str, port: int, @@ -505,7 +490,7 @@ def htmlBlogPageRSS2(authorized: bool, session, if port != 80 and port != 443: domainFull = domain + ':' + str(port) - blogRSS2 = rss2Header(httpPrefix, nickname, domainFull, translate) + blogRSS2 = rss2Header(httpPrefix, nickname, domainFull, 'Blog', translate) blogsIndex = baseDir + '/accounts/' + \ nickname + '@' + domain + '/tlblogs.index' @@ -515,7 +500,7 @@ def htmlBlogPageRSS2(authorized: bool, session, timelineJson = createBlogsTimeline(session, baseDir, nickname, domain, port, httpPrefix, - noOfItems, False, False, + noOfItems, False, pageNumber) if not timelineJson: @@ -561,7 +546,7 @@ def htmlBlogPageRSS3(authorized: bool, session, timelineJson = createBlogsTimeline(session, baseDir, nickname, domain, port, httpPrefix, - noOfItems, False, False, + noOfItems, False, pageNumber) if not timelineJson: diff --git a/content.py b/content.py index 2fc754718..d1a170192 100644 --- a/content.py +++ b/content.py @@ -14,6 +14,32 @@ from utils import fileLastModified from utils import getLinkPrefixes +def removeQuotesWithinQuotes(content: str) -> str: + """Removes any blockquote inside blockquote + """ + if '
' not in content: + return content + if '
' not in content: + return content + ctr = 1 + found = True + while found: + prefix = content.split('
', ctr)[0] + '
' + quotedStr = content.split('
', ctr)[1] + if '
' not in quotedStr: + found = False + else: + endStr = quotedStr.split('
')[1] + quotedStr = quotedStr.split('
')[0] + if '
' not in endStr: + found = False + if '
' in quotedStr: + quotedStr = quotedStr.replace('
', '') + content = prefix + quotedStr + '
' + endStr + ctr += 1 + return content + + def htmlReplaceEmailQuote(content: str) -> str: """Replaces an email style quote "> Some quote" with html blockquote """ @@ -44,9 +70,12 @@ def htmlReplaceEmailQuote(content: str) -> str: newContent += '

' + lineStr + '

' else: lineStr = lineStr.replace('>> ', '>
') - lineStr = lineStr.replace('>', '
') + if lineStr.startswith('>'): + lineStr = lineStr.replace('>', '
', 1) + else: + lineStr = lineStr.replace('>', '
') newContent += '

' + lineStr + '

' - return newContent + return removeQuotesWithinQuotes(newContent) def htmlReplaceQuoteMarks(content: str) -> str: diff --git a/daemon.py b/daemon.py index ed6278765..dabaefb56 100644 --- a/daemon.py +++ b/daemon.py @@ -61,6 +61,7 @@ from person import removeAccount from person import canRemovePost from person import personSnooze from person import personUnsnooze +from posts import isModerator from posts import mutePost from posts import unmutePost from posts import createQuestionPost @@ -144,6 +145,8 @@ from webinterface import htmlSearchEmojiTextEntry from webinterface import htmlUnfollowConfirm from webinterface import htmlProfileAfterSearch from webinterface import htmlEditProfile +from webinterface import htmlEditLinks +from webinterface import htmlEditNewswire from webinterface import htmlTermsOfService from webinterface import htmlSkillsSearch from webinterface import htmlHistorySearch @@ -200,6 +203,7 @@ from followingCalendar import removePersonFromCalendar from devices import E2EEdevicesCollection from devices import E2EEvalidDevice from devices import E2EEaddDevice +from newswire import getRSSfromDict import os @@ -2689,6 +2693,216 @@ class PubServer(BaseHTTPRequestHandler): cookie, callingDomain) self.server.POSTbusy = False + def _linksUpdate(self, callingDomain: str, cookie: str, + authorized: bool, path: str, + baseDir: str, httpPrefix: str, + domain: str, domainFull: str, + onionDomain: str, i2pDomain: str, debug: bool, + defaultTimeline: str): + """Updates the left links column of the timeline + """ + usersPath = path.replace('/linksdata', '') + usersPath = usersPath.replace('/editlinks', '') + actorStr = httpPrefix + '://' + domainFull + usersPath + if ' boundary=' in self.headers['Content-type']: + boundary = self.headers['Content-type'].split('boundary=')[1] + if ';' in boundary: + boundary = boundary.split(';')[0] + + # get the nickname + nickname = getNicknameFromActor(actorStr) + moderator = None + if nickname: + moderator = isModerator(baseDir, nickname) + if not nickname or not moderator: + if callingDomain.endswith('.onion') and \ + onionDomain: + actorStr = \ + 'http://' + onionDomain + usersPath + elif (callingDomain.endswith('.i2p') and + i2pDomain): + actorStr = \ + 'http://' + i2pDomain + usersPath + if not nickname: + print('WARN: nickname not found in ' + actorStr) + else: + print('WARN: nickname is not a moderator' + actorStr) + self._redirect_headers(actorStr, cookie, callingDomain) + self.server.POSTbusy = False + return + + length = int(self.headers['Content-length']) + + # check that the POST isn't too large + if length > self.server.maxPostLength: + if callingDomain.endswith('.onion') and \ + onionDomain: + actorStr = \ + 'http://' + onionDomain + usersPath + elif (callingDomain.endswith('.i2p') and + i2pDomain): + actorStr = \ + 'http://' + i2pDomain + usersPath + print('Maximum links data length exceeded ' + str(length)) + self._redirect_headers(actorStr, cookie, callingDomain) + self.server.POSTbusy = False + return + + try: + # read the bytes of the http form POST + postBytes = self.rfile.read(length) + except SocketError as e: + if e.errno == errno.ECONNRESET: + print('WARN: connection was reset while ' + + 'reading bytes from http form POST') + else: + print('WARN: error while reading bytes ' + + 'from http form POST') + self.send_response(400) + self.end_headers() + self.server.POSTbusy = False + return + except ValueError as e: + print('ERROR: failed to read bytes for POST') + print(e) + self.send_response(400) + self.end_headers() + self.server.POSTbusy = False + return + + linksFilename = baseDir + '/accounts/links.txt' + + # extract all of the text fields into a dict + fields = \ + extractTextFieldsInPOST(postBytes, boundary, debug) + if fields.get('editedLinks'): + linksStr = fields['editedLinks'] + linksFile = open(linksFilename, "w+") + if linksFile: + linksFile.write(linksStr) + linksFile.close() + else: + if os.path.isfile(linksFilename): + os.remove(linksFilename) + + # redirect back to the default timeline + if callingDomain.endswith('.onion') and \ + onionDomain: + actorStr = \ + 'http://' + onionDomain + usersPath + elif (callingDomain.endswith('.i2p') and + i2pDomain): + actorStr = \ + 'http://' + i2pDomain + usersPath + self._redirect_headers(actorStr + '/' + defaultTimeline, + cookie, callingDomain) + self.server.POSTbusy = False + + def _newswireUpdate(self, callingDomain: str, cookie: str, + authorized: bool, path: str, + baseDir: str, httpPrefix: str, + domain: str, domainFull: str, + onionDomain: str, i2pDomain: str, debug: bool, + defaultTimeline: str): + """Updates the right newswire column of the timeline + """ + usersPath = path.replace('/newswiredata', '') + usersPath = usersPath.replace('/editnewswire', '') + actorStr = httpPrefix + '://' + domainFull + usersPath + if ' boundary=' in self.headers['Content-type']: + boundary = self.headers['Content-type'].split('boundary=')[1] + if ';' in boundary: + boundary = boundary.split(';')[0] + + # get the nickname + nickname = getNicknameFromActor(actorStr) + moderator = None + if nickname: + moderator = isModerator(baseDir, nickname) + if not nickname or not moderator: + if callingDomain.endswith('.onion') and \ + onionDomain: + actorStr = \ + 'http://' + onionDomain + usersPath + elif (callingDomain.endswith('.i2p') and + i2pDomain): + actorStr = \ + 'http://' + i2pDomain + usersPath + if not nickname: + print('WARN: nickname not found in ' + actorStr) + else: + print('WARN: nickname is not a moderator' + actorStr) + self._redirect_headers(actorStr, cookie, callingDomain) + self.server.POSTbusy = False + return + + length = int(self.headers['Content-length']) + + # check that the POST isn't too large + if length > self.server.maxPostLength: + if callingDomain.endswith('.onion') and \ + onionDomain: + actorStr = \ + 'http://' + onionDomain + usersPath + elif (callingDomain.endswith('.i2p') and + i2pDomain): + actorStr = \ + 'http://' + i2pDomain + usersPath + print('Maximum newswire data length exceeded ' + str(length)) + self._redirect_headers(actorStr, cookie, callingDomain) + self.server.POSTbusy = False + return + + try: + # read the bytes of the http form POST + postBytes = self.rfile.read(length) + except SocketError as e: + if e.errno == errno.ECONNRESET: + print('WARN: connection was reset while ' + + 'reading bytes from http form POST') + else: + print('WARN: error while reading bytes ' + + 'from http form POST') + self.send_response(400) + self.end_headers() + self.server.POSTbusy = False + return + except ValueError as e: + print('ERROR: failed to read bytes for POST') + print(e) + self.send_response(400) + self.end_headers() + self.server.POSTbusy = False + return + + newswireFilename = baseDir + '/accounts/newswire.txt' + + # extract all of the text fields into a dict + fields = \ + extractTextFieldsInPOST(postBytes, boundary, debug) + if fields.get('editedNewswire'): + newswireStr = fields['editedNewswire'] + newswireFile = open(newswireFilename, "w+") + if newswireFile: + newswireFile.write(newswireStr) + newswireFile.close() + else: + if os.path.isfile(newswireFilename): + os.remove(newswireFilename) + + # redirect back to the default timeline + if callingDomain.endswith('.onion') and \ + onionDomain: + actorStr = \ + 'http://' + onionDomain + usersPath + elif (callingDomain.endswith('.i2p') and + i2pDomain): + actorStr = \ + 'http://' + i2pDomain + usersPath + self._redirect_headers(actorStr + '/' + defaultTimeline, + cookie, callingDomain) + self.server.POSTbusy = False + def _profileUpdate(self, callingDomain: str, cookie: str, authorized: bool, path: str, baseDir: str, httpPrefix: str, @@ -2765,7 +2979,8 @@ class PubServer(BaseHTTPRequestHandler): actorChanged = True profileMediaTypes = ('avatar', 'image', 'banner', 'search_banner', - 'instanceLogo') + 'instanceLogo', + 'left_col_image', 'right_col_image') profileMediaTypesUploaded = {} for mType in profileMediaTypes: if debug: @@ -2834,8 +3049,7 @@ class PubServer(BaseHTTPRequestHandler): # extract all of the text fields into a dict fields = \ - extractTextFieldsInPOST(postBytes, boundary, - debug) + extractTextFieldsInPOST(postBytes, boundary, debug) if debug: if fields: print('DEBUG: profile update text ' + @@ -3205,10 +3419,14 @@ class PubServer(BaseHTTPRequestHandler): self.server.defaultTimeline = 'inbox' if fields['mediaInstance'] == 'on': self.server.mediaInstance = True + self.server.blogsInstance = False self.server.defaultTimeline = 'tlmedia' setConfigParam(baseDir, "mediaInstance", self.server.mediaInstance) + setConfigParam(baseDir, + "blogsInstance", + self.server.blogsInstance) else: if self.server.mediaInstance: self.server.mediaInstance = False @@ -3223,10 +3441,14 @@ class PubServer(BaseHTTPRequestHandler): self.server.defaultTimeline = 'inbox' if fields['blogsInstance'] == 'on': self.server.blogsInstance = True + self.server.mediaInstance = False self.server.defaultTimeline = 'tlblogs' setConfigParam(baseDir, "blogsInstance", self.server.blogsInstance) + setConfigParam(baseDir, + "mediaInstance", + self.server.mediaInstance) else: if self.server.blogsInstance: self.server.blogsInstance = False @@ -3733,6 +3955,42 @@ class PubServer(BaseHTTPRequestHandler): path + ' ' + callingDomain) self._404() + def _getNewswireFeed(self, authorized: bool, + callingDomain: str, path: str, + baseDir: str, httpPrefix: str, + domain: str, port: int, proxyType: str, + GETstartTime, GETtimings: {}, + debug: bool): + """Returns the newswire feed + """ + if not self.server.session: + print('Starting new session during RSS request') + self.server.session = \ + createSession(proxyType) + if not self.server.session: + print('ERROR: GET failed to create session ' + + 'during RSS request') + self._404() + return + + msg = getRSSfromDict(self.server.baseDir, self.server.newswire, + self.server.httpPrefix, + self.server.domainFull, + 'Newswire', self.server.translate) + if msg: + msg = msg.encode('utf-8') + self._set_headers('text/xml', len(msg), + None, callingDomain) + self._write(msg) + if debug: + print('Sent rss2 newswire feed: ' + + path + ' ' + callingDomain) + return + if debug: + print('Failed to get rss2 newswire feed: ' + + path + ' ' + callingDomain) + self._404() + def _getRSS3feed(self, authorized: bool, callingDomain: str, path: str, baseDir: str, httpPrefix: str, @@ -7047,6 +7305,47 @@ class PubServer(BaseHTTPRequestHandler): self._404() return True + def _columImage(self, side: str, callingDomain: str, path: str, + baseDir: str, domain: str, port: int, + GETstartTime, GETtimings: {}) -> bool: + """Shows an image at the top of the left/right column + """ + nickname = getNicknameFromActor(path) + if not nickname: + self._404() + return True + bannerFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/' + side + '_col_image.png' + if os.path.isfile(bannerFilename): + if self._etag_exists(bannerFilename): + # The file has not changed + self._304() + return True + + tries = 0 + mediaBinary = None + while tries < 5: + try: + with open(bannerFilename, 'rb') as avFile: + mediaBinary = avFile.read() + break + except Exception as e: + print(e) + time.sleep(1) + tries += 1 + if mediaBinary: + self._set_headers_etag(bannerFilename, 'image/png', + mediaBinary, None, + callingDomain) + self._write(mediaBinary) + self._benchmarkGETtimings(GETstartTime, GETtimings, + 'account qrcode done', + side + ' col image') + return True + self._404() + return True + def _showBackgroundImage(self, callingDomain: str, path: str, baseDir: str, GETstartTime, GETtimings: {}) -> bool: @@ -7307,6 +7606,50 @@ class PubServer(BaseHTTPRequestHandler): return True return False + def _editLinks(self, callingDomain: str, path: str, + translate: {}, baseDir: str, + httpPrefix: str, domain: str, port: int, + cookie: str) -> bool: + """Show the links from the left column + """ + if '/users/' in path and path.endswith('/editlinks'): + msg = htmlEditLinks(translate, + baseDir, + path, domain, + port, + httpPrefix).encode('utf-8') + if msg: + self._set_headers('text/html', len(msg), + cookie, callingDomain) + self._write(msg) + else: + self._404() + self.server.GETbusy = False + return True + return False + + def _editNewswire(self, callingDomain: str, path: str, + translate: {}, baseDir: str, + httpPrefix: str, domain: str, port: int, + cookie: str) -> bool: + """Show the newswire from the right column + """ + if '/users/' in path and path.endswith('/editnewswire'): + msg = htmlEditNewswire(translate, + baseDir, + path, domain, + port, + httpPrefix).encode('utf-8') + if msg: + self._set_headers('text/html', len(msg), + cookie, callingDomain) + self._write(msg) + else: + self._404() + self.server.GETbusy = False + return True + return False + def _editEvent(self, callingDomain: str, path: str, httpPrefix: str, domain: str, domainFull: str, baseDir: str, translate: {}, @@ -7507,6 +7850,18 @@ class PubServer(BaseHTTPRequestHandler): self._benchmarkGETtimings(GETstartTime, GETtimings, 'fonts', 'sharedInbox enabled') + if self.path == '/newswire.xml': + self._getNewswireFeed(authorized, + callingDomain, self.path, + self.server.baseDir, + self.server.httpPrefix, + self.server.domain, + self.server.port, + self.server.proxyType, + GETstartTime, GETtimings, + self.server.debug) + return + # RSS 2.0 if self.path.startswith('/blog/') and \ self.path.endswith('/rss.xml'): @@ -7965,14 +8320,30 @@ class PubServer(BaseHTTPRequestHandler): 'account qrcode done') # search screen banner image - if '/users/' in self.path and \ - self.path.endswith('/search_banner.png'): - if self._searchScreenBanner(callingDomain, self.path, - self.server.baseDir, - self.server.domain, - self.server.port, - GETstartTime, GETtimings): - return + if '/users/' in self.path: + if self.path.endswith('/search_banner.png'): + if self._searchScreenBanner(callingDomain, self.path, + self.server.baseDir, + self.server.domain, + self.server.port, + GETstartTime, GETtimings): + return + + if self.path.endswith('/left_col_image.png'): + if self._columImage('left', callingDomain, self.path, + self.server.baseDir, + self.server.domain, + self.server.port, + GETstartTime, GETtimings): + return + + if self.path.endswith('/right_col_image.png'): + if self._columImage('right', callingDomain, self.path, + self.server.baseDir, + self.server.domain, + self.server.port, + GETstartTime, GETtimings): + return self._benchmarkGETtimings(GETstartTime, GETtimings, 'account qrcode done', @@ -8601,6 +8972,26 @@ class PubServer(BaseHTTPRequestHandler): cookie): return + # edit links from the left column of the timeline in web interface + if self._editLinks(callingDomain, self.path, + self.server.translate, + self.server.baseDir, + self.server.httpPrefix, + self.server.domain, + self.server.port, + cookie): + return + + # edit newswire from the right column of the timeline + if self._editNewswire(callingDomain, self.path, + self.server.translate, + self.server.baseDir, + self.server.httpPrefix, + self.server.domain, + self.server.port, + cookie): + return + if self._showNewPost(callingDomain, self.path, self.server.mediaInstance, self.server.translate, @@ -10049,6 +10440,26 @@ class PubServer(BaseHTTPRequestHandler): self.server.i2pDomain, self.server.debug) return + if authorized and self.path.endswith('/linksdata'): + self._linksUpdate(callingDomain, cookie, authorized, self.path, + self.server.baseDir, self.server.httpPrefix, + self.server.domain, + self.server.domainFull, + self.server.onionDomain, + self.server.i2pDomain, self.server.debug, + self.server.defaultTimeline) + return + + if authorized and self.path.endswith('/newswiredata'): + self._newswireUpdate(callingDomain, cookie, authorized, self.path, + self.server.baseDir, self.server.httpPrefix, + self.server.domain, + self.server.domainFull, + self.server.onionDomain, + self.server.i2pDomain, self.server.debug, + self.server.defaultTimeline) + return + self._benchmarkPOSTtimings(POSTstartTime, POSTtimings, 3) # moderator action buttons @@ -10660,6 +11071,9 @@ def runDaemon(blogsInstance: bool, mediaInstance: bool, httpd.unitTest = unitTest httpd.YTReplacementDomain = YTReplacementDomain + # newswire storing rss feeds + httpd.newswire = {} + # This counter is used to update the list of blocked domains in memory. # It helps to avoid touching the disk and so improves flooding resistance httpd.blocklistUpdateCtr = 0 diff --git a/epicyon-links.css b/epicyon-links.css new file mode 100644 index 000000000..1434a4df7 --- /dev/null +++ b/epicyon-links.css @@ -0,0 +1,1905 @@ +@charset "UTF-8"; + +:root { + --main-bg-color: #282c37; + --link-bg-color: #282c37; + --dropdown-fg-color: #dddddd; + --dropdown-bg-color: #111; + --dropdown-bg-color-hover: #333; + --dropdown-fg-color-hover: #dddddd; + --main-bg-color-reply: #212c37; + --main-bg-color-dm: #222; + --main-bg-color-report: #221c27; + --main-header-color-roles: #282237; + --main-fg-color: #dddddd; + --main-link-color: #999; + --main-link-color-hover: #bbb; + --main-visited-color: #888; + --border-color: #505050; + --border-width: 2px; + --font-size-header: 18px; + --font-size-header-mobile: 32px; + --font-color-header: #ccc; + --font-size-button-mobile: 34px; + --font-size: 30px; + --font-size2: 24px; + --font-size3: 38px; + --font-size4: 22px; + --font-size5: 20px; + --font-size-likes: 20px; + --font-size-likes-mobile: 32px; + --font-size-pgp-key: 16px; + --font-size-pgp-key2: 8px; + --font-size-tox: 16px; + --font-size-tox2: 8px; + --time-color: #aaa; + --time-vertical-align: 4px; + --button-text: #FFFFFF; + --button-background: #999; + --button-background-hover: #777; + --button-selected: #666; + --button-highlighted: green; + --button-fg-highlighted: #FFFFFF; + --button-selected-highlighted: darkgreen; + --button-approve: darkgreen; + --button-deny: darkred; + --button-height: 10px; + --button-height-padding-mobile: 20px; + --button-height-padding: 10px; + --image-corners: 10%, + --gallery-border: #ccc; + --gallery-hover: #777; + --gallery-text-color: #ccc; + --gallery-font-size: 22px; + --gallery-font-size-mobile: 35px; + --button-corner-radius: 15px; + --timeline-border-radius: 30px; + --icons-side: right; + --title-color: #999; + --focus-color: white; + --quote-right-margin: 0.1em; + --quote-font-weight: normal; + --quote-font-size: 120%; + --line-spacing: 130%; + --column-left-width: 10vw; + --column-center-width: 80vw; + --column-right-width: 10vw; +} + +@font-face { + font-family: 'Bedstead'; + font-style: italic; + font-weight: normal; + font-display: block; + src: url('./fonts/bedstead.otf') format('opentype'); +} +@font-face { + font-family: 'Bedstead'; + font-style: normal; + font-weight: normal; + font-display: block; + src: url('./fonts/bedstead.otf') format('opentype'); +} + +body, html { + background-color: var(--main-bg-color); + color: var(--main-fg-color); + + height: 100%; + font-family: Arial, Helvetica, sans-serif; + min-width: 950px; + font-size: var(--font-size); + line-height: var(--line-spacing); +} + +blockquote { + border-left: 10px; + margin: 1.5em 10px; + padding: 0.5em 10px; + font-weight: var(--quote-font-weight); + font-style: italic; + font-size: var(--quote-font-size); + quotes: "\201C""\201D""\2018""\2019"; +} +blockquote:before { + content: open-quote; + font-size: 2em; + line-height: 0.1em; + margin-right: 0.25em; + vertical-align: -0.4em; +} +blockquote:after { + content: close-quote; + font-size: 2em; + line-height: 0.1em; + margin-left: var(--quote-right-margin); + vertical-align: -0.4em; +} +blockquote p { + display: inline; +} + +.editLinksIcon { + width: 50px; +} + +.imageAnchor:focus img{ + border: 2px solid var(--focus-color); +} + +h1 { + color: var(--title-color); +} + +a, u { + color: var(--main-fg-color); +} + +.editLinksBtn { + border-radius: var(--button-corner-radius); + background-color: var(--button-background); + border: none; + color: var(--button-text); + text-align: center; + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding); + width: 90%; + max-width: 200px; + min-width: 10ch; + transition: all 0.5s; + cursor: pointer; + margin: 5px; +} + +a:visited{ + color: var(--main-visited-color); + background: var(--link-bg-color); + font-weight: bold; +} + +a:link { + color: var(--main-link-color); + background: var(--link-bg-color); + font-weight: bold; +} + +a:link:hover { + color: var(--main-link-color-hover); +} + +a:visited:hover { + color: var(--main-link-color-hover); +} + +.buttonevent:hover { + filter: brightness(150%); +} + +a:focus { + border: 2px solid var(--focus-color); +} + +.timeline-banner { + background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("banner.png"); + height: 15%; + background-repeat: no-repeat; + background-size: 100vw; + position: relative; +} + +.hero-image { + background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("image.png"); + height: 50%; + background-position: center; + background-repeat: no-repeat; + background-size: cover; + position: relative; +} + +.rssfeed img { + width: 5%; + float: right; +} + +.about { + font-size: var(--font-size5); + font-family: Arial, Helvetica, sans-serif; + float: right; +} + +.blogreplies { + color: var(--button-highlighted); + font-size: var(--font-size2); + font-family: Arial, Helvetica, sans-serif; + float: right; +} + +.buttonprev { + float: left; + width: 10%; + -ms-transform: translateY(30%); + transform: translateY(30%); +} + +.buttonnext { + float: right; + width: 10%; + -ms-transform: translateY(30%) scaleX(-1); + transform: translateY(30%) scaleX(-1); +} + +.hero-image img { + width: 50%; +} + +.new-post-text { + font-size: var(--font-size2); + font-family: Arial, Helvetica, sans-serif; + padding: 4px 0; +} + +.new-post-subtext { + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; + padding: 4px 0; +} + +.highlight { + width: 2%; +} + +.roles { + text-align: center; + left: 35%; + background-color: var(--main-header-color-roles); +} + +.roles-inner { + padding: 10px 25px; + background-color: var(--main-bg-color); +} + +.hero-text img.emoji { + width: 50px; + padding: 0 0; + margin: 0 0; + float: none; +} + +.hero-text button { + border: none; + outline: 0; + display: inline-block; + padding: 10px 25px; + color: black; + background-color: #ddd; + text-align: center; + cursor: pointer; + font-family: Arial, Helvetica, sans-serif; +} + +.hero-text button:hover { + background-color: var(--button-background); + color: var(--button-text); +} + +.timelineIcon { + width: 10%; +} + +.container img.timelineicon:hover { + filter: brightness(150%); +} + +.buttonunfollow:hover { + background-color: var(--button-background-hover); +} + +.followRequestHandle { + padding: 0px 20px; +} + +.button span { + font-family: Arial, Helvetica, sans-serif; + cursor: pointer; + display: inline-block; + position: relative; + transition: 0.5s; +} + +.button span:after { + font-family: Arial, Helvetica, sans-serif; + content: '\00bb'; + position: absolute; + opacity: 0; + top: 0; + right: -20px; + transition: 0.5s; +} + +.button:hover { + background-color: var(--button-background-hover); +} + +.donateButton:hover { + background-color: var(--button-background-hover); +} + +.buttonselected span { + font-family: Arial, Helvetica, sans-serif; + cursor: pointer; + display: inline-block; + position: relative; + transition: 0.5s; +} + +.buttonselected span:after { + font-family: Arial, Helvetica, sans-serif; + content: '\00bb'; + position: absolute; + opacity: 0; + top: 0; + right: -20px; + transition: 0.5s; +} + +.buttonselected:hover { + background-color: var(--button-background-hover); +} + +.container { + border: var(--border-width) solid var(--border-color); + background-color: var(--main-bg-color); + border-radius: var(--timeline-border-radius); + padding: 20px; + margin: 10px; +} + +.media { + width: 80%; + border-radius: 5px; + padding: 10px; + margin: 10px 0; +} + +.message { + margin-left: 7%; + width: 90%; +} + +.message:focus{ + border: 2px solid var(--focus-color); +} + +.message:focus img{ + border: 2px solid var(--focus-color); +} + +.gitpatch { + width: 90%; + font-family: 'monospace'; +} + +.container::after { + content: ""; + clear: both; + display: table; +} + +.searchEmoji { + vertical-align: middle; + float: none; + width: 80px; + margin: 0px 10px; + padding: 0px 0px; + border-radius: 0px; +} + +.container img.emoji { + float: none; + width: 50px; + margin-left: 0px; + margin-right: 0px; + padding-right: 0px; + border-radius: 0px; + vertical-align: middle; +} + +.hero-text img.emojiprofile { + float: none; + width: 50px; + margin-left: 0px; + margin-right: 0px; + padding-right: 0px; + border-radius: 0px; + vertical-align: middle; +} + +.containericons { + padding: 0px 0px; + margin: 0px 0px; +} + +.replyingto { + color: var(--main-fg-color); +} + +.imText { + font-size: var(--font-size4); + font-family: Arial, Helvetica, sans-serif; + color: var(--main-link-color); + background: var(--link-bg-color); +} + +.container img.announceOrReply { + float: none; + width: 30px; + margin: 0 0; + padding: 0 0; + border-radius: 0; + vertical-align: middle; +} + +.container img.DMicon { + float: none; + width: 40px; + margin: 0 0; + padding: 0 0; + border-radius: 0; + vertical-align: middle; +} + +.darker { + background-color: var(--main-bg-color-reply); +} + +.dm { + background-color: var(--main-bg-color-dm); +} + +.report { + border-color: #255; + background-color: var(--main-bg-color-report); +} + +.container img.attachment { + max-width: 120%; + margin-left: 5%; + width: 120%; + padding-bottom: 3%; +} +.container img.right { + float: var(--icons-side); + margin-left: 0px; + margin-right:0; + padding: 0 0; + margin: 0 0; +} +.containericons img.right { + float: var(--icons-side); + margin-left: 20px; + margin-right: 0; +} + +.containericons img:hover { + filter: brightness(150%); +} + +.post-title { + margin-top: 0px; + color: #444; +} + +.share-title { + margin-top: 0px; + color: var(--main-fg-color); +} + +.skill-title { + margin-left: 25%; + text-align: left; + font-size: var(--font-size2); + font-family: Arial, Helvetica, sans-serif; + font-weight: bold; + color: var(--button-selected); + line-height: 40px; +} + +#myProgress { + float: left; + width: 70%; + background-color: #f1f1f1; +} + +#myBar { + float: left; + width: 10%; + height: 30px; + background-color: var(--button-background); + font-family: Arial, Helvetica, sans-serif; +} + +input[type=number] { + width: 10%; + padding: 12px; + border: 1px solid #ccc; + border-radius: var(--button-corner-radius); + box-sizing: border-box; + margin-top: 6px; + margin-bottom: 16px; + resize: vertical; + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; +} + +.labels { + font-size: var(--font-size); + font-family: Arial, Helvetica, sans-serif; +} + +.transparent { + color: rgba(0, 0, 0, 0.0); + font-size: 0px; + font-family: Arial, Helvetica, sans-serif; + line-height: 0; +} + +.labelsright { + float: right; + font-size: var(--font-size); + font-family: Arial, Helvetica, sans-serif; +} + +input[type=date] { + background-color: var(--main-bg-color-reply); + color: var(--main-fg-color); + font-size: var(--font-size); + font-family: Arial, Helvetica, sans-serif; + border: none; +} + +input[type=time] { + background-color: var(--main-bg-color-reply); + color: var(--main-fg-color); + font-size: var(--font-size); + font-family: Arial, Helvetica, sans-serif; + border: none; +} + +input[type=submit] { + background-color: var(--button-background); + color: var(--button-text); + margin: 10px 10px; + padding: 12px 10px; + border: none; + border-radius: var(--button-corner-radius); + cursor: pointer; + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; + width: 120px; +} + +.loginButton { + background-color: #2965; + color: #000; + float: none; + margin: 0px 10px; + padding: 12px 40px; + border: none; + border-radius: var(--button-corner-radius); + cursor: pointer; + font-size: var(--font-size2); + font-family: Arial, Helvetica, sans-serif; + opacity: 0.7; +} + +input[type=submit]:hover { + background-color: var(--button-background); +} + +.vertical-center { + max-width: 90%; + min-width: 600px; + margin: 0 auto; + padding: 5% 0px; +} + +/* The container
- 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; +} + +.timeline-avatar:hover { + filter: brightness(120%); +} + +.timeline-avatar-reply { + padding: 0px 0px; + width: 80%; +} + +.search-result-text { + font-size: var(--font-size); + font-family: Arial, Helvetica, sans-serif; +} + +.search-result img { + width: 7%; + 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; +} + +/* 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; +} + +.dropdown-content img { + width: 32px; + height: 32px; + padding: 0px 0px; +} + +/* Change color of dropdown links on hover */ +.dropdown-content a:hover { + color: var(--dropdown-fg-color-hover); + background-color: var(--dropdown-bg-color-hover); +} + +/* Show the dropdown menu on hover */ +.show {display: block;} + +.slider { + -webkit-appearance: none; + width: 57%; + height: 25px; + background: #d3d3d3; + outline: none; + opacity: 0.7; + -webkit-transition: .2s; + transition: opacity .2s; + float: right; + margin: 5px 0; + padding: 12px 0; +} + +.slider:hover { + opacity: 1; +} + +.slider::-moz-range-thumb { + width: 25px; + height: 25px; + background: var(--main-bg-color); + cursor: pointer; +} + +.dropbtn { + margin: 3%; + padding: 0px 14px; + position: relative; + display: inline-block; + border: none; + cursor: pointer; +} + +.dropbtn img { + opacity: 1.0; + width: 3%; + height: 3%; + min-width: 40px; + -ms-transform: translateY(10%); + transform: translateY(10%); +} + +div.gallery:hover { + border: 1px solid var(--gallery-hover); +} + +div.gallery img { + width: 100%; + height: auto; +} + +.invisible { + color: transparent; + font-size: 0; + font-family: Arial, Helvetica, sans-serif; +} + +.gallerytext { + text-decoration: none; +} + +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 { + -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; + 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:'\002b'; /* open */ + text-align: left; + float: left; +} +[id*='toggle'toggle'toggle']:checked + label div:after { + content:'\2212'; /* 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; + width: 170px; + 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:' '; /* open */ +} +.nav [id*='toggle']:checked + label div:after { + content:' '; /* 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; +} + +@media screen and (min-width: 400px) { + body, html { + background-color: var(--main-bg-color); + color: var(--main-fg-color); + + height: 100%; + font-family: Arial, Helvetica, sans-serif; + min-width: 950px; + font-size: var(--font-size); + line-height: var(--line-spacing); + } + .timeline { + border: 0; + width: 100vw; + } + .column-left { + width: var(--column-left-width); + } + .col-left { + float: left; + width: var(--column-left-width); + } + .col-center { + width: var(--column-center-width); + } + .column-right { + width: var(--column-right-width); + } + .col-right { + float: right; + width: var(--column-right-width); + } + .column-center { + display: inline-block; + width: var(--column-center-width); + } + .likesCount { + font-size: var(--font-size-likes); + font-family: Arial, Helvetica, sans-serif; + float: right; + padding: 10px 0; + transform: translateX(-10px); + font-weight: bold; + } + .container p.administeredby { + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; + } + .toxaddr { + font-size: var(--font-size-tox); + font-family: Arial, Helvetica, sans-serif; + } + .ssbaddr { + font-size: var(--font-size-pgp-key); + font-family: Arial, Helvetica, sans-serif; + } + .pgp { + font-size: var(--font-size-pgp-key); + color: var(--main-link-color); + background: var(--link-bg-color); + font-family: 'monospace'; + } + body, html { + font-size: var(--font-size4); + font-family: Arial, Helvetica, sans-serif; + } + .galleryContainer { + display: grid; + grid-template-columns: 50% 50%; + grid-column-gap: 5px; + background-color: var(--main-bg-color); + } + div.gallerytext { + color: var(--gallery-text-color); + font-size: var(--gallery-font-size); + font-family: Arial, Helvetica, sans-serif; + } + div.gallery { + margin: 5px; + border: 1px solid var(--gallery-border); + float: left; + width: 100%; + object-fit: scale-down; + } + div.imagedesc { + padding: 22px; + text-align: center; + } + .container img { + float: left; + max-width: 400px; + width: 5%; + padding: 0px 7px; + margin-right: 20px; + border-radius: var(--image-corners); + } + .container img.emojisearch { + width: 15%; + float: right; + } + .container img.emojicalendar { + float: left; + max-width: 400px; + width: 8%; + -ms-transform: translateY(-25%); + transform: translateY(-25%); + } + .container img.timelineicon { + float: var(--icons-side); + margin-left: 0px; + margin-right:0; + padding: 0 0; + margin: 0 0; + width: 50px; + } + .container img.emojiheader { + float: none; + width: 25px; + margin-left: 0px; + margin-right: 0px; + padding-right: 0px; + border-radius: 0px; + vertical-align: middle; + } + .containericons img { + float: var(--icons-side); + max-width: 200px; + width: 3%; + margin: 0px 1%; + border-radius: 0%; + } + div.mediaicons img { + float: right; + max-width: 200px; + width: 6%; + margin: 0px 1%; + border-radius: 0%; + } + div.mediaavatar img { + float: left; + max-width: 200px; + width: 5%; + margin: 0px 1%; + border-radius: 0%; + } + .timeline-avatar img { + opacity: 1.0; + width: 8%; + height: 8%; + padding: 0px 0px; + -ms-transform: translateY(-10%); + transform: translateY(-10%); + } + .buttonevent { + border-radius: var(--button-corner-radius); + background-color: var(--button-highlighted); + border: none; + color: var(--button-fg-highlighted); + text-align: center; + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding); + transition: all 0.5s; + cursor: pointer; + margin: 5px; + } + .button { + border-radius: var(--button-corner-radius); + background-color: var(--button-background); + border: none; + color: var(--button-text); + text-align: center; + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding); + width: 10%; + max-width: 200px; + min-width: 10ch; + transition: all 0.5s; + cursor: pointer; + margin: 5px; + } + .buttonhighlighted { + border-radius: var(--button-corner-radius); + background-color: var(--button-highlighted); + border: none; + color: var(--button-fg-highlighted); + text-align: center; + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding); + width: 10%; + max-width: 100px; + min-width: 10ch; + transition: all 0.5s; + cursor: pointer; + margin: 5px; + } + .buttonselected { + border-radius: var(--button-corner-radius); + background-color: var(--button-selected); + border: none; + color: var(--button-text); + text-align: center; + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding); + width: 10%; + max-width: 100px; + min-width: 10ch; + transition: all 0.5s; + cursor: pointer; + margin: 5px; + } + .buttonselectedhighlighted { + border-radius: var(--button-corner-radius); + background-color: var(--button-selected-highlighted); + border: none; + color: var(--button-text); + text-align: center; + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding); + width: 10%; + max-width: 100px; + min-width: 10ch; + transition: all 0.5s; + cursor: pointer; + margin: 5px; + } + .followApprove { + border-radius: var(--button-corner-radius); + background-color: var(--button-approve); + border: none; + color: #FFFFFF; + text-align: center; + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding); + width: 20%; + max-width: 200px; + min-width: 80px; + cursor: pointer; + margin: 0 5px; + float: right; + } + .followDeny { + border-radius: var(--button-corner-radius); + background-color: var(--button-deny); + border: none; + color: #FFFFFF; + text-align: center; + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding); + width: 20%; + max-width: 200px; + min-width: 80px; + cursor: pointer; + margin: 0 5px; + float: right; + } + .pageicon { + width: 4%; + } + .time-right { + float: var(--icons-side); + color: var(--time-color); + margin: var(--time-vertical-align) 20px; + } + input[type=text], select, textarea { + width: 100%; + padding: 12px; + border: 1px solid #ccc; + border-radius: var(--button-corner-radius); + box-sizing: border-box; + margin-top: 6px; + margin-bottom: 16px; + resize: vertical; + font-size: var(--font-size2); + font-family: Arial, Helvetica, sans-serif; + background-color: var(--main-bg-color-reply); + color: var(--main-fg-color); + } + input[type=button], input[type=submit] { + background-color: var(--button-background); + color: var(--button-text); + padding: var(--button-height-padding); + display: inline-block; + margin: 15px; + border: none; + border-radius: var(--button-corner-radius); + cursor: pointer; + font-size: var(--font-size); + font-family: Arial, Helvetica, sans-serif; + width: 20%; + } + .question { + font-size: var(--font-size); + font-family: Arial, Helvetica, sans-serif; + } + .questionresult { + font-size: var(--font-size); + font-family: Arial, Helvetica, sans-serif; + } + input[type=radio] { + font-size: var(--font-size); + font-family: Arial, Helvetica, sans-serif; + width: 32px; + vertical-align: middle; + margin-right: 20px; + } + input.vote[type=submit] { + background-color: var(--button-background); + color: var(--button-text); + float: left; + padding: var(--button-height-padding); + margin: 15px; + border: none; + border-radius: var(--button-corner-radius); + cursor: pointer; + font-size: var(--font-size); + font-family: Arial, Helvetica, sans-serif; + width: 20%; + } + input[type=file] { + background-color: var(--button-background); + color: var(--button-text); + padding: 20px; + margin: 0px; + border: none; + border-radius: var(--button-corner-radius); + cursor: pointer; + font-size: var(--font-size); + font-family: Arial, Helvetica, sans-serif; + width: 96%; + } + .cancelbtn { + background-color: var(--button-background); + color: var(--button-text); + padding: var(--button-height-padding); + margin: 15px; + border: none; + display: inline-block; + border-radius: var(--button-corner-radius); + cursor: pointer; + font-size: var(--font-size); + font-family: Arial, Helvetica, sans-serif; + width: 20%; + } + .scope-desc { + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + } + .buttonunfollow { + border-radius: var(--button-corner-radius); + background-color: var(--button-background); + border: none; + color: var(--button-text); + text-align: center; + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding); + width: 20%; + max-width: 200px; + min-width: 100px; + transition: all 0.5s; + cursor: pointer; + margin: 5px; + float: right; + } + .license { + float: right; + margin: 0% 1%; + width: 10%; + } + .donateButton { + background-color: var(--button-background); + color: var(--button-text); + float: none; + margin: 0px 10px; + padding: 12px 40px; + border: none; + border-radius: var(--button-corner-radius); + cursor: pointer; + font-size: var(--font-size2); + font-family: Arial, Helvetica, sans-serif; + opacity: 0.7; + } + .hero-text { + text-align: center; + position: absolute; + top: 50%; + left: 50%; + width: 70%; + transform: translate(-50%, -50%); + color: var(--font-color-header); + font-size: var(--font-size-header); + font-family: Arial, Helvetica, sans-serif; + } + .hero-text img.qrcode { + border-radius: 1%; + width: 5%; + min-width: 20px; + } + .hero-text img.title { + 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); + -moz-transform: scale(2); + -webkit-transform: scale(2); + -o-transform: scale(2); + transform: scale(2); + padding: 10px; + margin: 20px 30px; + } + input[type=radio] + { + -ms-transform: scale(2); + -moz-transform: scale(2); + -webkit-transform: scale(2); + -o-transform: scale(2); + transform: scale(2); + padding: 10px; + margin: 20px 30px; + } + input[type=number] + { + -ms-transform: scale(2); + -moz-transform: scale(2); + -webkit-transform: scale(2); + -o-transform: scale(2); + transform: scale(2); + padding: 10px; + margin: 20px 60px; + } +} + +@media screen and (min-width: 2200px) { + .galleryContainer { + display: grid; + grid-template-columns: 33% 33% 33%; + grid-column-gap: 5px; + background-color: var(--main-bg-color); + } +} + +@media screen and (max-width: 1000px) { + body, html { + background-color: var(--main-bg-color); + color: var(--main-fg-color); + + height: 100%; + font-family: Arial, Helvetica, sans-serif; + min-width: 950px; + margin-left: 0; + font-size: var(--font-size); + line-height: var(--line-spacing); + } + .timeline { + border: 0; + width: 100vw; + } + .column-left { + width: 0%; + } + .col-left { + float: left; + width: 0%; + display: none; + } + .col-center { + width: 100vw; + } + .col-right { + float: right; + width: 0%; + display: none; + } + .column-right { + width: 0%; + } + .column-center { + display: inline-block; + width: 100%; + } + .likesCount { + font-size: var(--font-size-likes-mobile); + font-family: Arial, Helvetica, sans-serif; + float: right; + padding: 32px 0; + transform: translateX(-20px); + font-weight: bold; + } + .container p.administeredby { + font-size: var(--font-size-tox2); + font-family: Arial, Helvetica, sans-serif; + } + .toxaddr { + font-size: var(--font-size-tox2); + font-family: Arial, Helvetica, sans-serif; + } + .ssbaddr { + font-size: var(--font-size-pgp-key2); + font-family: Arial, Helvetica, sans-serif; + } + .pgp { + font-size: var(--font-size-pgp-key2); + color: var(--main-link-color); + background: var(--link-bg-color); + font-family: 'monospace'; + } + body, html { + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + } + div.gallerytext { + color: var(--gallery-text-color); + font-size: var(--gallery-font-size-mobile); + font-family: Arial, Helvetica, sans-serif; + } + .galleryContainer { + display: grid; + grid-template-columns: auto; + background-color: var(--main-bg-color); + } + div.gallery { + margin: 5px; + border: 1px solid var(--gallery-border); + float: left; + width: 100%; + } + div.imagedesc { + padding: 35px; + text-align: center; + } + .container img { + float: left; + max-width: 400px; + width: 15%; + padding: 0px 7px; + margin-right: 20px; + border-radius: var(--image-corners); + } + .container img.emojisearch { + width: 25%; + float: right; + } + .container img.emojicalendar { + float: left; + max-width: 400px; + width: 12%; + -ms-transform: translateY(-25%); + transform: translateY(-25%); + } + .container img.timelineicon { + float: var(--icons-side); + margin-left: 0px; + margin-right:0; + padding: 0 0; + margin: 0 0; + width: 100px; + } + .container img.emojiheader { + float: none; + width: 45px; + margin-left: 0px; + margin-right: 0px; + padding-right: 0px; + border-radius: 0px; + vertical-align: middle; + } + div.mediaavatar img { + float: left; + max-width: 200px; + width: 8%; + margin: 0px 1%; + border-radius: 0%; + } + div.mediaicons img { + float: right; + max-width: 200px; + width: 10%; + margin: 0px 1%; + border-radius: 0%; + } + .containericons img { + float: var(--icons-side); + max-width: 200px; + width: 7%; + margin: 1% 3%; + border-radius: 0%; + } + .timeline-avatar img { + opacity: 1.0; + width: 15%; + height: 15%; + padding: 0px 0px; + -ms-transform: translateY(-10%); + transform: translateY(-10%); + } + .buttonevent { + border-radius: var(--button-corner-radius); + background-color: var(--button-highlighted); + border: none; + color: var(--button-fg-highlighted); + text-align: center; + font-size: var(--font-size-button-mobile); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding-mobile); + transition: all 0.5s; + cursor: pointer; + margin: 15px; + } + .button { + border-radius: var(--button-corner-radius); + background-color: var(--button-background); + border: none; + color: var(--button-text); + text-align: center; + font-size: var(--font-size-button-mobile); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding-mobile); + width: 20%; + max-width: 400px; + min-width: 10ch; + transition: all 0.5s; + cursor: pointer; + margin: 15px; + } + .buttonhighlighted { + border-radius: var(--button-corner-radius); + background-color: var(--button-highlighted); + border: none; + color: var(--button-fg-highlighted); + text-align: center; + font-size: var(--font-size-button-mobile); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding-mobile); + width: 20%; + max-width: 400px; + min-width: 10ch; + transition: all 0.5s; + cursor: pointer; + margin: 15px; + } + .buttonselected { + border-radius: var(--button-corner-radius); + background-color: var(--button-selected); + border: none; + color: var(--button-text); + text-align: center; + font-size: var(--font-size-button-mobile); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding-mobile); + width: 20%; + max-width: 400px; + min-width: 10ch; + transition: all 0.5s; + cursor: pointer; + margin: 15px; + } + .buttonselectedhighlighted { + border-radius: var(--button-corner-radius); + background-color: var(--button-selected-highlighted); + border: none; + color: var(--button-text); + text-align: center; + font-size: var(--font-size-button-mobile); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding-mobile); + width: 20%; + max-width: 400px; + min-width: 10ch; + transition: all 0.5s; + cursor: pointer; + margin: 15px; + } + .followApprove { + border-radius: var(--button-corner-radius); + background-color: var(--button-approve); + border: none; + color: #FFFFFF; + text-align: center; + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding-mobile); + width: 20%; + max-width: 400px; + min-width: 80px; + cursor: pointer; + margin: 0 15px; + float: right; + } + .followDeny { + border-radius: var(--button-corner-radius); + background-color: var(--button-deny); + border: none; + color: #FFFFFF; + text-align: center; + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding-mobile); + width: 20%; + max-width: 400px; + min-width: 80px; + cursor: pointer; + margin: 0 15px; + float: right; + } + .pageicon { + width: 14%; + } + .time-right { + float: var(--icons-side); + color: var(--time-color); + margin: 25px 20px; + } + input[type=text], select, textarea { + width: 100%; + padding: 12px; + border: 1px solid #ccc; + border-radius: var(--button-corner-radius); + box-sizing: border-box; + margin-top: 6px; + margin-bottom: 16px; + resize: vertical; + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + background-color: var(--main-bg-color-reply); + color: var(--main-fg-color); + } + input[type=button], input[type=submit] { + background-color: var(--button-background); + color: var(--button-text); + display: inline-block; + padding: var(--button-height-padding-mobile); + margin: 15px; + border: none; + border-radius: var(--button-corner-radius); + cursor: pointer; + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + width: 20%; + } + .question { + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + } + .questionresult { + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + } + input[type=radio] { + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + height: 90px; + vertical-align: middle; + margin-right: 20px; + } + input.vote[type=submit] { + background-color: var(--button-background); + color: var(--button-text); + float: left; + padding: var(--button-height-padding-mobile); + margin: 15px; + border: none; + border-radius: var(--button-corner-radius); + cursor: pointer; + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + width: 20%; + } + input[type=file] { + background-color: var(--button-background); + color: var(--button-text); + padding: 20px; + margin: 0px; + border: none; + border-radius: var(--button-corner-radius); + cursor: pointer; + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + width: 95.4%; + } + .cancelbtn { + background-color: var(--button-background); + color: var(--button-text); + display: inline-block; + padding: var(--button-height-padding-mobile); + margin: 15px; + border: none; + border-radius: var(--button-corner-radius); + cursor: pointer; + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + width: 20%; + } + .scope-desc { + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + } + .buttonunfollow { + border-radius: var(--button-corner-radius); + background-color: var(--button-background); + border: none; + color: var(--button-text); + text-align: center; + font-size: var(--font-size-button-mobile); + font-family: Arial, Helvetica, sans-serif; + padding: var(--button-height-padding-mobile); + width: 20%; + max-width: 200px; + min-width: 100px; + transition: all 0.5s; + cursor: pointer; + margin: 5px; + float: right; + } + .license { + float: right; + margin: 0% 1%; + width: 20%; + } + .donateButton { + background-color: var(--button-background); + color: var(--button-text); + float: none; + margin: 0px 10px; + padding: 12px 40px; + border: none; + border-radius: var(--button-corner-radius); + cursor: pointer; + font-size: var(--font-size3); + font-family: Arial, Helvetica, sans-serif; + opacity: 0.7; + } + .hero-text { + text-align: center; + position: absolute; + top: 50%; + left: 50%; + width: 70%; + transform: translate(-50%, -50%); + color: var(--font-color-header); + font-size: var(--font-size-header-mobile); + font-family: Arial, Helvetica, sans-serif; + } + .hero-text img.qrcode { + border-radius: 1%; + width: 15%; + min-width: 20px; + } + .hero-text img.title { + 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); + -moz-transform: scale(4); + -webkit-transform: scale(4); + -o-transform: scale(4); + transform: scale(4); + padding: 20px; + margin: 30px 40px; + } + input[type=radio] + { + -ms-transform: scale(2); + -moz-transform: scale(2); + -webkit-transform: scale(2); + -o-transform: scale(2); + transform: scale(2); + padding: 20px; + margin: 30px 40px; + } + input[type=number] + { + -ms-transform: scale(2); + -moz-transform: scale(2); + -webkit-transform: scale(2); + -o-transform: scale(2); + transform: scale(2); + padding: 10px; + margin: 40px 80px; + } +} diff --git a/epicyon-profile.css b/epicyon-profile.css index 1cb9d1b9c..eac77ae78 100644 --- a/epicyon-profile.css +++ b/epicyon-profile.css @@ -2,6 +2,7 @@ :root { --main-bg-color: #282c37; + --column-left-color: #282c37; --link-bg-color: #282c37; --dropdown-fg-color: #dddddd; --dropdown-bg-color: #111; @@ -12,6 +13,7 @@ --main-bg-color-report: #221c27; --main-header-color-roles: #282237; --main-fg-color: #dddddd; + --column-left-fg-color: #dddddd; --main-link-color: #999; --main-link-color-hover: #bbb; --main-visited-color: #888; @@ -21,6 +23,7 @@ --font-size-header-mobile: 32px; --font-color-header: #ccc; --font-size-button-mobile: 34px; + --font-size-links: 18px; --font-size: 30px; --font-size2: 24px; --font-size3: 38px; @@ -61,6 +64,14 @@ --quote-font-weight: normal; --quote-font-size: 120%; --line-spacing: 130%; + --column-left-width: 10vw; + --column-center-width: 80vw; + --column-right-width: 10vw; + --column-left-header-background: #555; + --column-left-header-color: #fff; + --column-left-header-size: 20px; + --column-left-icon-size: 20%; + --column-right-icon-size: 20%; } @font-face { @@ -84,38 +95,36 @@ body, html { height: 100%; font-family: Arial, Helvetica, sans-serif; - max-width: 80%; min-width: 950px; - margin: 0 auto; font-size: var(--font-size); line-height: var(--line-spacing); } blockquote { - border-left: 10px; - margin: 1.5em 10px; - padding: 0.5em 10px; - font-weight: var(--quote-font-weight); - font-style: italic; - font-size: var(--quote-font-size); - quotes: "\201C""\201D""\2018""\2019"; + border-left: 10px; + margin: 1.5em 10px; + padding: 0.5em 10px; + font-weight: var(--quote-font-weight); + font-style: italic; + font-size: var(--quote-font-size); + quotes: "\201C""\201D""\2018""\2019"; } blockquote:before { - content: open-quote; - font-size: 2em; - line-height: 0.1em; - margin-right: 0.25em; - vertical-align: -0.4em; + content: open-quote; + font-size: 2em; + line-height: 0.1em; + margin-right: 0.25em; + vertical-align: -0.4em; } blockquote:after { - content: close-quote; - font-size: 2em; - line-height: 0.1em; - margin-left: var(--quote-right-margin); - vertical-align: -0.4em; + content: close-quote; + font-size: 2em; + line-height: 0.1em; + margin-left: var(--quote-right-margin); + vertical-align: -0.4em; } blockquote p { - display: inline; + display: inline; } .imageAnchor:focus img{ @@ -126,6 +135,15 @@ h1 { color: var(--title-color); } +h3.linksHeader { + background-color: var(--column-left-header-background); + color: var(--column-left-header-color); + font-size: var(--column-left-header-size); + text-transform: uppercase; + padding: 4px; + border: none; +} + a, u { color: var(--main-fg-color); } @@ -158,15 +176,6 @@ a:focus { border: 2px solid var(--focus-color); } -.timeline-banner { - background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("banner.png"); - height: 10%; - background-position: center; - background-repeat: no-repeat; - background-size: cover; - position: relative; -} - .hero-image { background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("image.png"); height: 50%; @@ -735,22 +744,22 @@ a, button, input:focus, input[type='button'], input[type='reset'], input[type='s text-decoration: none; } .button-msgScope { - display:flex; - flex-direction:row; - justify-content:center; - width:100%; - min-height:100%; + 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; + 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; @@ -923,6 +932,101 @@ aside .toggle-inside li { } @media screen and (min-width: 400px) { + body, html { + background-color: var(--main-bg-color); + color: var(--main-fg-color); + + height: 100%; + font-family: Arial, Helvetica, sans-serif; + min-width: 950px; + font-size: var(--font-size); + line-height: var(--line-spacing); + } + .timeline-banner { + background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("banner.png"); + height: 15%; + background-position: center; + background-repeat: no-repeat; + background-size: 100vw; + position: relative; + } + .timeline { + border: 0; + width: 100vw; + } + .col-left a:link { + background: var(--column-left-color); + } + .col-left a:visited { + background: var(--column-left-color); + } + .column-left { + background-color: var(--column-left-color); + width: var(--column-left-width); + } + .col-left { + color: var(--column-left-fg-color); + padding: 10px 10px; + font-size: var(--font-size-links); + float: left; + width: var(--column-left-width); + } + .col-left img.leftColEdit { + background: var(--column-left-color); + margin: 40px 0; + width: var(--column-left-icon-size); + } + .col-left img.leftColEditImage { + background: var(--column-left-color); + width: var(--column-left-icon-size); + float: right; + } + .col-left img.leftColImg { + background: var(--column-left-color); + width: 100%; + margin: 0 0; + padding: 0 0; + } + .column-center { + width: var(--column-center-width); + } + .col-center { + width: var(--column-center-width); + background-color: var(--main-bg-color); + } + .col-right a:link { + background: var(--column-left-color); + } + .col-right a:visited { + background: var(--column-left-color); + } + .column-right { + background-color: var(--column-left-color); + width: var(--column-right-width); + } + .col-right { + background-color: var(--column-left-color); + color: var(--column-left-fg-color); + padding-right: 30px; + font-size: var(--font-size-links); + width: var(--column-right-width); + } + .col-right img.rightColEdit { + background: var(--column-left-color); + margin: 40px 0; + width: var(--column-right-icon-size); + } + .col-right img.rightColEditImage { + background: var(--column-left-color); + width: var(--column-right-icon-size); + float: right; + } + .col-right img.rightColImg { + background: var(--column-left-color); + width: 100%; + margin: 0 0; + padding: 0 0; + } .likesCount { font-size: var(--font-size-likes); font-family: Arial, Helvetica, sans-serif; @@ -1372,6 +1476,53 @@ aside .toggle-inside li { } @media screen and (max-width: 1000px) { + body, html { + background-color: var(--main-bg-color); + color: var(--main-fg-color); + + height: 100%; + font-family: Arial, Helvetica, sans-serif; + min-width: 950px; + margin-left: 0; + font-size: var(--font-size); + line-height: var(--line-spacing); + } + .timeline-banner { + background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("banner.png"); + height: 6%; + background-position: center; + background-repeat: no-repeat; + background-size: 145vw; + position: relative; + } + .timeline { + border: 0; + width: 100vw; + } + .column-left { + display: none; + width: 0%; + } + .col-left { + float: left; + width: 0%; + display: none; + } + .col-center { + width: 100vw; + } + .col-right { + float: right; + width: 0%; + display: none; + } + .column-right { + display: none; + width: 0%; + } + .column-center { + width: 100%; + } .likesCount { font-size: var(--font-size-likes-mobile); font-family: Arial, Helvetica, sans-serif; diff --git a/epicyon.py b/epicyon.py index 996200808..55cb19c7a 100644 --- a/epicyon.py +++ b/epicyon.py @@ -28,6 +28,7 @@ from posts import getUserUrl from posts import checkDomains from session import createSession from session import getJson +from newswire import getRSS from filters import addFilter from filters import removeFilter import os @@ -176,6 +177,8 @@ parser.add_argument('--postsraw', dest='postsraw', type=str, help='Show raw json of posts for the given handle') parser.add_argument('--json', dest='json', type=str, default=None, help='Show the json for a given activitypub url') +parser.add_argument('--rss', dest='rss', type=str, default=None, + help='Show an rss feed for a given url') parser.add_argument('-f', '--federate', nargs='+', dest='federationList', help='Specify federation list separated by spaces') parser.add_argument("--repliesEnabled", "--commentsEnabled", @@ -595,6 +598,12 @@ if args.json: pprint(testJson) sys.exit() +if args.rss: + session = createSession(None) + testRSS = getRSS(session, args.rss) + pprint(testRSS) + sys.exit() + # create cache for actors if not os.path.isdir(baseDir + '/cache'): os.mkdir(baseDir + '/cache') @@ -615,11 +624,15 @@ if not args.mediainstance: mediaInstance = getConfigParam(baseDir, 'mediaInstance') if mediaInstance is not None: args.mediainstance = mediaInstance + if args.mediainstance: + args.blogsinstance = False if not args.blogsinstance: blogsInstance = getConfigParam(baseDir, 'blogsInstance') if blogsInstance is not None: args.blogsinstance = blogsInstance + if args.blogsinstance: + args.mediainstance = False # set the instance title in config.json title = getConfigParam(baseDir, 'instanceTitle') diff --git a/img/banner_indymedia.png b/img/banner_indymedia.png index 2d4c72ca0..a77d2f63e 100644 Binary files a/img/banner_indymedia.png and b/img/banner_indymedia.png differ diff --git a/img/icons/indymedia/rss.png b/img/icons/indymedia/rss.png index 2c9326ea7..e6fc9ae6d 100644 Binary files a/img/icons/indymedia/rss.png and b/img/icons/indymedia/rss.png differ diff --git a/img/left_col_image_indymedia.png b/img/left_col_image_indymedia.png new file mode 100644 index 000000000..173e847ab Binary files /dev/null and b/img/left_col_image_indymedia.png differ diff --git a/img/right_col_image_indymedia.png b/img/right_col_image_indymedia.png new file mode 100644 index 000000000..9030f71eb Binary files /dev/null and b/img/right_col_image_indymedia.png differ diff --git a/img/search_banner_indymedia.png b/img/search_banner_indymedia.png index 8d0c733ab..ee32b54ab 100644 Binary files a/img/search_banner_indymedia.png and b/img/search_banner_indymedia.png differ diff --git a/newswire.py b/newswire.py new file mode 100644 index 000000000..d295b1273 --- /dev/null +++ b/newswire.py @@ -0,0 +1,177 @@ +__filename__ = "newswire.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.1.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import os +import requests +from socket import error as SocketError +import errno +from datetime import datetime +from collections import OrderedDict + + +def rss2Header(httpPrefix: str, + nickname: str, domainFull: str, + title: str, translate: {}) -> str: + rssStr = "" + rssStr += "" + rssStr += '' + if title.startswith('News'): + rssStr += ' Newswire' + else: + rssStr += ' ' + translate[title] + '' + if title.startswith('News'): + rssStr += ' ' + httpPrefix + '://' + domainFull + \ + '/newswire.xml' + '' + else: + rssStr += ' ' + httpPrefix + '://' + domainFull + \ + '/users/' + nickname + '/rss.xml' + '' + return rssStr + + +def rss2Footer() -> str: + rssStr = '' + rssStr += '' + return rssStr + + +def xml2StrToDict(xmlStr: str) -> {}: + """Converts an xml 2.0 string to a dictionary + """ + if '' not in xmlStr: + return {} + result = {} + rssItems = xmlStr.split('') + for rssItem in rssItems: + if '' not in rssItem: + continue + if '' not in rssItem: + continue + if '' not in rssItem: + continue + if '' not in rssItem: + continue + if '' not in rssItem: + continue + if '' not in rssItem: + continue + title = rssItem.split('')[1] + title = title.split('')[0] + link = rssItem.split('')[1] + link = link.split('')[0] + pubDate = rssItem.split('')[1] + pubDate = pubDate.split('')[0] + parsed = False + try: + publishedDate = \ + datetime.strptime(pubDate, "%a, %d %b %Y %H:%M:%S %z") + result[str(publishedDate)] = [title, link] + parsed = True + except BaseException: + pass + if not parsed: + try: + publishedDate = \ + datetime.strptime(pubDate, "%a, %d %b %Y %H:%M:%S UT") + result[str(publishedDate) + '+00:00'] = [title, link] + parsed = True + except BaseException: + print('WARN: unrecognized RSS date format: ' + pubDate) + pass + return result + + +def xmlStrToDict(xmlStr: str) -> {}: + """Converts an xml string to a dictionary + """ + if 'rss version="2.0"' in xmlStr: + return xml2StrToDict(xmlStr) + return {} + + +def getRSS(session, url: str) -> {}: + """Returns an RSS url as a dict + """ + if not isinstance(url, str): + print('url: ' + str(url)) + print('ERROR: getRSS url should be a string') + return None + headers = { + 'Accept': 'text/xml; charset=UTF-8' + } + params = None + sessionParams = {} + sessionHeaders = {} + if headers: + sessionHeaders = headers + if params: + sessionParams = params + sessionHeaders['User-Agent'] = \ + 'Mozilla/5.0 (X11; Linux x86_64; rv:81.0) Gecko/20100101 Firefox/81.0' + if not session: + print('WARN: no session specified for getRSS') + try: + result = session.get(url, headers=sessionHeaders, params=sessionParams) + return xmlStrToDict(result.text) + except requests.exceptions.RequestException as e: + print('ERROR: getRSS failed\nurl: ' + str(url) + '\n' + + 'headers: ' + str(sessionHeaders) + '\n' + + 'params: ' + str(sessionParams) + '\n') + print(e) + except ValueError as e: + print('ERROR: getRSS failed\nurl: ' + str(url) + '\n' + + 'headers: ' + str(sessionHeaders) + '\n' + + 'params: ' + str(sessionParams) + '\n') + print(e) + except SocketError as e: + if e.errno == errno.ECONNRESET: + print('WARN: connection was reset during getRSS') + print(e) + return None + + +def getRSSfromDict(baseDir: str, newswire: {}, + httpPrefix: str, domainFull: str, + title: str, translate: {}) -> str: + """Returns an rss feed from the current newswire dict. + This allows other instances to subscribe to the same newswire + """ + rssStr = rss2Header(httpPrefix, + None, domainFull, + 'Newswire', translate) + for published, fields in newswire.items(): + rssStr += '\n' + rssStr += ' ' + fields[0] + '\n' + rssStr += ' ' + fields[1] + '\n' + pubDate = datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ") + rssDateStr = pubDate.strftime("%a, %d %b %Y %H:%M:%S UT") + rssStr += ' ' + rssDateStr + '\n' + rssStr += '\n' + rssStr += rss2Footer() + return rssStr + + +def getDictFromNewswire(session, baseDir: str) -> {}: + """Gets rss feeds as a dictionary from newswire file + """ + subscriptionsFilename = baseDir + '/accounts/newswire.txt' + if not os.path.isfile(subscriptionsFilename): + return {} + + rssFeed = [] + with open(subscriptionsFilename, 'r') as fp: + rssFeed = fp.readlines() + result = {} + for url in rssFeed: + url = url.strip() + if '://' not in url: + continue + if url.startswith('#'): + continue + result = dict(result.items() + getRSS(session, url).items()) + sortedResult = OrderedDict(sorted(result.items(), reverse=False)) + return sortedResult diff --git a/session.py b/session.py index 81a5c423f..96b6de026 100644 --- a/session.py +++ b/session.py @@ -54,7 +54,7 @@ def createSession(proxyType: str): def getJson(session, url: str, headers: {}, params: {}, - version='1.0.0', httpPrefix='https', + version='1.1.0', httpPrefix='https', domain='testdomain') -> {}: if not isinstance(url, str): print('url: ' + str(url)) diff --git a/tests.py b/tests.py index fa0de6165..cc0a472c8 100644 --- a/tests.py +++ b/tests.py @@ -2125,7 +2125,7 @@ def testReplaceEmailQuote(): "

Some other text.

" resultStr = htmlReplaceEmailQuote(testStr) if resultStr != expectedStr: - print('Result: ' + resultStr) + print('Result: ' + str(resultStr)) print('Expect: ' + expectedStr) assert resultStr == expectedStr @@ -2135,7 +2135,26 @@ def testReplaceEmailQuote(): "second line

Some question?

" resultStr = htmlReplaceEmailQuote(testStr) if resultStr != expectedStr: - print('Result: ' + resultStr) + print('Result: ' + str(resultStr)) + print('Expect: ' + expectedStr) + assert resultStr == expectedStr + + testStr = "

" + \ + "@somenick" + \ + "

> Text1.
>
" + \ + "> Text2
>
> Text3
" + \ + ">
> Text4
>
> " + \ + "Text5
>
> Text6

Text7

" + expectedStr = "

" + \ + "@somenick" + \ + "

Text1.

" + \ + "Text2

Text3
>
Text4
" + \ + "
Text5

Text6

Text7

" + resultStr = htmlReplaceEmailQuote(testStr) + if resultStr != expectedStr: + print('Result: ' + str(resultStr)) print('Expect: ' + expectedStr) assert resultStr == expectedStr diff --git a/theme.py b/theme.py index 1c092e630..ee64481c6 100644 --- a/theme.py +++ b/theme.py @@ -15,7 +15,7 @@ from shutil import copyfile def getThemeFiles() -> []: return ('epicyon.css', 'login.css', 'follow.css', 'suspended.css', 'calendar.css', 'blog.css', - 'options.css', 'search.css') + 'options.css', 'search.css', 'links.css') def getThemesList() -> []: @@ -264,12 +264,17 @@ def setThemeIndymedia(baseDir: str): "font-size4": "24px", "font-size5": "22px", "main-bg-color": "black", + "column-left-header-color": "#fff", + "column-left-header-background": "#555", + "column-left-header-size": "20px", + "column-left-color": "#003366", "text-entry-background": "#0f0d10", "link-bg-color": "black", "main-link-color": "#ff9900", "main-link-color-hover": "#d09338", "main-visited-color": "#ffb900", "main-fg-color": "white", + "column-left-fg-color": "white", "main-bg-color-dm": "#0b0a0a", "border-color": "#003366", "border-width": "0", @@ -292,6 +297,10 @@ def setThemeIndymedia(baseDir: str): "title-text": "white", "title-background": "#003366", "quote-right-margin": "0.1em", + "column-left-width": "10vw", + "column-center-width": "70vw", + "column-right-width": "20vw", + "column-right-icon-size": "11%" } setThemeFromDict(baseDir, name, themeParams, bgParams) @@ -311,6 +320,7 @@ def setThemeBlue(baseDir: str): "gallery-font-size": "35px", "gallery-font-size-mobile": "55px", "main-bg-color": "#002365", + "column-left-color": "#002365", "text-entry-background": "#002365", "link-bg-color": "#002365", "main-bg-color-reply": "#002365", @@ -348,11 +358,13 @@ def setThemeNight(baseDir: str): "font-size4": "24px", "font-size5": "22px", "main-bg-color": "#0f0d10", + "column-left-color": "#0f0d10", "text-entry-background": "#0f0d10", "link-bg-color": "#0f0d10", "main-link-color": "ff9900", "main-link-color-hover": "#d09338", "main-fg-color": "#a961ab", + "column-left-fg-color": "#a961ab", "main-bg-color-dm": "#0b0a0a", "border-color": "#606984", "main-bg-color-reply": "#0f0d10", @@ -398,6 +410,7 @@ def setThemeStarlight(baseDir: str): "font-size4": "24px", "font-size5": "22px", "main-bg-color": "#0f0d10", + "column-left-color": "#0f0d10", "text-entry-background": "#0f0d10", "link-bg-color": "#0f0d10", "main-link-color": "#ffc4bc", @@ -405,6 +418,7 @@ def setThemeStarlight(baseDir: str): "title-color": "#ffc4bc", "main-visited-color": "#e1c4bc", "main-fg-color": "#ffc4bc", + "column-left-fg-color": "#ffc4bc", "main-bg-color-dm": "#0b0a0a", "border-color": "#69282c", "border-width": "3px", @@ -457,6 +471,7 @@ def setThemeHenge(baseDir: str): "font-size4": "24px", "font-size5": "22px", "main-bg-color": "#383335", + "column-left-color": "#383335", "text-entry-background": "#383335", "link-bg-color": "#383335", "main-link-color": "white", @@ -464,6 +479,7 @@ def setThemeHenge(baseDir: str): "title-color": "white", "main-visited-color": "#e1c4bc", "main-fg-color": "white", + "column-left-fg-color": "white", "main-bg-color-dm": "#343335", "border-color": "#222", "border-width": "5px", @@ -506,6 +522,7 @@ def setThemeZen(baseDir: str): setThemeInConfig(baseDir, name) themeParams = { "main-bg-color": "#5c4e41", + "column-left-color": "#5c4e41", "text-entry-background": "#5c4e41", "link-bg-color": "#5c4e41", "main-bg-color-reply": "#5c4e41", @@ -565,6 +582,7 @@ def setThemeLCD(baseDir: str): name = 'lcd' themeParams = { "main-bg-color": "#9fb42b", + "column-left-color": "#9fb42b", "link-bg-color": "#33390d", "text-entry-foreground": "#33390d", "text-entry-background": "#9fb42b", @@ -573,6 +591,7 @@ def setThemeLCD(baseDir: str): "main-bg-color-dm": "#5fb42b", "main-header-color-roles": "#9fb42b", "main-fg-color": "#33390d", + "column-left-fg-color": "#33390d", "border-color": "#33390d", "border-width": "5px", "main-link-color": "#9fb42b", @@ -641,11 +660,13 @@ def setThemePurple(baseDir: str): "font-size4": "24px", "font-size5": "22px", "main-bg-color": "#1f152d", + "column-left-color": "#1f152d", "link-bg-color": "#1f152d", "main-bg-color-reply": "#1a142d", "main-bg-color-report": "#12152d", "main-header-color-roles": "#1f192d", "main-fg-color": "#f98bb0", + "column-left-fg-color": "#f98bb0", "border-color": "#3f2145", "main-link-color": "#ff42a0", "main-link-color-hover": "white", @@ -689,12 +710,14 @@ def setThemeHacker(baseDir: str): themeParams = { "focus-color": "green", "main-bg-color": "black", + "column-left-color": "black", "link-bg-color": "black", "main-bg-color-dm": "#0b0a0a", "main-bg-color-reply": "#030202", "main-bg-color-report": "#050202", "main-header-color-roles": "#1f192d", "main-fg-color": "#00ff00", + "column-left-fg-color": "#00ff00", "border-color": "#035103", "main-link-color": "#2fff2f", "main-link-color-hover": "#afff2f", @@ -747,6 +770,7 @@ def setThemeLight(baseDir: str): "font-size4": "24px", "font-size5": "22px", "rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)", + "column-left-color": "#e6ebf0", "main-bg-color": "#e6ebf0", "main-bg-color-dm": "#e3dbf0", "link-bg-color": "#e6ebf0", @@ -754,6 +778,7 @@ def setThemeLight(baseDir: str): "main-bg-color-report": "#e3dbf0", "main-header-color-roles": "#ebebf0", "main-fg-color": "#2d2c37", + "column-left-fg-color": "#2d2c37", "border-color": "#c0cdd9", "main-link-color": "#2a2c37", "main-link-color-hover": "#aa2c37", @@ -804,12 +829,14 @@ def setThemeSolidaric(baseDir: str): "font-size5": "22px", "rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)", "main-bg-color": "white", + "column-left-color": "white", "main-bg-color-dm": "white", "link-bg-color": "white", "main-bg-color-reply": "white", "main-bg-color-report": "white", "main-header-color-roles": "#ebebf0", "main-fg-color": "#2d2c37", + "column-left-fg-color": "#2d2c37", "border-color": "#c0cdd9", "main-link-color": "#2a2c37", "main-link-color-hover": "#aa2c37", @@ -864,6 +891,10 @@ def setThemeImages(baseDir: str, name: str) -> None: baseDir + '/img/banner.png' searchBannerFilename = \ baseDir + '/img/search_banner.png' + leftColImageFilename = \ + baseDir + '/img/left_col_image.png' + rightColImageFilename = \ + baseDir + '/img/right_col_image.png' else: profileImageFilename = \ baseDir + '/img/image_' + themeNameLower + '.png' @@ -871,6 +902,10 @@ def setThemeImages(baseDir: str, name: str) -> None: baseDir + '/img/banner_' + themeNameLower + '.png' searchBannerFilename = \ baseDir + '/img/search_banner_' + themeNameLower + '.png' + leftColImageFilename = \ + baseDir + '/img/left_col_image_' + themeNameLower + '.png' + rightColImageFilename = \ + baseDir + '/img/right_col_image_' + themeNameLower + '.png' backgroundNames = ('login', 'shares', 'delete', 'follow', 'options', 'block', 'search', 'calendar') @@ -937,6 +972,29 @@ def setThemeImages(baseDir: str, name: str) -> None: except BaseException: pass + try: + if os.path.isfile(leftColImageFilename): + copyfile(leftColImageFilename, + accountDir + '/left_col_image.png') + else: + if os.path.isfile(accountDir + + '/left_col_image.png'): + os.remove(accountDir + '/left_col_image.png') + + except BaseException: + pass + + try: + if os.path.isfile(rightColImageFilename): + copyfile(rightColImageFilename, + accountDir + '/right_col_image.png') + else: + if os.path.isfile(accountDir + + '/right_col_image.png'): + os.remove(accountDir + '/right_col_image.png') + except BaseException: + pass + def setTheme(baseDir: str, name: str) -> bool: result = False diff --git a/translations/ar.json b/translations/ar.json index b519fa594..ae92a954a 100644 --- a/translations/ar.json +++ b/translations/ar.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "علامات التجزئة المُنشأة تلقائيًا", "Autogenerated Content Warnings": "تحذيرات المحتوى المُنشأ تلقائيًا", "Indymedia": "Indymedia", - "Hashtag Blocked": "Hashtag محظور" + "Hashtag Blocked": "Hashtag محظور", + "This is a blogging instance": "هذا مثال على المدونات", + "Edit Links": "تحرير الارتباطات", + "One link per line. Description followed by the link.": "رابط واحد في كل سطر. الوصف متبوع بالرابط.", + "Left column image": "صورة العمود الأيسر", + "Right column image": "صورة العمود الأيمن", + "RSS feed for this site": "تغذية RSS لهذا الموقع", + "Edit newswire": "تحرير الأخبار", + "Add RSS feed links below.": "إضافة روابط تغذية RSS أدناه.", + "Newswire RSS Feed": "Newswire موجز RSS" } diff --git a/translations/ca.json b/translations/ca.json index 62736ad2d..5671b1dbc 100644 --- a/translations/ca.json +++ b/translations/ca.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "Hashtags autogenerats", "Autogenerated Content Warnings": "Advertiments de contingut autogenerats", "Indymedia": "Indymedia", - "Hashtag Blocked": "Hashtag bloquejat" + "Hashtag Blocked": "Hashtag bloquejat", + "This is a blogging instance": "Aquesta és una instància de blocs", + "Edit Links": "Edita els enllaços", + "One link per line. Description followed by the link.": "Un enllaç per línia. Descripció seguida de l'enllaç.", + "Left column image": "Imatge de la columna esquerra", + "Right column image": "Imatge de la columna dreta", + "RSS feed for this site": "Feed RSS per a aquest lloc", + "Edit newswire": "Editeu newswire", + "Add RSS feed links below.": "Afegiu enllaços de canals RSS a continuació.", + "Newswire RSS Feed": "Feed RSS de Newswire" } diff --git a/translations/cy.json b/translations/cy.json index 89f52d7ee..b5f824854 100644 --- a/translations/cy.json +++ b/translations/cy.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "Hashtags awtogeneiddiedig", "Autogenerated Content Warnings": "Rhybuddion Cynnwys Autogenerated", "Indymedia": "Indymedia", - "Hashtag Blocked": "Hashtag wedi'i Blocio" + "Hashtag Blocked": "Hashtag wedi'i Blocio", + "This is a blogging instance": "Dyma enghraifft blogio", + "Edit Links": "Golygu Dolenni", + "One link per line. Description followed by the link.": "Un dolen y llinell. Disgrifiad wedi'i ddilyn gan y ddolen.", + "Left column image": "Delwedd colofn chwith", + "Right column image": "Delwedd colofn dde", + "RSS feed for this site": "Porthiant RSS ar gyfer y wefan hon", + "Edit newswire": "Golygu newyddion", + "Add RSS feed links below.": "Ychwanegwch ddolenni porthiant RSS isod.", + "Newswire RSS Feed": "Newswire RSS Feed" } diff --git a/translations/de.json b/translations/de.json index e3bc529b6..00fad6c8a 100644 --- a/translations/de.json +++ b/translations/de.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "Automatisch generierte Hashtags", "Autogenerated Content Warnings": "Warnungen vor automatisch generierten Inhalten", "Indymedia": "Indymedia", - "Hashtag Blocked": "Hashtag blockiert" + "Hashtag Blocked": "Hashtag blockiert", + "This is a blogging instance": "Dies ist eine Blogging-Instanz", + "Edit Links": "Links bearbeiten", + "One link per line. Description followed by the link.": "Ein Link pro Zeile. Beschreibung gefolgt vom Link.", + "Left column image": "Bild in der linken Spalte", + "Right column image": "Bild in der rechten Spalte", + "RSS feed for this site": "RSS-Feed für diese Site", + "Edit newswire": "Newswire bearbeiten", + "Add RSS feed links below.": "Fügen Sie unten RSS-Feed-Links hinzu.", + "Newswire RSS Feed": "Newswire RSS Feed" } diff --git a/translations/en.json b/translations/en.json index c85bbfe85..2a12fc9db 100644 --- a/translations/en.json +++ b/translations/en.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "Autogenerated Hashtags", "Autogenerated Content Warnings": "Autogenerated Content Warnings", "Indymedia": "Indymedia", - "Hashtag Blocked": "Hashtag Blocked" + "Hashtag Blocked": "Hashtag Blocked", + "This is a blogging instance": "This is a blogging instance", + "Edit Links": "Edit Links", + "One link per line. Description followed by the link.": "One link per line. Description followed by the link. Titles should begin with #", + "Left column image": "Left column image", + "Right column image": "Right column image", + "RSS feed for this site": "RSS feed for this site", + "Edit newswire": "Edit newswire", + "Add RSS feed links below.": "Add RSS feed links below.", + "Newswire RSS Feed": "Newswire RSS Feed" } diff --git a/translations/es.json b/translations/es.json index 6fe9c141b..2ff5870f5 100644 --- a/translations/es.json +++ b/translations/es.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "Hashtags autogenerados", "Autogenerated Content Warnings": "Advertencias de contenido generado automáticamente", "Indymedia": "Indymedia", - "Hashtag Blocked": "Hashtag bloqueada" + "Hashtag Blocked": "Hashtag bloqueada", + "This is a blogging instance": "Esta es una instancia de blogs", + "Edit Links": "Editar enlaces", + "One link per line. Description followed by the link.": "Un enlace por línea. Descripción seguida del enlace.", + "Left column image": "Imagen de la columna izquierda", + "Right column image": "Imagen de la columna derecha", + "RSS feed for this site": "Fuente RSS para este sitio", + "Edit newswire": "Editar newswire", + "Add RSS feed links below.": "Agregue los enlaces de fuentes RSS a continuación.", + "Newswire RSS Feed": "Canal RSS de Newswire" } diff --git a/translations/fr.json b/translations/fr.json index 4a1fb6cb0..11dec422b 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "Hashtags générés automatiquement", "Autogenerated Content Warnings": "Avertissements de contenu générés automatiquement", "Indymedia": "Indymedia", - "Hashtag Blocked": "Hashtag bloqué" + "Hashtag Blocked": "Hashtag bloqué", + "This is a blogging instance": "Ceci est une instance de blog", + "Edit Links": "Modifier les liens", + "One link per line. Description followed by the link.": "Un lien par ligne. Description suivie du lien.", + "Left column image": "Image de la colonne de gauche", + "Right column image": "Image de la colonne de droite", + "RSS feed for this site": "Flux RSS de ce site", + "Edit newswire": "Modifier le fil d'actualité", + "Add RSS feed links below.": "Ajoutez des liens de flux RSS ci-dessous.", + "Newswire RSS Feed": "Flux RSS de Newswire" } diff --git a/translations/ga.json b/translations/ga.json index 1c1266bae..a08ecd81c 100644 --- a/translations/ga.json +++ b/translations/ga.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "Hashtags uathghinte", "Autogenerated Content Warnings": "Rabhaidh Ábhar Uathghinte", "Indymedia": "Indymedia", - "Hashtag Blocked": "Hashtag Blocáilte" + "Hashtag Blocked": "Hashtag Blocáilte", + "This is a blogging instance": "Seo sampla blagála", + "Edit Links": "Cuir Naisc in eagar", + "One link per line. Description followed by the link.": "Nasc amháin in aghaidh an líne. Cur síos agus an nasc ina dhiaidh sin.", + "Left column image": "Íomhá colún ar chlé", + "Right column image": "Íomhá colún ar dheis", + "RSS feed for this site": "Fotha RSS don láithreán seo", + "Edit newswire": "Cuir sreang nuachta in eagar", + "Add RSS feed links below.": "Cuir naisc beatha RSS thíos.", + "Newswire RSS Feed": "Newswire RSS Feed" } diff --git a/translations/hi.json b/translations/hi.json index bff59966f..4caac27f1 100644 --- a/translations/hi.json +++ b/translations/hi.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "ऑटोजेनरेटेड हैशटैग", "Autogenerated Content Warnings": "स्वतः प्राप्त सामग्री चेतावनी", "Indymedia": "Indymedia", - "Hashtag Blocked": "हैशटैग अवरुद्ध" + "Hashtag Blocked": "हैशटैग अवरुद्ध", + "This is a blogging instance": "यह एक ब्लॉगिंग उदाहरण है", + "Edit Links": "लिंक संपादित करें", + "One link per line. Description followed by the link.": "प्रति पंक्ति एक लिंक। लिंक के बाद विवरण।", + "Left column image": "बाएं स्तंभ की छवि", + "Right column image": "राइट कॉलम छवि", + "RSS feed for this site": "इस साइट के लिए आरएसएस फ़ीड", + "Edit newswire": "नवांश संपादित करें", + "Add RSS feed links below.": "नीचे आरएसएस फ़ीड लिंक जोड़ें।", + "Newswire RSS Feed": "Newswire RSS फ़ीड" } diff --git a/translations/it.json b/translations/it.json index 08ff2618c..32008810f 100644 --- a/translations/it.json +++ b/translations/it.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "Hashtag generati automaticamente", "Autogenerated Content Warnings": "Avvisi sui contenuti generati automaticamente", "Indymedia": "Indymedia", - "Hashtag Blocked": "Hashtag bloccato" + "Hashtag Blocked": "Hashtag bloccato", + "This is a blogging instance": "Questa è un'istanza di blog", + "Edit Links": "Modifica collegamenti", + "One link per line. Description followed by the link.": "Un collegamento per riga. Descrizione seguita dal collegamento.", + "Left column image": "Immagine della colonna di sinistra", + "Right column image": "Immagine della colonna di destra", + "RSS feed for this site": "Feed RSS per questo sito", + "Edit newswire": "Modifica newswire", + "Add RSS feed links below.": "Aggiungi i link ai feed RSS di seguito.", + "Newswire RSS Feed": "Feed RSS di Newswire" } diff --git a/translations/ja.json b/translations/ja.json index 382b43cc3..108f36f04 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "自動生成されたハッシュタグ", "Autogenerated Content Warnings": "自動生成されたコンテンツの警告", "Indymedia": "Indymedia", - "Hashtag Blocked": "ハッシュタグがブロックされました" + "Hashtag Blocked": "ハッシュタグがブロックされました", + "This is a blogging instance": "これはブログのインスタンスです", + "Edit Links": "リンクの編集", + "One link per line. Description followed by the link.": "1行に1つのリンク。 説明の後にリンクが続きます。", + "Left column image": "左の列の画像", + "Right column image": "右の列の画像", + "RSS feed for this site": "このサイトのRSSフィード", + "Edit newswire": "ニュースワイヤーを編集", + "Add RSS feed links below.": "以下にRSSフィードリンクを追加します。", + "Newswire RSS Feed": "NewswireRSSフィード" } diff --git a/translations/oc.json b/translations/oc.json index 9a0cf46e9..2a7e4d9f1 100644 --- a/translations/oc.json +++ b/translations/oc.json @@ -283,5 +283,14 @@ "Autogenerated Hashtags": "Autogenerated Hashtags", "Autogenerated Content Warnings": "Autogenerated Content Warnings", "Indymedia": "Indymedia", - "Hashtag Blocked": "Hashtag Blocked" + "Hashtag Blocked": "Hashtag Blocked", + "This is a blogging instance": "This is a blogging instance", + "Edit Links": "Edit Links", + "One link per line. Description followed by the link.": "One link per line. Description followed by the link. Titles should begin with #", + "Left column image": "Left column image", + "Right column image": "Right column image", + "RSS feed for this site": "RSS feed for this site", + "Edit newswire": "Edit newswire", + "Add RSS feed links below.": "Add RSS feed links below.", + "Newswire RSS Feed": "Newswire RSS Feed" } diff --git a/translations/pt.json b/translations/pt.json index 72314d67e..97df10f8c 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "Hashtags autogeradas", "Autogenerated Content Warnings": "Avisos de conteúdo gerado automaticamente", "Indymedia": "Indymedia", - "Hashtag Blocked": "Hashtag bloqueada" + "Hashtag Blocked": "Hashtag bloqueada", + "This is a blogging instance": "Esta é uma instância de blog", + "Edit Links": "Editar Links", + "One link per line. Description followed by the link.": "Um link por linha. Descrição seguida pelo link.", + "Left column image": "Imagem da coluna esquerda", + "Right column image": "Imagem da coluna direita", + "RSS feed for this site": "Feed RSS para este site", + "Edit newswire": "Editar notícias", + "Add RSS feed links below.": "Adicione links de feed RSS abaixo.", + "Newswire RSS Feed": "Feed RSS da Newswire" } diff --git a/translations/ru.json b/translations/ru.json index cb7a0b211..f91883dac 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "Автоматически сгенерированные хештеги", "Autogenerated Content Warnings": "Автоматические предупреждения о содержании", "Indymedia": "Indymedia", - "Hashtag Blocked": "Хештег заблокирован" + "Hashtag Blocked": "Хештег заблокирован", + "This is a blogging instance": "Это экземпляр блога", + "Edit Links": "Редактировать ссылки", + "One link per line. Description followed by the link.": "По одной ссылке в строке. Описание с последующей ссылкой.", + "Left column image": "Изображение в левом столбце", + "Right column image": "Изображение в правом столбце", + "RSS feed for this site": "RSS-канал для этого сайта", + "Edit newswire": "Редактировать ленту новостей", + "Add RSS feed links below.": "Добавьте ссылки на RSS-канал ниже.", + "Newswire RSS Feed": "Лента новостей RSS" } diff --git a/translations/zh.json b/translations/zh.json index a171135f0..c9f2aa9a7 100644 --- a/translations/zh.json +++ b/translations/zh.json @@ -287,5 +287,14 @@ "Autogenerated Hashtags": "自动生成的标签", "Autogenerated Content Warnings": "自动生成的内容警告", "Indymedia": "Indymedia", - "Hashtag Blocked": "标签被阻止" + "Hashtag Blocked": "标签被阻止", + "This is a blogging instance": "这是一个博客实例", + "Edit Links": "编辑连结", + "One link per line. Description followed by the link.": "每行一个链接。 描述,然后是链接。", + "Left column image": "左栏图片", + "Right column image": "右栏图片", + "RSS feed for this site": "该站点的RSS feed", + "Edit newswire": "编辑新闻专线", + "Add RSS feed links below.": "在下面添加RSS feed链接。", + "Newswire RSS Feed": "Newswire RSS提要" } diff --git a/webinterface.py b/webinterface.py index 200ae4c1b..a867e38c6 100644 --- a/webinterface.py +++ b/webinterface.py @@ -101,10 +101,10 @@ def getContentWarningButton(postID: str, translate: {}, content: str) -> str: """Returns the markup for a content warning button """ - return '
' + \ + return '
' + \ translate['SHOW MORE'] + '' + \ '
' + content + \ - '
' + '
\n' def getBlogAddress(actorJson: {}) -> str: @@ -581,7 +581,8 @@ def htmlSearchSharedItems(translate: {}, 'name="searchtext" value="' + \ searchStrLower + '">
\n' sharedItemsForm += \ - '
\n' sharedItemsForm += \ '
\n' sharedItemsForm += \ - '
\n' sharedItemsForm += \ ' 0: # previous page link hashtagSearchForm += \ - '
' + translate['Page up'] + \
-            '
\n' + '">\n
\n' index = startIndex while index <= endIndex: postId = lines[index].strip('\n').strip('\r') @@ -832,11 +835,13 @@ def htmlHashtagSearch(nickname: str, domain: str, port: int, if endIndex < noOfLines - 1: # next page link hashtagSearchForm += \ - '
' + translate['Page down'] + '
' + '" alt="' + translate['Page down'] + '">' + \ + '
' hashtagSearchForm += htmlFooter() return hashtagSearchForm @@ -1201,6 +1206,142 @@ def scheduledPostsExist(baseDir: str, nickname: str, domain: str) -> bool: return False +def htmlEditLinks(translate: {}, baseDir: str, path: str, + domain: str, port: int, httpPrefix: str) -> str: + """Shows the edit links screen + """ + if '/users/' not in path: + return '' + pathOriginal = path + path = path.replace('/inbox', '').replace('/outbox', '') + path = path.replace('/shares', '') + + nickname = getNicknameFromActor(path) + if not nickname: + return '' + + # is the user a moderator? + if not isModerator(baseDir, nickname): + return '' + + cssFilename = baseDir + '/epicyon-links.css' + if os.path.isfile(baseDir + '/links.css'): + cssFilename = baseDir + '/links.css' + with open(cssFilename, 'r') as cssFile: + editCSS = cssFile.read() + if httpPrefix != 'https': + editCSS = \ + editCSS.replace('https://', httpPrefix + '://') + + editLinksForm = htmlHeader(cssFilename, editCSS) + editLinksForm += \ + '
\n' + editLinksForm += \ + '
\n' + editLinksForm += \ + '

' + translate['Edit Links'] + '

' + editLinksForm += \ + '
\n' + editLinksForm += \ + ' \n' + editLinksForm += \ + ' \n' + editLinksForm += \ + '
\n' + + linksFilename = baseDir + '/accounts/links.txt' + linksStr = '' + if os.path.isfile(linksFilename): + with open(linksFilename, 'r') as fp: + linksStr = fp.read() + + editLinksForm += \ + '
' + editLinksForm += \ + ' ' + \ + translate['One link per line. Description followed by the link.'] + \ + '
' + editLinksForm += \ + ' ' + editLinksForm += \ + '
' + + editLinksForm += htmlFooter() + return editLinksForm + + +def htmlEditNewswire(translate: {}, baseDir: str, path: str, + domain: str, port: int, httpPrefix: str) -> str: + """Shows the edit newswire screen + """ + if '/users/' not in path: + return '' + pathOriginal = path + path = path.replace('/inbox', '').replace('/outbox', '') + path = path.replace('/shares', '') + + nickname = getNicknameFromActor(path) + if not nickname: + return '' + + # is the user a moderator? + if not isModerator(baseDir, nickname): + return '' + + cssFilename = baseDir + '/epicyon-links.css' + if os.path.isfile(baseDir + '/links.css'): + cssFilename = baseDir + '/links.css' + with open(cssFilename, 'r') as cssFile: + editCSS = cssFile.read() + if httpPrefix != 'https': + editCSS = \ + editCSS.replace('https://', httpPrefix + '://') + + editNewswireForm = htmlHeader(cssFilename, editCSS) + editNewswireForm += \ + '\n' + editNewswireForm += \ + '
\n' + editNewswireForm += \ + '

' + translate['Edit newswire'] + '

' + editNewswireForm += \ + '
\n' + editNewswireForm += \ + ' \n' + editNewswireForm += \ + ' \n' + editNewswireForm += \ + '
\n' + + newswireFilename = baseDir + '/accounts/newswire.txt' + newswireStr = '' + if os.path.isfile(newswireFilename): + with open(newswireFilename, 'r') as fp: + newswireStr = fp.read() + + editNewswireForm += \ + '
' + editNewswireForm += \ + ' ' + \ + translate['Add RSS feed links below.'] + \ + '
' + editNewswireForm += \ + ' ' + editNewswireForm += \ + '
' + + editNewswireForm += htmlFooter() + return editNewswireForm + + def htmlEditProfile(translate: {}, baseDir: str, path: str, domain: str, port: int, httpPrefix: str) -> str: """Shows the edit profile screen @@ -1230,6 +1371,7 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str, notifyLikes = '' hideLikeButton = '' mediaInstanceStr = '' + blogsInstanceStr = '' displayNickname = nickname bioStr = '' donateUrl = '' @@ -1287,6 +1429,13 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str, if mediaInstance: if mediaInstance is True: mediaInstanceStr = 'checked' + blogsInstanceStr = '' + + blogsInstance = getConfigParam(baseDir, "blogsInstance") + if blogsInstance: + if blogsInstance is True: + blogsInstanceStr = 'checked' + mediaInstanceStr = '' filterStr = '' filterFilename = \ @@ -1594,6 +1743,18 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str, editProfileForm += 'name="search_banner"' editProfileForm += ' accept="' + imageFormats + '">\n' + editProfileForm += '
\n' + editProfileForm += ' \n' + + editProfileForm += '
\n' + editProfileForm += ' \n' + editProfileForm += '
\n' editProfileForm += '
\n' editProfileForm += \ @@ -1607,6 +1768,19 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str, editProfileForm += \ ' \n' editProfileForm += '
\n' + + if path.startswith('/users/' + adminNickname + '/'): + editProfileForm += '
\n' + editProfileForm += \ + ' ' + \ + translate['This is a media instance'] + '
\n' + editProfileForm += \ + ' ' + \ + translate['This is a blogging instance'] + '
\n' + editProfileForm += '
\n' + editProfileForm += '
\n' editProfileForm += \ ' ' + \ translate['Remove Twitter posts'] + '
\n' - if path.startswith('/users/' + adminNickname + '/'): - editProfileForm += \ - ' ' + \ - translate['This is a media instance'] + '
\n' editProfileForm += \ ' ' + \ @@ -2639,6 +2808,7 @@ def htmlHeader(cssFilename: str, css: str, lang='en') -> str: htmlStr += ' \n' htmlStr += ' \n' htmlStr += ' \n' + htmlStr += ' Epicyon\n' htmlStr += ' \n' htmlStr += ' \n' return htmlStr @@ -2719,12 +2889,14 @@ def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str, if authorized and pageNumber > 1: # page up arrow profileStr += \ - '
\n' + \
-                translate['Page up'] + '\n
\n' + translate['Page up'] + '">\n' + \ + ' \n' for item in followingJson['orderedItems']: profileStr += \ @@ -2737,12 +2909,14 @@ def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str, if len(followingJson['orderedItems']) >= maxItemsPerPage: # page down arrow profileStr += \ - '
\n' + \
-                translate['Page down'] + '\n
\n' + translate['Page down'] + '">\n' + \ + ' \n' return profileStr @@ -2899,11 +3073,13 @@ def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int, if pageNumber > 1: iconsDir = getIconsDir(baseDir) timelineStr += \ - '
\n' + translate['Page up'] + '\n
\n' + '" alt="' + translate['Page up'] + '">\n' + \ + ' \n' for published, item in sharesJson.items(): showContactButton = False @@ -2919,11 +3095,13 @@ def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int, if not lastPage: iconsDir = getIconsDir(baseDir) timelineStr += \ - '
\n' + translate['Page down'] + '\n
\n' + '" alt="' + translate['Page down'] + '">\n' + \ + ' \n' return timelineStr @@ -4127,23 +4305,23 @@ def individualPostAsHtml(allowDownloads: bool, if timeDiff > 100: print('TIMING INDIV ' + boxName + ' 7 = ' + str(timeDiff)) - avatarLink = ' ' + avatarLink = ' ' avatarLink += \ '  ' + translate['Show profile'] + '" alt=" "' + avatarPosition + '/>\n' if showAvatarOptions and \ fullDomain + '/users/' + nickname not in postActor: avatarLink = \ - ' \n' avatarLink += \ - ' \n' avatarImageInPost = \ - '
' + avatarLink.strip() + '
\n' + '
' + avatarLink.strip() + '
\n' # don't create new html within the bookmarks timeline # it should already have been created for the inbox @@ -4214,7 +4392,7 @@ def individualPostAsHtml(allowDownloads: bool, nickname, domain, displayName, False) titleStr += \ - '' + displayName + '\n' @@ -4229,7 +4407,7 @@ def individualPostAsHtml(allowDownloads: bool, # pprint(postJsonObject) print('ERROR: no actorDomain') titleStr += \ - '@' + actorNickname + '@' + actorDomain + '\n' @@ -4273,25 +4451,28 @@ def individualPostAsHtml(allowDownloads: bool, replyStr = '' if isPublicRepeat: replyStr += \ - '\n' else: if isDM(postJsonObject): replyStr += \ + ' ' + \ '\n' else: replyStr += \ + ' ' + \ '\n' replyStr += \ + ' ' + \ '' + \
             translate['Reply to this post'] + \
@@ -4317,6 +4498,7 @@ def individualPostAsHtml(allowDownloads: bool,
             if isBlogPost(postJsonObject):
                 blogPostId = postJsonObject['object']['id']
                 editStr += \
+                    '        ' + \
                     '<a class=\n' announceStr += \ + ' ' + \ '' + translate['Repeat this post'] + \
             ' |\n' @@ -4417,7 +4601,7 @@ def individualPostAsHtml(allowDownloads: bool, likeStr += likeCountStr.replace('(', '').replace(')', '').strip() likeStr += '\n' likeStr += \ - '\n' likeStr += \ + ' ' + \ '' + likeTitle + \
             ' |\n' @@ -4450,13 +4635,14 @@ def individualPostAsHtml(allowDownloads: bool, if timeDiff > 100: print('TIMING INDIV ' + boxName + ' 12.6 = ' + str(timeDiff)) bookmarkStr = \ - '\n' bookmarkStr += \ + ' ' + \ '' + \
             bookmarkTitle + ' |\n' @@ -4482,33 +4668,36 @@ def individualPostAsHtml(allowDownloads: bool, messageId.startswith(postActor))): if '/users/' + nickname + '/' in messageId: deleteStr = \ - '\n' deleteStr += \ + ' ' + \ '' + translate['Delete this post'] + \
                 ' |\n' else: if not isMuted: muteStr = \ - '\n' muteStr += \ + ' ' + \ '' + \
                 translate['Mute this post'] + \
                 ' |\n' else: muteStr = \ - '\n' muteStr += \ + ' ' + \ '' + translate['Undo mute'] + \
                 ' |\n' @@ -4528,7 +4717,7 @@ def individualPostAsHtml(allowDownloads: bool, attributedTo = postJsonObject['object']['attributedTo'] if attributedTo.startswith(postActor): titleStr += \ - ' ' + translate['announces'] + \
                         ' \n' + \ + ' ' + \ announceDisplayName + '\n' # show avatar of person replied to @@ -4599,8 +4790,9 @@ def individualPostAsHtml(allowDownloads: bool, if announceAvatarUrl: idx = 'Show options for this person' replyAvatarImageInPost = \ + ' ' \ '
\n' \ - '\n
\n' + '/>\n
\n' else: titleStr += \ - ' ' + translate['announces'] + \
                                 ' \n' + \ + ' @' + \ announceNickname + '@' + \ announceDomain + '\n' else: titleStr += \ - ' ' + \
                             translate['announces'] + ' \n' + \ + ' @unattributed\n' else: titleStr += \ - ' ' + translate['announces'] + \
                     ' \n' + \ + ' @unattributed\n' else: if postJsonObject['object'].get('inReplyTo'): @@ -4645,7 +4841,7 @@ def individualPostAsHtml(allowDownloads: bool, containerClass = 'container darker' if postJsonObject['object']['inReplyTo'].startswith(postActor): titleStr += \ - ' ' + translate['replying to themselves'] + \
                         ' ' + \ + 'class="announceOrReply"/>\n' + \ + ' ' + \ '' + replyDisplayName + '\n' @@ -4729,9 +4927,10 @@ def individualPostAsHtml(allowDownloads: bool, if replyAvatarUrl: replyAvatarImageInPost = \ - '
\n' replyAvatarImageInPost += \ + ' ' + \ '\n' replyAvatarImageInPost += \ + ' ' + \ ' \n
\n' + avatarPosition + '/>\n' + \ + '
\n' else: inReplyTo = \ postJsonObject['object']['inReplyTo'] titleStr += \ - ' ' + \
                                         translate['replying to'] + \
                                         ' ' + \ - '\n' + \ + ' @' + \ replyNickname + '@' + \ replyDomain + '\n' else: titleStr += \ - ' ' + \
                                 translate['replying to'] + \
                                 ' ' + \ - '\n' + \ + ' @unknown\n' else: @@ -4784,12 +4986,13 @@ def individualPostAsHtml(allowDownloads: bool, postDomain = postDomain.split('/', 1)[0] if postDomain: titleStr += \ - ' ' + translate['replying to'] + \
                                 ' \n' + \ + ' ' + postDomain + '\n' @@ -4849,12 +5052,12 @@ def individualPostAsHtml(allowDownloads: bool, containerClass = 'container dm' if showIcons: - footerStr = '
' + footerStr = '\n
\n' footerStr += replyStr + announceStr + likeStr + bookmarkStr + \ deleteStr + muteStr + editStr - footerStr += '' + publishedStr + '\n' - footerStr += '
' + footerStr += '
\n' postIsSensitive = False if postJsonObject['object'].get('sensitive'): @@ -4948,7 +5151,9 @@ def individualPostAsHtml(allowDownloads: bool, contentStr = '' else: if not isPatch: - contentStr = '
' + contentStr + '
\n' + contentStr = '
' + \ + contentStr + \ + '
\n' else: contentStr = \ '
' + contentStr + \
@@ -4956,13 +5161,14 @@ def individualPostAsHtml(allowDownloads: bool,
 
     postHtml = ''
     if boxName != 'tlmedia':
-        postHtml = '
\n' postHtml += avatarImageInPost - postHtml += '

' + titleStr + \ - replyAvatarImageInPost + '

\n' + postHtml += '
\n' + \ + ' ' + titleStr + \ + replyAvatarImageInPost + '
\n' postHtml += contentStr + footerStr + '\n' - postHtml += '
\n' + postHtml += '
\n' else: postHtml = galleryStr @@ -5019,6 +5225,184 @@ def htmlHighlightLabel(label: str, highlight: bool) -> str: return '*' + label + '*' +def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str, + httpPrefix: str, translate: {}, + iconsDir: str, moderator: bool) -> str: + """Returns html content for the left column + """ + htmlStr = '' + + domain = domainFull + if ':' in domain: + domain = domain.split(':') + + leftColumnImageFilename = \ + baseDir + '/accounts/' + nickname + '@' + domain + \ + '/left_col_image.png' + if not os.path.isfile(leftColumnImageFilename): + theme = getConfigParam(baseDir, 'theme').lower() + if theme == 'default': + theme = '' + else: + theme = '_' + theme + themeLeftColumnImageFilename = \ + baseDir + '/img/left_col_image' + theme + '.png' + if os.path.isfile(themeLeftColumnImageFilename): + copyfile(themeLeftColumnImageFilename, leftColumnImageFilename) + + # show the image at the top of the column + editImageClass = 'leftColEdit' + if os.path.isfile(leftColumnImageFilename): + editImageClass = 'leftColEditImage' + htmlStr += \ + '\n
\n' + \ + ' \n' + \ + '
\n' + + if editImageClass == 'leftColEdit': + htmlStr += '\n
\n' + + if moderator: + # show the edit icon + htmlStr += \ + ' ' + \ + '' + \
+            translate['Edit Links'] + '\n' + + # RSS icon + htmlStr += \ + ' ' + \ + '' + \
+        translate['RSS feed for this site'] + \
+        '\n' + + if editImageClass == 'leftColEdit': + htmlStr += '
\n' + else: + htmlStr += '
\n' + + linksFilename = baseDir + '/accounts/links.txt' + if os.path.isfile(linksFilename): + linksList = None + with open(linksFilename, "r") as f: + linksList = f.readlines() + if linksList: + for lineStr in linksList: + if ' ' not in lineStr: + if '#' not in lineStr: + if '*' not in lineStr: + continue + lineStr = lineStr.strip() + words = lineStr.split(' ') + # get the link + linkStr = None + for word in words: + if word == '#': + continue + if word == '*': + continue + if '://' in word: + linkStr = word + break + if linkStr: + lineStr = lineStr.replace(linkStr, '').strip() + # avoid any dubious scripts being added + if '<' not in lineStr: + # remove trailing comma if present + if lineStr.endswith(','): + lineStr = lineStr[:len(lineStr)-1] + # add link to the returned html + htmlStr += \ + '

' + \ + lineStr + '

\n' + else: + if lineStr.startswith('#') or lineStr.startswith('*'): + lineStr = lineStr[1:].strip() + htmlStr += \ + '

' + \ + lineStr + '

\n' + else: + htmlStr += \ + '

' + lineStr + '

\n' + + return htmlStr + + +def getRightColumnContent(baseDir: str, nickname: str, domainFull: str, + httpPrefix: str, translate: {}, + iconsDir: str, moderator: bool) -> str: + """Returns html content for the right column + """ + htmlStr = '' + + domain = domainFull + if ':' in domain: + domain = domain.split(':') + + rightColumnImageFilename = \ + baseDir + '/accounts/' + nickname + '@' + domain + \ + '/right_col_image.png' + if not os.path.isfile(rightColumnImageFilename): + theme = getConfigParam(baseDir, 'theme').lower() + if theme == 'default': + theme = '' + else: + theme = '_' + theme + themeRightColumnImageFilename = \ + baseDir + '/img/right_col_image' + theme + '.png' + if os.path.isfile(themeRightColumnImageFilename): + copyfile(themeRightColumnImageFilename, rightColumnImageFilename) + + # show the image at the top of the column + editImageClass = 'rightColEdit' + if os.path.isfile(rightColumnImageFilename): + editImageClass = 'rightColEditImage' + htmlStr += \ + '\n
\n' + \ + ' \n' + \ + '
\n' + + if editImageClass == 'rightColEdit': + htmlStr += '\n
\n' + + if moderator: + # show the edit icon + htmlStr += \ + ' ' + \ + '' + \
+            translate['Edit newswire'] + '\n' + + htmlStr += \ + ' ' + \ + '' + \
+        translate['Newswire RSS Feed'] + '\n' + + if editImageClass == 'rightColEdit': + htmlStr += '
\n' + else: + htmlStr += '
\n' + + return htmlStr + + def htmlTimeline(defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, @@ -5257,7 +5641,7 @@ def htmlTimeline(defaultTimeline: str, # what screen to go to when a new post is created if boxName == 'dm': newPostButtonStr = \ - '\n' elif boxName == 'tlblogs': newPostButtonStr = \ - '| ' + \
@@ -5273,7 +5657,7 @@ def htmlTimeline(defaultTimeline: str,
             '\n' elif boxName == 'tlevents': newPostButtonStr = \ - '| ' + \
@@ -5282,7 +5666,7 @@ def htmlTimeline(defaultTimeline: str,
     else:
         if not manuallyApproveFollowers:
             newPostButtonStr = \
-                '<a class=| ' + \
@@ -5290,7 +5674,7 @@ def htmlTimeline(defaultTimeline: str,
                 '\n' else: newPostButtonStr = \ - '\n' + '\n' # banner and row of buttons tlStr += \ @@ -5310,35 +5694,61 @@ def htmlTimeline(defaultTimeline: str, translate['Switch to profile view'] + '">\n' tlStr += '
' tlStr += '
\n\n' - tlStr += '
\n' + # start the timeline + tlStr += '\n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + + domainFull = domain + if port: + if port != 80 and port != 443: + domainFull = domain + ':' + str(port) + + # left column + leftColumnStr = \ + getLeftColumnContent(baseDir, nickname, domainFull, + httpPrefix, translate, iconsDir, + moderator) + tlStr += ' \n' + # center column containing posts + tlStr += ' \n' + + # right column + rightColumnStr = getRightColumnContent(baseDir, nickname, domainFull, + httpPrefix, translate, iconsDir, + moderator) + tlStr += ' \n' + tlStr += ' \n' + # benchmark 9 timeDiff = int((time.time() - timelineStartTime) * 1000) if timeDiff > 100: @@ -5612,12 +6034,23 @@ def htmlTimeline(defaultTimeline: str, # page down arrow if itemCtr > 2: tlStr += \ - '
\n\n' + \ + '
\n' + \ + ' \n' + \ + ' \n' + + tlStr += ' \n' + tlStr += '
' + \ + leftColumnStr + ' \n' + + # start of the button header with inbox, outbox, etc + tlStr += '
\n' # first button if defaultTimeline == 'tlmedia': tlStr += \ - ' \n' elif defaultTimeline == 'tlblogs': tlStr += \ - ' \n' else: tlStr += \ - ' \n' tlStr += \ - ' \n' tlStr += \ - ' \n' @@ -5347,14 +5757,14 @@ def htmlTimeline(defaultTimeline: str, if defaultTimeline != 'tlmedia': if not minimal: tlStr += \ - ' \n' else: if not minimal: tlStr += \ - ' \n' @@ -5364,21 +5774,21 @@ def htmlTimeline(defaultTimeline: str, if defaultTimeline != 'tlblogs': if not minimal: tlStr += \ - ' \n' else: if not minimal: tlStr += \ - ' \n' # button for the outbox tlStr += \ - ' \n' @@ -5388,9 +5798,35 @@ def htmlTimeline(defaultTimeline: str, 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 + tlStr += \ + ' \n' + + # happening this week button + if thisWeeksEventsCheck(baseDir, nickname, domain): + tlStr += \ + ' \n' + else: + # happening this week button + if thisWeeksEventsCheck(baseDir, nickname, domain): + tlStr += \ + ' \n' + # the search button tlStr += \ - ' | ' + \
@@ -5407,20 +5843,21 @@ def htmlTimeline(defaultTimeline: str,
         # indicate that the calendar icon is highlighted
         calendarAltText = '*' + calendarAltText + '*'
     tlStr += \
-        '    <a class=| ' + calendarAltText + '\n' # the show/hide button, for a simpler header appearance tlStr += \ - ' | ' + translate['Show/Hide Buttons'] + \
         '\n' tlStr += followApprovals - tlStr += '
' + # end of the button header with inbox, outbox, etc + tlStr += ' \n' # second row of buttons for moderator actions if moderator and boxName == 'moderation': @@ -5479,34 +5916,6 @@ def htmlTimeline(defaultTimeline: str, if timeDiff > 100: print('TIMELINE TIMING ' + boxName + ' 7 = ' + str(timeDiff)) - # show todays events buttons on the first inbox page - if boxName == 'inbox' and pageNumber == 1: - if todaysEventsCheck(baseDir, nickname, domain): - now = datetime.now() - - # happening today button - tlStr += \ - '
\n\n' - - # happening this week button - if thisWeeksEventsCheck(baseDir, nickname, domain): - tlStr += \ - '\n' - tlStr += '
\n' - else: - # happening this week button - if thisWeeksEventsCheck(baseDir, nickname, domain): - tlStr += \ - '
\n\n' + \ - '
\n' - # benchmark 8 timeDiff = int((time.time() - timelineStartTime) * 1000) if timeDiff > 100: @@ -5515,12 +5924,14 @@ def htmlTimeline(defaultTimeline: str, # page up arrow if pageNumber > 1: tlStr += \ - '
\n' + \
-            translate['Page up'] + '\n
\n' + translate['Page up'] + '">\n' + \ + ' \n' # show the posts itemCtr = 0 @@ -5604,6 +6015,17 @@ def htmlTimeline(defaultTimeline: str, if boxName == 'tlmedia': tlStr += '\n' + # end of column-center + tlStr += '
' + \ + rightColumnStr + '
\n' + \ + '
\n' + \ + ' ' + \
-            translate['Page down'] + '\n
\n' + translate['Page down'] + '">\n' + \ + ' \n' + \ + '
\n' tlStr += htmlFooter() return tlStr