From 77e7eff43747c2a584f4e600bc7a17acc5a3de5d Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Thu, 11 Feb 2021 12:27:12 +0000 Subject: [PATCH 01/27] Comments --- webapp_post.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webapp_post.py b/webapp_post.py index c9505c819..b5646c416 100644 --- a/webapp_post.py +++ b/webapp_post.py @@ -327,6 +327,10 @@ def _getEditIconHtml(baseDir: str, nickname: str, domainFull: str, """ editStr = '' actor = postJsonObject['actor'] + # This should either be a post which you created, + # or it could be generated from the newswire (see + # _addBlogsToNewswire) in which case anyone with + # editor status should be able to alter it if (actor.endswith('/' + domainFull + '/users/' + nickname) or (isEditor(baseDir, nickname) and actor.endswith('/' + domainFull + '/users/news'))): From eeb095fe8af7def3161c5845f0fff9c5c6600719 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Thu, 11 Feb 2021 12:40:56 +0000 Subject: [PATCH 02/27] Only include non-system users blog posts --- newswire.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/newswire.py b/newswire.py index ccaf983bf..35f35b2d9 100644 --- a/newswire.py +++ b/newswire.py @@ -794,7 +794,7 @@ def _addAccountBlogsToNewswire(baseDir: str, nickname: str, domain: str, locatePost(baseDir, nickname, domain, postUrl, False) if not fullPostFilename: - print('Unable to locate post ' + postUrl) + print('Unable to locate post for newswire ' + postUrl) ctr += 1 if ctr >= maxBlogsPerAccount: break @@ -840,7 +840,7 @@ def _addBlogsToNewswire(baseDir: str, domain: str, newswire: {}, for handle in dirs: if '@' not in handle: continue - if 'inbox@' in handle: + if 'inbox@' in handle or 'news@' in handle: continue nickname = handle.split('@')[0] From e104b9c5b0f029240b07964720519fb70ec9bae2 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Feb 2021 10:33:10 +0000 Subject: [PATCH 03/27] Title on calendat events --- webapp_calendar.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webapp_calendar.py b/webapp_calendar.py index 0135cc08e..48910ada1 100644 --- a/webapp_calendar.py +++ b/webapp_calendar.py @@ -358,7 +358,9 @@ def htmlCalendar(cssCache: {}, translate: {}, url = calActor + '/calendar?year=' + \ str(year) + '?month=' + \ str(monthNumber) + '?day=' + str(dayOfMonth) - dayLink = '' + \ + dayDescription = monthName + ' ' + str(dayOfMonth) + dayLink = '' + \ str(dayOfMonth) + '' # there are events for this day if not isToday: From 4308317b3d8d07c08a7499accb809b1ab8ac00b7 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Feb 2021 11:28:00 +0000 Subject: [PATCH 04/27] Support for json feeds on newswire --- newswire.py | 121 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 6 deletions(-) diff --git a/newswire.py b/newswire.py index 35f35b2d9..da9372e0b 100644 --- a/newswire.py +++ b/newswire.py @@ -7,6 +7,7 @@ __email__ = "bob@freedombone.net" __status__ = "Production" import os +import json import requests from socket import error as SocketError import errno @@ -332,12 +333,14 @@ def _xml2StrToDict(baseDir: str, domain: str, xmlStr: str, result, pubDateStr, title, link, votesStatus, postFilename, - description, moderated, mirrored) + description, moderated, + mirrored) postCtr += 1 if postCtr >= maxPostsPerSource: break if postCtr > 0: - print('Added ' + str(postCtr) + ' rss 2.0 feed items to newswire') + print('Added ' + str(postCtr) + + ' rss 2.0 feed items to newswire') return result @@ -416,12 +419,14 @@ def _xml1StrToDict(baseDir: str, domain: str, xmlStr: str, result, pubDateStr, title, link, votesStatus, postFilename, - description, moderated, mirrored) + description, moderated, + mirrored) postCtr += 1 if postCtr >= maxPostsPerSource: break if postCtr > 0: - print('Added ' + str(postCtr) + ' rss 1.0 feed items to newswire') + print('Added ' + str(postCtr) + + ' rss 1.0 feed items to newswire') return result @@ -488,12 +493,112 @@ def _atomFeedToDict(baseDir: str, domain: str, xmlStr: str, result, pubDateStr, title, link, votesStatus, postFilename, - description, moderated, mirrored) + description, moderated, + mirrored) postCtr += 1 if postCtr >= maxPostsPerSource: break if postCtr > 0: - print('Added ' + str(postCtr) + ' atom feed items to newswire') + print('Added ' + str(postCtr) + + ' atom feed items to newswire') + return result + + +def _jsonFeedToDict(baseDir: str, domain: str, xmlStr: str, + moderated: bool, mirrored: bool, + maxPostsPerSource: int, + maxFeedItemSizeKb: int) -> {}: + """Converts a json feed string to a dictionary + See https://jsonfeed.org/version/1 + """ + if '' not in xmlStr: + return {} + result = {} + try: + feedJson = json.loads(xmlStr) + except BaseException: + return {} + postCtr = 0 + maxBytes = maxFeedItemSizeKb * 1024 + if not feedJson.get('version'): + return {} + if feedJson['version'] != 'https://jsonfeed.org/version/1': + return {} + if not feedJson.get('items'): + return {} + if not isinstance(feedJson['items'], list): + return {} + for jsonFeedItem in feedJson['items']: + if not jsonFeedItem: + continue + if not isinstance(jsonFeedItem, dict): + continue + if not jsonFeedItem.get('url'): + continue + if not isinstance(jsonFeedItem['url'], str): + continue + if not jsonFeedItem.get('date_published'): + if not jsonFeedItem.get('date_modified'): + continue + if not jsonFeedItem.get('content_text'): + if not jsonFeedItem.get('content_html'): + continue + if jsonFeedItem.get('content_html'): + if not isinstance(jsonFeedItem['content_html'], str): + continue + title = removeHtml(jsonFeedItem['content_html']) + else: + if not isinstance(jsonFeedItem['content_text'], str): + continue + title = jsonFeedItem['content_text'] + if len(title) > maxBytes: + print('WARN: json feed title is too long') + continue + description = '' + if jsonFeedItem.get('description'): + if not isinstance(jsonFeedItem['description'], str): + continue + description = jsonFeedItem['description'] + if len(description) > maxBytes: + print('WARN: json feed description is too long') + continue + link = jsonFeedItem['url'] + if '://' not in link: + continue + if len(link) > maxBytes: + print('WARN: json feed link is too long') + continue + itemDomain = link.split('://')[1] + if '/' in itemDomain: + itemDomain = itemDomain.split('/')[0] + if isBlockedDomain(baseDir, itemDomain): + continue + if jsonFeedItem.get('date_published'): + if not isinstance(jsonFeedItem['date_published'], str): + continue + pubDate = jsonFeedItem['date_published'] + else: + if not isinstance(jsonFeedItem['date_modified'], str): + continue + pubDate = jsonFeedItem['date_modified'] + + pubDateStr = parseFeedDate(pubDate) + if pubDateStr: + if _validFeedDate(pubDateStr): + postFilename = '' + votesStatus = [] + _addNewswireDictEntry(baseDir, domain, + result, pubDateStr, + title, link, + votesStatus, postFilename, + description, moderated, + mirrored) + postCtr += 1 + if postCtr >= maxPostsPerSource: + break + if postCtr > 0: + print('Added ' + str(postCtr) + + ' json feed items to newswire') return result @@ -593,6 +698,10 @@ def _xmlStrToDict(baseDir: str, domain: str, xmlStr: str, return _atomFeedToDict(baseDir, domain, xmlStr, moderated, mirrored, maxPostsPerSource, maxFeedItemSizeKb) + elif 'https://jsonfeed.org/version/1' in xmlStr: + return _jsonFeedToDict(baseDir, domain, + xmlStr, moderated, mirrored, + maxPostsPerSource, maxFeedItemSizeKb) return {} From 000162d85c2ab84f7061389998ccfa619d5904b7 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Feb 2021 11:30:23 +0000 Subject: [PATCH 05/27] Json feed version --- newswire.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/newswire.py b/newswire.py index da9372e0b..6b229fbd0 100644 --- a/newswire.py +++ b/newswire.py @@ -504,10 +504,10 @@ def _atomFeedToDict(baseDir: str, domain: str, xmlStr: str, return result -def _jsonFeedToDict(baseDir: str, domain: str, xmlStr: str, - moderated: bool, mirrored: bool, - maxPostsPerSource: int, - maxFeedItemSizeKb: int) -> {}: +def _jsonFeedV1ToDict(baseDir: str, domain: str, xmlStr: str, + moderated: bool, mirrored: bool, + maxPostsPerSource: int, + maxFeedItemSizeKb: int) -> {}: """Converts a json feed string to a dictionary See https://jsonfeed.org/version/1 """ @@ -699,9 +699,9 @@ def _xmlStrToDict(baseDir: str, domain: str, xmlStr: str, xmlStr, moderated, mirrored, maxPostsPerSource, maxFeedItemSizeKb) elif 'https://jsonfeed.org/version/1' in xmlStr: - return _jsonFeedToDict(baseDir, domain, - xmlStr, moderated, mirrored, - maxPostsPerSource, maxFeedItemSizeKb) + return _jsonFeedV1ToDict(baseDir, domain, + xmlStr, moderated, mirrored, + maxPostsPerSource, maxFeedItemSizeKb) return {} From 43ffcd73022c08af7e8fffa82c2b03252d630eef Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Feb 2021 11:46:26 +0000 Subject: [PATCH 06/27] Support json feed 1.1 --- newswire.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/newswire.py b/newswire.py index 6b229fbd0..d7fa66df3 100644 --- a/newswire.py +++ b/newswire.py @@ -509,9 +509,9 @@ def _jsonFeedV1ToDict(baseDir: str, domain: str, xmlStr: str, maxPostsPerSource: int, maxFeedItemSizeKb: int) -> {}: """Converts a json feed string to a dictionary - See https://jsonfeed.org/version/1 + See https://jsonfeed.org/version/1.1 """ - if '' not in xmlStr: + if '"items"' not in xmlStr: return {} result = {} try: @@ -522,7 +522,7 @@ def _jsonFeedV1ToDict(baseDir: str, domain: str, xmlStr: str, maxBytes = maxFeedItemSizeKb * 1024 if not feedJson.get('version'): return {} - if feedJson['version'] != 'https://jsonfeed.org/version/1': + if not feedJson['version'].startswith('https://jsonfeed.org/version/1'): return {} if not feedJson.get('items'): return {} From c0c44e9d1a60094283c249c7be24ed115bacabe9 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Feb 2021 11:47:49 +0000 Subject: [PATCH 07/27] Result later --- newswire.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/newswire.py b/newswire.py index d7fa66df3..7b0465dbb 100644 --- a/newswire.py +++ b/newswire.py @@ -513,7 +513,6 @@ def _jsonFeedV1ToDict(baseDir: str, domain: str, xmlStr: str, """ if '"items"' not in xmlStr: return {} - result = {} try: feedJson = json.loads(xmlStr) except BaseException: @@ -528,6 +527,7 @@ def _jsonFeedV1ToDict(baseDir: str, domain: str, xmlStr: str, return {} if not isinstance(feedJson['items'], list): return {} + result = {} for jsonFeedItem in feedJson['items']: if not jsonFeedItem: continue From c74b208b53b3b1f16ba3d2d83a43665e9ba250ee Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Feb 2021 11:50:05 +0000 Subject: [PATCH 08/27] Counter later --- newswire.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/newswire.py b/newswire.py index 7b0465dbb..a9d2b58b1 100644 --- a/newswire.py +++ b/newswire.py @@ -517,7 +517,6 @@ def _jsonFeedV1ToDict(baseDir: str, domain: str, xmlStr: str, feedJson = json.loads(xmlStr) except BaseException: return {} - postCtr = 0 maxBytes = maxFeedItemSizeKb * 1024 if not feedJson.get('version'): return {} @@ -527,6 +526,7 @@ def _jsonFeedV1ToDict(baseDir: str, domain: str, xmlStr: str, return {} if not isinstance(feedJson['items'], list): return {} + postCtr = 0 result = {} for jsonFeedItem in feedJson['items']: if not jsonFeedItem: From 39a0c7ba9ee72816b3480d84ca136f4d7840f2e7 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Feb 2021 12:02:09 +0000 Subject: [PATCH 09/27] Remove html from json feeds --- newswire.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/newswire.py b/newswire.py index a9d2b58b1..777f31c22 100644 --- a/newswire.py +++ b/newswire.py @@ -550,7 +550,7 @@ def _jsonFeedV1ToDict(baseDir: str, domain: str, xmlStr: str, else: if not isinstance(jsonFeedItem['content_text'], str): continue - title = jsonFeedItem['content_text'] + title = removeHtml(jsonFeedItem['content_text']) if len(title) > maxBytes: print('WARN: json feed title is too long') continue @@ -558,7 +558,7 @@ def _jsonFeedV1ToDict(baseDir: str, domain: str, xmlStr: str, if jsonFeedItem.get('description'): if not isinstance(jsonFeedItem['description'], str): continue - description = jsonFeedItem['description'] + description = removeHtml(jsonFeedItem['description']) if len(description) > maxBytes: print('WARN: json feed description is too long') continue From b1c301dcb5289704e1f241da5ec1f3191c0f0fee Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Feb 2021 12:09:16 +0000 Subject: [PATCH 10/27] Support hashtags in json feeds --- newswire.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/newswire.py b/newswire.py index 777f31c22..2ae1eb71e 100644 --- a/newswire.py +++ b/newswire.py @@ -562,6 +562,18 @@ def _jsonFeedV1ToDict(baseDir: str, domain: str, xmlStr: str, if len(description) > maxBytes: print('WARN: json feed description is too long') continue + if jsonFeedItem.get('tags'): + if not isinstance(jsonFeedItem['tags'], list): + for tagName in jsonFeedItem['tags']: + if not isinstance(tagName, str): + continue + if ' ' in tagName: + continue + if not tagName.startswith('#'): + tagName = '#' + tagName + if tagName not in description: + description += ' ' + tagName + link = jsonFeedItem['url'] if '://' not in link: continue From 67172c31653fe5a8800fd1455d570157c36bce04 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Feb 2021 12:09:48 +0000 Subject: [PATCH 11/27] Invert logic --- newswire.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/newswire.py b/newswire.py index 2ae1eb71e..29abb2428 100644 --- a/newswire.py +++ b/newswire.py @@ -563,7 +563,7 @@ def _jsonFeedV1ToDict(baseDir: str, domain: str, xmlStr: str, print('WARN: json feed description is too long') continue if jsonFeedItem.get('tags'): - if not isinstance(jsonFeedItem['tags'], list): + if isinstance(jsonFeedItem['tags'], list): for tagName in jsonFeedItem['tags']: if not isinstance(tagName, str): continue From 49e5eda8234ba3366736b9d2cf99e55dbaa7ef0f Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Feb 2021 14:28:28 +0000 Subject: [PATCH 12/27] Indicate calendar columns --- webapp_calendar.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/webapp_calendar.py b/webapp_calendar.py index 48910ada1..72342a468 100644 --- a/webapp_calendar.py +++ b/webapp_calendar.py @@ -320,19 +320,19 @@ def htmlCalendar(cssCache: {}, translate: {}, calendarStr += '\n' calendarStr += '\n' calendarStr += '\n' - calendarStr += ' ' + \ + calendarStr += ' ' + \ translate['Sun'] + '\n' - calendarStr += ' ' + \ + calendarStr += ' ' + \ translate['Mon'] + '\n' - calendarStr += ' ' + \ + calendarStr += ' ' + \ translate['Tue'] + '\n' - calendarStr += ' ' + \ + calendarStr += ' ' + \ translate['Wed'] + '\n' - calendarStr += ' ' + \ + calendarStr += ' ' + \ translate['Thu'] + '\n' - calendarStr += ' ' + \ + calendarStr += ' ' + \ translate['Fri'] + '\n' - calendarStr += ' ' + \ + calendarStr += ' ' + \ translate['Sat'] + '\n' calendarStr += '\n' calendarStr += '\n' From f24f26a31f9785963669a744255a0ffdeb9fd1b3 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Feb 2021 15:13:07 +0000 Subject: [PATCH 13/27] Keyboard navigation menu for calendar --- daemon.py | 3 ++- webapp_calendar.py | 37 ++++++++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/daemon.py b/daemon.py index 3e091bebc..4d3041ad9 100644 --- a/daemon.py +++ b/daemon.py @@ -11085,7 +11085,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.translate, self.server.baseDir, self.path, self.server.httpPrefix, - self.server.domainFull).encode('utf-8') + self.server.domainFull, + self.server.textModeBanner).encode('utf-8') msglen = len(msg) self._set_headers('text/html', msglen, cookie, callingDomain) self._write(msg) diff --git a/webapp_calendar.py b/webapp_calendar.py index 72342a468..0b54cc36a 100644 --- a/webapp_calendar.py +++ b/webapp_calendar.py @@ -21,6 +21,8 @@ from happening import getCalendarEvents from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlFooter from webapp_utils import getAltPath +from webapp_utils import htmlHideFromScreenReader +from webapp_utils import htmlKeyboardNavigation def htmlCalendarDeleteConfirm(cssCache: {}, translate: {}, baseDir: str, @@ -200,7 +202,8 @@ def _htmlCalendarDay(cssCache: {}, translate: {}, def htmlCalendar(cssCache: {}, translate: {}, baseDir: str, path: str, - httpPrefix: str, domainFull: str) -> str: + httpPrefix: str, domainFull: str, + textModeBanner: str) -> str: """Show the calendar for a person """ domain = domainFull @@ -297,8 +300,10 @@ def htmlCalendar(cssCache: {}, translate: {}, instanceTitle = \ getConfigParam(baseDir, 'instanceTitle') - calendarStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) - calendarStr += '
\n' + headerStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) + + # the main graphical calendar as a table + calendarStr = '
\n' calendarStr += '
\n' calendarStr += \ ' ' + \ str(dayOfMonth) + '' + # accessibility menu links + menuOptionStr = \ + htmlHideFromScreenReader('📅') + ' ' + \ + dayDescription + navLinks[menuOptionStr] = url # there are events for this day if not isToday: calendarStr += \ @@ -389,5 +405,16 @@ def htmlCalendar(cssCache: {}, translate: {}, calendarStr += '\n' calendarStr += '
\n' - calendarStr += htmlFooter() - return calendarStr + + # end of the links used for accessibility + nextMonthStr = \ + htmlHideFromScreenReader('→') + ' ' + translate['Next month'] + navLinks[nextMonthStr] = calActor + '/calendar?year=' + str(nextYear) + \ + '?month=' + str(nextMonthNumber) + prevMonthStr = \ + htmlHideFromScreenReader('←') + ' ' + translate['Previous month'] + navLinks[prevMonthStr] = calActor + '/calendar?year=' + str(prevYear) + \ + '?month=' + str(prevMonthNumber) + screenReaderCal = htmlKeyboardNavigation(textModeBanner, navLinks) + + return headerStr + screenReaderCal + calendarStr + htmlFooter() From d77bd49c89b3383912f066cde952ba4a99afe8d4 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Feb 2021 15:15:26 +0000 Subject: [PATCH 14/27] Transparent style --- epicyon-calendar.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/epicyon-calendar.css b/epicyon-calendar.css index 223f3836a..f9d48b826 100644 --- a/epicyon-calendar.css +++ b/epicyon-calendar.css @@ -84,6 +84,14 @@ a:focus { border: 2px solid var(--focus-color); } +.transparent { + color: transparent; + background: transparent; + font-size: 0px; + line-height: 0px; + height: 0px; +} + .calendar__day__header, .calendar__day__cell { border: 2px solid var(--lines-color); From a18697d0fa343c40e8b8a89d2f6b9305b9fc315b Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Feb 2021 15:28:11 +0000 Subject: [PATCH 15/27] Show month name in keyboard navigation --- webapp_calendar.py | 3 ++- webapp_timeline.py | 2 +- webapp_utils.py | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/webapp_calendar.py b/webapp_calendar.py index 0b54cc36a..025614e79 100644 --- a/webapp_calendar.py +++ b/webapp_calendar.py @@ -415,6 +415,7 @@ def htmlCalendar(cssCache: {}, translate: {}, htmlHideFromScreenReader('←') + ' ' + translate['Previous month'] navLinks[prevMonthStr] = calActor + '/calendar?year=' + str(prevYear) + \ '?month=' + str(prevMonthNumber) - screenReaderCal = htmlKeyboardNavigation(textModeBanner, navLinks) + screenReaderCal = \ + htmlKeyboardNavigation(textModeBanner, navLinks, monthName) return headerStr + screenReaderCal + calendarStr + htmlFooter() diff --git a/webapp_timeline.py b/webapp_timeline.py index f3707b826..0c0407520 100644 --- a/webapp_timeline.py +++ b/webapp_timeline.py @@ -432,7 +432,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str, } if moderator: navLinks[menuModeration] = usersPath + '/moderation#modtimeline' - tlStr += htmlKeyboardNavigation(textModeBanner, navLinks, + tlStr += htmlKeyboardNavigation(textModeBanner, navLinks, None, usersPath, translate, followApprovals) # banner and row of buttons diff --git a/webapp_utils.py b/webapp_utils.py index 1511b1408..9d3e97244 100644 --- a/webapp_utils.py +++ b/webapp_utils.py @@ -887,6 +887,7 @@ def htmlHideFromScreenReader(htmlStr: str) -> str: def htmlKeyboardNavigation(banner: str, links: {}, + subHeading=None, usersPath=None, translate=None, followApprovals=False) -> str: """Given a set of links return the html for keyboard navigation @@ -896,6 +897,10 @@ def htmlKeyboardNavigation(banner: str, links: {}, if banner: htmlStr += '
' + banner + '

' + if subHeading: + htmlStr += '
' + # show new follower approvals if usersPath and translate and followApprovals: htmlStr += '