Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon into main
|
@ -250,13 +250,13 @@ If you want to use your own favicon then copy your `favicon.ico` file to the bas
|
|||
|
||||
## Adding Themes
|
||||
|
||||
If you want to add a new theme then open `theme.py` and add the theme name to the list within `getThemesList`. Add a function with the name `setTheme[YourThemeName]`. Have a look at the other themes to get an idea of how to set the colors and fonts.
|
||||
If you want to add a new theme then first add the name of your theme to the translations files.
|
||||
|
||||
Add the name of your theme to the translations files.
|
||||
Within the `theme` directory create a directory with the name of your theme and add icons and banners. As a quick way to begin you could copy the contents of `theme/default`, then edit the graphics. Keep the size of images as small as possible to avoid creating a laggy user interface.
|
||||
|
||||
Within the `img` directory add a default profile background image called `image_[YourThemeName].png` and a banner image `banner_[YourThemeName].png`. Because the banner image will be reloaded occasionally it should be small - preferably kilobytes rather than megabytes.
|
||||
On a running instance you can experiment with colors or fonts by editing `epicyon.css` and then reloading the web page. Once you are happy with the results then you can update the changed variable values within your `theme/yourtheme/theme.json` file.
|
||||
|
||||
On a running instance you can experiment with colors or fonts by editing `epicyon.css` and then reloading the web page. Once you are happy with the results then you can update the changed values within your `setTheme` function.
|
||||
Epicyon normally uses one set of CSS files whose variables are then altered per theme. If you want to use entirely bespoke CSS then copy `epicyon-*.css` into your theme directory and edit it to your requirements. This will be used rather than the default CSS files. Be warned that if you're maintaining the CSS files yourself then you may need to keep up with whatever changes are happening upstream, otherwise your user interface will break.
|
||||
|
||||
|
||||
## Running Unit Tests
|
||||
|
|
274
blog.py
|
@ -10,8 +10,8 @@ import os
|
|||
from datetime import datetime
|
||||
|
||||
from content import replaceEmojiFromTags
|
||||
from webapp import getIconsDir
|
||||
from webapp import htmlHeader
|
||||
from webapp import getIconsWebPath
|
||||
from webapp import htmlHeaderWithExternalStyle
|
||||
from webapp import htmlFooter
|
||||
from webapp_media import addEmbeddedElements
|
||||
from webapp_utils import getPostAttachmentsAsHtml
|
||||
|
@ -381,39 +381,36 @@ def htmlBlogPost(authorized: bool,
|
|||
cssFilename = baseDir + '/epicyon-blog.css'
|
||||
if os.path.isfile(baseDir + '/blog.css'):
|
||||
cssFilename = baseDir + '/blog.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
blogCSS = cssFile.read()
|
||||
blogStr = htmlHeader(cssFilename, blogCSS)
|
||||
htmlBlogRemoveCwButton(blogStr, translate)
|
||||
blogStr = htmlHeaderWithExternalStyle(cssFilename)
|
||||
htmlBlogRemoveCwButton(blogStr, translate)
|
||||
|
||||
blogStr += htmlBlogPostContent(authorized, baseDir,
|
||||
httpPrefix, translate,
|
||||
nickname, domain,
|
||||
domainFull, postJsonObject,
|
||||
None, False)
|
||||
blogStr += htmlBlogPostContent(authorized, baseDir,
|
||||
httpPrefix, translate,
|
||||
nickname, domain,
|
||||
domainFull, postJsonObject,
|
||||
None, False)
|
||||
|
||||
# show rss links
|
||||
iconsDir = getIconsDir(baseDir)
|
||||
blogStr += '<p class="rssfeed">'
|
||||
# show rss links
|
||||
iconsPath = getIconsWebPath(baseDir)
|
||||
blogStr += '<p class="rssfeed">'
|
||||
|
||||
blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
domainFull + '/blog/' + nickname + '/rss.xml">'
|
||||
blogStr += '<img style="width:3%;min-width:50px" ' + \
|
||||
'loading="lazy" alt="RSS 2.0" ' + \
|
||||
'title="RSS 2.0" src="/' + \
|
||||
iconsDir + '/logorss.png" /></a>'
|
||||
blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
domainFull + '/blog/' + nickname + '/rss.xml">'
|
||||
blogStr += '<img style="width:3%;min-width:50px" ' + \
|
||||
'loading="lazy" alt="RSS 2.0" ' + \
|
||||
'title="RSS 2.0" src="/' + \
|
||||
iconsPath + '/logorss.png" /></a>'
|
||||
|
||||
# blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
# domainFull + '/blog/' + nickname + '/rss.txt">'
|
||||
# blogStr += '<img style="width:3%;min-width:50px" ' + \
|
||||
# 'loading="lazy" alt="RSS 3.0" ' + \
|
||||
# 'title="RSS 3.0" src="/' + \
|
||||
# iconsDir + '/rss3.png" /></a>'
|
||||
# blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
# domainFull + '/blog/' + nickname + '/rss.txt">'
|
||||
# blogStr += '<img style="width:3%;min-width:50px" ' + \
|
||||
# 'loading="lazy" alt="RSS 3.0" ' + \
|
||||
# 'title="RSS 3.0" src="/' + \
|
||||
# iconsPath + '/rss3.png" /></a>'
|
||||
|
||||
blogStr += '</p>'
|
||||
blogStr += '</p>'
|
||||
|
||||
return blogStr + htmlFooter()
|
||||
return None
|
||||
return blogStr + htmlFooter()
|
||||
|
||||
|
||||
def htmlBlogPage(authorized: bool, session,
|
||||
|
@ -430,85 +427,81 @@ def htmlBlogPage(authorized: bool, session,
|
|||
cssFilename = baseDir + '/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||
cssFilename = baseDir + '/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
blogCSS = cssFile.read()
|
||||
blogStr = htmlHeader(cssFilename, blogCSS)
|
||||
htmlBlogRemoveCwButton(blogStr, translate)
|
||||
|
||||
blogsIndex = baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/tlblogs.index'
|
||||
if not os.path.isfile(blogsIndex):
|
||||
return blogStr + htmlFooter()
|
||||
|
||||
timelineJson = createBlogsTimeline(session, baseDir,
|
||||
nickname, domain, port,
|
||||
httpPrefix,
|
||||
noOfItems, False,
|
||||
pageNumber)
|
||||
|
||||
if not timelineJson:
|
||||
return blogStr + htmlFooter()
|
||||
|
||||
domainFull = domain
|
||||
if port:
|
||||
if port != 80 and port != 443:
|
||||
domainFull = domain + ':' + str(port)
|
||||
|
||||
# show previous and next buttons
|
||||
if pageNumber is not None:
|
||||
iconsDir = getIconsDir(baseDir)
|
||||
navigateStr = '<p>'
|
||||
if pageNumber > 1:
|
||||
# show previous button
|
||||
navigateStr += '<a href="' + httpPrefix + '://' + \
|
||||
domainFull + '/blog/' + \
|
||||
nickname + '?page=' + str(pageNumber-1) + '">' + \
|
||||
'<img loading="lazy" alt="<" title="<" ' + \
|
||||
'src="/' + iconsDir + \
|
||||
'/prev.png" class="buttonprev"/></a>\n'
|
||||
if len(timelineJson['orderedItems']) >= noOfItems:
|
||||
# show next button
|
||||
navigateStr += '<a href="' + httpPrefix + '://' + \
|
||||
domainFull + '/blog/' + nickname + \
|
||||
'?page=' + str(pageNumber + 1) + '">' + \
|
||||
'<img loading="lazy" alt=">" title=">" ' + \
|
||||
'src="/' + iconsDir + \
|
||||
'/prev.png" class="buttonnext"/></a>\n'
|
||||
navigateStr += '</p>'
|
||||
blogStr += navigateStr
|
||||
|
||||
for item in timelineJson['orderedItems']:
|
||||
if item['type'] != 'Create':
|
||||
continue
|
||||
|
||||
blogStr += htmlBlogPostContent(authorized, baseDir,
|
||||
httpPrefix, translate,
|
||||
nickname, domain,
|
||||
domainFull, item,
|
||||
None, True)
|
||||
|
||||
if len(timelineJson['orderedItems']) >= noOfItems:
|
||||
blogStr += navigateStr
|
||||
|
||||
# show rss link
|
||||
blogStr += '<p class="rssfeed">'
|
||||
|
||||
blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
domainFull + '/blog/' + nickname + '/rss.xml">'
|
||||
blogStr += '<img loading="lazy" alt="RSS 2.0" ' + \
|
||||
'title="RSS 2.0" src="/' + \
|
||||
iconsDir + '/logorss.png" /></a>'
|
||||
|
||||
# blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
# domainFull + '/blog/' + nickname + '/rss.txt">'
|
||||
# blogStr += '<img loading="lazy" alt="RSS 3.0" ' + \
|
||||
# 'title="RSS 3.0" src="/' + \
|
||||
# iconsDir + '/rss3.png" /></a>'
|
||||
|
||||
blogStr += '</p>'
|
||||
blogStr = htmlHeaderWithExternalStyle(cssFilename)
|
||||
htmlBlogRemoveCwButton(blogStr, translate)
|
||||
|
||||
blogsIndex = baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/tlblogs.index'
|
||||
if not os.path.isfile(blogsIndex):
|
||||
return blogStr + htmlFooter()
|
||||
return None
|
||||
|
||||
timelineJson = createBlogsTimeline(session, baseDir,
|
||||
nickname, domain, port,
|
||||
httpPrefix,
|
||||
noOfItems, False,
|
||||
pageNumber)
|
||||
|
||||
if not timelineJson:
|
||||
return blogStr + htmlFooter()
|
||||
|
||||
domainFull = domain
|
||||
if port:
|
||||
if port != 80 and port != 443:
|
||||
domainFull = domain + ':' + str(port)
|
||||
|
||||
# show previous and next buttons
|
||||
if pageNumber is not None:
|
||||
iconsPath = getIconsWebPath(baseDir)
|
||||
navigateStr = '<p>'
|
||||
if pageNumber > 1:
|
||||
# show previous button
|
||||
navigateStr += '<a href="' + httpPrefix + '://' + \
|
||||
domainFull + '/blog/' + \
|
||||
nickname + '?page=' + str(pageNumber-1) + '">' + \
|
||||
'<img loading="lazy" alt="<" title="<" ' + \
|
||||
'src="/' + iconsPath + \
|
||||
'/prev.png" class="buttonprev"/></a>\n'
|
||||
if len(timelineJson['orderedItems']) >= noOfItems:
|
||||
# show next button
|
||||
navigateStr += '<a href="' + httpPrefix + '://' + \
|
||||
domainFull + '/blog/' + nickname + \
|
||||
'?page=' + str(pageNumber + 1) + '">' + \
|
||||
'<img loading="lazy" alt=">" title=">" ' + \
|
||||
'src="/' + iconsPath + \
|
||||
'/prev.png" class="buttonnext"/></a>\n'
|
||||
navigateStr += '</p>'
|
||||
blogStr += navigateStr
|
||||
|
||||
for item in timelineJson['orderedItems']:
|
||||
if item['type'] != 'Create':
|
||||
continue
|
||||
|
||||
blogStr += htmlBlogPostContent(authorized, baseDir,
|
||||
httpPrefix, translate,
|
||||
nickname, domain,
|
||||
domainFull, item,
|
||||
None, True)
|
||||
|
||||
if len(timelineJson['orderedItems']) >= noOfItems:
|
||||
blogStr += navigateStr
|
||||
|
||||
# show rss link
|
||||
blogStr += '<p class="rssfeed">'
|
||||
|
||||
blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
domainFull + '/blog/' + nickname + '/rss.xml">'
|
||||
blogStr += '<img loading="lazy" alt="RSS 2.0" ' + \
|
||||
'title="RSS 2.0" src="/' + \
|
||||
iconsPath + '/logorss.png" /></a>'
|
||||
|
||||
# blogStr += '<a href="' + httpPrefix + '://' + \
|
||||
# domainFull + '/blog/' + nickname + '/rss.txt">'
|
||||
# blogStr += '<img loading="lazy" alt="RSS 3.0" ' + \
|
||||
# 'title="RSS 3.0" src="/' + \
|
||||
# iconsPath + '/rss3.png" /></a>'
|
||||
|
||||
blogStr += '</p>'
|
||||
return blogStr + htmlFooter()
|
||||
|
||||
|
||||
def htmlBlogPageRSS2(authorized: bool, session,
|
||||
|
@ -678,40 +671,37 @@ def htmlBlogView(authorized: bool,
|
|||
cssFilename = baseDir + '/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||
cssFilename = baseDir + '/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
blogCSS = cssFile.read()
|
||||
blogStr = htmlHeader(cssFilename, blogCSS)
|
||||
blogStr = htmlHeaderWithExternalStyle(cssFilename)
|
||||
|
||||
if noOfBlogAccounts(baseDir) <= 1:
|
||||
nickname = singleBlogAccountNickname(baseDir)
|
||||
if nickname:
|
||||
return htmlBlogPage(authorized, session,
|
||||
baseDir, httpPrefix, translate,
|
||||
nickname, domain, port,
|
||||
noOfItems, 1)
|
||||
if noOfBlogAccounts(baseDir) <= 1:
|
||||
nickname = singleBlogAccountNickname(baseDir)
|
||||
if nickname:
|
||||
return htmlBlogPage(authorized, session,
|
||||
baseDir, httpPrefix, translate,
|
||||
nickname, domain, port,
|
||||
noOfItems, 1)
|
||||
|
||||
domainFull = domain
|
||||
if port:
|
||||
if port != 80 and port != 443:
|
||||
domainFull = domain + ':' + str(port)
|
||||
domainFull = domain
|
||||
if port:
|
||||
if port != 80 and port != 443:
|
||||
domainFull = domain + ':' + str(port)
|
||||
|
||||
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
|
||||
for acct in dirs:
|
||||
if '@' not in acct:
|
||||
continue
|
||||
if 'inbox@' in acct:
|
||||
continue
|
||||
accountDir = os.path.join(baseDir + '/accounts', acct)
|
||||
blogsIndex = accountDir + '/tlblogs.index'
|
||||
if os.path.isfile(blogsIndex):
|
||||
blogStr += '<p class="blogaccount">'
|
||||
blogStr += '<a href="' + \
|
||||
httpPrefix + '://' + domainFull + '/blog/' + \
|
||||
acct.split('@')[0] + '">' + acct + '</a>'
|
||||
blogStr += '</p>'
|
||||
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
|
||||
for acct in dirs:
|
||||
if '@' not in acct:
|
||||
continue
|
||||
if 'inbox@' in acct:
|
||||
continue
|
||||
accountDir = os.path.join(baseDir + '/accounts', acct)
|
||||
blogsIndex = accountDir + '/tlblogs.index'
|
||||
if os.path.isfile(blogsIndex):
|
||||
blogStr += '<p class="blogaccount">'
|
||||
blogStr += '<a href="' + \
|
||||
httpPrefix + '://' + domainFull + '/blog/' + \
|
||||
acct.split('@')[0] + '">' + acct + '</a>'
|
||||
blogStr += '</p>'
|
||||
|
||||
return blogStr + htmlFooter()
|
||||
return None
|
||||
return blogStr + htmlFooter()
|
||||
|
||||
|
||||
def htmlEditBlog(mediaInstance: bool, translate: {},
|
||||
|
@ -732,7 +722,7 @@ def htmlEditBlog(mediaInstance: bool, translate: {},
|
|||
print('Edit blog: json not loaded for ' + postFilename)
|
||||
return None
|
||||
|
||||
iconsDir = getIconsDir(baseDir)
|
||||
iconsPath = getIconsWebPath(baseDir)
|
||||
|
||||
editBlogText = '<p class="new-post-text">' + \
|
||||
translate['Write your post text below.'] + '</p>'
|
||||
|
@ -744,10 +734,6 @@ def htmlEditBlog(mediaInstance: bool, translate: {},
|
|||
cssFilename = baseDir + '/epicyon-profile.css'
|
||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||
cssFilename = baseDir + '/epicyon.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
editBlogCSS = cssFile.read()
|
||||
if httpPrefix != 'https':
|
||||
editBlogCSS = editBlogCSS.replace('https://', httpPrefix+'://')
|
||||
|
||||
if '?' in path:
|
||||
path = path.split('?')[0]
|
||||
|
@ -781,7 +767,7 @@ def htmlEditBlog(mediaInstance: bool, translate: {},
|
|||
dateAndLocation += \
|
||||
'<p><img loading="lazy" alt="" title="" ' + \
|
||||
'class="emojicalendar" src="/' + \
|
||||
iconsDir + '/calendar.png"/>'
|
||||
iconsPath + '/calendar.png"/>'
|
||||
dateAndLocation += \
|
||||
'<label class="labels">' + translate['Date'] + ': </label>'
|
||||
dateAndLocation += '<input type="date" name="eventDate">'
|
||||
|
@ -794,7 +780,7 @@ def htmlEditBlog(mediaInstance: bool, translate: {},
|
|||
dateAndLocation += '<input type="text" name="location">'
|
||||
dateAndLocation += '</div>'
|
||||
|
||||
editBlogForm = htmlHeader(cssFilename, editBlogCSS)
|
||||
editBlogForm = htmlHeaderWithExternalStyle(cssFilename)
|
||||
|
||||
editBlogForm += \
|
||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||
|
@ -812,7 +798,7 @@ def htmlEditBlog(mediaInstance: bool, translate: {},
|
|||
|
||||
editBlogForm += ' <div class="dropbtn">'
|
||||
editBlogForm += \
|
||||
' <img loading="lazy" alt="" title="" src="/' + iconsDir + \
|
||||
' <img loading="lazy" alt="" title="" src="/' + iconsPath + \
|
||||
'/' + scopeIcon + '"/><b class="scope-desc">' + \
|
||||
scopeDescription + '</b>'
|
||||
editBlogForm += ' </div>'
|
||||
|
|
|
@ -159,6 +159,7 @@ def dangerousMarkup(content: str) -> bool:
|
|||
if '>' not in content:
|
||||
return False
|
||||
contentSections = content.split('<')
|
||||
invalidPartials = ('127.0.', '192.168', '10.0.')
|
||||
invalidStrings = ('script', 'canvas', 'style', 'abbr',
|
||||
'frame', 'iframe', 'html', 'body',
|
||||
'hr')
|
||||
|
@ -166,6 +167,9 @@ def dangerousMarkup(content: str) -> bool:
|
|||
if '>' not in markup:
|
||||
continue
|
||||
markup = markup.split('>')[0].strip()
|
||||
for partialMatch in invalidPartials:
|
||||
if partialMatch in markup:
|
||||
return True
|
||||
if ' ' not in markup:
|
||||
for badStr in invalidStrings:
|
||||
if badStr in markup:
|
||||
|
|
150
daemon.py
|
@ -166,6 +166,7 @@ from shares import getSharesFeedForPerson
|
|||
from shares import addShare
|
||||
from shares import removeShare
|
||||
from shares import expireShares
|
||||
from utils import mediaFileMimeType
|
||||
from utils import getCSS
|
||||
from utils import firstParagraphFromString
|
||||
from utils import clearFromPostCaches
|
||||
|
@ -2441,7 +2442,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.debug,
|
||||
self.server.projectVersion,
|
||||
self.server.YTReplacementDomain,
|
||||
self.server.showPublishedDateOnly)
|
||||
self.server.showPublishedDateOnly,
|
||||
self.server.defaultTimeline)
|
||||
if profileStr:
|
||||
msg = profileStr.encode('utf-8')
|
||||
self._login_headers('text/html',
|
||||
|
@ -3408,6 +3410,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
adminNickname = getConfigParam(self.server.baseDir, 'admin')
|
||||
|
||||
# get the various avatar, banner and background images
|
||||
actorChanged = True
|
||||
profileMediaTypes = ('avatar', 'image',
|
||||
|
@ -3416,6 +3420,13 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'left_col_image', 'right_col_image')
|
||||
profileMediaTypesUploaded = {}
|
||||
for mType in profileMediaTypes:
|
||||
# some images can only be changed by the admin
|
||||
if mType == 'instanceLogo':
|
||||
if nickname != adminNickname:
|
||||
print('WARN: only the admin can change ' +
|
||||
'instance logo')
|
||||
continue
|
||||
|
||||
if debug:
|
||||
print('DEBUG: profile update extracting ' + mType +
|
||||
' image or font from POST')
|
||||
|
@ -4371,12 +4382,16 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if 'image/avif' in self.headers['Accept']:
|
||||
favType = 'image/avif'
|
||||
favFilename = 'favicon.avif'
|
||||
themeName = getConfigParam(baseDir, 'theme')
|
||||
if not themeName:
|
||||
themeName = 'default'
|
||||
# custom favicon
|
||||
faviconFilename = baseDir + '/' + favFilename
|
||||
faviconFilename = \
|
||||
baseDir + '/theme/' + themeName + '/icons/' + favFilename
|
||||
if not os.path.isfile(faviconFilename):
|
||||
# default favicon
|
||||
faviconFilename = \
|
||||
baseDir + '/img/icons/' + favFilename
|
||||
baseDir + '/theme/default/icons/' + favFilename
|
||||
if self._etag_exists(faviconFilename):
|
||||
# The file has not changed
|
||||
if debug:
|
||||
|
@ -4764,25 +4779,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._304()
|
||||
return
|
||||
|
||||
mediaFileType = 'image/png'
|
||||
if mediaFilename.endswith('.png'):
|
||||
mediaFileType = 'image/png'
|
||||
elif mediaFilename.endswith('.jpg'):
|
||||
mediaFileType = 'image/jpeg'
|
||||
elif mediaFilename.endswith('.gif'):
|
||||
mediaFileType = 'image/gif'
|
||||
elif mediaFilename.endswith('.webp'):
|
||||
mediaFileType = 'image/webp'
|
||||
elif mediaFilename.endswith('.avif'):
|
||||
mediaFileType = 'image/avif'
|
||||
elif mediaFilename.endswith('.mp4'):
|
||||
mediaFileType = 'video/mp4'
|
||||
elif mediaFilename.endswith('.ogv'):
|
||||
mediaFileType = 'video/ogv'
|
||||
elif mediaFilename.endswith('.mp3'):
|
||||
mediaFileType = 'audio/mpeg'
|
||||
elif mediaFilename.endswith('.ogg'):
|
||||
mediaFileType = 'audio/ogg'
|
||||
mediaFileType = mediaFileMimeType(mediaFilename)
|
||||
|
||||
with open(mediaFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
|
@ -4841,7 +4838,13 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
"""
|
||||
if path.endswith('.png'):
|
||||
mediaStr = path.split('/icons/')[1]
|
||||
mediaFilename = baseDir + '/img/icons/' + mediaStr
|
||||
if '/' not in mediaStr:
|
||||
self._404()
|
||||
return
|
||||
theme = mediaStr.split('/')[0]
|
||||
iconFilename = mediaStr.split('/')[1]
|
||||
mediaFilename = \
|
||||
baseDir + '/theme/' + theme + '/icons/' + iconFilename
|
||||
if self._etag_exists(mediaFilename):
|
||||
# The file has not changed
|
||||
self._304()
|
||||
|
@ -4849,7 +4852,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if self.server.iconsCache.get(mediaStr):
|
||||
mediaBinary = self.server.iconsCache[mediaStr]
|
||||
self._set_headers_etag(mediaFilename,
|
||||
'image/png',
|
||||
mediaFileMimeType(mediaFilename),
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self._write(mediaBinary)
|
||||
|
@ -4858,8 +4861,9 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if os.path.isfile(mediaFilename):
|
||||
with open(mediaFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
mimeType = mediaFileMimeType(mediaFilename)
|
||||
self._set_headers_etag(mediaFilename,
|
||||
'image/png',
|
||||
mimeType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self._write(mediaBinary)
|
||||
|
@ -4883,39 +4887,11 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
return
|
||||
with open(mediaFilename, 'rb') as avFile:
|
||||
mediaBinary = avFile.read()
|
||||
if mediaFilename.endswith('.png'):
|
||||
self._set_headers_etag(mediaFilename,
|
||||
'image/png',
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
elif mediaFilename.endswith('.jpg'):
|
||||
self._set_headers_etag(mediaFilename,
|
||||
'image/jpeg',
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
elif mediaFilename.endswith('.gif'):
|
||||
self._set_headers_etag(mediaFilename,
|
||||
'image/gif',
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
elif mediaFilename.endswith('.webp'):
|
||||
self._set_headers_etag(mediaFilename,
|
||||
'image/webp',
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
elif mediaFilename.endswith('.avif'):
|
||||
self._set_headers_etag(mediaFilename,
|
||||
'image/avif',
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
else:
|
||||
# default to jpeg
|
||||
self._set_headers_etag(mediaFilename,
|
||||
'image/jpeg',
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
# self._404()
|
||||
return
|
||||
mimeType = mediaFileMimeType(mediaFilename)
|
||||
self._set_headers_etag(mediaFilename,
|
||||
mimeType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self._write(mediaBinary)
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'icon shown done',
|
||||
|
@ -8327,7 +8303,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
time.sleep(1)
|
||||
tries += 1
|
||||
if mediaBinary:
|
||||
self._set_headers_etag(qrFilename, 'image/png',
|
||||
mimeType = mediaFileMimeType(qrFilename)
|
||||
self._set_headers_etag(qrFilename, mimeType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self._write(mediaBinary)
|
||||
|
@ -8365,7 +8342,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
time.sleep(1)
|
||||
tries += 1
|
||||
if mediaBinary:
|
||||
self._set_headers_etag(bannerFilename, 'image/png',
|
||||
mimeType = mediaFileMimeType(bannerFilename)
|
||||
self._set_headers_etag(bannerFilename, mimeType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self._write(mediaBinary)
|
||||
|
@ -8406,7 +8384,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
time.sleep(1)
|
||||
tries += 1
|
||||
if mediaBinary:
|
||||
self._set_headers_etag(bannerFilename, 'image/png',
|
||||
mimeType = mediaFileMimeType(bannerFilename)
|
||||
self._set_headers_etag(bannerFilename, mimeType,
|
||||
mediaBinary, None,
|
||||
callingDomain)
|
||||
self._write(mediaBinary)
|
||||
|
@ -8955,6 +8934,13 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'create session', 'hasAccept')
|
||||
|
||||
# get css
|
||||
# Note that this comes before the busy flag to avoid conflicts
|
||||
if self.path.endswith('.css'):
|
||||
if self._getStyleSheet(callingDomain, self.path,
|
||||
GETstartTime, GETtimings):
|
||||
return
|
||||
|
||||
# get fonts
|
||||
if '/fonts/' in self.path:
|
||||
self._getFonts(callingDomain, self.path,
|
||||
|
@ -9316,17 +9302,6 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'robots txt',
|
||||
'show login screen done')
|
||||
|
||||
# get css
|
||||
# Note that this comes before the busy flag to avoid conflicts
|
||||
if self.path.endswith('.css'):
|
||||
if self._getStyleSheet(callingDomain, self.path,
|
||||
GETstartTime, GETtimings):
|
||||
return
|
||||
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'show login screen done',
|
||||
'profile.css done')
|
||||
|
||||
# manifest images used to create a home screen icon
|
||||
# when selecting "add to home screen" in browsers
|
||||
# which support progressive web apps
|
||||
|
@ -9358,8 +9333,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
time.sleep(1)
|
||||
tries += 1
|
||||
if mediaBinary:
|
||||
self._set_headers_etag(mediaFilename,
|
||||
'image/png',
|
||||
mimeType = mediaFileMimeType(mediaFilename)
|
||||
self._set_headers_etag(mediaFilename, mimeType,
|
||||
mediaBinary, cookie,
|
||||
callingDomain)
|
||||
self._write(mediaBinary)
|
||||
|
@ -9398,8 +9373,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
time.sleep(1)
|
||||
tries += 1
|
||||
if mediaBinary:
|
||||
self._set_headers_etag(screenFilename,
|
||||
'image/png',
|
||||
mimeType = mediaFileMimeType(screenFilename)
|
||||
self._set_headers_etag(screenFilename, mimeType,
|
||||
mediaBinary, cookie,
|
||||
callingDomain)
|
||||
self._write(mediaBinary)
|
||||
|
@ -9443,7 +9418,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
tries += 1
|
||||
if mediaBinary:
|
||||
self._set_headers_etag(iconFilename,
|
||||
'image/png',
|
||||
mediaFileMimeType(iconFilename),
|
||||
mediaBinary, cookie,
|
||||
callingDomain)
|
||||
self._write(mediaBinary)
|
||||
|
@ -10815,26 +10790,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
except BaseException:
|
||||
pass
|
||||
|
||||
mediaFileType = 'application/json'
|
||||
if checkPath.endswith('.png'):
|
||||
mediaFileType = 'image/png'
|
||||
elif checkPath.endswith('.jpg'):
|
||||
mediaFileType = 'image/jpeg'
|
||||
elif checkPath.endswith('.gif'):
|
||||
mediaFileType = 'image/gif'
|
||||
elif checkPath.endswith('.webp'):
|
||||
mediaFileType = 'image/webp'
|
||||
elif checkPath.endswith('.avif'):
|
||||
mediaFileType = 'image/avif'
|
||||
elif checkPath.endswith('.mp4'):
|
||||
mediaFileType = 'video/mp4'
|
||||
elif checkPath.endswith('.ogv'):
|
||||
mediaFileType = 'video/ogv'
|
||||
elif checkPath.endswith('.mp3'):
|
||||
mediaFileType = 'audio/mpeg'
|
||||
elif checkPath.endswith('.ogg'):
|
||||
mediaFileType = 'audio/ogg'
|
||||
|
||||
mediaFileType = mediaFileMimeType(checkPath)
|
||||
self._set_headers_head(mediaFileType, fileLength,
|
||||
etag, callingDomain)
|
||||
|
||||
|
|
|
@ -15,9 +15,12 @@
|
|||
--title-background: #ccc;
|
||||
--focus-color: white;
|
||||
--calendar-horizontal-padding: 0;
|
||||
--calendar-cell-size: 1.5vw;
|
||||
--calendar-cell-size-mobile: 1.5vw;
|
||||
--font-size-calendar-header: 3rem;
|
||||
--font-size-calendar-day: 1rem;
|
||||
--font-size-calendar-cell: 4rem;
|
||||
--font-size-calendar-cell: 2rem;
|
||||
--font-size-calendar-cell-mobile: 4rem;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
@ -38,15 +41,8 @@
|
|||
body {
|
||||
background-color: var(--main-bg-color);
|
||||
color: var(--day-number2);
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
font-family: 'Montserrat';
|
||||
font-weight: 700;
|
||||
margin: 5%;
|
||||
}
|
||||
|
||||
main {
|
||||
|
@ -55,12 +51,6 @@ main {
|
|||
flex-basis: 980px;
|
||||
}
|
||||
|
||||
.calendar {
|
||||
table-display: fixed;
|
||||
width: 100%;
|
||||
padding: 0 var(--calendar-horizontal-padding);
|
||||
}
|
||||
|
||||
a:visited{
|
||||
color: var(--day-number);
|
||||
text-decoration: none;
|
||||
|
@ -97,11 +87,6 @@ a:focus {
|
|||
border-right: none;
|
||||
}
|
||||
|
||||
.calendar__day__header,
|
||||
.calendar__day__cell {
|
||||
padding: .75rem 0 1.5rem;
|
||||
}
|
||||
|
||||
.calendar__banner--month {
|
||||
text-align: center;
|
||||
padding: .75rem;
|
||||
|
@ -123,11 +108,6 @@ a:focus {
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.calendar__day__cell {
|
||||
font-size: var(--font-size-calendar-cell);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.year {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
@ -210,3 +190,37 @@ tr:nth-child(even) > .calendar__day__cell:nth-child(even) {
|
|||
-ms-transform: translateY(30%) scaleX(-1);
|
||||
transform: translateY(30%) scaleX(-1);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 400px) {
|
||||
.calendar {
|
||||
table-display: fixed;
|
||||
margin: 0 20%;
|
||||
width: 60%;
|
||||
padding: 0 var(--calendar-horizontal-padding);
|
||||
}
|
||||
.calendar__day__cell {
|
||||
font-size: var(--font-size-calendar-cell);
|
||||
position: relative;
|
||||
}
|
||||
.calendar__day__header,
|
||||
.calendar__day__cell {
|
||||
padding: var(--calendar-cell-size) 0 var(--calendar-cell-size);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.calendar {
|
||||
table-display: fixed;
|
||||
margin: 0 0;
|
||||
width: 100%;
|
||||
padding: 0 var(--calendar-horizontal-padding);
|
||||
}
|
||||
.calendar__day__cell {
|
||||
font-size: var(--font-size-calendar-cell-mobile);
|
||||
position: relative;
|
||||
}
|
||||
.calendar__day__header,
|
||||
.calendar__day__cell {
|
||||
padding: var(--calendar-cell-size-mobile) 0 var(--calendar-cell-size-mobile);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,9 +39,9 @@
|
|||
--font-size-likes: 20px;
|
||||
--font-size-likes-mobile: 32px;
|
||||
--font-size-pgp-key: 16px;
|
||||
--font-size-pgp-key2: 8px;
|
||||
--font-size-pgp-key2: 18px;
|
||||
--font-size-tox: 16px;
|
||||
--font-size-tox2: 8px;
|
||||
--font-size-tox2: 18px;
|
||||
--time-color: #aaa;
|
||||
--time-vertical-align: 4px;
|
||||
--time-vertical-align-mobile: 25px;
|
||||
|
@ -130,6 +130,8 @@
|
|||
--post-separator-width: 95%;
|
||||
--post-separator-height: 1px;
|
||||
--header-vertical-offset: 0;
|
||||
--profile-background-height: 25vw;
|
||||
--profile-text-align: left;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
@ -251,13 +253,61 @@ a:focus {
|
|||
border: 2px solid var(--focus-color);
|
||||
}
|
||||
|
||||
.hero-image {
|
||||
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("image.png");
|
||||
height: 50%;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
.profileHeader {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: 10px;
|
||||
width: 100%;
|
||||
color: #ffffff;
|
||||
text-align: var(--profile-text-align);
|
||||
line-height: 1.4em;
|
||||
background-color: #141414;
|
||||
}
|
||||
|
||||
.profileHeader .title {
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 25px;
|
||||
z-index: 1;
|
||||
max-width: 20%;
|
||||
opacity: 1;
|
||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.profileHeader * {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-transition: all 0.25s ease;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
.profileHeader img.profileBackground {
|
||||
object-fit: cover;
|
||||
max-height: var(--profile-background-height);
|
||||
width: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.profileHeader figcaption {
|
||||
width: 100%;
|
||||
background-color: var(--main-bg-color);
|
||||
color: var(--main-fg-color);
|
||||
padding: 25px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.profileHeader figcaption:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 55px 0 0 400px;
|
||||
border-color: transparent transparent transparent var(--main-bg-color);
|
||||
}
|
||||
|
||||
.rssfeed img {
|
||||
|
@ -292,10 +342,6 @@ a:focus {
|
|||
transform: translateY(30%) scaleX(-1);
|
||||
}
|
||||
|
||||
.hero-image img {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.new-post-text {
|
||||
font-size: var(--font-size2);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
|
@ -323,30 +369,6 @@ a:focus {
|
|||
background-color: var(--main-bg-color);
|
||||
}
|
||||
|
||||
.hero-text img.emoji {
|
||||
width: 50px;
|
||||
padding: 0 0;
|
||||
margin: 0 0;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.hero-text button {
|
||||
border: none;
|
||||
outline: 0;
|
||||
display: inline-block;
|
||||
padding: 10px 25px;
|
||||
color: black;
|
||||
background-color: #ddd;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.hero-text button:hover {
|
||||
background-color: var(--button-background);
|
||||
color: var(--button-text);
|
||||
}
|
||||
|
||||
.timelineIcon {
|
||||
width: 10%;
|
||||
}
|
||||
|
@ -487,16 +509,6 @@ a:focus {
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.hero-text img.emojiprofile {
|
||||
float: none;
|
||||
width: 50px;
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
padding-right: 0px;
|
||||
border-radius: 0px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.containericons {
|
||||
padding: 0px 0px;
|
||||
margin: 0px var(--containericons-horizontal-offset);
|
||||
|
@ -980,8 +992,9 @@ div.container {
|
|||
display: none;
|
||||
}
|
||||
.timeline-banner {
|
||||
object-fit: cover;
|
||||
width: 98vw;
|
||||
height: var(--banner-height);
|
||||
max-height: var(--banner-height);
|
||||
}
|
||||
.timeline {
|
||||
border: 0;
|
||||
|
@ -1481,26 +1494,6 @@ div.container {
|
|||
font-family: Arial, Helvetica, sans-serif;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.hero-text {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 70%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: var(--font-color-header);
|
||||
font-size: var(--font-size-header);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.hero-text img.qrcode {
|
||||
border-radius: 1%;
|
||||
width: 5%;
|
||||
min-width: 20px;
|
||||
}
|
||||
.hero-text img.title {
|
||||
border-radius: 1%;
|
||||
width: 15%;
|
||||
}
|
||||
input[type=checkbox]
|
||||
{
|
||||
-ms-transform: scale(2);
|
||||
|
@ -1651,8 +1644,9 @@ div.container {
|
|||
display: inline;
|
||||
}
|
||||
.timeline-banner {
|
||||
object-fit: cover;
|
||||
width: 98vw;
|
||||
height: var(--banner-height-mobile);
|
||||
max-height: var(--banner-height-mobile);
|
||||
}
|
||||
.timeline {
|
||||
border: 0;
|
||||
|
@ -2114,26 +2108,6 @@ div.container {
|
|||
font-family: Arial, Helvetica, sans-serif;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.hero-text {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 70%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: var(--font-color-header);
|
||||
font-size: var(--font-size-header-mobile);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.hero-text img.qrcode {
|
||||
border-radius: 1%;
|
||||
width: 15%;
|
||||
min-width: 20px;
|
||||
}
|
||||
.hero-text img.title {
|
||||
border-radius: 1%;
|
||||
width: 25%;
|
||||
}
|
||||
input[type=checkbox]
|
||||
{
|
||||
-ms-transform: scale(4);
|
||||
|
|
|
@ -193,8 +193,9 @@ input[type=text] {
|
|||
|
||||
@media screen and (min-width: 400px) {
|
||||
.timeline-banner {
|
||||
object-fit: cover;
|
||||
width: 98vw;
|
||||
height: var(--search-banner-height);
|
||||
max-height: var(--search-banner-height);
|
||||
}
|
||||
.hashtagswarm {
|
||||
font-size: var(--hashtag-size1);
|
||||
|
@ -232,20 +233,21 @@ input[type=text] {
|
|||
}
|
||||
input[type=checkbox]
|
||||
{
|
||||
-ms-transform: scale(2);
|
||||
-moz-transform: scale(2);
|
||||
-webkit-transform: scale(2);
|
||||
-o-transform: scale(2);
|
||||
transform: scale(2);
|
||||
padding: 10px;
|
||||
margin: 20px 30px;
|
||||
-ms-transform: scale(2);
|
||||
-moz-transform: scale(2);
|
||||
-webkit-transform: scale(2);
|
||||
-o-transform: scale(2);
|
||||
transform: scale(2);
|
||||
padding: 10px;
|
||||
margin: 20px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.timeline-banner {
|
||||
object-fit: cover;
|
||||
width: 98vw;
|
||||
height: var(--search-banner-height-mobile);
|
||||
max-height: var(--search-banner-height-mobile);
|
||||
}
|
||||
.hashtagswarm {
|
||||
font-size: var(--hashtag-size2);
|
||||
|
|
Before Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 125 KiB |
4
inbox.py
|
@ -57,7 +57,7 @@ from posts import isImageMedia
|
|||
from posts import sendSignedJson
|
||||
from posts import sendToFollowersThread
|
||||
from webapp import individualPostAsHtml
|
||||
from webapp import getIconsDir
|
||||
from webapp import getIconsWebPath
|
||||
from question import questionUpdateVotes
|
||||
from media import replaceYouTube
|
||||
from git import isGitPatch
|
||||
|
@ -139,7 +139,7 @@ def inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
|
|||
if boxname != 'tlevents' and boxname != 'outbox':
|
||||
boxname = 'inbox'
|
||||
individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
|
||||
getIconsDir(baseDir), translate, pageNumber,
|
||||
getIconsWebPath(baseDir), translate, pageNumber,
|
||||
baseDir, session, cachedWebfingers, personCache,
|
||||
nickname, domain, port, postJsonObject,
|
||||
avatarUrl, True, allowDeletion,
|
||||
|
|
14
person.py
|
@ -483,17 +483,19 @@ def createPerson(baseDir: str, nickname: str, domain: str, port: int,
|
|||
baseDir + '/accounts/' + nickname + '@' + domain +
|
||||
'/avatar.png')
|
||||
theme = getConfigParam(baseDir, 'theme')
|
||||
defaultProfileImageFilename = baseDir + '/img/image.png'
|
||||
if not theme:
|
||||
theme = 'default'
|
||||
defaultProfileImageFilename = baseDir + '/theme/default/image.png'
|
||||
if theme:
|
||||
if os.path.isfile(baseDir + '/img/image_' + theme + '.png'):
|
||||
defaultBannerFilename = baseDir + '/img/image_' + theme + '.png'
|
||||
if os.path.isfile(baseDir + '/theme/' + theme + '/image.png'):
|
||||
defaultBannerFilename = baseDir + '/theme/' + theme + '/image.png'
|
||||
if os.path.isfile(defaultProfileImageFilename):
|
||||
copyfile(defaultProfileImageFilename, baseDir +
|
||||
'/accounts/' + nickname + '@' + domain + '/image.png')
|
||||
defaultBannerFilename = baseDir + '/img/banner.png'
|
||||
defaultBannerFilename = baseDir + '/theme/default/banner.png'
|
||||
if theme:
|
||||
if os.path.isfile(baseDir + '/img/banner_' + theme + '.png'):
|
||||
defaultBannerFilename = baseDir + '/img/banner_' + theme + '.png'
|
||||
if os.path.isfile(baseDir + '/theme/' + theme + '/banner.png'):
|
||||
defaultBannerFilename = baseDir + '/theme/' + theme + '/banner.png'
|
||||
if os.path.isfile(defaultBannerFilename):
|
||||
copyfile(defaultBannerFilename, baseDir + '/accounts/' +
|
||||
nickname + '@' + domain + '/banner.png')
|
||||
|
|
20
tests.py
|
@ -1943,32 +1943,52 @@ def testDangerousMarkup():
|
|||
print('testDangerousMarkup')
|
||||
content = '<p>This is a valid message</p>'
|
||||
assert(not dangerousMarkup(content))
|
||||
|
||||
content = 'This is a valid message without markup'
|
||||
assert(not dangerousMarkup(content))
|
||||
|
||||
content = '<p>This is a valid-looking message. But wait... ' + \
|
||||
'<script>document.getElementById("concentrated")' + \
|
||||
'.innerHTML = "evil";</script></p>'
|
||||
assert(dangerousMarkup(content))
|
||||
|
||||
content = '<p>This is a valid-looking message. But wait... ' + \
|
||||
'<script src="https://evilsite/payload.js" /></p>'
|
||||
assert(dangerousMarkup(content))
|
||||
|
||||
content = '<p>This message embeds an evil frame.' + \
|
||||
'<iframe src="somesite"></iframe></p>'
|
||||
assert(dangerousMarkup(content))
|
||||
|
||||
content = '<p>This message tries to obfuscate an evil frame.' + \
|
||||
'< iframe src = "somesite"></ iframe ></p>'
|
||||
assert(dangerousMarkup(content))
|
||||
|
||||
content = '<p>This message is not necessarily evil, but annoying.' + \
|
||||
'<hr><br><br><br><br><br><br><br><hr><hr></p>'
|
||||
assert(dangerousMarkup(content))
|
||||
|
||||
content = '<p>This message contans a ' + \
|
||||
'<a href="https://validsite/index.html">valid link.</a></p>'
|
||||
assert(not dangerousMarkup(content))
|
||||
|
||||
content = '<p>This message contans a ' + \
|
||||
'<a href="https://validsite/iframe.html">' + \
|
||||
'valid link having invalid but harmless name.</a></p>'
|
||||
assert(not dangerousMarkup(content))
|
||||
|
||||
content = '<p>This message which <a href="127.0.0.1:8736">' + \
|
||||
'tries to access the local network</a></p>'
|
||||
assert(dangerousMarkup(content))
|
||||
|
||||
content = '<p>This message which <a href="http://192.168.5.10:7235">' + \
|
||||
'tries to access the local network</a></p>'
|
||||
assert(dangerousMarkup(content))
|
||||
|
||||
content = '<p>127.0.0.1 This message which does not access ' + \
|
||||
'the local network</a></p>'
|
||||
assert(not dangerousMarkup(content))
|
||||
|
||||
|
||||
def runHtmlReplaceQuoteMarks():
|
||||
print('htmlReplaceQuoteMarks')
|
||||
|
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 978 B After Width: | Height: | Size: 978 B |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"newswire-publish-icon": "True",
|
||||
"full-width-timeline-buttons": "False",
|
||||
"icons-as-buttons": "False",
|
||||
"rss-icon-at-top": "True",
|
||||
"publish-button-at-top": "False",
|
||||
"banner-height": "20vh",
|
||||
"banner-height-mobile": "10vh",
|
||||
"newswire-date-color": "blue",
|
||||
"font-size-header": "22px",
|
||||
"font-size-header-mobile": "32px",
|
||||
"font-size": "45px",
|
||||
"font-size2": "45px",
|
||||
"font-size3": "45px",
|
||||
"font-size4": "35px",
|
||||
"font-size5": "29px",
|
||||
"gallery-font-size": "35px",
|
||||
"gallery-font-size-mobile": "55px",
|
||||
"main-bg-color": "#002365",
|
||||
"column-left-color": "#002365",
|
||||
"text-entry-background": "#002365",
|
||||
"link-bg-color": "#002365",
|
||||
"main-bg-color-reply": "#002365",
|
||||
"main-bg-color-report": "#002365",
|
||||
"day-number2": "#002365",
|
||||
"hashtag-vertical-spacing3": "100px",
|
||||
"hashtag-vertical-spacing4": "150px",
|
||||
"time-vertical-align": "-10px",
|
||||
"*font-family": "'Domestic_Manners'",
|
||||
"*src": "url('./fonts/Domestic_Manners.woff2') format('woff2')"
|
||||
}
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 200 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 981 B After Width: | Height: | Size: 981 B |
Before Width: | Height: | Size: 978 B After Width: | Height: | Size: 978 B |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 864 B After Width: | Height: | Size: 864 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |