__filename__ = "webapp_column_right.py" __author__ = "Bob Mottram" __license__ = "AGPL3+" __version__ = "1.2.0" __maintainer__ = "Bob Mottram" __email__ = "bob@freedombone.net" __status__ = "Production" import os from datetime import datetime from content import removeLongWords from utils import removeHtml from utils import locatePost from utils import loadJson from utils import votesOnNewswireItem from utils import getNicknameFromActor from utils import isEditor from utils import getConfigParam from posts import isModerator from webapp_utils import getRightImageFile from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlFooter from webapp_utils import getBannerFile from webapp_utils import htmlPostSeparator from webapp_utils import headerButtonsFrontScreen def _votesIndicator(totalVotes: int, positiveVoting: bool) -> str: """Returns an indicator of the number of votes on a newswire item """ if totalVotes <= 0: return '' totalVotesStr = ' ' for v in range(totalVotes): if positiveVoting: totalVotesStr += '✓' else: totalVotesStr += '✗' return totalVotesStr def getRightColumnContent(baseDir: str, nickname: str, domainFull: str, httpPrefix: str, translate: {}, moderator: bool, editor: bool, newswire: {}, positiveVoting: bool, showBackButton: bool, timelinePath: str, showPublishButton: bool, showPublishAsIcon: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, showHeaderImage: bool, theme: str, defaultTimeline: str) -> str: """Returns html content for the right column """ htmlStr = '' domain = domainFull if ':' in domain: domain = domain.split(':') if authorized: # only show the publish button if logged in, otherwise replace it with # a login button titleStr = translate['Publish a blog article'] if defaultTimeline == 'tlfeatures': titleStr = translate['Publish a news article'] publishButtonStr = \ ' ' + \ '\n' else: # if not logged in then replace the publish button with # a login button publishButtonStr = \ ' \n' # show publish button at the top if needed if publishButtonAtTop: htmlStr += '
' + publishButtonStr + '
' # show a column header image, eg. title of the theme or newswire banner editImageClass = '' if showHeaderImage: rightImageFile, rightColumnImageFilename = \ getRightImageFile(baseDir, nickname, domain, theme) # show the image at the top of the column editImageClass = 'rightColEdit' if os.path.isfile(rightColumnImageFilename): editImageClass = 'rightColEditImage' htmlStr += \ '\n
\n' + \ ' \n' + \ '
\n' if (showPublishButton or editor or rssIconAtTop) and not showHeaderImage: htmlStr += '
' if editImageClass == 'rightColEdit': htmlStr += '\n
\n' # whether to show a back icon # This is probably going to be osolete soon if showBackButton: htmlStr += \ ' ' + \ '\n' if showPublishButton and not publishButtonAtTop: if not showPublishAsIcon: htmlStr += publishButtonStr # show the edit icon if editor: if os.path.isfile(baseDir + '/accounts/newswiremoderation.txt'): # show the edit icon highlighted htmlStr += \ ' ' + \ '' + \
                translate['Edit newswire'] + ' | \n' else: # show the edit icon htmlStr += \ ' ' + \ '' + \
                translate['Edit newswire'] + ' | \n' # show the RSS icons rssIconStr = \ ' ' + \ '' + \
        translate['Hashtag Categories RSS Feed'] + ' | \n' rssIconStr += \ ' ' + \ '' + \
        translate['Newswire RSS Feed'] + ' | \n' if rssIconAtTop: htmlStr += rssIconStr # show publish icon at top if showPublishButton: if showPublishAsIcon: titleStr = translate['Publish a blog article'] if defaultTimeline == 'tlfeatures': titleStr = translate['Publish a news article'] htmlStr += \ ' ' + \ '' + \
                titleStr + '\n' if editImageClass == 'rightColEdit': htmlStr += '
\n' else: if showHeaderImage: htmlStr += '
\n' if (showPublishButton or editor or rssIconAtTop) and not showHeaderImage: htmlStr += '

' # show the newswire lines newswireContentStr = \ _htmlNewswire(baseDir, newswire, nickname, moderator, translate, positiveVoting) htmlStr += newswireContentStr # show the rss icon at the bottom, typically on the right hand side if newswireContentStr and not rssIconAtTop: htmlStr += '
' + rssIconStr + '
' return htmlStr def _getBrokenFavSubstitute() -> str: """Substitute link used if a favicon is not available """ return " onerror=\"this.onerror=null; this.src='/newswire_favicon.ico'\"" def _getNewswireFavicon(url: str) -> str: """Returns a favicon url from the given article link """ if '://' not in url: return '/newswire_favicon.ico' if url.startswith('http://'): if not (url.endswith('.onion') or url.endswith('.i2p')): return '/newswire_favicon.ico' domain = url.split('://')[1] if '/' not in domain: return url + '/favicon.ico' else: domain = domain.split('/')[0] return url.split('://')[0] + '://' + domain + '/favicon.ico' def _htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool, translate: {}, positiveVoting: bool) -> str: """Converts a newswire dict into html """ separatorStr = htmlPostSeparator(baseDir, 'right') htmlStr = '' for dateStr, item in newswire.items(): item[0] = removeHtml(item[0]).strip() if not item[0]: continue # remove any CDATA if 'CDATA[' in item[0]: item[0] = item[0].split('CDATA[')[1] if ']' in item[0]: item[0] = item[0].split(']')[0] try: publishedDate = \ datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S%z") except BaseException: print('WARN: bad date format ' + dateStr) continue dateShown = publishedDate.strftime("%Y-%m-%d %H:%M") dateStrLink = dateStr.replace('T', ' ') dateStrLink = dateStrLink.replace('Z', '') url = item[1] faviconUrl = _getNewswireFavicon(url) faviconLink = '' if faviconUrl: faviconLink = \ '' moderatedItem = item[5] htmlStr += separatorStr if moderatedItem and 'vote:' + nickname in item[2]: totalVotesStr = '' totalVotes = 0 if moderator: totalVotes = votesOnNewswireItem(item[2]) totalVotesStr = \ _votesIndicator(totalVotes, positiveVoting) title = removeLongWords(item[0], 16, []).replace('\n', '
') htmlStr += '

