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
|
## 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
|
## Running Unit Tests
|
||||||
|
|
|
||||||
274
blog.py
|
|
@ -10,8 +10,8 @@ import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from content import replaceEmojiFromTags
|
from content import replaceEmojiFromTags
|
||||||
from webapp import getIconsDir
|
from webapp import getIconsWebPath
|
||||||
from webapp import htmlHeader
|
from webapp import htmlHeaderWithExternalStyle
|
||||||
from webapp import htmlFooter
|
from webapp import htmlFooter
|
||||||
from webapp_media import addEmbeddedElements
|
from webapp_media import addEmbeddedElements
|
||||||
from webapp_utils import getPostAttachmentsAsHtml
|
from webapp_utils import getPostAttachmentsAsHtml
|
||||||
|
|
@ -381,39 +381,36 @@ def htmlBlogPost(authorized: bool,
|
||||||
cssFilename = baseDir + '/epicyon-blog.css'
|
cssFilename = baseDir + '/epicyon-blog.css'
|
||||||
if os.path.isfile(baseDir + '/blog.css'):
|
if os.path.isfile(baseDir + '/blog.css'):
|
||||||
cssFilename = baseDir + '/blog.css'
|
cssFilename = baseDir + '/blog.css'
|
||||||
with open(cssFilename, 'r') as cssFile:
|
blogStr = htmlHeaderWithExternalStyle(cssFilename)
|
||||||
blogCSS = cssFile.read()
|
htmlBlogRemoveCwButton(blogStr, translate)
|
||||||
blogStr = htmlHeader(cssFilename, blogCSS)
|
|
||||||
htmlBlogRemoveCwButton(blogStr, translate)
|
|
||||||
|
|
||||||
blogStr += htmlBlogPostContent(authorized, baseDir,
|
blogStr += htmlBlogPostContent(authorized, baseDir,
|
||||||
httpPrefix, translate,
|
httpPrefix, translate,
|
||||||
nickname, domain,
|
nickname, domain,
|
||||||
domainFull, postJsonObject,
|
domainFull, postJsonObject,
|
||||||
None, False)
|
None, False)
|
||||||
|
|
||||||
# show rss links
|
# show rss links
|
||||||
iconsDir = getIconsDir(baseDir)
|
iconsPath = getIconsWebPath(baseDir)
|
||||||
blogStr += '<p class="rssfeed">'
|
blogStr += '<p class="rssfeed">'
|
||||||
|
|
||||||
blogStr += '<a href="' + httpPrefix + '://' + \
|
blogStr += '<a href="' + httpPrefix + '://' + \
|
||||||
domainFull + '/blog/' + nickname + '/rss.xml">'
|
domainFull + '/blog/' + nickname + '/rss.xml">'
|
||||||
blogStr += '<img style="width:3%;min-width:50px" ' + \
|
blogStr += '<img style="width:3%;min-width:50px" ' + \
|
||||||
'loading="lazy" alt="RSS 2.0" ' + \
|
'loading="lazy" alt="RSS 2.0" ' + \
|
||||||
'title="RSS 2.0" src="/' + \
|
'title="RSS 2.0" src="/' + \
|
||||||
iconsDir + '/logorss.png" /></a>'
|
iconsPath + '/logorss.png" /></a>'
|
||||||
|
|
||||||
# blogStr += '<a href="' + httpPrefix + '://' + \
|
# blogStr += '<a href="' + httpPrefix + '://' + \
|
||||||
# domainFull + '/blog/' + nickname + '/rss.txt">'
|
# domainFull + '/blog/' + nickname + '/rss.txt">'
|
||||||
# blogStr += '<img style="width:3%;min-width:50px" ' + \
|
# blogStr += '<img style="width:3%;min-width:50px" ' + \
|
||||||
# 'loading="lazy" alt="RSS 3.0" ' + \
|
# 'loading="lazy" alt="RSS 3.0" ' + \
|
||||||
# 'title="RSS 3.0" src="/' + \
|
# 'title="RSS 3.0" src="/' + \
|
||||||
# iconsDir + '/rss3.png" /></a>'
|
# iconsPath + '/rss3.png" /></a>'
|
||||||
|
|
||||||
blogStr += '</p>'
|
blogStr += '</p>'
|
||||||
|
|
||||||
return blogStr + htmlFooter()
|
return blogStr + htmlFooter()
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def htmlBlogPage(authorized: bool, session,
|
def htmlBlogPage(authorized: bool, session,
|
||||||
|
|
@ -430,85 +427,81 @@ def htmlBlogPage(authorized: bool, session,
|
||||||
cssFilename = baseDir + '/epicyon-profile.css'
|
cssFilename = baseDir + '/epicyon-profile.css'
|
||||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||||
cssFilename = baseDir + '/epicyon.css'
|
cssFilename = baseDir + '/epicyon.css'
|
||||||
with open(cssFilename, 'r') as cssFile:
|
blogStr = htmlHeaderWithExternalStyle(cssFilename)
|
||||||
blogCSS = cssFile.read()
|
htmlBlogRemoveCwButton(blogStr, translate)
|
||||||
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>'
|
|
||||||
|
|
||||||
|
blogsIndex = baseDir + '/accounts/' + \
|
||||||
|
nickname + '@' + domain + '/tlblogs.index'
|
||||||
|
if not os.path.isfile(blogsIndex):
|
||||||
return blogStr + htmlFooter()
|
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,
|
def htmlBlogPageRSS2(authorized: bool, session,
|
||||||
|
|
@ -678,40 +671,37 @@ def htmlBlogView(authorized: bool,
|
||||||
cssFilename = baseDir + '/epicyon-profile.css'
|
cssFilename = baseDir + '/epicyon-profile.css'
|
||||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||||
cssFilename = baseDir + '/epicyon.css'
|
cssFilename = baseDir + '/epicyon.css'
|
||||||
with open(cssFilename, 'r') as cssFile:
|
blogStr = htmlHeaderWithExternalStyle(cssFilename)
|
||||||
blogCSS = cssFile.read()
|
|
||||||
blogStr = htmlHeader(cssFilename, blogCSS)
|
|
||||||
|
|
||||||
if noOfBlogAccounts(baseDir) <= 1:
|
if noOfBlogAccounts(baseDir) <= 1:
|
||||||
nickname = singleBlogAccountNickname(baseDir)
|
nickname = singleBlogAccountNickname(baseDir)
|
||||||
if nickname:
|
if nickname:
|
||||||
return htmlBlogPage(authorized, session,
|
return htmlBlogPage(authorized, session,
|
||||||
baseDir, httpPrefix, translate,
|
baseDir, httpPrefix, translate,
|
||||||
nickname, domain, port,
|
nickname, domain, port,
|
||||||
noOfItems, 1)
|
noOfItems, 1)
|
||||||
|
|
||||||
domainFull = domain
|
domainFull = domain
|
||||||
if port:
|
if port:
|
||||||
if port != 80 and port != 443:
|
if port != 80 and port != 443:
|
||||||
domainFull = domain + ':' + str(port)
|
domainFull = domain + ':' + str(port)
|
||||||
|
|
||||||
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
|
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
|
||||||
for acct in dirs:
|
for acct in dirs:
|
||||||
if '@' not in acct:
|
if '@' not in acct:
|
||||||
continue
|
continue
|
||||||
if 'inbox@' in acct:
|
if 'inbox@' in acct:
|
||||||
continue
|
continue
|
||||||
accountDir = os.path.join(baseDir + '/accounts', acct)
|
accountDir = os.path.join(baseDir + '/accounts', acct)
|
||||||
blogsIndex = accountDir + '/tlblogs.index'
|
blogsIndex = accountDir + '/tlblogs.index'
|
||||||
if os.path.isfile(blogsIndex):
|
if os.path.isfile(blogsIndex):
|
||||||
blogStr += '<p class="blogaccount">'
|
blogStr += '<p class="blogaccount">'
|
||||||
blogStr += '<a href="' + \
|
blogStr += '<a href="' + \
|
||||||
httpPrefix + '://' + domainFull + '/blog/' + \
|
httpPrefix + '://' + domainFull + '/blog/' + \
|
||||||
acct.split('@')[0] + '">' + acct + '</a>'
|
acct.split('@')[0] + '">' + acct + '</a>'
|
||||||
blogStr += '</p>'
|
blogStr += '</p>'
|
||||||
|
|
||||||
return blogStr + htmlFooter()
|
return blogStr + htmlFooter()
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def htmlEditBlog(mediaInstance: bool, translate: {},
|
def htmlEditBlog(mediaInstance: bool, translate: {},
|
||||||
|
|
@ -732,7 +722,7 @@ def htmlEditBlog(mediaInstance: bool, translate: {},
|
||||||
print('Edit blog: json not loaded for ' + postFilename)
|
print('Edit blog: json not loaded for ' + postFilename)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
iconsDir = getIconsDir(baseDir)
|
iconsPath = getIconsWebPath(baseDir)
|
||||||
|
|
||||||
editBlogText = '<p class="new-post-text">' + \
|
editBlogText = '<p class="new-post-text">' + \
|
||||||
translate['Write your post text below.'] + '</p>'
|
translate['Write your post text below.'] + '</p>'
|
||||||
|
|
@ -744,10 +734,6 @@ def htmlEditBlog(mediaInstance: bool, translate: {},
|
||||||
cssFilename = baseDir + '/epicyon-profile.css'
|
cssFilename = baseDir + '/epicyon-profile.css'
|
||||||
if os.path.isfile(baseDir + '/epicyon.css'):
|
if os.path.isfile(baseDir + '/epicyon.css'):
|
||||||
cssFilename = 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:
|
if '?' in path:
|
||||||
path = path.split('?')[0]
|
path = path.split('?')[0]
|
||||||
|
|
@ -781,7 +767,7 @@ def htmlEditBlog(mediaInstance: bool, translate: {},
|
||||||
dateAndLocation += \
|
dateAndLocation += \
|
||||||
'<p><img loading="lazy" alt="" title="" ' + \
|
'<p><img loading="lazy" alt="" title="" ' + \
|
||||||
'class="emojicalendar" src="/' + \
|
'class="emojicalendar" src="/' + \
|
||||||
iconsDir + '/calendar.png"/>'
|
iconsPath + '/calendar.png"/>'
|
||||||
dateAndLocation += \
|
dateAndLocation += \
|
||||||
'<label class="labels">' + translate['Date'] + ': </label>'
|
'<label class="labels">' + translate['Date'] + ': </label>'
|
||||||
dateAndLocation += '<input type="date" name="eventDate">'
|
dateAndLocation += '<input type="date" name="eventDate">'
|
||||||
|
|
@ -794,7 +780,7 @@ def htmlEditBlog(mediaInstance: bool, translate: {},
|
||||||
dateAndLocation += '<input type="text" name="location">'
|
dateAndLocation += '<input type="text" name="location">'
|
||||||
dateAndLocation += '</div>'
|
dateAndLocation += '</div>'
|
||||||
|
|
||||||
editBlogForm = htmlHeader(cssFilename, editBlogCSS)
|
editBlogForm = htmlHeaderWithExternalStyle(cssFilename)
|
||||||
|
|
||||||
editBlogForm += \
|
editBlogForm += \
|
||||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||||
|
|
@ -812,7 +798,7 @@ def htmlEditBlog(mediaInstance: bool, translate: {},
|
||||||
|
|
||||||
editBlogForm += ' <div class="dropbtn">'
|
editBlogForm += ' <div class="dropbtn">'
|
||||||
editBlogForm += \
|
editBlogForm += \
|
||||||
' <img loading="lazy" alt="" title="" src="/' + iconsDir + \
|
' <img loading="lazy" alt="" title="" src="/' + iconsPath + \
|
||||||
'/' + scopeIcon + '"/><b class="scope-desc">' + \
|
'/' + scopeIcon + '"/><b class="scope-desc">' + \
|
||||||
scopeDescription + '</b>'
|
scopeDescription + '</b>'
|
||||||
editBlogForm += ' </div>'
|
editBlogForm += ' </div>'
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,7 @@ def dangerousMarkup(content: str) -> bool:
|
||||||
if '>' not in content:
|
if '>' not in content:
|
||||||
return False
|
return False
|
||||||
contentSections = content.split('<')
|
contentSections = content.split('<')
|
||||||
|
invalidPartials = ('127.0.', '192.168', '10.0.')
|
||||||
invalidStrings = ('script', 'canvas', 'style', 'abbr',
|
invalidStrings = ('script', 'canvas', 'style', 'abbr',
|
||||||
'frame', 'iframe', 'html', 'body',
|
'frame', 'iframe', 'html', 'body',
|
||||||
'hr')
|
'hr')
|
||||||
|
|
@ -166,6 +167,9 @@ def dangerousMarkup(content: str) -> bool:
|
||||||
if '>' not in markup:
|
if '>' not in markup:
|
||||||
continue
|
continue
|
||||||
markup = markup.split('>')[0].strip()
|
markup = markup.split('>')[0].strip()
|
||||||
|
for partialMatch in invalidPartials:
|
||||||
|
if partialMatch in markup:
|
||||||
|
return True
|
||||||
if ' ' not in markup:
|
if ' ' not in markup:
|
||||||
for badStr in invalidStrings:
|
for badStr in invalidStrings:
|
||||||
if badStr in markup:
|
if badStr in markup:
|
||||||
|
|
|
||||||
150
daemon.py
|
|
@ -166,6 +166,7 @@ from shares import getSharesFeedForPerson
|
||||||
from shares import addShare
|
from shares import addShare
|
||||||
from shares import removeShare
|
from shares import removeShare
|
||||||
from shares import expireShares
|
from shares import expireShares
|
||||||
|
from utils import mediaFileMimeType
|
||||||
from utils import getCSS
|
from utils import getCSS
|
||||||
from utils import firstParagraphFromString
|
from utils import firstParagraphFromString
|
||||||
from utils import clearFromPostCaches
|
from utils import clearFromPostCaches
|
||||||
|
|
@ -2441,7 +2442,8 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.debug,
|
self.server.debug,
|
||||||
self.server.projectVersion,
|
self.server.projectVersion,
|
||||||
self.server.YTReplacementDomain,
|
self.server.YTReplacementDomain,
|
||||||
self.server.showPublishedDateOnly)
|
self.server.showPublishedDateOnly,
|
||||||
|
self.server.defaultTimeline)
|
||||||
if profileStr:
|
if profileStr:
|
||||||
msg = profileStr.encode('utf-8')
|
msg = profileStr.encode('utf-8')
|
||||||
self._login_headers('text/html',
|
self._login_headers('text/html',
|
||||||
|
|
@ -3408,6 +3410,8 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.POSTbusy = False
|
self.server.POSTbusy = False
|
||||||
return
|
return
|
||||||
|
|
||||||
|
adminNickname = getConfigParam(self.server.baseDir, 'admin')
|
||||||
|
|
||||||
# get the various avatar, banner and background images
|
# get the various avatar, banner and background images
|
||||||
actorChanged = True
|
actorChanged = True
|
||||||
profileMediaTypes = ('avatar', 'image',
|
profileMediaTypes = ('avatar', 'image',
|
||||||
|
|
@ -3416,6 +3420,13 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
'left_col_image', 'right_col_image')
|
'left_col_image', 'right_col_image')
|
||||||
profileMediaTypesUploaded = {}
|
profileMediaTypesUploaded = {}
|
||||||
for mType in profileMediaTypes:
|
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:
|
if debug:
|
||||||
print('DEBUG: profile update extracting ' + mType +
|
print('DEBUG: profile update extracting ' + mType +
|
||||||
' image or font from POST')
|
' image or font from POST')
|
||||||
|
|
@ -4371,12 +4382,16 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
if 'image/avif' in self.headers['Accept']:
|
if 'image/avif' in self.headers['Accept']:
|
||||||
favType = 'image/avif'
|
favType = 'image/avif'
|
||||||
favFilename = 'favicon.avif'
|
favFilename = 'favicon.avif'
|
||||||
|
themeName = getConfigParam(baseDir, 'theme')
|
||||||
|
if not themeName:
|
||||||
|
themeName = 'default'
|
||||||
# custom favicon
|
# custom favicon
|
||||||
faviconFilename = baseDir + '/' + favFilename
|
faviconFilename = \
|
||||||
|
baseDir + '/theme/' + themeName + '/icons/' + favFilename
|
||||||
if not os.path.isfile(faviconFilename):
|
if not os.path.isfile(faviconFilename):
|
||||||
# default favicon
|
# default favicon
|
||||||
faviconFilename = \
|
faviconFilename = \
|
||||||
baseDir + '/img/icons/' + favFilename
|
baseDir + '/theme/default/icons/' + favFilename
|
||||||
if self._etag_exists(faviconFilename):
|
if self._etag_exists(faviconFilename):
|
||||||
# The file has not changed
|
# The file has not changed
|
||||||
if debug:
|
if debug:
|
||||||
|
|
@ -4764,25 +4779,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self._304()
|
self._304()
|
||||||
return
|
return
|
||||||
|
|
||||||
mediaFileType = 'image/png'
|
mediaFileType = mediaFileMimeType(mediaFilename)
|
||||||
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'
|
|
||||||
|
|
||||||
with open(mediaFilename, 'rb') as avFile:
|
with open(mediaFilename, 'rb') as avFile:
|
||||||
mediaBinary = avFile.read()
|
mediaBinary = avFile.read()
|
||||||
|
|
@ -4841,7 +4838,13 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
"""
|
"""
|
||||||
if path.endswith('.png'):
|
if path.endswith('.png'):
|
||||||
mediaStr = path.split('/icons/')[1]
|
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):
|
if self._etag_exists(mediaFilename):
|
||||||
# The file has not changed
|
# The file has not changed
|
||||||
self._304()
|
self._304()
|
||||||
|
|
@ -4849,7 +4852,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
if self.server.iconsCache.get(mediaStr):
|
if self.server.iconsCache.get(mediaStr):
|
||||||
mediaBinary = self.server.iconsCache[mediaStr]
|
mediaBinary = self.server.iconsCache[mediaStr]
|
||||||
self._set_headers_etag(mediaFilename,
|
self._set_headers_etag(mediaFilename,
|
||||||
'image/png',
|
mediaFileMimeType(mediaFilename),
|
||||||
mediaBinary, None,
|
mediaBinary, None,
|
||||||
callingDomain)
|
callingDomain)
|
||||||
self._write(mediaBinary)
|
self._write(mediaBinary)
|
||||||
|
|
@ -4858,8 +4861,9 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
if os.path.isfile(mediaFilename):
|
if os.path.isfile(mediaFilename):
|
||||||
with open(mediaFilename, 'rb') as avFile:
|
with open(mediaFilename, 'rb') as avFile:
|
||||||
mediaBinary = avFile.read()
|
mediaBinary = avFile.read()
|
||||||
|
mimeType = mediaFileMimeType(mediaFilename)
|
||||||
self._set_headers_etag(mediaFilename,
|
self._set_headers_etag(mediaFilename,
|
||||||
'image/png',
|
mimeType,
|
||||||
mediaBinary, None,
|
mediaBinary, None,
|
||||||
callingDomain)
|
callingDomain)
|
||||||
self._write(mediaBinary)
|
self._write(mediaBinary)
|
||||||
|
|
@ -4883,39 +4887,11 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
return
|
return
|
||||||
with open(mediaFilename, 'rb') as avFile:
|
with open(mediaFilename, 'rb') as avFile:
|
||||||
mediaBinary = avFile.read()
|
mediaBinary = avFile.read()
|
||||||
if mediaFilename.endswith('.png'):
|
mimeType = mediaFileMimeType(mediaFilename)
|
||||||
self._set_headers_etag(mediaFilename,
|
self._set_headers_etag(mediaFilename,
|
||||||
'image/png',
|
mimeType,
|
||||||
mediaBinary, None,
|
mediaBinary, None,
|
||||||
callingDomain)
|
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
|
|
||||||
self._write(mediaBinary)
|
self._write(mediaBinary)
|
||||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||||
'icon shown done',
|
'icon shown done',
|
||||||
|
|
@ -8327,7 +8303,8 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
tries += 1
|
tries += 1
|
||||||
if mediaBinary:
|
if mediaBinary:
|
||||||
self._set_headers_etag(qrFilename, 'image/png',
|
mimeType = mediaFileMimeType(qrFilename)
|
||||||
|
self._set_headers_etag(qrFilename, mimeType,
|
||||||
mediaBinary, None,
|
mediaBinary, None,
|
||||||
callingDomain)
|
callingDomain)
|
||||||
self._write(mediaBinary)
|
self._write(mediaBinary)
|
||||||
|
|
@ -8365,7 +8342,8 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
tries += 1
|
tries += 1
|
||||||
if mediaBinary:
|
if mediaBinary:
|
||||||
self._set_headers_etag(bannerFilename, 'image/png',
|
mimeType = mediaFileMimeType(bannerFilename)
|
||||||
|
self._set_headers_etag(bannerFilename, mimeType,
|
||||||
mediaBinary, None,
|
mediaBinary, None,
|
||||||
callingDomain)
|
callingDomain)
|
||||||
self._write(mediaBinary)
|
self._write(mediaBinary)
|
||||||
|
|
@ -8406,7 +8384,8 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
tries += 1
|
tries += 1
|
||||||
if mediaBinary:
|
if mediaBinary:
|
||||||
self._set_headers_etag(bannerFilename, 'image/png',
|
mimeType = mediaFileMimeType(bannerFilename)
|
||||||
|
self._set_headers_etag(bannerFilename, mimeType,
|
||||||
mediaBinary, None,
|
mediaBinary, None,
|
||||||
callingDomain)
|
callingDomain)
|
||||||
self._write(mediaBinary)
|
self._write(mediaBinary)
|
||||||
|
|
@ -8955,6 +8934,13 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||||
'create session', 'hasAccept')
|
'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
|
# get fonts
|
||||||
if '/fonts/' in self.path:
|
if '/fonts/' in self.path:
|
||||||
self._getFonts(callingDomain, self.path,
|
self._getFonts(callingDomain, self.path,
|
||||||
|
|
@ -9316,17 +9302,6 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
'robots txt',
|
'robots txt',
|
||||||
'show login screen done')
|
'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
|
# manifest images used to create a home screen icon
|
||||||
# when selecting "add to home screen" in browsers
|
# when selecting "add to home screen" in browsers
|
||||||
# which support progressive web apps
|
# which support progressive web apps
|
||||||
|
|
@ -9358,8 +9333,8 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
tries += 1
|
tries += 1
|
||||||
if mediaBinary:
|
if mediaBinary:
|
||||||
self._set_headers_etag(mediaFilename,
|
mimeType = mediaFileMimeType(mediaFilename)
|
||||||
'image/png',
|
self._set_headers_etag(mediaFilename, mimeType,
|
||||||
mediaBinary, cookie,
|
mediaBinary, cookie,
|
||||||
callingDomain)
|
callingDomain)
|
||||||
self._write(mediaBinary)
|
self._write(mediaBinary)
|
||||||
|
|
@ -9398,8 +9373,8 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
tries += 1
|
tries += 1
|
||||||
if mediaBinary:
|
if mediaBinary:
|
||||||
self._set_headers_etag(screenFilename,
|
mimeType = mediaFileMimeType(screenFilename)
|
||||||
'image/png',
|
self._set_headers_etag(screenFilename, mimeType,
|
||||||
mediaBinary, cookie,
|
mediaBinary, cookie,
|
||||||
callingDomain)
|
callingDomain)
|
||||||
self._write(mediaBinary)
|
self._write(mediaBinary)
|
||||||
|
|
@ -9443,7 +9418,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
tries += 1
|
tries += 1
|
||||||
if mediaBinary:
|
if mediaBinary:
|
||||||
self._set_headers_etag(iconFilename,
|
self._set_headers_etag(iconFilename,
|
||||||
'image/png',
|
mediaFileMimeType(iconFilename),
|
||||||
mediaBinary, cookie,
|
mediaBinary, cookie,
|
||||||
callingDomain)
|
callingDomain)
|
||||||
self._write(mediaBinary)
|
self._write(mediaBinary)
|
||||||
|
|
@ -10815,26 +10790,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
except BaseException:
|
except BaseException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
mediaFileType = 'application/json'
|
mediaFileType = mediaFileMimeType(checkPath)
|
||||||
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'
|
|
||||||
|
|
||||||
self._set_headers_head(mediaFileType, fileLength,
|
self._set_headers_head(mediaFileType, fileLength,
|
||||||
etag, callingDomain)
|
etag, callingDomain)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,12 @@
|
||||||
--title-background: #ccc;
|
--title-background: #ccc;
|
||||||
--focus-color: white;
|
--focus-color: white;
|
||||||
--calendar-horizontal-padding: 0;
|
--calendar-horizontal-padding: 0;
|
||||||
|
--calendar-cell-size: 1.5vw;
|
||||||
|
--calendar-cell-size-mobile: 1.5vw;
|
||||||
--font-size-calendar-header: 3rem;
|
--font-size-calendar-header: 3rem;
|
||||||
--font-size-calendar-day: 1rem;
|
--font-size-calendar-day: 1rem;
|
||||||
--font-size-calendar-cell: 4rem;
|
--font-size-calendar-cell: 2rem;
|
||||||
|
--font-size-calendar-cell-mobile: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
|
@ -38,15 +41,8 @@
|
||||||
body {
|
body {
|
||||||
background-color: var(--main-bg-color);
|
background-color: var(--main-bg-color);
|
||||||
color: var(--day-number2);
|
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-family: 'Montserrat';
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin: 5%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
|
|
@ -55,12 +51,6 @@ main {
|
||||||
flex-basis: 980px;
|
flex-basis: 980px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar {
|
|
||||||
table-display: fixed;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0 var(--calendar-horizontal-padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
a:visited{
|
a:visited{
|
||||||
color: var(--day-number);
|
color: var(--day-number);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
@ -97,11 +87,6 @@ a:focus {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar__day__header,
|
|
||||||
.calendar__day__cell {
|
|
||||||
padding: .75rem 0 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar__banner--month {
|
.calendar__banner--month {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: .75rem;
|
padding: .75rem;
|
||||||
|
|
@ -123,11 +108,6 @@ a:focus {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar__day__cell {
|
|
||||||
font-size: var(--font-size-calendar-cell);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.year {
|
.year {
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
}
|
}
|
||||||
|
|
@ -210,3 +190,37 @@ tr:nth-child(even) > .calendar__day__cell:nth-child(even) {
|
||||||
-ms-transform: translateY(30%) scaleX(-1);
|
-ms-transform: translateY(30%) scaleX(-1);
|
||||||
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: 20px;
|
||||||
--font-size-likes-mobile: 32px;
|
--font-size-likes-mobile: 32px;
|
||||||
--font-size-pgp-key: 16px;
|
--font-size-pgp-key: 16px;
|
||||||
--font-size-pgp-key2: 8px;
|
--font-size-pgp-key2: 18px;
|
||||||
--font-size-tox: 16px;
|
--font-size-tox: 16px;
|
||||||
--font-size-tox2: 8px;
|
--font-size-tox2: 18px;
|
||||||
--time-color: #aaa;
|
--time-color: #aaa;
|
||||||
--time-vertical-align: 4px;
|
--time-vertical-align: 4px;
|
||||||
--time-vertical-align-mobile: 25px;
|
--time-vertical-align-mobile: 25px;
|
||||||
|
|
@ -130,6 +130,8 @@
|
||||||
--post-separator-width: 95%;
|
--post-separator-width: 95%;
|
||||||
--post-separator-height: 1px;
|
--post-separator-height: 1px;
|
||||||
--header-vertical-offset: 0;
|
--header-vertical-offset: 0;
|
||||||
|
--profile-background-height: 25vw;
|
||||||
|
--profile-text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
|
@ -251,13 +253,61 @@ a:focus {
|
||||||
border: 2px solid var(--focus-color);
|
border: 2px solid var(--focus-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-image {
|
.profileHeader {
|
||||||
background-image: linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)), url("image.png");
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
height: 50%;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
position: relative;
|
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 {
|
.rssfeed img {
|
||||||
|
|
@ -292,10 +342,6 @@ a:focus {
|
||||||
transform: translateY(30%) scaleX(-1);
|
transform: translateY(30%) scaleX(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-image img {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-post-text {
|
.new-post-text {
|
||||||
font-size: var(--font-size2);
|
font-size: var(--font-size2);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
|
@ -323,30 +369,6 @@ a:focus {
|
||||||
background-color: var(--main-bg-color);
|
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 {
|
.timelineIcon {
|
||||||
width: 10%;
|
width: 10%;
|
||||||
}
|
}
|
||||||
|
|
@ -487,16 +509,6 @@ a:focus {
|
||||||
vertical-align: middle;
|
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 {
|
.containericons {
|
||||||
padding: 0px 0px;
|
padding: 0px 0px;
|
||||||
margin: 0px var(--containericons-horizontal-offset);
|
margin: 0px var(--containericons-horizontal-offset);
|
||||||
|
|
@ -980,8 +992,9 @@ div.container {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.timeline-banner {
|
.timeline-banner {
|
||||||
|
object-fit: cover;
|
||||||
width: 98vw;
|
width: 98vw;
|
||||||
height: var(--banner-height);
|
max-height: var(--banner-height);
|
||||||
}
|
}
|
||||||
.timeline {
|
.timeline {
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
@ -1481,26 +1494,6 @@ div.container {
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
opacity: 0.7;
|
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]
|
input[type=checkbox]
|
||||||
{
|
{
|
||||||
-ms-transform: scale(2);
|
-ms-transform: scale(2);
|
||||||
|
|
@ -1651,8 +1644,9 @@ div.container {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
.timeline-banner {
|
.timeline-banner {
|
||||||
|
object-fit: cover;
|
||||||
width: 98vw;
|
width: 98vw;
|
||||||
height: var(--banner-height-mobile);
|
max-height: var(--banner-height-mobile);
|
||||||
}
|
}
|
||||||
.timeline {
|
.timeline {
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
@ -2114,26 +2108,6 @@ div.container {
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
opacity: 0.7;
|
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]
|
input[type=checkbox]
|
||||||
{
|
{
|
||||||
-ms-transform: scale(4);
|
-ms-transform: scale(4);
|
||||||
|
|
|
||||||
|
|
@ -193,8 +193,9 @@ input[type=text] {
|
||||||
|
|
||||||
@media screen and (min-width: 400px) {
|
@media screen and (min-width: 400px) {
|
||||||
.timeline-banner {
|
.timeline-banner {
|
||||||
|
object-fit: cover;
|
||||||
width: 98vw;
|
width: 98vw;
|
||||||
height: var(--search-banner-height);
|
max-height: var(--search-banner-height);
|
||||||
}
|
}
|
||||||
.hashtagswarm {
|
.hashtagswarm {
|
||||||
font-size: var(--hashtag-size1);
|
font-size: var(--hashtag-size1);
|
||||||
|
|
@ -232,20 +233,21 @@ input[type=text] {
|
||||||
}
|
}
|
||||||
input[type=checkbox]
|
input[type=checkbox]
|
||||||
{
|
{
|
||||||
-ms-transform: scale(2);
|
-ms-transform: scale(2);
|
||||||
-moz-transform: scale(2);
|
-moz-transform: scale(2);
|
||||||
-webkit-transform: scale(2);
|
-webkit-transform: scale(2);
|
||||||
-o-transform: scale(2);
|
-o-transform: scale(2);
|
||||||
transform: scale(2);
|
transform: scale(2);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 20px 30px;
|
margin: 20px 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1000px) {
|
@media screen and (max-width: 1000px) {
|
||||||
.timeline-banner {
|
.timeline-banner {
|
||||||
|
object-fit: cover;
|
||||||
width: 98vw;
|
width: 98vw;
|
||||||
height: var(--search-banner-height-mobile);
|
max-height: var(--search-banner-height-mobile);
|
||||||
}
|
}
|
||||||
.hashtagswarm {
|
.hashtagswarm {
|
||||||
font-size: var(--hashtag-size2);
|
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 sendSignedJson
|
||||||
from posts import sendToFollowersThread
|
from posts import sendToFollowersThread
|
||||||
from webapp import individualPostAsHtml
|
from webapp import individualPostAsHtml
|
||||||
from webapp import getIconsDir
|
from webapp import getIconsWebPath
|
||||||
from question import questionUpdateVotes
|
from question import questionUpdateVotes
|
||||||
from media import replaceYouTube
|
from media import replaceYouTube
|
||||||
from git import isGitPatch
|
from git import isGitPatch
|
||||||
|
|
@ -139,7 +139,7 @@ def inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
|
||||||
if boxname != 'tlevents' and boxname != 'outbox':
|
if boxname != 'tlevents' and boxname != 'outbox':
|
||||||
boxname = 'inbox'
|
boxname = 'inbox'
|
||||||
individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
|
individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
|
||||||
getIconsDir(baseDir), translate, pageNumber,
|
getIconsWebPath(baseDir), translate, pageNumber,
|
||||||
baseDir, session, cachedWebfingers, personCache,
|
baseDir, session, cachedWebfingers, personCache,
|
||||||
nickname, domain, port, postJsonObject,
|
nickname, domain, port, postJsonObject,
|
||||||
avatarUrl, True, allowDeletion,
|
avatarUrl, True, allowDeletion,
|
||||||
|
|
|
||||||
14
person.py
|
|
@ -483,17 +483,19 @@ def createPerson(baseDir: str, nickname: str, domain: str, port: int,
|
||||||
baseDir + '/accounts/' + nickname + '@' + domain +
|
baseDir + '/accounts/' + nickname + '@' + domain +
|
||||||
'/avatar.png')
|
'/avatar.png')
|
||||||
theme = getConfigParam(baseDir, 'theme')
|
theme = getConfigParam(baseDir, 'theme')
|
||||||
defaultProfileImageFilename = baseDir + '/img/image.png'
|
if not theme:
|
||||||
|
theme = 'default'
|
||||||
|
defaultProfileImageFilename = baseDir + '/theme/default/image.png'
|
||||||
if theme:
|
if theme:
|
||||||
if os.path.isfile(baseDir + '/img/image_' + theme + '.png'):
|
if os.path.isfile(baseDir + '/theme/' + theme + '/image.png'):
|
||||||
defaultBannerFilename = baseDir + '/img/image_' + theme + '.png'
|
defaultBannerFilename = baseDir + '/theme/' + theme + '/image.png'
|
||||||
if os.path.isfile(defaultProfileImageFilename):
|
if os.path.isfile(defaultProfileImageFilename):
|
||||||
copyfile(defaultProfileImageFilename, baseDir +
|
copyfile(defaultProfileImageFilename, baseDir +
|
||||||
'/accounts/' + nickname + '@' + domain + '/image.png')
|
'/accounts/' + nickname + '@' + domain + '/image.png')
|
||||||
defaultBannerFilename = baseDir + '/img/banner.png'
|
defaultBannerFilename = baseDir + '/theme/default/banner.png'
|
||||||
if theme:
|
if theme:
|
||||||
if os.path.isfile(baseDir + '/img/banner_' + theme + '.png'):
|
if os.path.isfile(baseDir + '/theme/' + theme + '/banner.png'):
|
||||||
defaultBannerFilename = baseDir + '/img/banner_' + theme + '.png'
|
defaultBannerFilename = baseDir + '/theme/' + theme + '/banner.png'
|
||||||
if os.path.isfile(defaultBannerFilename):
|
if os.path.isfile(defaultBannerFilename):
|
||||||
copyfile(defaultBannerFilename, baseDir + '/accounts/' +
|
copyfile(defaultBannerFilename, baseDir + '/accounts/' +
|
||||||
nickname + '@' + domain + '/banner.png')
|
nickname + '@' + domain + '/banner.png')
|
||||||
|
|
|
||||||
20
tests.py
|
|
@ -1943,32 +1943,52 @@ def testDangerousMarkup():
|
||||||
print('testDangerousMarkup')
|
print('testDangerousMarkup')
|
||||||
content = '<p>This is a valid message</p>'
|
content = '<p>This is a valid message</p>'
|
||||||
assert(not dangerousMarkup(content))
|
assert(not dangerousMarkup(content))
|
||||||
|
|
||||||
content = 'This is a valid message without markup'
|
content = 'This is a valid message without markup'
|
||||||
assert(not dangerousMarkup(content))
|
assert(not dangerousMarkup(content))
|
||||||
|
|
||||||
content = '<p>This is a valid-looking message. But wait... ' + \
|
content = '<p>This is a valid-looking message. But wait... ' + \
|
||||||
'<script>document.getElementById("concentrated")' + \
|
'<script>document.getElementById("concentrated")' + \
|
||||||
'.innerHTML = "evil";</script></p>'
|
'.innerHTML = "evil";</script></p>'
|
||||||
assert(dangerousMarkup(content))
|
assert(dangerousMarkup(content))
|
||||||
|
|
||||||
content = '<p>This is a valid-looking message. But wait... ' + \
|
content = '<p>This is a valid-looking message. But wait... ' + \
|
||||||
'<script src="https://evilsite/payload.js" /></p>'
|
'<script src="https://evilsite/payload.js" /></p>'
|
||||||
assert(dangerousMarkup(content))
|
assert(dangerousMarkup(content))
|
||||||
|
|
||||||
content = '<p>This message embeds an evil frame.' + \
|
content = '<p>This message embeds an evil frame.' + \
|
||||||
'<iframe src="somesite"></iframe></p>'
|
'<iframe src="somesite"></iframe></p>'
|
||||||
assert(dangerousMarkup(content))
|
assert(dangerousMarkup(content))
|
||||||
|
|
||||||
content = '<p>This message tries to obfuscate an evil frame.' + \
|
content = '<p>This message tries to obfuscate an evil frame.' + \
|
||||||
'< iframe src = "somesite"></ iframe ></p>'
|
'< iframe src = "somesite"></ iframe ></p>'
|
||||||
assert(dangerousMarkup(content))
|
assert(dangerousMarkup(content))
|
||||||
|
|
||||||
content = '<p>This message is not necessarily evil, but annoying.' + \
|
content = '<p>This message is not necessarily evil, but annoying.' + \
|
||||||
'<hr><br><br><br><br><br><br><br><hr><hr></p>'
|
'<hr><br><br><br><br><br><br><br><hr><hr></p>'
|
||||||
assert(dangerousMarkup(content))
|
assert(dangerousMarkup(content))
|
||||||
|
|
||||||
content = '<p>This message contans a ' + \
|
content = '<p>This message contans a ' + \
|
||||||
'<a href="https://validsite/index.html">valid link.</a></p>'
|
'<a href="https://validsite/index.html">valid link.</a></p>'
|
||||||
assert(not dangerousMarkup(content))
|
assert(not dangerousMarkup(content))
|
||||||
|
|
||||||
content = '<p>This message contans a ' + \
|
content = '<p>This message contans a ' + \
|
||||||
'<a href="https://validsite/iframe.html">' + \
|
'<a href="https://validsite/iframe.html">' + \
|
||||||
'valid link having invalid but harmless name.</a></p>'
|
'valid link having invalid but harmless name.</a></p>'
|
||||||
assert(not dangerousMarkup(content))
|
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():
|
def runHtmlReplaceQuoteMarks():
|
||||||
print('htmlReplaceQuoteMarks')
|
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 |