__filename__ = "webapp_column_right.py" __author__ = "Bob Mottram" __license__ = "AGPL3+" __version__ = "1.2.0" __maintainer__ = "Bob Mottram" __email__ = "bob@libreserver.org" __status__ = "Production" __module_group__ = "Web Interface Columns" import os from datetime import datetime from content import removeLongWords from content import limitRepeatedWords from utils import getFavFilenameFromUrl from utils import getBaseContentFromPost 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 utils import removeDomainPort from utils import acctDir from posts import isModerator from newswire import getNewswireFaviconUrl 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 from webapp_utils import editTextField def _votesIndicator(totalVotes: int, positive_voting: 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 positive_voting: totalVotesStr += '✓' else: totalVotesStr += '✗' return totalVotesStr def getRightColumnContent(base_dir: str, nickname: str, domainFull: str, http_prefix: str, translate: {}, moderator: bool, editor: bool, newswire: {}, positive_voting: bool, showBackButton: bool, timelinePath: str, showPublishButton: bool, show_publish_as_icon: bool, rss_icon_at_top: bool, publish_button_at_top: bool, authorized: bool, showHeaderImage: bool, theme: str, defaultTimeline: str, accessKeys: {}) -> str: """Returns html content for the right column """ htmlStr = '' domain = removeDomainPort(domainFull) 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 publish_button_at_top: htmlStr += '
' + publishButtonStr + '
' # show a column header image, eg. title of the theme or newswire banner editImageClass = '' if showHeaderImage: rightImageFile, rightColumnImageFilename = \ getRightImageFile(base_dir, 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 rss_icon_at_top: if 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 publish_button_at_top: if not show_publish_as_icon: htmlStr += publishButtonStr # show the edit icon if editor: if os.path.isfile(base_dir + '/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 rss_icon_at_top: htmlStr += rssIconStr # show publish icon at top if showPublishButton: if show_publish_as_icon: 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 rss_icon_at_top: if not showHeaderImage: htmlStr += '

' # show the newswire lines newswireContentStr = \ _htmlNewswire(base_dir, newswire, nickname, moderator, translate, positive_voting) htmlStr += newswireContentStr # show the rss icon at the bottom, typically on the right hand side if newswireContentStr and not rss_icon_at_top: 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 _htmlNewswire(base_dir: str, newswire: {}, nickname: str, moderator: bool, translate: {}, positive_voting: bool) -> str: """Converts a newswire dict into html """ separatorStr = htmlPostSeparator(base_dir, '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('EX: _htmlNewswire 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 = getNewswireFaviconUrl(url) faviconLink = '' if faviconUrl: cachedFaviconFilename = getFavFilenameFromUrl(base_dir, faviconUrl) if os.path.isfile(cachedFaviconFilename): faviconUrl = \ cachedFaviconFilename.replace(base_dir, '') else: extensions = ('png', 'jpg', 'gif', 'avif', 'svg', 'webp') for ext in extensions: cachedFaviconFilename = \ getFavFilenameFromUrl(base_dir, faviconUrl) cachedFaviconFilename = \ cachedFaviconFilename.replace('.ico', '.' + ext) if os.path.isfile(cachedFaviconFilename): faviconUrl = \ cachedFaviconFilename.replace(base_dir, '') 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, positive_voting) title = removeLongWords(item[0], 16, []).replace('\n', '
') title = limitRepeatedWords(title, 6) 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, positive_voting) title = removeLongWords(item[0], 16, []).replace('\n', '
') title = limitRepeatedWords(title, 6) 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(base_dir: str, nickname: str, domain: str, http_prefix: 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 = \ acctDir(base_dir, 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 = base_dir + '/epicyon-profile.css' if os.path.isfile(base_dir + '/epicyon.css'): cssFilename = base_dir + '/epicyon.css' instanceTitle = \ getConfigParam(base_dir, 'instanceTitle') htmlStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle, None) # top banner bannerFile, bannerFilename = \ getBannerFile(base_dir, 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', '
') title = limitRepeatedWords(title, 6) link = item[1] citationValue = \ dateStr + citationsSeparator + \ title + citationsSeparator + \ link htmlStr += \ '' + \ '' + title + ' ' htmlStr += '' + \ dateShown + '
\n' ctr += 1 htmlStr += '
\n' return htmlStr + htmlFooter() def htmlNewswireMobile(cssCache: {}, base_dir: str, nickname: str, domain: str, domainFull: str, http_prefix: str, translate: {}, newswire: {}, positive_voting: bool, timelinePath: str, show_publish_as_icon: bool, authorized: bool, rss_icon_at_top: bool, icons_as_buttons: bool, defaultTimeline: str, theme: str, accessKeys: {}) -> str: """Shows the mobile version of the newswire right column """ htmlStr = '' # the css filename cssFilename = base_dir + '/epicyon-profile.css' if os.path.isfile(base_dir + '/epicyon.css'): cssFilename = base_dir + '/epicyon.css' if nickname == 'news': editor = False moderator = False else: # is the user a moderator? moderator = isModerator(base_dir, nickname) # is the user a site editor? editor = isEditor(base_dir, nickname) showPublishButton = editor instanceTitle = \ getConfigParam(base_dir, 'instanceTitle') htmlStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle, None) bannerFile, bannerFilename = \ getBannerFile(base_dir, nickname, domain, theme) htmlStr += \ '' + \ '\n' htmlStr += '
\n' htmlStr += '
' + \ headerButtonsFrontScreen(translate, nickname, 'newswire', authorized, icons_as_buttons) + '
' htmlStr += \ getRightColumnContent(base_dir, nickname, domainFull, http_prefix, translate, moderator, editor, newswire, positive_voting, False, timelinePath, showPublishButton, show_publish_as_icon, rss_icon_at_top, False, authorized, False, theme, defaultTimeline, accessKeys) if editor and not newswire: 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: {}, base_dir: str, path: str, domain: str, port: int, http_prefix: str, defaultTimeline: str, theme: str, accessKeys: {}) -> str: """Shows the edit newswire screen """ if '/users/' not in path: return '' path = path.replace('/inbox', '').replace('/outbox', '') path = path.replace('/shares', '').replace('/wanted', '') nickname = getNicknameFromActor(path) if not nickname: return '' # is the user a moderator? if not isModerator(base_dir, nickname): return '' cssFilename = base_dir + '/epicyon-links.css' if os.path.isfile(base_dir + '/links.css'): cssFilename = base_dir + '/links.css' # filename of the banner shown at the top bannerFile, bannerFilename = \ getBannerFile(base_dir, nickname, domain, theme) instanceTitle = \ getConfigParam(base_dir, 'instanceTitle') editNewswireForm = \ htmlHeaderWithExternalStyle(cssFilename, instanceTitle, None) # top banner editNewswireForm += \ '
' + \ '\n' editNewswireForm += '\n
' editNewswireForm += \ '
\n' editNewswireForm += \ '
\n' editNewswireForm += \ '

' + translate['Edit newswire'] + '

' editNewswireForm += \ '
\n' editNewswireForm += \ ' \n' editNewswireForm += \ '
\n' newswireFilename = base_dir + '/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.'] + \ '
' newFeedStr = translate['New feed URL'] editNewswireForm += editTextField(None, 'newNewswireFeed', '', newFeedStr) editNewswireForm += \ ' ' filterStr = '' filterFilename = \ base_dir + '/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 = \ base_dir + '/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: {}, base_dir: str, path: str, domain: str, port: int, http_prefix: str, postUrl: str, system_language: 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(base_dir, nickname): return '' postUrl = postUrl.replace('/', '#') postFilename = locatePost(base_dir, nickname, domain, postUrl) if not postFilename: return '' post_json_object = loadJson(postFilename) if not post_json_object: return '' cssFilename = base_dir + '/epicyon-links.css' if os.path.isfile(base_dir + '/links.css'): cssFilename = base_dir + '/links.css' instanceTitle = \ getConfigParam(base_dir, 'instanceTitle') editNewsPostForm = \ htmlHeaderWithExternalStyle(cssFilename, instanceTitle, None) editNewsPostForm += \ '\n' editNewsPostForm += \ '
\n' editNewsPostForm += \ '

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

' editNewsPostForm += \ '
\n' editNewsPostForm += \ ' ' + \ '\n' editNewsPostForm += \ ' \n' editNewsPostForm += \ '
\n' editNewsPostForm += \ '
' editNewsPostForm += \ ' \n' newsPostTitle = post_json_object['object']['summary'] editNewsPostForm += \ '
\n' newsPostContent = getBaseContentFromPost(post_json_object, system_language) editNewsPostForm += \ ' ' editNewsPostForm += \ '
' editNewsPostForm += htmlFooter() return editNewsPostForm