' + \ '' + \ '' + \ faviconLink + title + \ '' + totalVotesStr if moderator: htmlStr += \ ' ' + dateShown + '' htmlStr += '

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

\n' else: totalVotesStr = '' totalVotes = 0 if moderator: if moderatedItem: totalVotes = votesOnNewswireItem(item[2]) # show a number of ticks or crosses for how many # votes for or against totalVotesStr = \ _votesIndicator(totalVotes, positiveVoting) title = removeLongWords(item[0], 16, []).replace('\n', '
') if moderator and moderatedItem: htmlStr += '

' + \ '' + \ faviconLink + title + '' + totalVotesStr htmlStr += ' ' + dateShown htmlStr += '' htmlStr += '' htmlStr += '

\n' else: htmlStr += '

' + \ '' + \ faviconLink + title + '' + \ totalVotesStr htmlStr += ' ' htmlStr += dateShown + '

\n' if htmlStr: htmlStr = '\n' return htmlStr def htmlCitations(baseDir: str, nickname: str, domain: str, httpPrefix: str, defaultTimeline: str, translate: {}, newswire: {}, cssCache: {}, blogTitle: str, blogContent: str, blogImageFilename: str, blogImageAttachmentMediaType: str, blogImageDescription: str, theme: str) -> str: """Show the citations screen when creating a blog """ htmlStr = '' # create a list of dates for citations # these can then be used to re-select checkboxes later citationsFilename = \ baseDir + '/accounts/' + \ nickname + '@' + domain + '/.citations.txt' citationsSelected = [] if os.path.isfile(citationsFilename): citationsSeparator = '#####' with open(citationsFilename, "r") as f: citations = f.readlines() for line in citations: if citationsSeparator not in line: continue sections = line.strip().split(citationsSeparator) if len(sections) != 3: continue dateStr = sections[0] citationsSelected.append(dateStr) # the css filename cssFilename = baseDir + '/epicyon-profile.css' if os.path.isfile(baseDir + '/epicyon.css'): cssFilename = baseDir + '/epicyon.css' instanceTitle = \ getConfigParam(baseDir, 'instanceTitle') htmlStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) # top banner bannerFile, bannerFilename = \ getBannerFile(baseDir, nickname, domain, theme) htmlStr += \ '\n' htmlStr += '\n' htmlStr += \ '
\n' htmlStr += '
\n' htmlStr += translate['Choose newswire items ' + 'referenced in your article'] + '
' if blogTitle is None: blogTitle = '' htmlStr += \ ' \n' if blogContent is None: blogContent = '' htmlStr += \ ' \n' # submit button htmlStr += \ ' \n' htmlStr += '
\n' citationsSeparator = '#####' # list of newswire items if newswire: ctr = 0 for dateStr, item in newswire.items(): item[0] = removeHtml(item[0]).strip() if not item[0]: continue # remove any CDATA if 'CDATA[' in item[0]: item[0] = item[0].split('CDATA[')[1] if ']' in item[0]: item[0] = item[0].split(']')[0] # should this checkbox be selected? selectedStr = '' if dateStr in citationsSelected: selectedStr = ' checked' publishedDate = \ datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S%z") dateShown = publishedDate.strftime("%Y-%m-%d %H:%M") title = removeLongWords(item[0], 16, []).replace('\n', '
') link = item[1] citationValue = \ dateStr + citationsSeparator + \ title + citationsSeparator + \ link htmlStr += \ '' + \ '' + title + ' ' htmlStr += '' + \ dateShown + '
\n' ctr += 1 htmlStr += '
\n' return htmlStr + htmlFooter() def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str, domain: str, domainFull: str, httpPrefix: str, translate: {}, newswire: {}, positiveVoting: bool, timelinePath: str, showPublishAsIcon: bool, authorized: bool, rssIconAtTop: bool, iconsAsButtons: bool, defaultTimeline: str, theme: str) -> str: """Shows the mobile version of the newswire right column """ htmlStr = '' # the css filename cssFilename = baseDir + '/epicyon-profile.css' if os.path.isfile(baseDir + '/epicyon.css'): cssFilename = baseDir + '/epicyon.css' if nickname == 'news': editor = False moderator = False else: # is the user a moderator? moderator = isModerator(baseDir, nickname) # is the user a site editor? editor = isEditor(baseDir, nickname) showPublishButton = editor instanceTitle = \ getConfigParam(baseDir, 'instanceTitle') htmlStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) bannerFile, bannerFilename = \ getBannerFile(baseDir, nickname, domain, theme) htmlStr += \ '' + \ '\n' htmlStr += '
\n' htmlStr += '
' + \ headerButtonsFrontScreen(translate, nickname, 'newswire', authorized, iconsAsButtons) + '
' if newswire: htmlStr += \ getRightColumnContent(baseDir, nickname, domainFull, httpPrefix, translate, moderator, editor, newswire, positiveVoting, False, timelinePath, showPublishButton, showPublishAsIcon, rssIconAtTop, False, authorized, False, theme, defaultTimeline) else: if editor: htmlStr += '


