Merge branch 'main' of gitlab.com:bashrc2/epicyon

merge-requests/30/head
Bob Mottram 2021-12-05 18:21:13 +00:00
commit a23112feb3
43 changed files with 844 additions and 325 deletions

150
daemon.py
View File

@ -154,6 +154,7 @@ from blog import htmlBlogPage
from blog import htmlBlogPost
from blog import htmlEditBlog
from blog import getBlogAddress
from webapp_themeDesigner import htmlThemeDesigner
from webapp_minimalbutton import setMinimal
from webapp_minimalbutton import isMinimal
from webapp_utils import getAvatarImageUrl
@ -316,6 +317,8 @@ from cache import storePersonInCache
from cache import getPersonFromCache
from cache import getPersonPubKey
from httpsig import verifyPostHeaders
from theme import resetThemeDesignerSettings
from theme import setThemeFromDesigner
from theme import scanThemesForScripts
from theme import importTheme
from theme import exportTheme
@ -2109,6 +2112,92 @@ class PubServer(BaseHTTPRequestHandler):
self.server.POSTbusy = False
return
def _themeDesigner(self, path: str,
callingDomain: str, cookie: str,
baseDir: str, httpPrefix: str, nickname: str,
domain: str, domainFull: str, port: int,
onionDomain: str, i2pDomain: str,
debug: bool, accessKeys: {},
defaultTimeline: str, themeName: str,
allowLocalNetworkAccess: bool,
systemLanguage: str) -> None:
"""Receive POST from webapp_themeDesigner
"""
usersPath = '/users/' + nickname
originPathStr = \
httpPrefix + '://' + domainFull + usersPath + '/' + defaultTimeline
length = int(self.headers['Content-length'])
try:
themeParams = self.rfile.read(length).decode('utf-8')
except SocketError as e:
if e.errno == errno.ECONNRESET:
print('WARN: POST themeParams ' +
'connection reset by peer')
else:
print('WARN: POST themeParams socket error')
self.send_response(400)
self.end_headers()
self.server.POSTbusy = False
return
except ValueError as e:
print('ERROR: POST themeParams rfile.read failed, ' + str(e))
self.send_response(400)
self.end_headers()
self.server.POSTbusy = False
return
themeParams = \
urllib.parse.unquote_plus(themeParams)
# theme designer screen, reset button
# See htmlThemeDesigner
if 'submitThemeDesignerReset=' in themeParams or \
'submitThemeDesigner=' not in themeParams:
if 'submitThemeDesignerReset=' in themeParams:
resetThemeDesignerSettings(baseDir, themeName, domain,
allowLocalNetworkAccess,
systemLanguage)
if callingDomain.endswith('.onion') and onionDomain:
originPathStr = \
'http://' + onionDomain + usersPath + '/' + defaultTimeline
elif callingDomain.endswith('.i2p') and i2pDomain:
originPathStr = \
'http://' + i2pDomain + usersPath + '/' + defaultTimeline
self._redirect_headers(originPathStr, cookie, callingDomain)
self.server.POSTbusy = False
return
fields = {}
fieldsList = themeParams.split('&')
for fieldStr in fieldsList:
if '=' not in fieldStr:
continue
fields[fieldStr.split('=')[0]] = fieldStr.split('=')[1].strip()
# get the parameters from the theme designer screen
themeDesignerParams = {}
for variableName, key in fields.items():
if variableName.startswith('themeSetting_'):
variableName = variableName.replace('themeSetting_', '')
themeDesignerParams[variableName] = key
setThemeFromDesigner(baseDir, themeName, domain,
themeDesignerParams,
allowLocalNetworkAccess,
systemLanguage)
# redirect back from theme designer screen
if callingDomain.endswith('.onion') and onionDomain:
originPathStr = \
'http://' + onionDomain + usersPath + '/' + defaultTimeline
elif callingDomain.endswith('.i2p') and i2pDomain:
originPathStr = \
'http://' + i2pDomain + usersPath + '/' + defaultTimeline
self._redirect_headers(originPathStr, cookie, callingDomain)
self.server.POSTbusy = False
return
def _personOptions(self, path: str,
callingDomain: str, cookie: str,
baseDir: str, httpPrefix: str,
@ -10849,6 +10938,7 @@ class PubServer(BaseHTTPRequestHandler):
currNickname = currNickname.split('/')[0]
moderator = isModerator(baseDir, currNickname)
editor = isEditor(baseDir, currNickname)
artist = isArtist(baseDir, currNickname)
fullWidthTimelineButtonHeader = \
self.server.fullWidthTimelineButtonHeader
minimalNick = isMinimal(baseDir, domain, nickname)
@ -10881,7 +10971,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.twitterReplacementDomain,
self.server.showPublishedDateOnly,
self.server.newswire,
moderator, editor,
moderator, editor, artist,
self.server.positiveVoting,
self.server.showPublishAsIcon,
fullWidthTimelineButtonHeader,
@ -14363,6 +14453,33 @@ class PubServer(BaseHTTPRequestHandler):
self.server.debug)
return
if htmlGET and usersInPath and authorized and \
self.path.endswith('/themedesigner'):
nickname = self.path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
if not isArtist(self.server.baseDir, nickname):
self._403()
return
msg = \
htmlThemeDesigner(self.server.cssCache,
self.server.baseDir,
nickname, self.server.domain,
self.server.translate,
self.server.defaultTimeline,
self.server.themeName,
self.server.accessKeys)
msg = msg.encode('utf-8')
msglen = len(msg)
self._login_headers('text/html', msglen, callingDomain)
self._write(msg)
fitnessPerformance(GETstartTime, self.server.fitness,
'_GET', 'show theme designer screen',
self.server.debug)
return
fitnessPerformance(GETstartTime, self.server.fitness,
'_GET', 'show about screen done',
self.server.debug)
@ -17728,6 +17845,36 @@ class PubServer(BaseHTTPRequestHandler):
self.server.defaultTimeline)
return
# theme designer submit/cancel button
if usersInPath and \
self.path.endswith('/changeThemeSettings'):
nickname = self.path.split('/users/')[1]
if '/' in nickname:
nickname = nickname.split('/')[0]
if not self.server.keyShortcuts.get(nickname):
accessKeys = self.server.accessKeys
self.server.keyShortcuts[nickname] = accessKeys.copy()
accessKeys = self.server.keyShortcuts[nickname]
self._themeDesigner(self.path,
callingDomain, cookie,
self.server.baseDir,
self.server.httpPrefix,
nickname,
self.server.domain,
self.server.domainFull,
self.server.port,
self.server.onionDomain,
self.server.i2pDomain,
self.server.debug,
accessKeys,
self.server.defaultTimeline,
self.server.themeName,
self.server.allowLocalNetworkAccess,
self.server.systemLanguage)
return
# update the shared item federation token for the calling domain
# if it is within the permitted federation
if self.headers.get('Origin') and \
@ -18330,6 +18477,7 @@ def runDaemon(contentLicenseUrl: str,
'enterNotes': 'n',
'menuTimeline': 't',
'menuEdit': 'e',
'menuThemeDesigner': 'z',
'menuProfile': 'p',
'menuInbox': 'i',
'menuSearch': '/',

File diff suppressed because one or more lines are too long

View File

@ -2160,8 +2160,8 @@ def threadSendPost(session, postJsonStr: str, federationList: [],
except Exception as e:
print('ERROR: postJsonString failed ' + str(e))
if unauthorized:
print(postJsonStr)
print('threadSendPost: Post is unauthorized')
print('WARN: threadSendPost: Post is unauthorized ' +
inboxUrl + ' ' + postJsonStr)
break
if postResult:
logStr = 'Success on try ' + str(tries) + ': ' + postJsonStr

View File

@ -492,6 +492,31 @@ def _setCustomFont(baseDir: str):
cssfile.write(css)
def setThemeFromDesigner(baseDir: str, themeName: str, domain: str,
themeParams: {},
allowLocalNetworkAccess: bool,
systemLanguage: str):
customThemeFilename = baseDir + '/accounts/theme.json'
saveJson(themeParams, customThemeFilename)
setTheme(baseDir, themeName, domain,
allowLocalNetworkAccess, systemLanguage)
def resetThemeDesignerSettings(baseDir: str, themeName: str, domain: str,
allowLocalNetworkAccess: bool,
systemLanguage: str) -> None:
"""Resets the theme designer settings
"""
customVariablesFile = baseDir + '/accounts/theme.json'
if os.path.isfile(customVariablesFile):
try:
os.remove(customVariablesFile)
except OSError:
print('EX: unable to remove theme designer settings on reset')
setTheme(baseDir, themeName, domain,
allowLocalNetworkAccess, systemLanguage)
def _readVariablesFile(baseDir: str, themeName: str,
variablesFile: str,
allowLocalNetworkAccess: bool) -> None:
@ -500,6 +525,15 @@ def _readVariablesFile(baseDir: str, themeName: str,
themeParams = loadJson(variablesFile, 0)
if not themeParams:
return
# set custom theme parameters
customVariablesFile = baseDir + '/accounts/theme.json'
if os.path.isfile(customVariablesFile):
customThemeParams = loadJson(customVariablesFile, 0)
if customThemeParams:
for variableName, value in customThemeParams.items():
themeParams[variableName] = value
bgParams = {
"login": "jpg",
"follow": "jpg",
@ -789,6 +823,17 @@ def setTheme(baseDir: str, name: str, domain: str,
result = False
prevThemeName = getTheme(baseDir)
# if the theme has changed then remove any custom settings
if prevThemeName != name:
customVariablesFile = baseDir + '/accounts/theme.json'
if os.path.isfile(customVariablesFile):
print('Removing theme designer settings')
try:
os.remove(customVariablesFile)
except OSError:
print('EX: removing theme designer settings')
_removeTheme(baseDir)
themes = getThemesList(baseDir)

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -498,5 +498,7 @@
"Select reaction": "حدد رد الفعل",
"Don't show the Reaction button": "لا تظهر زر رد الفعل",
"New feed URL": "موجز جديد URL",
"New link title and URL": "عنوان الارتباط الجديد وعنوان URL"
"New link title and URL": "عنوان الارتباط الجديد وعنوان URL",
"Theme Designer": "مصمم المظهر",
"Reset": "إعادة ضبط"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "Seleccioneu la reacció",
"Don't show the Reaction button": "No mostris el botó de reacció",
"New feed URL": "URL de feed nou",
"New link title and URL": "Títol i URL de l'enllaç nous"
"New link title and URL": "Títol i URL de l'enllaç nous",
"Theme Designer": "Dissenyador temàtic",
"Reset": "Restableix"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "Dewiswch adwaith",
"Don't show the Reaction button": "Peidiwch â dangos y botwm Adwaith",
"New feed URL": "URL porthiant newydd",
"New link title and URL": "Teitl dolen ac URL newydd"
"New link title and URL": "Teitl dolen ac URL newydd",
"Theme Designer": "Dylunydd Thema",
"Reset": "Ail gychwyn"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "Reaktion auswählen",
"Don't show the Reaction button": "Reaktionstaste nicht anzeigen",
"New feed URL": "Neue Feed-URL",
"New link title and URL": "Neuer Linktitel und URL"
"New link title and URL": "Neuer Linktitel und URL",
"Theme Designer": "Themendesigner",
"Reset": "Zurücksetzen"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "Select reaction",
"Don't show the Reaction button": "Don't show the Reaction button",
"New feed URL": "New feed URL",
"New link title and URL": "New link title and URL"
"New link title and URL": "New link title and URL",
"Theme Designer": "Theme Designer",
"Reset": "Reset"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "Seleccionar reacción",
"Don't show the Reaction button": "No mostrar el botón de reacción",
"New feed URL": "URL de nuevo feed",
"New link title and URL": "Nuevo título de enlace y URL"
"New link title and URL": "Nuevo título de enlace y URL",
"Theme Designer": "Diseñadora de temas",
"Reset": "Reiniciar"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "Sélectionnez la réaction",
"Don't show the Reaction button": "Ne pas afficher le bouton Réaction",
"New feed URL": "Nouvelle URL de flux",
"New link title and URL": "Nouveau titre et URL du lien"
"New link title and URL": "Nouveau titre et URL du lien",
"Theme Designer": "Concepteur de thème",
"Reset": "Réinitialiser"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "Roghnaigh imoibriú",
"Don't show the Reaction button": "Ná taispeáin an cnaipe Imoibriú",
"New feed URL": "URL beathaithe nua",
"New link title and URL": "Teideal nasc nua agus URL"
"New link title and URL": "Teideal nasc nua agus URL",
"Theme Designer": "Dearthóir Téama",
"Reset": "Athshocraigh"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "प्रतिक्रिया का चयन करें",
"Don't show the Reaction button": "प्रतिक्रिया बटन न दिखाएं",
"New feed URL": "नया फ़ीड URL",
"New link title and URL": "नया लिंक शीर्षक और URL"
"New link title and URL": "नया लिंक शीर्षक और URL",
"Theme Designer": "थीम डिजाइनर",
"Reset": "रीसेट"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "Seleziona reazione",
"Don't show the Reaction button": "Non mostrare il pulsante Reazione",
"New feed URL": "Nuovo URL del feed",
"New link title and URL": "Nuovo titolo e URL del collegamento"
"New link title and URL": "Nuovo titolo e URL del collegamento",
"Theme Designer": "Progettista di temi",
"Reset": "Ripristina"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "反応を選択",
"Don't show the Reaction button": "反応ボタンを表示しない",
"New feed URL": "新しいフィードURL",
"New link title and URL": "新しいリンクのタイトルとURL"
"New link title and URL": "新しいリンクのタイトルとURL",
"Theme Designer": "テーマデザイナー",
"Reset": "リセット"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "Reaksiyonê hilbijêrin",
"Don't show the Reaction button": "Bişkoka Reaksiyonê nîşan nede",
"New feed URL": "URL-ya feed nû",
"New link title and URL": "Sernav û URL-ya girêdana nû"
"New link title and URL": "Sernav û URL-ya girêdana nû",
"Theme Designer": "Theme Designer",
"Reset": "Reset"
}

View File

@ -494,5 +494,7 @@
"Select reaction": "Select reaction",
"Don't show the Reaction button": "Don't show the Reaction button",
"New feed URL": "New feed URL",
"New link title and URL": "New link title and URL"
"New link title and URL": "New link title and URL",
"Theme Designer": "Theme Designer",
"Reset": "Reset"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "Selecione a reação",
"Don't show the Reaction button": "Não mostrar o botão de reação",
"New feed URL": "Novo URL de feed",
"New link title and URL": "Novo título e URL do link"
"New link title and URL": "Novo título e URL do link",
"Theme Designer": "Designer de Tema",
"Reset": "Redefinir"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "Выберите реакцию",
"Don't show the Reaction button": "Не показывать кнопку реакции",
"New feed URL": "URL нового канала",
"New link title and URL": "Новое название ссылки и URL"
"New link title and URL": "Новое название ссылки и URL",
"Theme Designer": "Дизайнер тем",
"Reset": "Сброс настроек"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "Chagua majibu",
"Don't show the Reaction button": "Usionyeshe kitufe cha Majibu",
"New feed URL": "URL mpya ya mipasho",
"New link title and URL": "Kichwa kipya cha kiungo na URL"
"New link title and URL": "Kichwa kipya cha kiungo na URL",
"Theme Designer": "Mbuni wa Mandhari",
"Reset": "Weka upya"
}

View File

@ -498,5 +498,7 @@
"Select reaction": "选择反应",
"Don't show the Reaction button": "不显示“反应”按钮",
"New feed URL": "新供稿网址",
"New link title and URL": "新链接标题和 URL"
"New link title and URL": "新链接标题和 URL",
"Theme Designer": "主题设计师",
"Reset": "重启"
}

View File

@ -11,6 +11,7 @@ import os
from utils import getConfigParam
from utils import getNicknameFromActor
from utils import isEditor
from utils import isArtist
from utils import removeDomainPort
from utils import localActorUrl
from webapp_utils import sharesTimelineJson
@ -113,7 +114,7 @@ def _getLeftColumnWanted(baseDir: str,
def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
httpPrefix: str, translate: {},
editor: bool,
editor: bool, artist: bool,
showBackButton: bool, timelinePath: str,
rssIconAtTop: bool, showHeaderImage: bool,
frontPage: bool, theme: str,
@ -154,6 +155,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
htmlStr += '\n <center>\n'
htmlStr += ' <div class="leftColIcons">\n'
if editor:
# show the edit icon
htmlStr += \
@ -163,6 +165,15 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
translate['Edit Links'] + ' | " title="' + \
translate['Edit Links'] + '" src="/icons/edit.png" /></a>\n'
if artist:
# show the theme designer icon
htmlStr += \
' <a href="/users/' + nickname + '/themedesigner" ' + \
'accesskey="' + accessKeys['menuThemeDesigner'] + '">' + \
'<img class="' + editImageClass + '" loading="lazy" alt="' + \
translate['Theme Designer'] + ' | " title="' + \
translate['Theme Designer'] + '" src="/icons/theme.png" /></a>\n'
# RSS icon
if nickname != 'news':
# rss feed for this account
@ -358,8 +369,10 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
# is the user a site editor?
if nickname == 'news':
editor = False
artist = False
else:
editor = isEditor(baseDir, nickname)
artist = isArtist(baseDir, nickname)
domain = removeDomainPort(domainFull)
@ -383,7 +396,7 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
htmlStr += \
getLeftColumnContent(baseDir, nickname, domainFull,
httpPrefix, translate,
editor,
editor, artist,
False, timelinePath,
rssIconAtTop, False, False,
theme, accessKeys,

View File

@ -155,7 +155,8 @@ def htmlFrontScreen(signingPrivateKeyPem: str,
profileHeaderStr += \
getLeftColumnContent(baseDir, 'news', domainFull,
httpPrefix, translate,
False, False, None, rssIconAtTop, True,
False, False,
False, None, rssIconAtTop, True,
True, theme, accessKeys,
sharedItemsFederatedDomains)
profileHeaderStr += \

View File

@ -8,6 +8,7 @@ __status__ = "Production"
__module_group__ = "Moderation"
import os
from utils import isArtist
from utils import isAccountDir
from utils import getFullDomain
from utils import isEditor
@ -57,6 +58,7 @@ def htmlModeration(cssCache: {}, defaultTimeline: str,
"""Show the moderation feed as html
This is what you see when selecting the "mod" timeline
"""
artist = isArtist(baseDir, nickname)
return htmlTimeline(cssCache, defaultTimeline,
recentPostsCache, maxRecentPosts,
translate, pageNumber,
@ -66,7 +68,7 @@ def htmlModeration(cssCache: {}, defaultTimeline: str,
YTReplacementDomain,
twitterReplacementDomain,
showPublishedDateOnly,
newswire, False, False, positiveVoting,
newswire, False, False, artist, positiveVoting,
showPublishAsIcon, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, moderationActionStr, theme,

View File

@ -878,6 +878,10 @@ def htmlProfile(signingPrivateKeyPem: str,
menuSkills: userPathStr + '/skills#timeline',
menuLogout: '/logout'
}
if isArtist(baseDir, nickname):
menuThemeDesigner = \
htmlHideFromScreenReader('🎨') + ' ' + translate['Theme Designer']
navLinks[menuThemeDesigner] = userPathStr + '/themedesigner'
navAccessKeys = {}
for variableName, key in accessKeys.items():
if not locals().get(variableName):

View File

@ -0,0 +1,262 @@
__filename__ = "webapp_themeDesigner.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
import os
from utils import loadJson
from utils import getConfigParam
from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter
from webapp_utils import getBannerFile
color_to_hex = {
"aliceblue": "#f0f8ff",
"antiquewhite": "#faebd7",
"aqua": "#00ffff",
"aquamarine": "#7fffd4",
"azure": "#f0ffff",
"beige": "#f5f5dc",
"bisque": "#ffe4c4",
"black": "#000000",
"blanchedalmond": "#ffebcd",
"blue": "#0000ff",
"blueviolet": "#8a2be2",
"brown": "#a52a2a",
"burlywood": "#deb887",
"cadetblue": "#5f9ea0",
"chartreuse": "#7fff00",
"chocolate": "#d2691e",
"coral": "#ff7f50",
"cornflowerblue": "#6495ed",
"cornsilk": "#fff8dc",
"crimson": "#dc143c",
"cyan": "#00ffff",
"darkblue": "#00008b",
"darkcyan": "#008b8b",
"darkgoldenrod": "#b8860b",
"darkgray": "#a9a9a9",
"darkgrey": "#a9a9a9",
"darkgreen": "#006400",
"darkkhaki": "#bdb76b",
"darkmagenta": "#8b008b",
"darkolivegreen": "#556b2f",
"darkorange": "#ff8c00",
"darkorchid": "#9932cc",
"darkred": "#8b0000",
"darksalmon": "#e9967a",
"darkseagreen": "#8fbc8f",
"darkslateblue": "#483d8b",
"darkslategray": "#2f4f4f",
"darkslategrey": "#2f4f4f",
"darkturquoise": "#00ced1",
"darkviolet": "#9400d3",
"deeppink": "#ff1493",
"deepskyblue": "#00bfff",
"dimgray": "#696969",
"dimgrey": "#696969",
"dodgerblue": "#1e90ff",
"firebrick": "#b22222",
"floralwhite": "#fffaf0",
"forestgreen": "#228b22",
"fuchsia": "#ff00ff",
"gainsboro": "#dcdcdc",
"ghostwhite": "#f8f8ff",
"gold": "#ffd700",
"goldenrod": "#daa520",
"gray": "#808080",
"grey": "#808080",
"green": "#008000",
"greenyellow": "#adff2f",
"honeydew": "#f0fff0",
"hotpink": "#ff69b4",
"indianred": "#cd5c5c",
"indigo": "#4b0082",
"ivory": "#fffff0",
"khaki": "#f0e68c",
"lavender": "#e6e6fa",
"lavenderblush": "#fff0f5",
"lawngreen": "#7cfc00",
"lemonchiffon": "#fffacd",
"lightblue": "#add8e6",
"lightcoral": "#f08080",
"lightcyan": "#e0ffff",
"lightgoldenrodyellow": "#fafad2",
"lightgray": "#d3d3d3",
"lightgrey": "#d3d3d3",
"lightgreen": "#90ee90",
"lightpink": "#ffb6c1",
"lightsalmon": "#ffa07a",
"lightseagreen": "#20b2aa",
"lightskyblue": "#87cefa",
"lightslategray": "#778899",
"lightslategrey": "#778899",
"lightsteelblue": "#b0c4de",
"lightyellow": "#ffffe0",
"lime": "#00ff00",
"limegreen": "#32cd32",
"linen": "#faf0e6",
"magenta": "#ff00ff",
"maroon": "#800000",
"mediumaquamarine": "#66cdaa",
"mediumblue": "#0000cd",
"mediumorchid": "#ba55d3",
"mediumpurple": "#9370db",
"mediumseagreen": "#3cb371",
"mediumslateblue": "#7b68ee",
"mediumspringgreen": "#00fa9a",
"mediumturquoise": "#48d1cc",
"mediumvioletred": "#c71585",
"midnightblue": "#191970",
"mintcream": "#f5fffa",
"mistyrose": "#ffe4e1",
"moccasin": "#ffe4b5",
"navajowhite": "#ffdead",
"navy": "#000080",
"oldlace": "#fdf5e6",
"olive": "#808000",
"olivedrab": "#6b8e23",
"orange": "#ffa500",
"orangered": "#ff4500",
"orchid": "#da70d6",
"palegoldenrod": "#eee8aa",
"palegreen": "#98fb98",
"paleturquoise": "#afeeee",
"palevioletred": "#db7093",
"papayawhip": "#ffefd5",
"peachpuff": "#ffdab9",
"peru": "#cd853f",
"pink": "#ffc0cb",
"plum": "#dda0dd",
"powderblue": "#b0e0e6",
"purple": "#800080",
"red": "#ff0000",
"rosybrown": "#bc8f8f",
"royalblue": "#4169e1",
"saddlebrown": "#8b4513",
"salmon": "#fa8072",
"sandybrown": "#f4a460",
"seagreen": "#2e8b57",
"seashell": "#fff5ee",
"sienna": "#a0522d",
"silver": "#c0c0c0",
"skyblue": "#87ceeb",
"slateblue": "#6a5acd",
"slategray": "#708090",
"slategrey": "#708090",
"snow": "#fffafa",
"springgreen": "#00ff7f",
"steelblue": "#4682b4",
"tan": "#d2b48c",
"teal": "#008080",
"thistle": "#d8bfd8",
"tomato": "#ff6347",
"turquoise": "#40e0d0",
"violet": "#ee82ee",
"wheat": "#f5deb3",
"white": "#ffffff",
"whitesmoke": "#f5f5f5",
"yellow": "#ffff00",
"yellowgreen": "#9acd32",
}
def htmlThemeDesigner(cssCache: {}, baseDir: str,
nickname: str, domain: str,
translate: {}, defaultTimeline: str,
themeName: str, accessKeys: {}) -> str:
"""Edit theme settings
"""
themeFilename = baseDir + '/theme/' + themeName + '/theme.json'
themeJson = {}
if os.path.isfile(themeFilename):
themeJson = loadJson(themeFilename)
# set custom theme parameters
customVariablesFile = baseDir + '/accounts/theme.json'
if os.path.isfile(customVariablesFile):
customThemeParams = loadJson(customVariablesFile, 0)
if customThemeParams:
for variableName, value in customThemeParams.items():
themeJson[variableName] = value
themeForm = ''
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
themeForm = \
htmlHeaderWithExternalStyle(cssFilename, instanceTitle, None)
bannerFile, bannerFilename = \
getBannerFile(baseDir, nickname, domain, themeName)
themeForm += \
'<a href="/users/' + nickname + '/' + defaultTimeline + '" ' + \
'accesskey="' + accessKeys['menuTimeline'] + '">' + \
'<img loading="lazy" class="timeline-banner" ' + \
'title="' + translate['Switch to timeline view'] + '" ' + \
'alt="' + translate['Switch to timeline view'] + '" ' + \
'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n'
themeForm += '<div class="container">\n'
themeForm += \
' <h1>' + translate['Theme Designer'] + '</h1>\n'
themeForm += ' <form method="POST" action="' + \
'/users/' + nickname + '/changeThemeSettings">\n'
resetKey = accessKeys['menuLogout']
submitKey = accessKeys['submitButton']
themeForm += \
' <center>\n' + \
' <button type="submit" class="button" ' + \
'name="submitThemeDesignerReset" ' + \
'accesskey="' + resetKey + '">' + \
translate['Reset'] + '</button>\n' + \
' <button type="submit" class="button" ' + \
'name="submitThemeDesigner" accesskey="' + submitKey + '">' + \
translate['Submit'] + '</button>\n </center>\n'
themeForm += ' <table class="accesskeys">\n'
themeForm += ' <colgroup>\n'
themeForm += ' <col span="1" class="accesskeys-left">\n'
themeForm += ' <col span="1" class="accesskeys-center">\n'
themeForm += ' </colgroup>\n'
themeForm += ' <tbody>\n'
for variableName, value in themeJson.items():
# only use colors defined as hex
if not value.startswith('#'):
if color_to_hex.get(value):
value = color_to_hex[value]
else:
continue
if variableName.endswith('-color') or \
variableName.endswith('-text'):
variableNameStr = variableName.replace('-', ' ')
if variableNameStr.endswith(' color'):
variableNameStr = variableNameStr.replace(' color', '')
if variableNameStr.endswith(' bg'):
variableNameStr = variableNameStr.replace(' bg', ' background')
elif variableNameStr.endswith(' fg'):
variableNameStr = variableNameStr.replace(' fg', ' foreground')
variableNameStr = variableNameStr.title()
themeForm += \
' <tr><td><label class="labels">' + \
variableNameStr + '</label></td>'
themeForm += \
'<td><input type="color" name="themeSetting_' + \
variableName + '" value="' + str(value) + \
'" title="' + variableNameStr + '"></p></td></tr>\n'
themeForm += ' </table>\n'
themeForm += ' </form>\n'
themeForm += '</div>\n'
themeForm += htmlFooter()
return themeForm

View File

@ -10,6 +10,7 @@ __module_group__ = "Timeline"
import os
import time
from shutil import copyfile
from utils import isArtist
from utils import dangerousMarkup
from utils import getConfigParam
from utils import getFullDomain
@ -430,7 +431,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
twitterReplacementDomain: str,
showPublishedDateOnly: bool,
newswire: {}, moderator: bool,
editor: bool,
editor: bool, artist: bool,
positiveVoting: bool,
showPublishAsIcon: bool,
fullWidthTimelineButtonHeader: bool,
@ -744,7 +745,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
leftColumnStr = \
getLeftColumnContent(baseDir, nickname, domainFull,
httpPrefix, translate,
editor, False, None, rssIconAtTop,
editor, artist, False, None, rssIconAtTop,
True, False, theme, accessKeys,
sharedItemsFederatedDomains)
tlStr += ' <td valign="top" class="col-left" ' + \
@ -1146,6 +1147,7 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
"""
manuallyApproveFollowers = \
followerApprovalActive(baseDir, nickname, domain)
artist = isArtist(baseDir, nickname)
return htmlTimeline(cssCache, defaultTimeline,
recentPostsCache, maxRecentPosts,
@ -1159,7 +1161,7 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
YTReplacementDomain,
twitterReplacementDomain,
showPublishedDateOnly,
newswire, False, False,
newswire, False, False, artist,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
@ -1200,6 +1202,7 @@ def htmlWanted(cssCache: {}, defaultTimeline: str,
"""
manuallyApproveFollowers = \
followerApprovalActive(baseDir, nickname, domain)
artist = isArtist(baseDir, nickname)
return htmlTimeline(cssCache, defaultTimeline,
recentPostsCache, maxRecentPosts,
@ -1213,7 +1216,7 @@ def htmlWanted(cssCache: {}, defaultTimeline: str,
YTReplacementDomain,
twitterReplacementDomain,
showPublishedDateOnly,
newswire, False, False,
newswire, False, False, artist,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
@ -1255,6 +1258,7 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
"""
manuallyApproveFollowers = \
followerApprovalActive(baseDir, nickname, domain)
artist = isArtist(baseDir, nickname)
return htmlTimeline(cssCache, defaultTimeline,
recentPostsCache, maxRecentPosts,
@ -1268,7 +1272,7 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
YTReplacementDomain,
twitterReplacementDomain,
showPublishedDateOnly,
newswire, False, False,
newswire, False, False, artist,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
@ -1310,6 +1314,7 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
"""
manuallyApproveFollowers = \
followerApprovalActive(baseDir, nickname, domain)
artist = isArtist(baseDir, nickname)
return htmlTimeline(cssCache, defaultTimeline,
recentPostsCache, maxRecentPosts,
@ -1323,7 +1328,7 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
YTReplacementDomain,
twitterReplacementDomain,
showPublishedDateOnly,
newswire, False, False,
newswire, False, False, artist,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
@ -1363,6 +1368,7 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
CWlists: {}, listsEnabled: str) -> str:
"""Show the DM timeline as html
"""
artist = isArtist(baseDir, nickname)
return htmlTimeline(cssCache, defaultTimeline,
recentPostsCache, maxRecentPosts,
translate, pageNumber,
@ -1373,7 +1379,7 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
YTReplacementDomain,
twitterReplacementDomain,
showPublishedDateOnly,
newswire, False, False, positiveVoting,
newswire, False, False, artist, positiveVoting,
showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
@ -1413,6 +1419,7 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
CWlists: {}, listsEnabled: str) -> str:
"""Show the replies timeline as html
"""
artist = isArtist(baseDir, nickname)
return htmlTimeline(cssCache, defaultTimeline,
recentPostsCache, maxRecentPosts,
translate, pageNumber,
@ -1424,7 +1431,7 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
YTReplacementDomain,
twitterReplacementDomain,
showPublishedDateOnly,
newswire, False, False,
newswire, False, False, artist,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
@ -1464,6 +1471,7 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
CWlists: {}, listsEnabled: str) -> str:
"""Show the media timeline as html
"""
artist = isArtist(baseDir, nickname)
return htmlTimeline(cssCache, defaultTimeline,
recentPostsCache, maxRecentPosts,
translate, pageNumber,
@ -1475,7 +1483,7 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
YTReplacementDomain,
twitterReplacementDomain,
showPublishedDateOnly,
newswire, False, False,
newswire, False, False, artist,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
@ -1515,6 +1523,7 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
CWlists: {}, listsEnabled: str) -> str:
"""Show the blogs timeline as html
"""
artist = isArtist(baseDir, nickname)
return htmlTimeline(cssCache, defaultTimeline,
recentPostsCache, maxRecentPosts,
translate, pageNumber,
@ -1526,7 +1535,7 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
YTReplacementDomain,
twitterReplacementDomain,
showPublishedDateOnly,
newswire, False, False,
newswire, False, False, artist,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
@ -1578,7 +1587,7 @@ def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
YTReplacementDomain,
twitterReplacementDomain,
showPublishedDateOnly,
newswire, False, False,
newswire, False, False, False,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
@ -1601,7 +1610,7 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
YTReplacementDomain: str,
twitterReplacementDomain: str,
showPublishedDateOnly: bool,
newswire: {}, moderator: bool, editor: bool,
newswire: {}, moderator: bool, editor: bool, artist: bool,
positiveVoting: bool, showPublishAsIcon: bool,
fullWidthTimelineButtonHeader: bool,
iconsAsButtons: bool,
@ -1629,7 +1638,7 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
YTReplacementDomain,
twitterReplacementDomain,
showPublishedDateOnly,
newswire, moderator, editor,
newswire, moderator, editor, artist,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
@ -1671,6 +1680,7 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
"""
manuallyApproveFollowers = \
followerApprovalActive(baseDir, nickname, domain)
artist = isArtist(baseDir, nickname)
return htmlTimeline(cssCache, defaultTimeline,
recentPostsCache, maxRecentPosts,
translate, pageNumber,
@ -1682,7 +1692,7 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
YTReplacementDomain,
twitterReplacementDomain,
showPublishedDateOnly,
newswire, False, False, positiveVoting,
newswire, False, False, artist, positiveVoting,
showPublishAsIcon, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances,