diff --git a/webapp_timeline.py b/webapp_timeline.py
index 80f2f17d..9e65b3d6 100644
--- a/webapp_timeline.py
+++ b/webapp_timeline.py
@@ -18,7 +18,7 @@ from happening import thisWeeksEventsCheck
from webapp_utils import getIconsDir
from webapp_utils import htmlPostSeparator
from webapp_utils import getBannerFile
-from webapp_utils import htmlHeader
+from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter
from webapp_utils import sharesTimelineJson
from webapp_post import preparePostFromHtmlCache
@@ -29,6 +29,600 @@ from posts import isModerator
from posts import isEditor
+def htmlTimeline(cssCache: {}, defaultTimeline: str,
+ recentPostsCache: {}, maxRecentPosts: int,
+ translate: {}, pageNumber: int,
+ itemsPerPage: int, session, baseDir: str,
+ wfRequest: {}, personCache: {},
+ nickname: str, domain: str, port: int, timelineJson: {},
+ boxName: str, allowDeletion: bool,
+ httpPrefix: str, projectVersion: str,
+ manuallyApproveFollowers: bool,
+ minimal: bool,
+ YTReplacementDomain: str,
+ showPublishedDateOnly: bool,
+ newswire: {}, moderator: bool,
+ editor: bool,
+ positiveVoting: bool,
+ showPublishAsIcon: bool,
+ fullWidthTimelineButtonHeader: bool,
+ iconsAsButtons: bool,
+ rssIconAtTop: bool,
+ publishButtonAtTop: bool,
+ authorized: bool) -> str:
+ """Show the timeline as html
+ """
+ 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':
+ separatorStr = htmlPostSeparator(baseDir, None)
+
+ # the css filename
+ cssFilename = baseDir + '/epicyon-profile.css'
+ if os.path.isfile(baseDir + '/epicyon.css'):
+ cssFilename = baseDir + '/epicyon.css'
+
+ # filename of the banner shown at the top
+ bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
+
+ # benchmark 1
+ timeDiff = int((time.time() - timelineStartTime) * 1000)
+ if timeDiff > 100:
+ print('TIMELINE TIMING ' + boxName + ' 1 = ' + str(timeDiff))
+
+ profileStyle = getCSS(baseDir, cssFilename, cssCache)
+ if not profileStyle:
+ print('ERROR: css file not found ' + cssFilename)
+ return None
+
+ # replace any https within the css with whatever prefix is needed
+ if httpPrefix != 'https':
+ profileStyle = \
+ profileStyle.replace('https://',
+ httpPrefix + '://')
+
+ # is the user a moderator?
+ if not moderator:
+ moderator = isModerator(baseDir, nickname)
+
+ # is the user a site editor?
+ if not editor:
+ editor = isEditor(baseDir, nickname)
+
+ # benchmark 2
+ timeDiff = int((time.time() - timelineStartTime) * 1000)
+ if timeDiff > 100:
+ print('TIMELINE TIMING ' + boxName + ' 2 = ' + str(timeDiff))
+
+ # the appearance of buttons - highlighted or not
+ inboxButton = 'button'
+ blogsButton = '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 == '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 = domain
+ if port != 80 and port != 443:
+ if ':' not in domain:
+ fullDomain = domain + ':' + str(port)
+
+ usersPath = '/users/' + nickname
+ actor = httpPrefix + '://' + fullDomain + usersPath
+
+ 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 = \
+ '' + \
+ '\n'
+ break
+
+ # benchmark 3
+ timeDiff = int((time.time() - timelineStartTime) * 1000)
+ if timeDiff > 100:
+ print('TIMELINE TIMING ' + boxName + ' 3 = ' + str(timeDiff))
+
+ # 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, profileStyle)
+
+ # benchmark 4
+ timeDiff = int((time.time() - timelineStartTime) * 1000)
+ if timeDiff > 100:
+ print('TIMELINE TIMING ' + boxName + ' 4 = ' + str(timeDiff))
+
+ # if this is a news instance and we are viewing the news timeline
+ newsHeader = False
+ if defaultTimeline == 'tlnews' and boxName == 'tlnews':
+ newsHeader = True
+
+ newPostButtonStr = ''
+ # start of headericons div
+ if not newsHeader:
+ if not iconsAsButtons:
+ newPostButtonStr += '