\n' htmlStr += '
\n ' htmlStr += translate['Select the edit icon to add RSS feeds'] htmlStr += '\n
\n' # end of col-right-mobile htmlStr += '' htmlStr += htmlFooter() return htmlStr def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str, domain: str, port: int, httpPrefix: str, defaultTimeline: str, theme: str) -> str: """Shows the edit newswire screen """ if '/users/' not in path: return '' path = path.replace('/inbox', '').replace('/outbox', '') path = path.replace('/shares', '') nickname = getNicknameFromActor(path) if not nickname: return '' # is the user a moderator? if not isModerator(baseDir, nickname): return '' cssFilename = baseDir + '/epicyon-links.css' if os.path.isfile(baseDir + '/links.css'): cssFilename = baseDir + '/links.css' # filename of the banner shown at the top bannerFile, bannerFilename = \ getBannerFile(baseDir, nickname, domain, theme) instanceTitle = \ getConfigParam(baseDir, 'instanceTitle') editNewswireForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) # top banner editNewswireForm += \ '
' + \ '\n' editNewswireForm += '\n
' editNewswireForm += \ '
\n' editNewswireForm += \ '
\n' editNewswireForm += \ '

' + translate['Edit newswire'] + '

' editNewswireForm += \ '
\n' editNewswireForm += \ ' \n' editNewswireForm += \ '
\n' newswireFilename = baseDir + '/accounts/newswire.txt' newswireStr = '' if os.path.isfile(newswireFilename): with open(newswireFilename, 'r') as fp: newswireStr = fp.read() editNewswireForm += \ '
' editNewswireForm += \ ' ' + \ translate['Add RSS feed links below.'] + \ '
' editNewswireForm += \ ' ' filterStr = '' filterFilename = \ baseDir + '/accounts/news@' + domain + '/filters.txt' if os.path.isfile(filterFilename): with open(filterFilename, 'r') as filterfile: filterStr = filterfile.read() editNewswireForm += \ '
\n' editNewswireForm += '
' editNewswireForm += ' \n' hashtagRulesStr = '' hashtagRulesFilename = \ baseDir + '/accounts/hashtagrules.txt' if os.path.isfile(hashtagRulesFilename): with open(hashtagRulesFilename, 'r') as rulesfile: hashtagRulesStr = rulesfile.read() editNewswireForm += \ '
\n' editNewswireForm += '
\n' editNewswireForm += \ ' ' + translate['See instructions'] + '\n' editNewswireForm += ' \n' editNewswireForm += \ '
' editNewswireForm += htmlFooter() return editNewswireForm def htmlEditNewsPost(cssCache: {}, translate: {}, baseDir: str, path: str, domain: str, port: int, httpPrefix: str, postUrl: str) -> str: """Edits a news post on the news/features timeline """ if '/users/' not in path: return '' pathOriginal = path nickname = getNicknameFromActor(path) if not nickname: return '' # is the user an editor? if not isEditor(baseDir, nickname): return '' postUrl = postUrl.replace('/', '#') postFilename = locatePost(baseDir, nickname, domain, postUrl) if not postFilename: return '' postJsonObject = loadJson(postFilename) if not postJsonObject: return '' cssFilename = baseDir + '/epicyon-links.css' if os.path.isfile(baseDir + '/links.css'): cssFilename = baseDir + '/links.css' instanceTitle = \ getConfigParam(baseDir, 'instanceTitle') editNewsPostForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) editNewsPostForm += \ '\n' editNewsPostForm += \ '
\n' editNewsPostForm += \ '

' + translate['Edit News Post'] + '

' editNewsPostForm += \ '
\n' editNewsPostForm += \ ' ' + \ '\n' editNewsPostForm += \ ' \n' editNewsPostForm += \ '
\n' editNewsPostForm += \ '
' editNewsPostForm += \ ' \n' newsPostTitle = postJsonObject['object']['summary'] editNewsPostForm += \ '
\n' newsPostContent = postJsonObject['object']['content'] editNewsPostForm += \ ' ' editNewsPostForm += \ '
' editNewsPostForm += htmlFooter() return editNewsPostForm