diff --git a/webapp_timeline.py b/webapp_timeline.py index 8d54ca4e..e8e48e05 100644 --- a/webapp_timeline.py +++ b/webapp_timeline.py @@ -69,51 +69,9 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str, timelineStartTime = time.time() - accountDir = baseDir + '/accounts/' + nickname + '@' + domain - - # should the calendar icon be highlighted? - newCalendarEvent = False - calendarImage = 'calendar.png' - calendarPath = '/calendar' - calendarFile = accountDir + '/.newCalendar' - if os.path.isfile(calendarFile): - newCalendarEvent = True - calendarImage = 'calendar_notify.png' - with open(calendarFile, 'r') as calfile: - calendarPath = calfile.read().replace('##sent##', '') - calendarPath = calendarPath.replace('\n', '').replace('\r', '') - - # should the DM button be highlighted? - newDM = False - dmFile = accountDir + '/.newDM' - if os.path.isfile(dmFile): - newDM = True - if boxName == 'dm': - os.remove(dmFile) - - # should the Replies button be highlighted? - newReply = False - replyFile = accountDir + '/.newReply' - if os.path.isfile(replyFile): - newReply = True - if boxName == 'tlreplies': - os.remove(replyFile) - - # should the Shares button be highlighted? - newShare = False - newShareFile = accountDir + '/.newShare' - if os.path.isfile(newShareFile): - newShare = True - if boxName == 'tlshares': - os.remove(newShareFile) - - # should the Moderation/reports button be highlighted? - newReport = False - newReportFile = accountDir + '/.newReport' - if os.path.isfile(newReportFile): - newReport = True - if boxName == 'moderation': - os.remove(newReportFile) + # directory where icons are found + # This changes depending upon theme + iconsDir = getIconsDir(baseDir) separatorStr = '' if boxName != 'tlmedia': @@ -140,60 +98,6 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str, _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '2') - # the appearance of buttons - highlighted or not - inboxButton = 'button' - blogsButton = 'button' - featuresButton = 'button' - newsButton = 'button' - dmButton = 'button' - if newDM: - dmButton = 'buttonhighlighted' - repliesButton = 'button' - if newReply: - repliesButton = 'buttonhighlighted' - mediaButton = 'button' - bookmarksButton = 'button' - eventsButton = 'button' - sentButton = 'button' - sharesButton = 'button' - if newShare: - sharesButton = 'buttonhighlighted' - moderationButton = 'button' - if newReport: - moderationButton = 'buttonhighlighted' - if boxName == 'inbox': - inboxButton = 'buttonselected' - elif boxName == 'tlblogs': - blogsButton = 'buttonselected' - elif boxName == 'tlfeatures': - featuresButton = 'buttonselected' - elif boxName == 'tlnews': - newsButton = 'buttonselected' - elif boxName == 'dm': - dmButton = 'buttonselected' - if newDM: - dmButton = 'buttonselectedhighlighted' - elif boxName == 'tlreplies': - repliesButton = 'buttonselected' - if newReply: - repliesButton = 'buttonselectedhighlighted' - elif boxName == 'tlmedia': - mediaButton = 'buttonselected' - elif boxName == 'outbox': - sentButton = 'buttonselected' - elif boxName == 'moderation': - moderationButton = 'buttonselected' - if newReport: - moderationButton = 'buttonselectedhighlighted' - elif boxName == 'tlshares': - sharesButton = 'buttonselected' - if newShare: - sharesButton = 'buttonselectedhighlighted' - elif boxName == 'tlbookmarks' or boxName == 'bookmarks': - bookmarksButton = 'buttonselected' - elif boxName == 'tlevents': - eventsButton = 'buttonselected' - # get the full domain, including any port number fullDomain = getFullDomain(domain, port) @@ -202,60 +106,12 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str, showIndividualPostIcons = True - # show an icon for new follow approvals - followApprovals = '' - followRequestsFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/followrequests.txt' - if os.path.isfile(followRequestsFilename): - with open(followRequestsFilename, 'r') as f: - for line in f: - if len(line) > 0: - # show follow approvals icon - followApprovals = \ - '' + \ - '' + \
-                        translate['Approve follow requests'] + \
-                        '\n' - break + # benchmark 3 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 3 = ' + str(timeDiff)) - _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '3') - - # moderation / reports button - moderationButtonStr = '' - if moderator and not minimal: - moderationButtonStr = \ - '' - - # shares, bookmarks and events buttons - sharesButtonStr = '' - bookmarksButtonStr = '' - eventsButtonStr = '' - if not minimal: - sharesButtonStr = \ - '' - - bookmarksButtonStr = \ - '' - - eventsButtonStr = \ - '' - - tlStr = htmlHeaderWithExternalStyle(cssFilename) + tlStr = htmlHeaderWithExternalStyle(cssFilename, profileStyle) _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '4') @@ -264,133 +120,29 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str, if defaultTimeline == 'tlfeatures' and boxName == 'tlfeatures': newsHeader = True - newPostButtonStr = '' - # start of headericons div - if not newsHeader: - if not iconsAsButtons: - newPostButtonStr += '
' + # Banner and "profile toggle" link - # what screen to go to when a new post is created - if boxName == 'dm': - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + translate['Create a new DM'] + \
-                '\n' - else: - newPostButtonStr += \ - '' + \ - '' - elif (boxName == 'tlblogs' or - boxName == 'tlnews' or - boxName == 'tlfeatures'): - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + \
-                translate['Create a new post'] + \
-                '\n' - else: - newPostButtonStr += \ - '' + \ - '' - elif boxName == 'tlevents': - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + \
-                translate['Create a new event'] + \
-                '\n' - else: - newPostButtonStr += \ - '' + \ - '' - elif boxName == 'tlshares': - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + \
-                translate['Create a new shared item'] + \
-                '\n' - else: - newPostButtonStr += \ - '' + \ - '' - else: - if not manuallyApproveFollowers: - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + \
-                    translate['Create a new post'] + \
-                    '\n' - else: - newPostButtonStr += \ - '' + \ - '' - else: - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + translate['Create a new post'] + \
-                    '\n' - else: - newPostButtonStr += \ - '' + \ - '' + # TODO: This CSS should be moved out of the code + # Items like this that can be different per user should be kept + # in the user's folder and loaded as final CSS, to allow overwrite(s) - # This creates a link to the profile page when viewed - # in lynx, but should be invisible in a graphical web browser tlStr += \ - '
\n' - - # banner and row of buttons - tlStr += \ - '
\n' + \ - '\n' - tlStr += '\n' + \ - '
\n' + translate['Switch to profile view'] + '" style="' + \ + 'background-image: url(\'' + usersPath + '/' + bannerFile + '\');">' + \ + translate['Switch to profile view'] + '\n' - if fullWidthTimelineButtonHeader: - tlStr += \ - headerButtonsTimeline(defaultTimeline, boxName, pageNumber, - translate, usersPath, mediaButton, - blogsButton, featuresButton, - newsButton, inboxButton, - dmButton, newDM, repliesButton, - newReply, minimal, sentButton, - sharesButtonStr, bookmarksButtonStr, - eventsButtonStr, moderationButtonStr, - newPostButtonStr, baseDir, nickname, - domain, timelineStartTime, - newCalendarEvent, calendarPath, - calendarImage, followApprovals, - iconsAsButtons) + + # Full Row of Buttons + tlStr += \ + headerButtonsTimeline(defaultTimeline, boxName, pageNumber, + translate, usersPath, + minimal, moderator, + manuallyApproveFollowers, + baseDir, nickname, + domain, iconsDir, timelineStartTime, + iconsAsButtons) # start the timeline tlStr += '
\n' @@ -408,24 +160,6 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str, # center column containing posts tlStr += '
\n' - if not fullWidthTimelineButtonHeader: - tlStr += \ - headerButtonsTimeline(defaultTimeline, boxName, pageNumber, - translate, usersPath, mediaButton, - blogsButton, featuresButton, - newsButton, inboxButton, - dmButton, newDM, repliesButton, - newReply, minimal, sentButton, - sharesButtonStr, bookmarksButtonStr, - eventsButtonStr, moderationButtonStr, - newPostButtonStr, baseDir, nickname, - domain, timelineStartTime, - newCalendarEvent, calendarPath, - calendarImage, followApprovals, - iconsAsButtons) - - tlStr += '
\n' - # second row of buttons for moderator actions if moderator and boxName == 'moderation': tlStr += \ @@ -701,6 +435,449 @@ def _htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int, return timelineStr +# TODO: Can this be re-implemented as injecting an additional CSS class ? +# Would allow for much more opportunity to style +def htmlHighlightLabel(label: str, highlight: bool) -> str: + """If the give text should be highlighted then return + the appropriate markup. + This is so that in shell browsers, like lynx, it's possible + to see if the replies or DM button are highlighted. + """ + if not highlight: + return label + return '*' + str(label) + '*' + + +def headerButtonsTimeline(defaultTimeline: str, + boxName: str, + pageNumber: int, + translate: {}, + usersPath: str, + minimal: bool, + moderator: bool, + manuallyApproveFollowers: bool, + baseDir: str, + nickname: str, domain: str, + iconsDir: str, + timelineStartTime, + iconsAsButtons: bool) -> str: + """Returns the header at the top of the timeline, containing + buttons for inbox, outbox, search, calendar, etc + """ + # TODO: Create menu buttons via loop(s) + + accountDir = baseDir + '/accounts/' + nickname + '@' + domain + + # should the calendar icon be highlighted? + newCalendarEvent = False + calendarImage = 'calendar.png' + calendarPath = '/calendar' + calendarFile = accountDir + '/.newCalendar' + if os.path.isfile(calendarFile): + newCalendarEvent = True + calendarImage = 'calendar_notify.png' + with open(calendarFile, 'r') as calfile: + calendarPath = calfile.read().replace('##sent##', '') + calendarPath = calendarPath.replace('\n', '').replace('\r', '') + + # should the DM button be highlighted? + newDM = False + dmFile = accountDir + '/.newDM' + if os.path.isfile(dmFile): + newDM = True + if boxName == 'dm': + os.remove(dmFile) + + # should the Replies button be highlighted? + newReply = False + replyFile = accountDir + '/.newReply' + if os.path.isfile(replyFile): + newReply = True + if boxName == 'tlreplies': + os.remove(replyFile) + + # should the Shares button be highlighted? + newShare = False + newShareFile = accountDir + '/.newShare' + if os.path.isfile(newShareFile): + newShare = True + if boxName == 'tlshares': + os.remove(newShareFile) + + # should the Moderation/reports button be highlighted? + newReport = False + newReportFile = accountDir + '/.newReport' + if os.path.isfile(newReportFile): + newReport = True + if boxName == 'moderation': + os.remove(newReportFile) + + + # start of the button header with inbox, outbox, etc + # TODO: [rename] containerHeader -> menu (or similar) + + tlStr = '
\n' + + # if this is a news instance and we are viewing the news timeline + newsHeader = False + if defaultTimeline == 'tlnews' and boxName == 'tlnews': + newsHeader = True + +# if not newsHeader: +# tlStr += \ +# '' +# +# tlStr += \ +# '' + + isFeaturesTimeline = \ + defaultTimeline == 'tlnews' and boxName == 'tlnews' + + # TODO: Convert to new approach - append to 'actionButtonList' + # show todays events buttons on the first inbox page + happeningStr = '' + if boxName == 'inbox' and pageNumber == 1: + if todaysEventsCheck(baseDir, nickname, domain): + now = datetime.now() + + # happening today button + if not iconsAsButtons: + happeningStr += \ + '' + \ + '' + else: + happeningStr += \ + '' + \ + '' + + # happening this week button + if thisWeeksEventsCheck(baseDir, nickname, domain): + if not iconsAsButtons: + happeningStr += \ + '' + else: + happeningStr += \ + '' + else: + # happening this week button + if thisWeeksEventsCheck(baseDir, nickname, domain): + if not iconsAsButtons: + happeningStr += \ + '' + else: + happeningStr += \ + '' + + + # TODO: Group _all_ items that are hidden in 'minimal' mode ? + # - Are there others elsewhere in the code ? + + # All menu items default to text buttons + # Add class "icon-button" to overwrite default behaviour + + # Menu buttons are split into two sections + # - Navigation buttons : Informational pages, e.g. Inbox + # - Action buttons : More clear action, e.g. Search, Create Post + # default appearance text; use CSS class(es) to re-styled + + # A list of buttons to be rendered as html, + # each item being a dictionary containing its unique info. + # Each dict has: + # - pageRef : the url snippet + # - translationText : text to be displayed + # - highlightLabel : boolean to determine highlight + # - isIcon : boolean to trigger specific CSS class addition(s) for icon buttons + # - class : string of class(es) to append + # - iconClass : string of class(es) to append, only regarding icons + + # Buttons should be added in the order you wish them to appear in the menu + navButtonList = [] + actionButtonList = [] + + # Too many buttons - some visibility can be toggled + # TODO: Show/Hide menu buttons should not require page reload! + # Implement checkbox method + # Requires grouping the buttons that are shown/hidden ? + # TODO: The 'minimal' attribute could be done using CSS class ? + + # Minimal View only shows: + # - Inbox + # - Outbox + # - DM + # - Replies + # - News # TODO: Should this really be even for non-news instances ? + + # NOTE: "Action" buttons (icons in default Epicyon) are always visible + + navButtonList.append(('inbox', + {'pageRef': '/inbox', + 'translateText': 'Inbox'} + )) + navButtonList.append(('outbox', + {'pageRef': '/outbox', + 'translateText': 'Outbox'} + )) + navButtonList.append(('dm', + {'pageRef': '/dm', + 'translateText': 'DM', + 'highlightLabel': newDM} + )) + navButtonList.append(('tlreplies', + {'pageRef': '/tlreplies', + 'translateText': 'Replies', + 'highlightLabel': newReply} + )) + + if not minimal: + navButtonList.append(('tlnews', + {'pageRef': '/tlnews', + 'translateText': 'News'} + )) + # The following translationText should be 'Article' for News instances + navButtonList.append(('tlblogs', + {'pageRef': '/tlblogs', + 'translateText': 'Blogs'} + )) + navButtonList.append(('tlmedia', + {'pageRef': '/tlmedia', + 'translateText': 'Media'} + )) + navButtonList.append(('tlshares', + {'pageRef': '/tlshares', + 'translateText': 'Shares', + 'highlightLabel': newShare} + )) + navButtonList.append(('tlbookmarks', + {'pageRef': '/tlbookmarks', + 'translateText': 'Bookmarks'} + )) + navButtonList.append(('tlevents', + {'pageRef': '/tlevents', + 'translateText': 'Events'} + )) + if moderator: + navButtonList.append(('moderation', + {'pageRef': '/moderation', + 'translateText': 'Mod', + 'highlightLabel': newReport} + )) + + # Single out the "instance-type" button, and move to front of list + # NOTE: Current instance types are: 'tlblogs', 'tlmedia', 'tlnews' + for i, (name, config) in enumerate(navButtonList): + if name == defaultTimeline: + tmp = navButtonList.pop(i) + navButtonList.insert(0, tmp) + break + + # Generate HTML list + navButtonStr = '\n' + + # start of headericons list + + # show an icon for new follow approvals + followApprovals = False + followRequestsFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/followrequests.txt' + if os.path.isfile(followRequestsFilename): + with open(followRequestsFilename, 'r') as f: + for line in f: + if len(line) > 0: + # show follow approvals icon + followApprovals = True + break + + if followApprovals: + actionButtonList.append(('followers', + {'pageRef': '/followers', + 'translateText': 'Approve follow requests', + 'iconClass': 'icon-newfollow'} + )) + + # what screen to go to when a new post is created + # The following produces one button/icon for "new post" dependent on current viewed page + if boxName == 'dm': + actionButtonList.append(('newdm', + {'pageRef': '/newdm', + 'translateText': 'Create a new DM'} + )) + elif boxName == 'tlblogs' or boxName == 'tlnews': + actionButtonList.append(('newblog', + {'pageRef': '/newblog', + 'translateText': 'Create a new post'} + )) + elif boxName == 'tlevents': + actionButtonList.append(('newevent', + {'pageRef': '/newevent', + 'translateText': 'Create a new event'} + )) + else: + if not manuallyApproveFollowers: + actionButtonList.append(('newpost', + {'pageRef': '/newpost', + 'translateText': 'Create a new post'} + )) + else: + actionButtonList.append(('newfollowers', + {'pageRef': '/newfollowers', + 'translateText': 'Create a new post'} + )) + # 'icon-' is solely used for the CSS to load appropriate icon + actionButtonList[-1][1]['iconClass'] = 'icon-newpost' + + actionButtonList.append(('newswire', + {'pageRef': '/newswiremobile', + 'translateText': 'Newswire', + 'class': 'mobile-only', + 'iconClass': 'icon-newswire'} + )) + actionButtonList.append(('links', + {'pageRef': '/linksmobile', + 'translateText': 'Edit Links', + 'class': 'mobile-only', + 'iconClass': 'icon-links'} + )) + actionButtonList.append(('calendar', + {'pageRef': calendarPath, + 'translateText': 'Calendar', + 'iconClass': 'icon-calendar'} + )) + actionButtonList.append(('search', + {'pageRef': '/search', + 'translateText': 'Search', + 'iconClass': 'icon-search'} + )) + actionButtonList.append(('minimal', + {'pageRef': '/minimal', + 'translateText': 'Show/Hide Buttons', + 'iconClass': 'icon-showhide'} + )) + + actionButtonStr = '
\n' + actionButtonStr += '\n
\n' + + + # benchmark 5 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 5 = ' + str(timeDiff)) + +# if not newsHeader: +# # the show/hide button, for a simpler header appearance +# if not iconsAsButtons: +# tlStr += \ +# ' | ' + translate['Show/Hide Buttons'] + \
+#                '\n' +# else: +# tlStr += \ +# '' +# else: +# tlStr += \ +# '' + \ +# '' +# tlStr += \ +# '' + \ +# '' + + # Compile HTML parts + tlStr += navButtonStr + actionButtonStr + happeningStr + +# TODO: Integrate this logic in new approach - if ultimately necessary +# if not newsHeader: +# tlStr += followApprovals + + # end of the button header with inbox, outbox, etc + tlStr += '
\n' + return tlStr + + def htmlShares(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int,