Modern ActivityPub compliant server, designed for simplicity and accessibility. Includes calendar and sharing economy features to empower your federated community. https://code.freedombone.net/bashrc/epicyon Docs: https://epicyon.net/#install
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

webapp_timeline.py 57KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352
  1. __filename__ = "webapp_timeline.py"
  2. __author__ = "Bob Mottram"
  3. __license__ = "AGPL3+"
  4. __version__ = "1.2.0"
  5. __maintainer__ = "Bob Mottram"
  6. __email__ = "bob@freedombone.net"
  7. __status__ = "Production"
  8. import os
  9. import time
  10. from shutil import copyfile
  11. from utils import dangerousMarkup
  12. from utils import getConfigParam
  13. from utils import getFullDomain
  14. from utils import isEditor
  15. from utils import removeIdEnding
  16. from follow import followerApprovalActive
  17. from person import isPersonSnoozed
  18. from webapp_utils import markdownToHtml
  19. from webapp_utils import htmlKeyboardNavigation
  20. from webapp_utils import htmlHideFromScreenReader
  21. from webapp_utils import htmlPostSeparator
  22. from webapp_utils import getBannerFile
  23. from webapp_utils import htmlHeaderWithExternalStyle
  24. from webapp_utils import htmlFooter
  25. from webapp_utils import sharesTimelineJson
  26. from webapp_utils import htmlHighlightLabel
  27. from webapp_post import preparePostFromHtmlCache
  28. from webapp_post import individualPostAsHtml
  29. from webapp_column_left import getLeftColumnContent
  30. from webapp_column_right import getRightColumnContent
  31. from webapp_headerbuttons import headerButtonsTimeline
  32. from posts import isModerator
  33. def _logTimelineTiming(enableTimingLog: bool, timelineStartTime,
  34. boxName: str, debugId: str) -> None:
  35. """Create a log of timings for performance tuning
  36. """
  37. if not enableTimingLog:
  38. return
  39. timeDiff = int((time.time() - timelineStartTime) * 1000)
  40. if timeDiff > 100:
  41. print('TIMELINE TIMING ' +
  42. boxName + ' ' + debugId + ' = ' + str(timeDiff))
  43. def _getHelpForTimeline(baseDir: str, boxName: str) -> str:
  44. """Shows help text for the given timeline
  45. """
  46. # get the filename for help for this timeline
  47. helpFilename = baseDir + '/accounts/help_' + boxName + '.md'
  48. if not os.path.isfile(helpFilename):
  49. language = \
  50. getConfigParam(baseDir, 'language')
  51. if not language:
  52. language = 'en'
  53. themeName = \
  54. getConfigParam(baseDir, 'theme')
  55. defaultFilename = None
  56. if themeName:
  57. defaultFilename = \
  58. baseDir + '/theme/' + themeName + '/welcome/' + \
  59. 'help_' + boxName + '_' + language + '.md'
  60. if not os.path.isfile(defaultFilename):
  61. defaultFilename = None
  62. if not defaultFilename:
  63. defaultFilename = \
  64. baseDir + '/defaultwelcome/' + \
  65. 'help_' + boxName + '_' + language + '.md'
  66. if not os.path.isfile(defaultFilename):
  67. defaultFilename = \
  68. baseDir + '/defaultwelcome/help_' + boxName + '_en.md'
  69. if os.path.isfile(defaultFilename):
  70. copyfile(defaultFilename, helpFilename)
  71. # show help text
  72. if os.path.isfile(helpFilename):
  73. instanceTitle = \
  74. getConfigParam(baseDir, 'instanceTitle')
  75. if not instanceTitle:
  76. instanceTitle = 'Epicyon'
  77. with open(helpFilename, 'r') as helpFile:
  78. helpText = helpFile.read()
  79. if dangerousMarkup(helpText, False):
  80. return ''
  81. helpText = helpText.replace('INSTANCE', instanceTitle)
  82. return '<div class="container">\n' + \
  83. markdownToHtml(helpText) + '\n' + \
  84. '</div>\n'
  85. return ''
  86. def htmlTimeline(cssCache: {}, defaultTimeline: str,
  87. recentPostsCache: {}, maxRecentPosts: int,
  88. translate: {}, pageNumber: int,
  89. itemsPerPage: int, session, baseDir: str,
  90. cachedWebfingers: {}, personCache: {},
  91. nickname: str, domain: str, port: int, timelineJson: {},
  92. boxName: str, allowDeletion: bool,
  93. httpPrefix: str, projectVersion: str,
  94. manuallyApproveFollowers: bool,
  95. minimal: bool,
  96. YTReplacementDomain: str,
  97. showPublishedDateOnly: bool,
  98. newswire: {}, moderator: bool,
  99. editor: bool,
  100. positiveVoting: bool,
  101. showPublishAsIcon: bool,
  102. fullWidthTimelineButtonHeader: bool,
  103. iconsAsButtons: bool,
  104. rssIconAtTop: bool,
  105. publishButtonAtTop: bool,
  106. authorized: bool,
  107. moderationActionStr: str,
  108. theme: str,
  109. peertubeInstances: [],
  110. allowLocalNetworkAccess: bool,
  111. textModeBanner: str,
  112. accessKeys: {}) -> str:
  113. """Show the timeline as html
  114. """
  115. enableTimingLog = False
  116. timelineStartTime = time.time()
  117. accountDir = baseDir + '/accounts/' + nickname + '@' + domain
  118. # should the calendar icon be highlighted?
  119. newCalendarEvent = False
  120. calendarImage = 'calendar.png'
  121. calendarPath = '/calendar'
  122. calendarFile = accountDir + '/.newCalendar'
  123. if os.path.isfile(calendarFile):
  124. newCalendarEvent = True
  125. calendarImage = 'calendar_notify.png'
  126. with open(calendarFile, 'r') as calfile:
  127. calendarPath = calfile.read().replace('##sent##', '')
  128. calendarPath = calendarPath.replace('\n', '').replace('\r', '')
  129. # should the DM button be highlighted?
  130. newDM = False
  131. dmFile = accountDir + '/.newDM'
  132. if os.path.isfile(dmFile):
  133. newDM = True
  134. if boxName == 'dm':
  135. os.remove(dmFile)
  136. # should the Replies button be highlighted?
  137. newReply = False
  138. replyFile = accountDir + '/.newReply'
  139. if os.path.isfile(replyFile):
  140. newReply = True
  141. if boxName == 'tlreplies':
  142. os.remove(replyFile)
  143. # should the Shares button be highlighted?
  144. newShare = False
  145. newShareFile = accountDir + '/.newShare'
  146. if os.path.isfile(newShareFile):
  147. newShare = True
  148. if boxName == 'tlshares':
  149. os.remove(newShareFile)
  150. # should the Moderation/reports button be highlighted?
  151. newReport = False
  152. newReportFile = accountDir + '/.newReport'
  153. if os.path.isfile(newReportFile):
  154. newReport = True
  155. if boxName == 'moderation':
  156. os.remove(newReportFile)
  157. separatorStr = ''
  158. if boxName != 'tlmedia':
  159. separatorStr = htmlPostSeparator(baseDir, None)
  160. # the css filename
  161. cssFilename = baseDir + '/epicyon-profile.css'
  162. if os.path.isfile(baseDir + '/epicyon.css'):
  163. cssFilename = baseDir + '/epicyon.css'
  164. # filename of the banner shown at the top
  165. bannerFile, bannerFilename = \
  166. getBannerFile(baseDir, nickname, domain, theme)
  167. _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '1')
  168. # is the user a moderator?
  169. if not moderator:
  170. moderator = isModerator(baseDir, nickname)
  171. # is the user a site editor?
  172. if not editor:
  173. editor = isEditor(baseDir, nickname)
  174. _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '2')
  175. # the appearance of buttons - highlighted or not
  176. inboxButton = 'button'
  177. blogsButton = 'button'
  178. featuresButton = 'button'
  179. newsButton = 'button'
  180. dmButton = 'button'
  181. if newDM:
  182. dmButton = 'buttonhighlighted'
  183. repliesButton = 'button'
  184. if newReply:
  185. repliesButton = 'buttonhighlighted'
  186. mediaButton = 'button'
  187. bookmarksButton = 'button'
  188. # eventsButton = 'button'
  189. sentButton = 'button'
  190. sharesButton = 'button'
  191. if newShare:
  192. sharesButton = 'buttonhighlighted'
  193. moderationButton = 'button'
  194. if newReport:
  195. moderationButton = 'buttonhighlighted'
  196. if boxName == 'inbox':
  197. inboxButton = 'buttonselected'
  198. elif boxName == 'tlblogs':
  199. blogsButton = 'buttonselected'
  200. elif boxName == 'tlfeatures':
  201. featuresButton = 'buttonselected'
  202. elif boxName == 'tlnews':
  203. newsButton = 'buttonselected'
  204. elif boxName == 'dm':
  205. dmButton = 'buttonselected'
  206. if newDM:
  207. dmButton = 'buttonselectedhighlighted'
  208. elif boxName == 'tlreplies':
  209. repliesButton = 'buttonselected'
  210. if newReply:
  211. repliesButton = 'buttonselectedhighlighted'
  212. elif boxName == 'tlmedia':
  213. mediaButton = 'buttonselected'
  214. elif boxName == 'outbox':
  215. sentButton = 'buttonselected'
  216. elif boxName == 'moderation':
  217. moderationButton = 'buttonselected'
  218. if newReport:
  219. moderationButton = 'buttonselectedhighlighted'
  220. elif boxName == 'tlshares':
  221. sharesButton = 'buttonselected'
  222. if newShare:
  223. sharesButton = 'buttonselectedhighlighted'
  224. elif boxName == 'tlbookmarks' or boxName == 'bookmarks':
  225. bookmarksButton = 'buttonselected'
  226. # elif boxName == 'tlevents':
  227. # eventsButton = 'buttonselected'
  228. # get the full domain, including any port number
  229. fullDomain = getFullDomain(domain, port)
  230. usersPath = '/users/' + nickname
  231. actor = httpPrefix + '://' + fullDomain + usersPath
  232. showIndividualPostIcons = True
  233. # show an icon for new follow approvals
  234. followApprovals = ''
  235. followRequestsFilename = \
  236. baseDir + '/accounts/' + \
  237. nickname + '@' + domain + '/followrequests.txt'
  238. if os.path.isfile(followRequestsFilename):
  239. with open(followRequestsFilename, 'r') as f:
  240. for line in f:
  241. if len(line) > 0:
  242. # show follow approvals icon
  243. followApprovals = \
  244. '<a href="' + usersPath + \
  245. '/followers#buttonheader" ' + \
  246. 'accesskey="' + accessKeys['followButton'] + '">' + \
  247. '<img loading="lazy" ' + \
  248. 'class="timelineicon" alt="' + \
  249. translate['Approve follow requests'] + \
  250. '" title="' + translate['Approve follow requests'] + \
  251. '" src="/icons/person.png"/></a>\n'
  252. break
  253. _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '3')
  254. # moderation / reports button
  255. moderationButtonStr = ''
  256. if moderator and not minimal:
  257. moderationButtonStr = \
  258. '<a href="' + usersPath + \
  259. '/moderation"><button class="' + \
  260. moderationButton + '"><span>' + \
  261. htmlHighlightLabel(translate['Mod'], newReport) + \
  262. ' </span></button></a>'
  263. # shares, bookmarks and events buttons
  264. sharesButtonStr = ''
  265. bookmarksButtonStr = ''
  266. eventsButtonStr = ''
  267. if not minimal:
  268. sharesButtonStr = \
  269. '<a href="' + usersPath + '/tlshares"><button class="' + \
  270. sharesButton + '"><span>' + \
  271. htmlHighlightLabel(translate['Shares'], newShare) + \
  272. '</span></button></a>'
  273. bookmarksButtonStr = \
  274. '<a href="' + usersPath + '/tlbookmarks"><button class="' + \
  275. bookmarksButton + '"><span>' + translate['Bookmarks'] + \
  276. '</span></button></a>'
  277. #
  278. # eventsButtonStr = \
  279. # '<a href="' + usersPath + '/tlevents"><button class="' + \
  280. # eventsButton + '"><span>' + translate['Events'] + \
  281. # '</span></button></a>'
  282. instanceTitle = \
  283. getConfigParam(baseDir, 'instanceTitle')
  284. tlStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
  285. _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '4')
  286. # if this is a news instance and we are viewing the news timeline
  287. newsHeader = False
  288. if defaultTimeline == 'tlfeatures' and boxName == 'tlfeatures':
  289. newsHeader = True
  290. newPostButtonStr = ''
  291. # start of headericons div
  292. if not newsHeader:
  293. if not iconsAsButtons:
  294. newPostButtonStr += '<div class="headericons">'
  295. # what screen to go to when a new post is created
  296. if boxName == 'dm':
  297. if not iconsAsButtons:
  298. newPostButtonStr += \
  299. '<a class="imageAnchor" href="' + usersPath + \
  300. '/newdm?nodropdown"><img loading="lazy" src="/' + \
  301. 'icons/newpost.png" title="' + \
  302. translate['Create a new DM'] + \
  303. '" alt="| ' + translate['Create a new DM'] + \
  304. '" class="timelineicon"/></a>\n'
  305. else:
  306. newPostButtonStr += \
  307. '<a href="' + usersPath + '/newdm?nodropdown">' + \
  308. '<button class="button"><span>' + \
  309. translate['Post'] + ' </span></button></a>'
  310. elif (boxName == 'tlblogs' or
  311. boxName == 'tlnews' or
  312. boxName == 'tlfeatures'):
  313. if not iconsAsButtons:
  314. newPostButtonStr += \
  315. '<a class="imageAnchor" href="' + usersPath + \
  316. '/newblog"><img loading="lazy" src="/' + \
  317. 'icons/newpost.png" title="' + \
  318. translate['Create a new post'] + '" alt="| ' + \
  319. translate['Create a new post'] + \
  320. '" class="timelineicon"/></a>\n'
  321. else:
  322. newPostButtonStr += \
  323. '<a href="' + usersPath + '/newblog">' + \
  324. '<button class="button"><span>' + \
  325. translate['Post'] + '</span></button></a>'
  326. elif boxName == 'tlevents':
  327. if not iconsAsButtons:
  328. newPostButtonStr += \
  329. '<a class="imageAnchor" href="' + usersPath + \
  330. '/newevent?nodropdown"><img loading="lazy" src="/' + \
  331. 'icons/newpost.png" title="' + \
  332. translate['Create a new event'] + '" alt="| ' + \
  333. translate['Create a new event'] + \
  334. '" class="timelineicon"/></a>\n'
  335. else:
  336. newPostButtonStr += \
  337. '<a href="' + usersPath + '/newevent?nodropdown">' + \
  338. '<button class="button"><span>' + \
  339. translate['Post'] + '</span></button></a>'
  340. elif boxName == 'tlshares':
  341. if not iconsAsButtons:
  342. newPostButtonStr += \
  343. '<a class="imageAnchor" href="' + usersPath + \
  344. '/newshare?nodropdown"><img loading="lazy" src="/' + \
  345. 'icons/newpost.png" title="' + \
  346. translate['Create a new shared item'] + '" alt="| ' + \
  347. translate['Create a new shared item'] + \
  348. '" class="timelineicon"/></a>\n'
  349. else:
  350. newPostButtonStr += \
  351. '<a href="' + usersPath + '/newshare?nodropdown">' + \
  352. '<button class="button"><span>' + \
  353. translate['Post'] + '</span></button></a>'
  354. else:
  355. if not manuallyApproveFollowers:
  356. if not iconsAsButtons:
  357. newPostButtonStr += \
  358. '<a class="imageAnchor" href="' + usersPath + \
  359. '/newpost"><img loading="lazy" src="/' + \
  360. 'icons/newpost.png" title="' + \
  361. translate['Create a new post'] + '" alt="| ' + \
  362. translate['Create a new post'] + \
  363. '" class="timelineicon"/></a>\n'
  364. else:
  365. newPostButtonStr += \
  366. '<a href="' + usersPath + '/newpost">' + \
  367. '<button class="button"><span>' + \
  368. translate['Post'] + '</span></button></a>'
  369. else:
  370. if not iconsAsButtons:
  371. newPostButtonStr += \
  372. '<a class="imageAnchor" href="' + usersPath + \
  373. '/newfollowers"><img loading="lazy" src="/' + \
  374. 'icons/newpost.png" title="' + \
  375. translate['Create a new post'] + \
  376. '" alt="| ' + translate['Create a new post'] + \
  377. '" class="timelineicon"/></a>\n'
  378. else:
  379. newPostButtonStr += \
  380. '<a href="' + usersPath + '/newfollowers">' + \
  381. '<button class="button"><span>' + \
  382. translate['Post'] + '</span></button></a>'
  383. # keyboard navigation
  384. calendarStr = translate['Calendar']
  385. if newCalendarEvent:
  386. calendarStr = '<strong>' + calendarStr + '</strong>'
  387. dmStr = translate['DM']
  388. if newDM:
  389. dmStr = '<strong>' + dmStr + '</strong>'
  390. repliesStr = translate['Replies']
  391. if newReply:
  392. repliesStr = '<strong>' + repliesStr + '</strong>'
  393. sharesStr = translate['Shares']
  394. if newShare:
  395. sharesStr = '<strong>' + sharesStr + '</strong>'
  396. menuProfile = \
  397. htmlHideFromScreenReader('👤') + ' ' + \
  398. translate['Switch to profile view']
  399. menuInbox = \
  400. htmlHideFromScreenReader('📥') + ' ' + translate['Inbox']
  401. menuOutbox = \
  402. htmlHideFromScreenReader('📤') + ' ' + translate['Sent']
  403. menuSearch = \
  404. htmlHideFromScreenReader('🔍') + ' ' + \
  405. translate['Search and follow']
  406. menuCalendar = \
  407. htmlHideFromScreenReader('📅') + ' ' + calendarStr
  408. menuDM = \
  409. htmlHideFromScreenReader('📩') + ' ' + dmStr
  410. menuReplies = \
  411. htmlHideFromScreenReader('📨') + ' ' + repliesStr
  412. menuBookmarks = \
  413. htmlHideFromScreenReader('🔖') + ' ' + \
  414. translate['Bookmarks']
  415. menuShares = \
  416. htmlHideFromScreenReader('🤝') + ' ' + sharesStr
  417. # menuEvents = \
  418. # htmlHideFromScreenReader('🎫') + ' ' + translate['Events']
  419. menuBlogs = \
  420. htmlHideFromScreenReader('📝') + ' ' + translate['Blogs']
  421. menuNewswire = \
  422. htmlHideFromScreenReader('📰') + ' ' + translate['Newswire']
  423. menuLinks = \
  424. htmlHideFromScreenReader('🔗') + ' ' + translate['Links']
  425. menuNewPost = \
  426. htmlHideFromScreenReader('➕') + ' ' + \
  427. translate['Create a new post']
  428. menuModeration = \
  429. htmlHideFromScreenReader('⚡️') + ' ' + \
  430. translate['Mod']
  431. navLinks = {
  432. menuProfile: '/users/' + nickname,
  433. menuInbox: usersPath + '/inbox#timelineposts',
  434. menuSearch: usersPath + '/search',
  435. menuNewPost: usersPath + '/newpost',
  436. menuCalendar: usersPath + '/calendar',
  437. menuDM: usersPath + '/dm#timelineposts',
  438. menuReplies: usersPath + '/tlreplies#timelineposts',
  439. menuOutbox: usersPath + '/outbox#timelineposts',
  440. menuBookmarks: usersPath + '/tlbookmarks#timelineposts',
  441. menuShares: usersPath + '/tlshares#timelineposts',
  442. menuBlogs: usersPath + '/tlblogs#timelineposts',
  443. menuNewswire: usersPath + '/newswiremobile',
  444. menuLinks: usersPath + '/linksmobile'
  445. }
  446. navAccessKeys = {}
  447. for variableName, key in accessKeys.items():
  448. if not locals().get(variableName):
  449. continue
  450. navAccessKeys[locals()[variableName]] = key
  451. if moderator:
  452. navLinks[menuModeration] = usersPath + '/moderation#modtimeline'
  453. tlStr += htmlKeyboardNavigation(textModeBanner, navLinks, navAccessKeys,
  454. None, usersPath, translate,
  455. followApprovals)
  456. # banner and row of buttons
  457. tlStr += \
  458. '<header>\n' + \
  459. '<a href="/users/' + nickname + '" title="' + \
  460. translate['Switch to profile view'] + '" alt="' + \
  461. translate['Switch to profile view'] + '">\n'
  462. tlStr += '<img loading="lazy" class="timeline-banner" ' + \
  463. 'alt="" ' + \
  464. 'src="' + usersPath + '/' + bannerFile + '" /></a>\n' + \
  465. '</header>\n'
  466. if fullWidthTimelineButtonHeader:
  467. tlStr += \
  468. headerButtonsTimeline(defaultTimeline, boxName, pageNumber,
  469. translate, usersPath, mediaButton,
  470. blogsButton, featuresButton,
  471. newsButton, inboxButton,
  472. dmButton, newDM, repliesButton,
  473. newReply, minimal, sentButton,
  474. sharesButtonStr, bookmarksButtonStr,
  475. eventsButtonStr, moderationButtonStr,
  476. newPostButtonStr, baseDir, nickname,
  477. domain, timelineStartTime,
  478. newCalendarEvent, calendarPath,
  479. calendarImage, followApprovals,
  480. iconsAsButtons, accessKeys)
  481. # start the timeline
  482. tlStr += '<table class="timeline">\n'
  483. tlStr += ' <colgroup>\n'
  484. tlStr += ' <col span="1" class="column-left">\n'
  485. tlStr += ' <col span="1" class="column-center">\n'
  486. tlStr += ' <col span="1" class="column-right">\n'
  487. tlStr += ' </colgroup>\n'
  488. tlStr += ' <tbody>\n'
  489. tlStr += ' <tr>\n'
  490. domainFull = getFullDomain(domain, port)
  491. # left column
  492. leftColumnStr = \
  493. getLeftColumnContent(baseDir, nickname, domainFull,
  494. httpPrefix, translate,
  495. editor, False, None, rssIconAtTop,
  496. True, False, theme, accessKeys)
  497. tlStr += ' <td valign="top" class="col-left" ' + \
  498. 'id="links" tabindex="-1">' + \
  499. leftColumnStr + ' </td>\n'
  500. # center column containing posts
  501. tlStr += ' <td valign="top" class="col-center">\n'
  502. if not fullWidthTimelineButtonHeader:
  503. tlStr += \
  504. headerButtonsTimeline(defaultTimeline, boxName, pageNumber,
  505. translate, usersPath, mediaButton,
  506. blogsButton, featuresButton,
  507. newsButton, inboxButton,
  508. dmButton, newDM, repliesButton,
  509. newReply, minimal, sentButton,
  510. sharesButtonStr, bookmarksButtonStr,
  511. eventsButtonStr, moderationButtonStr,
  512. newPostButtonStr, baseDir, nickname,
  513. domain, timelineStartTime,
  514. newCalendarEvent, calendarPath,
  515. calendarImage, followApprovals,
  516. iconsAsButtons, accessKeys)
  517. tlStr += ' <div id="timelineposts" class="timeline-posts">\n'
  518. # second row of buttons for moderator actions
  519. if moderator and boxName == 'moderation':
  520. tlStr += \
  521. '<form id="modtimeline" method="POST" action="/users/' + \
  522. nickname + '/moderationaction">'
  523. tlStr += '<div class="container">\n'
  524. idx = 'Nickname or URL. Block using *@domain or nickname@domain'
  525. tlStr += \
  526. ' <b>' + translate[idx] + '</b><br>\n'
  527. if moderationActionStr:
  528. tlStr += ' <input type="text" ' + \
  529. 'name="moderationAction" value="' + \
  530. moderationActionStr + '" autofocus><br>\n'
  531. else:
  532. tlStr += ' <input type="text" ' + \
  533. 'name="moderationAction" value="" autofocus><br>\n'
  534. tlStr += \
  535. ' <input type="submit" title="' + \
  536. translate['Information about current blocks/suspensions'] + \
  537. '" alt="' + \
  538. translate['Information about current blocks/suspensions'] + \
  539. ' | " ' + \
  540. 'name="submitInfo" value="' + translate['Info'] + '">\n'
  541. tlStr += \
  542. ' <input type="submit" title="' + \
  543. translate['Remove the above item'] + '" ' + \
  544. 'alt="' + translate['Remove the above item'] + ' | " ' + \
  545. 'name="submitRemove" value="' + \
  546. translate['Remove'] + '">\n'
  547. tlStr += \
  548. ' <input type="submit" title="' + \
  549. translate['Suspend the above account nickname'] + '" ' + \
  550. 'alt="' + \
  551. translate['Suspend the above account nickname'] + ' | " ' + \
  552. 'name="submitSuspend" value="' + translate['Suspend'] + '">\n'
  553. tlStr += \
  554. ' <input type="submit" title="' + \
  555. translate['Remove a suspension for an account nickname'] + '" ' + \
  556. 'alt="' + \
  557. translate['Remove a suspension for an account nickname'] + \
  558. ' | " ' + \
  559. 'name="submitUnsuspend" value="' + \
  560. translate['Unsuspend'] + '">\n'
  561. tlStr += \
  562. ' <input type="submit" title="' + \
  563. translate['Block an account on another instance'] + '" ' + \
  564. 'alt="' + \
  565. translate['Block an account on another instance'] + ' | " ' + \
  566. 'name="submitBlock" value="' + translate['Block'] + '">\n'
  567. tlStr += \
  568. ' <input type="submit" title="' + \
  569. translate['Unblock an account on another instance'] + '" ' + \
  570. 'alt="' + \
  571. translate['Unblock an account on another instance'] + ' | " ' + \
  572. 'name="submitUnblock" value="' + translate['Unblock'] + '">\n'
  573. tlStr += \
  574. ' <input type="submit" title="' + \
  575. translate['Filter out words'] + '" ' + \
  576. 'alt="' + \
  577. translate['Filter out words'] + ' | " ' + \
  578. 'name="submitFilter" value="' + translate['Filter'] + '">\n'
  579. tlStr += \
  580. ' <input type="submit" title="' + \
  581. translate['Unfilter words'] + '" ' + \
  582. 'alt="' + \
  583. translate['Unfilter words'] + ' | " ' + \
  584. 'name="submitUnfilter" value="' + translate['Unfilter'] + '">\n'
  585. tlStr += '</div>\n</form>\n'
  586. _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '6')
  587. if boxName == 'tlshares':
  588. maxSharesPerAccount = itemsPerPage
  589. return (tlStr +
  590. _htmlSharesTimeline(translate, pageNumber, itemsPerPage,
  591. baseDir, actor, nickname, domain, port,
  592. maxSharesPerAccount, httpPrefix) +
  593. htmlFooter())
  594. _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '7')
  595. # separator between posts which only appears in shell browsers
  596. # such as Lynx and is not read by screen readers
  597. if boxName != 'tlmedia':
  598. textModeSeparator = \
  599. '<div class="transparent"><hr></div>'
  600. else:
  601. textModeSeparator = ''
  602. # page up arrow
  603. if pageNumber > 1:
  604. tlStr += textModeSeparator
  605. tlStr += \
  606. ' <center>\n' + \
  607. ' <a href="' + usersPath + '/' + boxName + \
  608. '?page=' + str(pageNumber - 1) + \
  609. '" accesskey="' + accessKeys['Page up'] + '">' + \
  610. '<img loading="lazy" class="pageicon" src="/' + \
  611. 'icons/pageup.png" title="' + \
  612. translate['Page up'] + '" alt="' + \
  613. translate['Page up'] + '"></a>\n' + \
  614. ' </center>\n'
  615. # show the posts
  616. itemCtr = 0
  617. if timelineJson:
  618. # if this is the media timeline then add an extra gallery container
  619. if boxName == 'tlmedia':
  620. if pageNumber > 1:
  621. tlStr += '<br>'
  622. tlStr += '<div class="galleryContainer">\n'
  623. # show each post in the timeline
  624. for item in timelineJson['orderedItems']:
  625. if item['type'] == 'Create' or \
  626. item['type'] == 'Announce':
  627. # is the actor who sent this post snoozed?
  628. if isPersonSnoozed(baseDir, nickname, domain, item['actor']):
  629. continue
  630. # is the post in the memory cache of recent ones?
  631. currTlStr = None
  632. if boxName != 'tlmedia' and \
  633. recentPostsCache.get('index'):
  634. postId = \
  635. removeIdEnding(item['id']).replace('/', '#')
  636. if postId in recentPostsCache['index']:
  637. if not item.get('muted'):
  638. if recentPostsCache['html'].get(postId):
  639. currTlStr = recentPostsCache['html'][postId]
  640. currTlStr = \
  641. preparePostFromHtmlCache(nickname,
  642. currTlStr,
  643. boxName,
  644. pageNumber)
  645. _logTimelineTiming(enableTimingLog,
  646. timelineStartTime,
  647. boxName, '10')
  648. else:
  649. print('Muted post in timeline ' + boxName)
  650. if not currTlStr:
  651. _logTimelineTiming(enableTimingLog,
  652. timelineStartTime,
  653. boxName, '11')
  654. # read the post from disk
  655. currTlStr = \
  656. individualPostAsHtml(False, recentPostsCache,
  657. maxRecentPosts,
  658. translate, pageNumber,
  659. baseDir, session,
  660. cachedWebfingers,
  661. personCache,
  662. nickname, domain, port,
  663. item, None, True,
  664. allowDeletion,
  665. httpPrefix, projectVersion,
  666. boxName,
  667. YTReplacementDomain,
  668. showPublishedDateOnly,
  669. peertubeInstances,
  670. allowLocalNetworkAccess,
  671. theme,
  672. boxName != 'dm',
  673. showIndividualPostIcons,
  674. manuallyApproveFollowers,
  675. False, True)
  676. _logTimelineTiming(enableTimingLog,
  677. timelineStartTime, boxName, '12')
  678. if currTlStr:
  679. itemCtr += 1
  680. tlStr += textModeSeparator + currTlStr
  681. if separatorStr:
  682. tlStr += separatorStr
  683. if boxName == 'tlmedia':
  684. tlStr += '</div>\n'
  685. if itemCtr < 3:
  686. print('Items added to html timeline ' + boxName + ': ' +
  687. str(itemCtr) + ' ' + str(timelineJson['orderedItems']))
  688. # page down arrow
  689. if itemCtr > 0:
  690. tlStr += textModeSeparator
  691. tlStr += \
  692. ' <center>\n' + \
  693. ' <a href="' + usersPath + '/' + boxName + '?page=' + \
  694. str(pageNumber + 1) + \
  695. '" accesskey="' + accessKeys['Page down'] + '">' + \
  696. '<img loading="lazy" class="pageicon" src="/' + \
  697. 'icons/pagedown.png" title="' + \
  698. translate['Page down'] + '" alt="' + \
  699. translate['Page down'] + '"></a>\n' + \
  700. ' </center>\n'
  701. tlStr += textModeSeparator
  702. elif itemCtr == 0:
  703. tlStr += _getHelpForTimeline(baseDir, boxName)
  704. # end of timeline-posts
  705. tlStr += ' </div>\n'
  706. # end of column-center
  707. tlStr += ' </td>\n'
  708. # right column
  709. rightColumnStr = getRightColumnContent(baseDir, nickname, domainFull,
  710. httpPrefix, translate,
  711. moderator, editor,
  712. newswire, positiveVoting,
  713. False, None, True,
  714. showPublishAsIcon,
  715. rssIconAtTop, publishButtonAtTop,
  716. authorized, True, theme,
  717. defaultTimeline, accessKeys)
  718. tlStr += ' <td valign="top" class="col-right" ' + \
  719. 'id="newswire" tabindex="-1">' + \
  720. rightColumnStr + ' </td>\n'
  721. tlStr += ' </tr>\n'
  722. _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '9')
  723. tlStr += ' </tbody>\n'
  724. tlStr += '</table>\n'
  725. tlStr += htmlFooter()
  726. return tlStr
  727. def htmlIndividualShare(actor: str, item: {}, translate: {},
  728. showContact: bool, removeButton: bool) -> str:
  729. """Returns an individual shared item as html
  730. """
  731. profileStr = '<div class="container">\n'
  732. profileStr += '<p class="share-title">' + item['displayName'] + '</p>\n'
  733. if item.get('imageUrl'):
  734. profileStr += '<a href="' + item['imageUrl'] + '">\n'
  735. profileStr += \
  736. '<img loading="lazy" src="' + item['imageUrl'] + \
  737. '" alt="' + translate['Item image'] + '">\n</a>\n'
  738. profileStr += '<p>' + item['summary'] + '</p>\n'
  739. profileStr += \
  740. '<p><b>' + translate['Type'] + ':</b> ' + item['itemType'] + ' '
  741. profileStr += \
  742. '<b>' + translate['Category'] + ':</b> ' + item['category'] + ' '
  743. profileStr += \
  744. '<b>' + translate['Location'] + ':</b> ' + item['location'] + '</p>\n'
  745. sharedesc = item['displayName']
  746. if '<' not in sharedesc and '?' not in sharedesc:
  747. if showContact:
  748. contactActor = item['actor']
  749. profileStr += \
  750. '<p><a href="' + actor + \
  751. '?replydm=sharedesc:' + sharedesc + \
  752. '?mention=' + contactActor + '"><button class="button">' + \
  753. translate['Contact'] + '</button></a>\n'
  754. if removeButton:
  755. profileStr += \
  756. ' <a href="' + actor + '?rmshare=' + sharedesc + \
  757. '"><button class="button">' + \
  758. translate['Remove'] + '</button></a>\n'
  759. profileStr += '</div>\n'
  760. return profileStr
  761. def _htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
  762. baseDir: str, actor: str,
  763. nickname: str, domain: str, port: int,
  764. maxSharesPerAccount: int, httpPrefix: str) -> str:
  765. """Show shared items timeline as html
  766. """
  767. sharesJson, lastPage = \
  768. sharesTimelineJson(actor, pageNumber, itemsPerPage,
  769. baseDir, maxSharesPerAccount)
  770. domainFull = getFullDomain(domain, port)
  771. actor = httpPrefix + '://' + domainFull + '/users/' + nickname
  772. timelineStr = ''
  773. if pageNumber > 1:
  774. timelineStr += \
  775. ' <center>\n' + \
  776. ' <a href="' + actor + '/tlshares?page=' + \
  777. str(pageNumber - 1) + \
  778. '"><img loading="lazy" class="pageicon" src="/' + \
  779. 'icons/pageup.png" title="' + translate['Page up'] + \
  780. '" alt="' + translate['Page up'] + '"></a>\n' + \
  781. ' </center>\n'
  782. separatorStr = htmlPostSeparator(baseDir, None)
  783. ctr = 0
  784. for published, item in sharesJson.items():
  785. showContactButton = False
  786. if item['actor'] != actor:
  787. showContactButton = True
  788. showRemoveButton = False
  789. if item['actor'] == actor:
  790. showRemoveButton = True
  791. timelineStr += \
  792. htmlIndividualShare(actor, item, translate,
  793. showContactButton, showRemoveButton)
  794. timelineStr += separatorStr
  795. ctr += 1
  796. if ctr == 0:
  797. timelineStr += _getHelpForTimeline(baseDir, 'tlshares')
  798. if not lastPage:
  799. timelineStr += \
  800. ' <center>\n' + \
  801. ' <a href="' + actor + '/tlshares?page=' + \
  802. str(pageNumber + 1) + \
  803. '"><img loading="lazy" class="pageicon" src="/' + \
  804. 'icons/pagedown.png" title="' + translate['Page down'] + \
  805. '" alt="' + translate['Page down'] + '"></a>\n' + \
  806. ' </center>\n'
  807. return timelineStr
  808. def htmlShares(cssCache: {}, defaultTimeline: str,
  809. recentPostsCache: {}, maxRecentPosts: int,
  810. translate: {}, pageNumber: int, itemsPerPage: int,
  811. session, baseDir: str,
  812. cachedWebfingers: {}, personCache: {},
  813. nickname: str, domain: str, port: int,
  814. allowDeletion: bool,
  815. httpPrefix: str, projectVersion: str,
  816. YTReplacementDomain: str,
  817. showPublishedDateOnly: bool,
  818. newswire: {}, positiveVoting: bool,
  819. showPublishAsIcon: bool,
  820. fullWidthTimelineButtonHeader: bool,
  821. iconsAsButtons: bool,
  822. rssIconAtTop: bool,
  823. publishButtonAtTop: bool,
  824. authorized: bool, theme: str,
  825. peertubeInstances: [],
  826. allowLocalNetworkAccess: bool,
  827. textModeBanner: str,
  828. accessKeys: {}) -> str:
  829. """Show the shares timeline as html
  830. """
  831. manuallyApproveFollowers = \
  832. followerApprovalActive(baseDir, nickname, domain)
  833. return htmlTimeline(cssCache, defaultTimeline,
  834. recentPostsCache, maxRecentPosts,
  835. translate, pageNumber,
  836. itemsPerPage, session, baseDir,
  837. cachedWebfingers, personCache,
  838. nickname, domain, port, None,
  839. 'tlshares', allowDeletion,
  840. httpPrefix, projectVersion, manuallyApproveFollowers,
  841. False, YTReplacementDomain,
  842. showPublishedDateOnly,
  843. newswire, False, False,
  844. positiveVoting, showPublishAsIcon,
  845. fullWidthTimelineButtonHeader,
  846. iconsAsButtons, rssIconAtTop, publishButtonAtTop,
  847. authorized, None, theme, peertubeInstances,
  848. allowLocalNetworkAccess, textModeBanner,
  849. accessKeys)
  850. def htmlInbox(cssCache: {}, defaultTimeline: str,
  851. recentPostsCache: {}, maxRecentPosts: int,
  852. translate: {}, pageNumber: int, itemsPerPage: int,
  853. session, baseDir: str,
  854. cachedWebfingers: {}, personCache: {},
  855. nickname: str, domain: str, port: int, inboxJson: {},
  856. allowDeletion: bool,
  857. httpPrefix: str, projectVersion: str,
  858. minimal: bool, YTReplacementDomain: str,
  859. showPublishedDateOnly: bool,
  860. newswire: {}, positiveVoting: bool,
  861. showPublishAsIcon: bool,
  862. fullWidthTimelineButtonHeader: bool,
  863. iconsAsButtons: bool,
  864. rssIconAtTop: bool,
  865. publishButtonAtTop: bool,
  866. authorized: bool, theme: str,
  867. peertubeInstances: [],
  868. allowLocalNetworkAccess: bool,
  869. textModeBanner: str,
  870. accessKeys: {}) -> str:
  871. """Show the inbox as html
  872. """
  873. manuallyApproveFollowers = \
  874. followerApprovalActive(baseDir, nickname, domain)
  875. return htmlTimeline(cssCache, defaultTimeline,
  876. recentPostsCache, maxRecentPosts,
  877. translate, pageNumber,
  878. itemsPerPage, session, baseDir,
  879. cachedWebfingers, personCache,
  880. nickname, domain, port, inboxJson,
  881. 'inbox', allowDeletion,
  882. httpPrefix, projectVersion, manuallyApproveFollowers,
  883. minimal, YTReplacementDomain,
  884. showPublishedDateOnly,
  885. newswire, False, False,
  886. positiveVoting, showPublishAsIcon,
  887. fullWidthTimelineButtonHeader,
  888. iconsAsButtons, rssIconAtTop, publishButtonAtTop,
  889. authorized, None, theme, peertubeInstances,
  890. allowLocalNetworkAccess, textModeBanner,
  891. accessKeys)
  892. def htmlBookmarks(cssCache: {}, defaultTimeline: str,
  893. recentPostsCache: {}, maxRecentPosts: int,
  894. translate: {}, pageNumber: int, itemsPerPage: int,
  895. session, baseDir: str,
  896. cachedWebfingers: {}, personCache: {},
  897. nickname: str, domain: str, port: int, bookmarksJson: {},
  898. allowDeletion: bool,
  899. httpPrefix: str, projectVersion: str,
  900. minimal: bool, YTReplacementDomain: str,
  901. showPublishedDateOnly: bool,
  902. newswire: {}, positiveVoting: bool,
  903. showPublishAsIcon: bool,
  904. fullWidthTimelineButtonHeader: bool,
  905. iconsAsButtons: bool,
  906. rssIconAtTop: bool,
  907. publishButtonAtTop: bool,
  908. authorized: bool, theme: str,
  909. peertubeInstances: [],
  910. allowLocalNetworkAccess: bool,
  911. textModeBanner: str,
  912. accessKeys: {}) -> str:
  913. """Show the bookmarks as html
  914. """
  915. manuallyApproveFollowers = \
  916. followerApprovalActive(baseDir, nickname, domain)
  917. return htmlTimeline(cssCache, defaultTimeline,
  918. recentPostsCache, maxRecentPosts,
  919. translate, pageNumber,
  920. itemsPerPage, session, baseDir,
  921. cachedWebfingers, personCache,
  922. nickname, domain, port, bookmarksJson,
  923. 'tlbookmarks', allowDeletion,
  924. httpPrefix, projectVersion, manuallyApproveFollowers,
  925. minimal, YTReplacementDomain,
  926. showPublishedDateOnly,
  927. newswire, False, False,
  928. positiveVoting, showPublishAsIcon,
  929. fullWidthTimelineButtonHeader,
  930. iconsAsButtons, rssIconAtTop, publishButtonAtTop,
  931. authorized, None, theme, peertubeInstances,
  932. allowLocalNetworkAccess, textModeBanner,
  933. accessKeys)
  934. def htmlEvents(cssCache: {}, defaultTimeline: str,
  935. recentPostsCache: {}, maxRecentPosts: int,
  936. translate: {}, pageNumber: int, itemsPerPage: int,
  937. session, baseDir: str,
  938. cachedWebfingers: {}, personCache: {},
  939. nickname: str, domain: str, port: int, bookmarksJson: {},
  940. allowDeletion: bool,
  941. httpPrefix: str, projectVersion: str,
  942. minimal: bool, YTReplacementDomain: str,
  943. showPublishedDateOnly: bool,
  944. newswire: {}, positiveVoting: bool,
  945. showPublishAsIcon: bool,
  946. fullWidthTimelineButtonHeader: bool,
  947. iconsAsButtons: bool,
  948. rssIconAtTop: bool,
  949. publishButtonAtTop: bool,
  950. authorized: bool, theme: str,
  951. peertubeInstances: [],
  952. allowLocalNetworkAccess: bool,
  953. textModeBanner: str,
  954. accessKeys: {}) -> str:
  955. """Show the events as html
  956. """
  957. manuallyApproveFollowers = \
  958. followerApprovalActive(baseDir, nickname, domain)
  959. return htmlTimeline(cssCache, defaultTimeline,
  960. recentPostsCache, maxRecentPosts,
  961. translate, pageNumber,
  962. itemsPerPage, session, baseDir,
  963. cachedWebfingers, personCache,
  964. nickname, domain, port, bookmarksJson,
  965. 'tlevents', allowDeletion,
  966. httpPrefix, projectVersion, manuallyApproveFollowers,
  967. minimal, YTReplacementDomain,
  968. showPublishedDateOnly,
  969. newswire, False, False,
  970. positiveVoting, showPublishAsIcon,
  971. fullWidthTimelineButtonHeader,
  972. iconsAsButtons, rssIconAtTop, publishButtonAtTop,
  973. authorized, None, theme, peertubeInstances,
  974. allowLocalNetworkAccess, textModeBanner,
  975. accessKeys)
  976. def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
  977. recentPostsCache: {}, maxRecentPosts: int,
  978. translate: {}, pageNumber: int, itemsPerPage: int,
  979. session, baseDir: str,
  980. cachedWebfingers: {}, personCache: {},
  981. nickname: str, domain: str, port: int, inboxJson: {},
  982. allowDeletion: bool,
  983. httpPrefix: str, projectVersion: str,
  984. minimal: bool, YTReplacementDomain: str,
  985. showPublishedDateOnly: bool,
  986. newswire: {}, positiveVoting: bool,
  987. showPublishAsIcon: bool,
  988. fullWidthTimelineButtonHeader: bool,
  989. iconsAsButtons: bool,
  990. rssIconAtTop: bool,
  991. publishButtonAtTop: bool,
  992. authorized: bool, theme: str,
  993. peertubeInstances: [],
  994. allowLocalNetworkAccess: bool,
  995. textModeBanner: str,
  996. accessKeys: {}) -> str:
  997. """Show the DM timeline as html
  998. """
  999. return htmlTimeline(cssCache, defaultTimeline,
  1000. recentPostsCache, maxRecentPosts,
  1001. translate, pageNumber,
  1002. itemsPerPage, session, baseDir,
  1003. cachedWebfingers, personCache,
  1004. nickname, domain, port, inboxJson, 'dm', allowDeletion,
  1005. httpPrefix, projectVersion, False, minimal,
  1006. YTReplacementDomain, showPublishedDateOnly,
  1007. newswire, False, False, positiveVoting,
  1008. showPublishAsIcon,
  1009. fullWidthTimelineButtonHeader,
  1010. iconsAsButtons, rssIconAtTop, publishButtonAtTop,
  1011. authorized, None, theme, peertubeInstances,
  1012. allowLocalNetworkAccess, textModeBanner,
  1013. accessKeys)
  1014. def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
  1015. recentPostsCache: {}, maxRecentPosts: int,
  1016. translate: {}, pageNumber: int, itemsPerPage: int,
  1017. session, baseDir: str,
  1018. cachedWebfingers: {}, personCache: {},
  1019. nickname: str, domain: str, port: int, inboxJson: {},
  1020. allowDeletion: bool,
  1021. httpPrefix: str, projectVersion: str,
  1022. minimal: bool, YTReplacementDomain: str,
  1023. showPublishedDateOnly: bool,
  1024. newswire: {}, positiveVoting: bool,
  1025. showPublishAsIcon: bool,
  1026. fullWidthTimelineButtonHeader: bool,
  1027. iconsAsButtons: bool,
  1028. rssIconAtTop: bool,
  1029. publishButtonAtTop: bool,
  1030. authorized: bool, theme: str,
  1031. peertubeInstances: [],
  1032. allowLocalNetworkAccess: bool,
  1033. textModeBanner: str,
  1034. accessKeys: {}) -> str:
  1035. """Show the replies timeline as html
  1036. """
  1037. return htmlTimeline(cssCache, defaultTimeline,
  1038. recentPostsCache, maxRecentPosts,
  1039. translate, pageNumber,
  1040. itemsPerPage, session, baseDir,
  1041. cachedWebfingers, personCache,
  1042. nickname, domain, port, inboxJson, 'tlreplies',
  1043. allowDeletion, httpPrefix, projectVersion, False,
  1044. minimal, YTReplacementDomain,
  1045. showPublishedDateOnly,
  1046. newswire, False, False,
  1047. positiveVoting, showPublishAsIcon,
  1048. fullWidthTimelineButtonHeader,
  1049. iconsAsButtons, rssIconAtTop, publishButtonAtTop,
  1050. authorized, None, theme, peertubeInstances,
  1051. allowLocalNetworkAccess, textModeBanner,
  1052. accessKeys)
  1053. def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
  1054. recentPostsCache: {}, maxRecentPosts: int,
  1055. translate: {}, pageNumber: int, itemsPerPage: int,
  1056. session, baseDir: str,
  1057. cachedWebfingers: {}, personCache: {},
  1058. nickname: str, domain: str, port: int, inboxJson: {},
  1059. allowDeletion: bool,
  1060. httpPrefix: str, projectVersion: str,
  1061. minimal: bool, YTReplacementDomain: str,
  1062. showPublishedDateOnly: bool,
  1063. newswire: {}, positiveVoting: bool,
  1064. showPublishAsIcon: bool,
  1065. fullWidthTimelineButtonHeader: bool,
  1066. iconsAsButtons: bool,
  1067. rssIconAtTop: bool,
  1068. publishButtonAtTop: bool,
  1069. authorized: bool, theme: str,
  1070. peertubeInstances: [],
  1071. allowLocalNetworkAccess: bool,
  1072. textModeBanner: str,
  1073. accessKeys: {}) -> str:
  1074. """Show the media timeline as html
  1075. """
  1076. return htmlTimeline(cssCache, defaultTimeline,
  1077. recentPostsCache, maxRecentPosts,
  1078. translate, pageNumber,
  1079. itemsPerPage, session, baseDir,
  1080. cachedWebfingers, personCache,
  1081. nickname, domain, port, inboxJson, 'tlmedia',
  1082. allowDeletion, httpPrefix, projectVersion, False,
  1083. minimal, YTReplacementDomain,
  1084. showPublishedDateOnly,
  1085. newswire, False, False,
  1086. positiveVoting, showPublishAsIcon,
  1087. fullWidthTimelineButtonHeader,
  1088. iconsAsButtons, rssIconAtTop, publishButtonAtTop,
  1089. authorized, None, theme, peertubeInstances,
  1090. allowLocalNetworkAccess, textModeBanner,
  1091. accessKeys)
  1092. def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
  1093. recentPostsCache: {}, maxRecentPosts: int,
  1094. translate: {}, pageNumber: int, itemsPerPage: int,
  1095. session, baseDir: str,
  1096. cachedWebfingers: {}, personCache: {},
  1097. nickname: str, domain: str, port: int, inboxJson: {},
  1098. allowDeletion: bool,
  1099. httpPrefix: str, projectVersion: str,
  1100. minimal: bool, YTReplacementDomain: str,
  1101. showPublishedDateOnly: bool,
  1102. newswire: {}, positiveVoting: bool,
  1103. showPublishAsIcon: bool,
  1104. fullWidthTimelineButtonHeader: bool,
  1105. iconsAsButtons: bool,
  1106. rssIconAtTop: bool,
  1107. publishButtonAtTop: bool,
  1108. authorized: bool, theme: str,
  1109. peertubeInstances: [],
  1110. allowLocalNetworkAccess: bool,
  1111. textModeBanner: str,
  1112. accessKeys: {}) -> str:
  1113. """Show the blogs timeline as html
  1114. """
  1115. return htmlTimeline(cssCache, defaultTimeline,
  1116. recentPostsCache, maxRecentPosts,
  1117. translate, pageNumber,
  1118. itemsPerPage, session, baseDir,
  1119. cachedWebfingers, personCache,
  1120. nickname, domain, port, inboxJson, 'tlblogs',
  1121. allowDeletion, httpPrefix, projectVersion, False,
  1122. minimal, YTReplacementDomain,
  1123. showPublishedDateOnly,
  1124. newswire, False, False,
  1125. positiveVoting, showPublishAsIcon,
  1126. fullWidthTimelineButtonHeader,
  1127. iconsAsButtons, rssIconAtTop, publishButtonAtTop,
  1128. authorized, None, theme, peertubeInstances,
  1129. allowLocalNetworkAccess, textModeBanner,
  1130. accessKeys)
  1131. def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
  1132. recentPostsCache: {}, maxRecentPosts: int,
  1133. translate: {}, pageNumber: int, itemsPerPage: int,
  1134. session, baseDir: str,
  1135. cachedWebfingers: {}, personCache: {},
  1136. nickname: str, domain: str, port: int, inboxJson: {},
  1137. allowDeletion: bool,
  1138. httpPrefix: str, projectVersion: str,
  1139. minimal: bool, YTReplacementDomain: str,
  1140. showPublishedDateOnly: bool,
  1141. newswire: {}, positiveVoting: bool,
  1142. showPublishAsIcon: bool,
  1143. fullWidthTimelineButtonHeader: bool,
  1144. iconsAsButtons: bool,
  1145. rssIconAtTop: bool,
  1146. publishButtonAtTop: bool,
  1147. authorized: bool,
  1148. theme: str,
  1149. peertubeInstances: [],
  1150. allowLocalNetworkAccess: bool,
  1151. textModeBanner: str,
  1152. accessKeys: {}) -> str:
  1153. """Show the features timeline as html
  1154. """
  1155. return htmlTimeline(cssCache, defaultTimeline,
  1156. recentPostsCache, maxRecentPosts,
  1157. translate, pageNumber,
  1158. itemsPerPage, session, baseDir,
  1159. cachedWebfingers, personCache,
  1160. nickname, domain, port, inboxJson, 'tlfeatures',
  1161. allowDeletion, httpPrefix, projectVersion, False,
  1162. minimal, YTReplacementDomain,
  1163. showPublishedDateOnly,
  1164. newswire, False, False,
  1165. positiveVoting, showPublishAsIcon,
  1166. fullWidthTimelineButtonHeader,
  1167. iconsAsButtons, rssIconAtTop, publishButtonAtTop,
  1168. authorized, None, theme, peertubeInstances,
  1169. allowLocalNetworkAccess, textModeBanner,
  1170. accessKeys)
  1171. def htmlInboxNews(cssCache: {}, defaultTimeline: str,
  1172. recentPostsCache: {}, maxRecentPosts: int,
  1173. translate: {}, pageNumber: int, itemsPerPage: int,
  1174. session, baseDir: str,
  1175. cachedWebfingers: {}, personCache: {},
  1176. nickname: str, domain: str, port: int, inboxJson: {},
  1177. allowDeletion: bool,
  1178. httpPrefix: str, projectVersion: str,
  1179. minimal: bool, YTReplacementDomain: str,
  1180. showPublishedDateOnly: bool,
  1181. newswire: {}, moderator: bool, editor: bool,
  1182. positiveVoting: bool, showPublishAsIcon: bool,
  1183. fullWidthTimelineButtonHeader: bool,
  1184. iconsAsButtons: bool,
  1185. rssIconAtTop: bool,
  1186. publishButtonAtTop: bool,
  1187. authorized: bool, theme: str,
  1188. peertubeInstances: [],
  1189. allowLocalNetworkAccess: bool,
  1190. textModeBanner: str,
  1191. accessKeys: {}) -> str:
  1192. """Show the news timeline as html
  1193. """
  1194. return htmlTimeline(cssCache, defaultTimeline,
  1195. recentPostsCache, maxRecentPosts,
  1196. translate, pageNumber,
  1197. itemsPerPage, session, baseDir,
  1198. cachedWebfingers, personCache,
  1199. nickname, domain, port, inboxJson, 'tlnews',
  1200. allowDeletion, httpPrefix, projectVersion, False,
  1201. minimal, YTReplacementDomain,
  1202. showPublishedDateOnly,
  1203. newswire, moderator, editor,
  1204. positiveVoting, showPublishAsIcon,
  1205. fullWidthTimelineButtonHeader,
  1206. iconsAsButtons, rssIconAtTop, publishButtonAtTop,
  1207. authorized, None, theme, peertubeInstances,
  1208. allowLocalNetworkAccess, textModeBanner,
  1209. accessKeys)
  1210. def htmlOutbox(cssCache: {}, defaultTimeline: str,
  1211. recentPostsCache: {}, maxRecentPosts: int,
  1212. translate: {}, pageNumber: int, itemsPerPage: int,
  1213. session, baseDir: str,
  1214. cachedWebfingers: {}, personCache: {},
  1215. nickname: str, domain: str, port: int, outboxJson: {},
  1216. allowDeletion: bool,
  1217. httpPrefix: str, projectVersion: str,
  1218. minimal: bool, YTReplacementDomain: str,
  1219. showPublishedDateOnly: bool,
  1220. newswire: {}, positiveVoting: bool,
  1221. showPublishAsIcon: bool,
  1222. fullWidthTimelineButtonHeader: bool,
  1223. iconsAsButtons: bool,
  1224. rssIconAtTop: bool,
  1225. publishButtonAtTop: bool,
  1226. authorized: bool, theme: str,
  1227. peertubeInstances: [],
  1228. allowLocalNetworkAccess: bool,
  1229. textModeBanner: str,
  1230. accessKeys: {}) -> str:
  1231. """Show the Outbox as html
  1232. """
  1233. manuallyApproveFollowers = \
  1234. followerApprovalActive(baseDir, nickname, domain)
  1235. return htmlTimeline(cssCache, defaultTimeline,
  1236. recentPostsCache, maxRecentPosts,
  1237. translate, pageNumber,
  1238. itemsPerPage, session, baseDir,
  1239. cachedWebfingers, personCache,
  1240. nickname, domain, port, outboxJson, 'outbox',
  1241. allowDeletion, httpPrefix, projectVersion,
  1242. manuallyApproveFollowers, minimal,
  1243. YTReplacementDomain, showPublishedDateOnly,
  1244. newswire, False, False, positiveVoting,
  1245. showPublishAsIcon, fullWidthTimelineButtonHeader,
  1246. iconsAsButtons, rssIconAtTop, publishButtonAtTop,
  1247. authorized, None, theme, peertubeInstances,
  1248. allowLocalNetworkAccess, textModeBanner,
  1249. accessKeys)