diff --git a/Makefile b/Makefile index 6573ca38c..5420916ad 100644 --- a/Makefile +++ b/Makefile @@ -23,3 +23,4 @@ clean: rm -f deploy/*~ rm -f translations/*~ rm -rf __pycache__ + rm calendar.css blog.css epicyon.css follow.css login.css options.css search.css suspended.css diff --git a/content.py b/content.py index 3f85eff5b..2fc754718 100644 --- a/content.py +++ b/content.py @@ -14,6 +14,41 @@ from utils import fileLastModified from utils import getLinkPrefixes +def htmlReplaceEmailQuote(content: str) -> str: + """Replaces an email style quote "> Some quote" with html blockquote + """ + # replace quote paragraph + if '

"' in content: + if '"

' in content: + content = content.replace('

"', '

') + content = content.replace('"

', '

') + if '>\u201c' in content: + if '\u201d<' in content: + content = content.replace('>\u201c', '>
') + content = content.replace('\u201d<', '
<') + # replace email style quote + if '>> ' not in content: + return content + contentStr = content.replace('

', '') + contentLines = contentStr.split('

') + newContent = '' + for lineStr in contentLines: + if not lineStr: + continue + if '>> ' not in lineStr: + if lineStr.startswith('> '): + lineStr = lineStr.replace('> ', '
') + lineStr = lineStr.replace('>', '
') + newContent += '

' + lineStr + '

' + else: + newContent += '

' + lineStr + '

' + else: + lineStr = lineStr.replace('>> ', '>
') + lineStr = lineStr.replace('>', '
') + newContent += '

' + lineStr + '

' + return newContent + + def htmlReplaceQuoteMarks(content: str) -> str: """Replaces quotes with html formatting "hello" becomes hello @@ -612,6 +647,7 @@ def addHtmlTags(baseDir: str, httpPrefix: str, by matching against known following accounts """ if content.startswith('

'): + content = htmlReplaceEmailQuote(content) return htmlReplaceQuoteMarks(content) maxWordLength = 40 content = content.replace('\r', '') @@ -718,6 +754,7 @@ def addHtmlTags(baseDir: str, httpPrefix: str, if longWordsList: content = removeLongWords(content, maxWordLength, longWordsList) content = content.replace(' --linebreak-- ', '

') + content = htmlReplaceEmailQuote(content) return '

' + htmlReplaceQuoteMarks(content) + '

' diff --git a/daemon.py b/daemon.py index d3076d150..fe192e17b 100644 --- a/daemon.py +++ b/daemon.py @@ -3351,6 +3351,30 @@ class PubServer(BaseHTTPRequestHandler): if os.path.isfile(switchFilename): os.remove(switchFilename) + # autogenerated tags + autoTagsFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + \ + '/autotags.txt' + if fields.get('autoTags'): + with open(autoTagsFilename, 'w+') as autoTagsFile: + autoTagsFile.write(fields['autoTags']) + else: + if os.path.isfile(autoTagsFilename): + os.remove(autoTagsFilename) + + # autogenerated content warnings + autoCWFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + \ + '/autocw.txt' + if fields.get('autoCW'): + with open(autoCWFilename, 'w+') as autoCWFile: + autoCWFile.write(fields['autoCW']) + else: + if os.path.isfile(autoCWFilename): + os.remove(autoCWFilename) + # save blocked accounts list blockedFilename = \ baseDir + '/accounts/' + \ diff --git a/emoji/default_emoji.json b/emoji/default_emoji.json index 4f4ef1c60..2f4fc92d1 100644 --- a/emoji/default_emoji.json +++ b/emoji/default_emoji.json @@ -767,5 +767,6 @@ "parrot": "1F99C", "budgie": "1F424", "canary": "1F424", - "linux": "1F427" + "linux": "1F427", + "valid": "valid" } diff --git a/emoji/valid.png b/emoji/valid.png new file mode 100644 index 000000000..66917f631 Binary files /dev/null and b/emoji/valid.png differ diff --git a/epicyon-blog.css b/epicyon-blog.css index 46dfd30fc..0e78fa287 100644 --- a/epicyon-blog.css +++ b/epicyon-blog.css @@ -43,8 +43,16 @@ --button-corner-radius: 15px; --timeline-border-radius: 30px; --focus-color: white; + --line-spacing: 130%; } +@font-face { + font-family: 'Bedstead'; + font-style: italic; + font-weight: normal; + font-display: block; + src: url('./fonts/bedstead.otf') format('opentype'); +} @font-face { font-family: 'Bedstead'; font-style: normal; @@ -63,6 +71,7 @@ body, html { min-width: 950px; margin: 0 auto; font-size: var(--font-size); + line-height: var(--line-spacing); } a, u { diff --git a/epicyon-calendar.css b/epicyon-calendar.css index 9c11fd0cb..8d369ec8f 100644 --- a/epicyon-calendar.css +++ b/epicyon-calendar.css @@ -16,6 +16,13 @@ --focus-color: white; } +@font-face { + font-family: 'Bedstead'; + font-style: italic; + font-weight: normal; + font-display: block; + src: url('./fonts/bedstead.otf') format('opentype'); +} @font-face { font-family: 'Bedstead'; font-style: normal; diff --git a/epicyon-follow.css b/epicyon-follow.css index ed7aca1ed..1e255985d 100644 --- a/epicyon-follow.css +++ b/epicyon-follow.css @@ -33,6 +33,13 @@ --focus-color: white; } +@font-face { + font-family: 'Bedstead'; + font-style: italic; + font-weight: normal; + font-display: block; + src: url('./fonts/bedstead.otf') format('opentype'); +} @font-face { font-family: 'Bedstead'; font-style: normal; diff --git a/epicyon-login.css b/epicyon-login.css index 1a02de7a6..e0a5ebec0 100644 --- a/epicyon-login.css +++ b/epicyon-login.css @@ -20,8 +20,16 @@ --button-selected: #666; --form-border-radius: 30px; --focus-color: white; + --line-spacing: 130%; } +@font-face { + font-family: 'Bedstead'; + font-style: italic; + font-weight: normal; + font-display: block; + src: url('./fonts/bedstead.otf') format('opentype'); +} @font-face { font-family: 'Bedstead'; font-style: normal; @@ -46,6 +54,7 @@ body, html { min-width: 600px; margin: 0 auto; font-size: var(--font-size); + line-height: var(--line-spacing); } a, u { diff --git a/epicyon-options.css b/epicyon-options.css index 328a39ffc..7886378e6 100644 --- a/epicyon-options.css +++ b/epicyon-options.css @@ -35,6 +35,13 @@ --focus-color: white; } +@font-face { + font-family: 'Bedstead'; + font-style: italic; + font-weight: normal; + font-display: block; + src: url('./fonts/bedstead.otf') format('opentype'); +} @font-face { font-family: 'Bedstead'; font-style: normal; @@ -153,6 +160,19 @@ a:focus { cursor: pointer; margin: 30px; } + .buttonIcon { + border-radius: 4px; + background-color: var(--button-background); + font-family: Arial, Helvetica, sans-serif; + border: none; + color: var(--button-text); + text-align: center; + padding: 10px 65px; + font-size: 24px; + max-width: 200px; + min-width: 100px; + cursor: pointer; + } .buttonsmall { border-radius: 4px; background-color: var(--button-small-background); @@ -215,6 +235,19 @@ a:focus { cursor: pointer; margin: 30px; } + .buttonIcon { + border-radius: 4px; + background-color: var(--button-background); + font-family: Arial, Helvetica, sans-serif; + border: none; + color: var(--button-text); + text-align: center; + padding: 6px 80px; + font-size: 40px; + max-width: 200px; + min-width: 100px; + cursor: pointer; + } .buttonsmall { border-radius: 4px; background-color: var(--button-small-background); diff --git a/epicyon-profile.css b/epicyon-profile.css index 43b9dd4e4..1cb9d1b9c 100644 --- a/epicyon-profile.css +++ b/epicyon-profile.css @@ -57,8 +57,19 @@ --icons-side: right; --title-color: #999; --focus-color: white; + --quote-right-margin: 0.1em; + --quote-font-weight: normal; + --quote-font-size: 120%; + --line-spacing: 130%; } +@font-face { + font-family: 'Bedstead'; + font-style: italic; + font-weight: normal; + font-display: block; + src: url('./fonts/bedstead.otf') format('opentype'); +} @font-face { font-family: 'Bedstead'; font-style: normal; @@ -77,6 +88,34 @@ body, html { min-width: 950px; margin: 0 auto; font-size: var(--font-size); + line-height: var(--line-spacing); +} + +blockquote { + border-left: 10px; + margin: 1.5em 10px; + padding: 0.5em 10px; + font-weight: var(--quote-font-weight); + font-style: italic; + font-size: var(--quote-font-size); + quotes: "\201C""\201D""\2018""\2019"; +} +blockquote:before { + content: open-quote; + font-size: 2em; + line-height: 0.1em; + margin-right: 0.25em; + vertical-align: -0.4em; +} +blockquote:after { + content: close-quote; + font-size: 2em; + line-height: 0.1em; + margin-left: var(--quote-right-margin); + vertical-align: -0.4em; +} +blockquote p { + display: inline; } .imageAnchor:focus img{ diff --git a/epicyon-search.css b/epicyon-search.css index eda0516de..92ed3217e 100644 --- a/epicyon-search.css +++ b/epicyon-search.css @@ -35,6 +35,13 @@ --focus-color: white; } +@font-face { + font-family: 'Bedstead'; + font-style: italic; + font-weight: normal; + font-display: block; + src: url('./fonts/bedstead.otf') format('opentype'); +} @font-face { font-family: 'Bedstead'; font-style: normal; diff --git a/epicyon-suspended.css b/epicyon-suspended.css index 03ddc7dc2..4a5d03304 100644 --- a/epicyon-suspended.css +++ b/epicyon-suspended.css @@ -22,6 +22,13 @@ --focus-color: white; } +@font-face { + font-family: 'Bedstead'; + font-style: italic; + font-weight: normal; + font-display: block; + src: url('./fonts/bedstead.otf') format('opentype'); +} @font-face { font-family: 'Bedstead'; font-style: normal; diff --git a/follow.py b/follow.py index 6b764df37..ea3ac55b0 100644 --- a/follow.py +++ b/follow.py @@ -244,7 +244,7 @@ def clearFollows(baseDir: str, nickname: str, domain: str, followFile='following.txt') -> None: """Removes all follows """ - handle = nickname.lower() + '@' + domain.lower() + handle = nickname + '@' + domain if not os.path.isdir(baseDir + '/accounts'): os.mkdir(baseDir + '/accounts') if not os.path.isdir(baseDir + '/accounts/' + handle): @@ -269,7 +269,7 @@ def getNoOfFollows(baseDir: str, nickname: str, domain: str, # account holders # if not authenticated: # return 9999 - handle = nickname.lower() + '@' + domain.lower() + handle = nickname + '@' + domain filename = baseDir + '/accounts/' + handle + '/' + followFile if not os.path.isfile(filename): return 0 @@ -378,7 +378,7 @@ def getFollowingFeed(baseDir: str, domain: str, port: int, path: str, handleDomain = domain if ':' in handleDomain: handleDomain = domain.split(':')[0] - handle = nickname.lower() + '@' + handleDomain.lower() + handle = nickname + '@' + handleDomain filename = baseDir + '/accounts/' + handle + '/' + followFile + '.txt' if not os.path.isfile(filename): return following diff --git a/gemini/EN/index.gmi b/gemini/EN/index.gmi index c8fed18df..aab2ee499 100644 --- a/gemini/EN/index.gmi +++ b/gemini/EN/index.gmi @@ -17,7 +17,7 @@ Epicyon is an AGPL licensed ActivityPub protocol compliant federated social netw ## An Internet of People, Not Corporate Agendas -Epicyon is written in Python with a HTML+CSS web interface and uses no javascript which makes display in a web browser very lightweight. +Epicyon is written in Python with a HTML+CSS web interface and uses no javascript which makes display in a web browser very lightweight. It can run as a Progressive Web App on mobile. Just say "no" to boring social media sites packed with generic adverts and zombified corporate influencers. Emojis, hashtags, photos, video and audio attachments, instance and account level blocking controls, moderation functions and reports are all supported. Build the community you want and avoid the stuff you don't. No ads. No blockchains or other Silicon Valley garbage. diff --git a/gemini/EN/install.gmi b/gemini/EN/install.gmi index 0eb54ba71..6f0b419c7 100644 --- a/gemini/EN/install.gmi +++ b/gemini/EN/install.gmi @@ -158,7 +158,7 @@ Enable the site: Forward port 443 from your internet router to your server. If you have dynamic DNS make sure its configured. Add a TLS certificate: - certbot certonly -n --server https://acme-v01.api.letsencrypt.org/directory --standalone -d YOUR_DOMAIN --renew-by-default --agree-tos --email YOUR_EMAIL + certbot certonly -n --server https://acme-v02.api.letsencrypt.org/directory --standalone -d YOUR_DOMAIN --renew-by-default --agree-tos --email YOUR_EMAIL Restart your web server: diff --git a/img/banner_indymedia.png b/img/banner_indymedia.png new file mode 100644 index 000000000..2d4c72ca0 Binary files /dev/null and b/img/banner_indymedia.png differ diff --git a/img/icons/indymedia/add.png b/img/icons/indymedia/add.png new file mode 100644 index 000000000..67b9501c7 Binary files /dev/null and b/img/icons/indymedia/add.png differ diff --git a/img/icons/indymedia/bookmark.png b/img/icons/indymedia/bookmark.png new file mode 100644 index 000000000..b2d34ed44 Binary files /dev/null and b/img/icons/indymedia/bookmark.png differ diff --git a/img/icons/indymedia/bookmark_inactive.png b/img/icons/indymedia/bookmark_inactive.png new file mode 100644 index 000000000..94e1c3358 Binary files /dev/null and b/img/icons/indymedia/bookmark_inactive.png differ diff --git a/img/icons/indymedia/calendar.png b/img/icons/indymedia/calendar.png new file mode 100644 index 000000000..adb801774 Binary files /dev/null and b/img/icons/indymedia/calendar.png differ diff --git a/img/icons/indymedia/calendar_notify.png b/img/icons/indymedia/calendar_notify.png new file mode 100644 index 000000000..641f92f70 Binary files /dev/null and b/img/icons/indymedia/calendar_notify.png differ diff --git a/img/icons/indymedia/delete.png b/img/icons/indymedia/delete.png new file mode 100644 index 000000000..8d7a5dfda Binary files /dev/null and b/img/icons/indymedia/delete.png differ diff --git a/img/icons/indymedia/dm.png b/img/icons/indymedia/dm.png new file mode 100644 index 000000000..8e40f56b4 Binary files /dev/null and b/img/icons/indymedia/dm.png differ diff --git a/img/icons/indymedia/download.png b/img/icons/indymedia/download.png new file mode 100644 index 000000000..c666bad26 Binary files /dev/null and b/img/icons/indymedia/download.png differ diff --git a/img/icons/indymedia/edit.png b/img/icons/indymedia/edit.png new file mode 100644 index 000000000..4767ce0f2 Binary files /dev/null and b/img/icons/indymedia/edit.png differ diff --git a/img/icons/indymedia/like.png b/img/icons/indymedia/like.png new file mode 100644 index 000000000..34782ddbf Binary files /dev/null and b/img/icons/indymedia/like.png differ diff --git a/img/icons/indymedia/like_inactive.png b/img/icons/indymedia/like_inactive.png new file mode 100644 index 000000000..efd55d912 Binary files /dev/null and b/img/icons/indymedia/like_inactive.png differ diff --git a/img/icons/indymedia/mute.png b/img/icons/indymedia/mute.png new file mode 100644 index 000000000..7590a224d Binary files /dev/null and b/img/icons/indymedia/mute.png differ diff --git a/img/icons/indymedia/new.png b/img/icons/indymedia/new.png new file mode 100644 index 000000000..27e974b6e Binary files /dev/null and b/img/icons/indymedia/new.png differ diff --git a/img/icons/indymedia/newpost.png b/img/icons/indymedia/newpost.png new file mode 100644 index 000000000..84664fbe2 Binary files /dev/null and b/img/icons/indymedia/newpost.png differ diff --git a/img/icons/indymedia/pagedown.png b/img/icons/indymedia/pagedown.png new file mode 100644 index 000000000..059eee161 Binary files /dev/null and b/img/icons/indymedia/pagedown.png differ diff --git a/img/icons/indymedia/pageup.png b/img/icons/indymedia/pageup.png new file mode 100644 index 000000000..7a84d0c91 Binary files /dev/null and b/img/icons/indymedia/pageup.png differ diff --git a/img/icons/indymedia/person.png b/img/icons/indymedia/person.png new file mode 100644 index 000000000..22fe60956 Binary files /dev/null and b/img/icons/indymedia/person.png differ diff --git a/img/icons/indymedia/prev.png b/img/icons/indymedia/prev.png new file mode 100644 index 000000000..f8844cacc Binary files /dev/null and b/img/icons/indymedia/prev.png differ diff --git a/img/icons/indymedia/qrcode.png b/img/icons/indymedia/qrcode.png new file mode 100644 index 000000000..933a2671c Binary files /dev/null and b/img/icons/indymedia/qrcode.png differ diff --git a/img/icons/indymedia/repeat.png b/img/icons/indymedia/repeat.png new file mode 100644 index 000000000..70a8ba5fc Binary files /dev/null and b/img/icons/indymedia/repeat.png differ diff --git a/img/icons/indymedia/repeat_inactive.png b/img/icons/indymedia/repeat_inactive.png new file mode 100644 index 000000000..d6ad23831 Binary files /dev/null and b/img/icons/indymedia/repeat_inactive.png differ diff --git a/img/icons/indymedia/reply.png b/img/icons/indymedia/reply.png new file mode 100644 index 000000000..058a4ebce Binary files /dev/null and b/img/icons/indymedia/reply.png differ diff --git a/img/icons/indymedia/rss.png b/img/icons/indymedia/rss.png new file mode 100644 index 000000000..533453fc7 Binary files /dev/null and b/img/icons/indymedia/rss.png differ diff --git a/img/icons/indymedia/rss3.png b/img/icons/indymedia/rss3.png new file mode 100644 index 000000000..83521cd1b Binary files /dev/null and b/img/icons/indymedia/rss3.png differ diff --git a/img/icons/indymedia/scope_blog.png b/img/icons/indymedia/scope_blog.png new file mode 100644 index 000000000..c1a1b2596 Binary files /dev/null and b/img/icons/indymedia/scope_blog.png differ diff --git a/img/icons/indymedia/scope_dm.png b/img/icons/indymedia/scope_dm.png new file mode 100644 index 000000000..222ad6548 Binary files /dev/null and b/img/icons/indymedia/scope_dm.png differ diff --git a/img/icons/indymedia/scope_event.png b/img/icons/indymedia/scope_event.png new file mode 100644 index 000000000..de0fc7575 Binary files /dev/null and b/img/icons/indymedia/scope_event.png differ diff --git a/img/icons/indymedia/scope_followers.png b/img/icons/indymedia/scope_followers.png new file mode 100644 index 000000000..e2fa18fda Binary files /dev/null and b/img/icons/indymedia/scope_followers.png differ diff --git a/img/icons/indymedia/scope_public.png b/img/icons/indymedia/scope_public.png new file mode 100644 index 000000000..dd9762d9f Binary files /dev/null and b/img/icons/indymedia/scope_public.png differ diff --git a/img/icons/indymedia/scope_question.png b/img/icons/indymedia/scope_question.png new file mode 100644 index 000000000..bc4274457 Binary files /dev/null and b/img/icons/indymedia/scope_question.png differ diff --git a/img/icons/indymedia/scope_reminder.png b/img/icons/indymedia/scope_reminder.png new file mode 100644 index 000000000..a08fe1f5f Binary files /dev/null and b/img/icons/indymedia/scope_reminder.png differ diff --git a/img/icons/indymedia/scope_report.png b/img/icons/indymedia/scope_report.png new file mode 100644 index 000000000..9cb23e085 Binary files /dev/null and b/img/icons/indymedia/scope_report.png differ diff --git a/img/icons/indymedia/scope_share.png b/img/icons/indymedia/scope_share.png new file mode 100644 index 000000000..200a57c8a Binary files /dev/null and b/img/icons/indymedia/scope_share.png differ diff --git a/img/icons/indymedia/scope_unlisted.png b/img/icons/indymedia/scope_unlisted.png new file mode 100644 index 000000000..9d518201b Binary files /dev/null and b/img/icons/indymedia/scope_unlisted.png differ diff --git a/img/icons/indymedia/search.png b/img/icons/indymedia/search.png new file mode 100644 index 000000000..27a9c77a4 Binary files /dev/null and b/img/icons/indymedia/search.png differ diff --git a/img/icons/indymedia/showhide.png b/img/icons/indymedia/showhide.png new file mode 100644 index 000000000..9840a3e76 Binary files /dev/null and b/img/icons/indymedia/showhide.png differ diff --git a/img/icons/indymedia/unmute.png b/img/icons/indymedia/unmute.png new file mode 100644 index 000000000..56caef851 Binary files /dev/null and b/img/icons/indymedia/unmute.png differ diff --git a/img/image_indymedia.png b/img/image_indymedia.png new file mode 100644 index 000000000..b7458173d Binary files /dev/null and b/img/image_indymedia.png differ diff --git a/img/login_background_indymedia.jpg b/img/login_background_indymedia.jpg new file mode 100644 index 000000000..c71a8a03a Binary files /dev/null and b/img/login_background_indymedia.jpg differ diff --git a/img/options_background_indymedia.jpg b/img/options_background_indymedia.jpg new file mode 100644 index 000000000..1e2ce54df Binary files /dev/null and b/img/options_background_indymedia.jpg differ diff --git a/img/screenshot_emojisearch.jpg b/img/screenshot_emojisearch.jpg new file mode 100644 index 000000000..ffc55c5bb Binary files /dev/null and b/img/screenshot_emojisearch.jpg differ diff --git a/img/screenshot_light.jpg b/img/screenshot_light.jpg index 76dc2c752..5d2aeee7c 100644 Binary files a/img/screenshot_light.jpg and b/img/screenshot_light.jpg differ diff --git a/img/screenshot_light2.jpg b/img/screenshot_light2.jpg index ab8b48106..dd5548f48 100644 Binary files a/img/screenshot_light2.jpg and b/img/screenshot_light2.jpg differ diff --git a/img/screenshot_login.jpg b/img/screenshot_login.jpg new file mode 100644 index 000000000..fd099540e Binary files /dev/null and b/img/screenshot_login.jpg differ diff --git a/img/screenshot_tagswarm.jpg b/img/screenshot_tagswarm.jpg index 8e17f1c5b..13651e09e 100644 Binary files a/img/screenshot_tagswarm.jpg and b/img/screenshot_tagswarm.jpg differ diff --git a/img/screenshots.jpg b/img/screenshots.jpg index 419426fff..7985bca34 100644 Binary files a/img/screenshots.jpg and b/img/screenshots.jpg differ diff --git a/img/search_banner_indymedia.png b/img/search_banner_indymedia.png new file mode 100644 index 000000000..8d0c733ab Binary files /dev/null and b/img/search_banner_indymedia.png differ diff --git a/person.py b/person.py index b1fa69444..071f4beda 100644 --- a/person.py +++ b/person.py @@ -73,7 +73,7 @@ def setProfileImage(baseDir: str, httpPrefix: str, nickname: str, domain: str, if ':' not in domain: fullDomain = domain + ':' + str(port) - handle = nickname.lower() + '@' + domain.lower() + handle = nickname + '@' + domain personFilename = baseDir + '/accounts/' + handle + '.json' if not os.path.isfile(personFilename): print('person definition not found: ' + personFilename) @@ -210,7 +210,7 @@ def createPersonBase(baseDir: str, nickname: str, domain: str, port: int, storeWebfingerEndpoint(nickname, domain, port, baseDir, webfingerEndpoint) - handle = nickname.lower() + '@' + domain.lower() + handle = nickname + '@' + domain originalDomain = domain if port: if port != 80 and port != 443: @@ -736,8 +736,8 @@ def setDisplayNickname(baseDir: str, nickname: str, domain: str, displayName: str) -> bool: if len(displayName) > 32: return False - handle = nickname.lower() + '@' + domain.lower() - filename = baseDir + '/accounts/' + handle.lower() + '.json' + handle = nickname + '@' + domain + filename = baseDir + '/accounts/' + handle + '.json' if not os.path.isfile(filename): return False @@ -752,8 +752,8 @@ def setDisplayNickname(baseDir: str, nickname: str, domain: str, def setBio(baseDir: str, nickname: str, domain: str, bio: str) -> bool: if len(bio) > 32: return False - handle = nickname.lower() + '@' + domain.lower() - filename = baseDir + '/accounts/' + handle.lower() + '.json' + handle = nickname + '@' + domain + filename = baseDir + '/accounts/' + handle + '.json' if not os.path.isfile(filename): return False diff --git a/posts.py b/posts.py index 1d32205f7..3a594a811 100644 --- a/posts.py +++ b/posts.py @@ -668,6 +668,41 @@ def validContentWarning(cw: str) -> str: return cw +def loadAutoCW(baseDir: str, nickname: str, domain: str) -> []: + """Loads automatic CWs file and returns a list containing + the lines of the file + """ + filename = baseDir + '/accounts/' + \ + nickname + '@' + domain + '/autocw.txt' + if not os.path.isfile(filename): + return [] + with open(filename, "r") as f: + return f.readlines() + return [] + + +def addAutoCW(baseDir: str, nickname: str, domain: str, + subject: str, content: str) -> str: + """Appends any automatic CW to the subject line + and returns the new subject line + """ + newSubject = subject + autoCWList = loadAutoCW(baseDir, nickname, domain) + for cwRule in autoCWList: + if '->' not in cwRule: + continue + match = cwRule.split('->')[0].strip() + if match not in content: + continue + cwStr = cwRule.split('->')[1].strip() + if newSubject: + if cwStr not in newSubject: + newSubject += ', ' + cwStr + else: + newSubject = cwStr + return newSubject + + def createPostBase(baseDir: str, nickname: str, domain: str, port: int, toUrl: str, ccUrl: str, httpPrefix: str, content: str, followersOnly: bool, saveToFile: bool, clientToServer: bool, @@ -687,6 +722,8 @@ def createPostBase(baseDir: str, nickname: str, domain: str, port: int, eventStatus=None, ticketUrl=None) -> {}: """Creates a message """ + subject = addAutoCW(baseDir, nickname, domain, subject, content) + mentionedRecipients = \ getMentionedPeople(baseDir, httpPrefix, content, domain, False) diff --git a/tests.py b/tests.py index 51b00cbd4..abb35f43e 100644 --- a/tests.py +++ b/tests.py @@ -68,6 +68,7 @@ from delete import sendDeleteViaServer from inbox import jsonPostAllowsComments from inbox import validInbox from inbox import validInboxFilenames +from content import htmlReplaceEmailQuote from content import htmlReplaceQuoteMarks from content import dangerousMarkup from content import removeHtml @@ -2124,8 +2125,48 @@ def testConstantTimeStringCheck(): assert timeDiffMicroseconds < 10 +def testReplaceEmailQuote(): + print('testReplaceEmailQuote') + testStr = '

This content has no quote.

' + assert htmlReplaceEmailQuote(testStr) == testStr + + testStr = '

This content has no quote.

' + \ + '

With multiple

lines

' + assert htmlReplaceEmailQuote(testStr) == testStr + + testStr = '

"This is a quoted paragraph."

' + assert htmlReplaceEmailQuote(testStr) == \ + '

This is a quoted paragraph.

' + + testStr = "

" + \ + "@nickname " + \ + "
> This is a quote

Some other text.

" + expectedStr = "

" + \ + "@nickname " + \ + "

This is a quote

" + \ + "

Some other text.

" + resultStr = htmlReplaceEmailQuote(testStr) + if resultStr != expectedStr: + print('Result: ' + resultStr) + print('Expect: ' + expectedStr) + assert resultStr == expectedStr + + testStr = "

Some text:

> first line->second line

" + \ + "

Some question?

" + expectedStr = "

Some text:

first line-
" + \ + "second line

Some question?

" + resultStr = htmlReplaceEmailQuote(testStr) + if resultStr != expectedStr: + print('Result: ' + resultStr) + print('Expect: ' + expectedStr) + assert resultStr == expectedStr + + def runAllTests(): print('Running tests...') + testReplaceEmailQuote() testConstantTimeStringCheck() testTranslations() testValidContentWarning() diff --git a/theme.py b/theme.py index 6a7bd5377..1c092e630 100644 --- a/theme.py +++ b/theme.py @@ -25,8 +25,8 @@ def getThemesList() -> []: and to lookup function names """ return ('Default', 'Blue', 'Hacker', 'Henge', 'HighVis', - 'LCD', 'Light', 'Night', 'Purple', 'Solidaric', - 'Starlight', 'Zen') + 'Indymedia', 'LCD', 'Light', 'Night', 'Purple', + 'Solidaric', 'Starlight', 'Zen') def setThemeInConfig(baseDir: str, name: str) -> bool: @@ -67,13 +67,21 @@ def setCSSparam(css: str, param: str, value: str) -> str: if param.startswith('rgba('): return css.replace(param, value) # if the parameter begins with * then don't prepend -- + onceOnly = False if param.startswith('*'): - searchStr = param.replace('*', '') + ':' + if param.startswith('**'): + onceOnly = True + searchStr = param.replace('**', '') + ':' + else: + searchStr = param.replace('*', '') + ':' else: searchStr = '--' + param + ':' if searchStr not in css: return css - s = css.split(searchStr) + if onceOnly: + s = css.split(searchStr, 1) + else: + s = css.split(searchStr) newcss = '' for sectionStr in s: if not newcss: @@ -235,6 +243,59 @@ def setThemeDefault(baseDir: str): setThemeFromDict(baseDir, name, themeParams, bgParams) +def setThemeIndymedia(baseDir: str): + name = 'indymedia' + removeTheme(baseDir) + setThemeInConfig(baseDir, name) + bgParams = { + "login": "jpg", + "follow": "jpg", + "options": "jpg", + "search": "jpg" + } + themeParams = { + "button-corner-radius": "5px", + "timeline-border-radius": "5px", + "focus-color": "blue", + "font-size-button-mobile": "36px", + "font-size": "32px", + "font-size2": "26px", + "font-size3": "40px", + "font-size4": "24px", + "font-size5": "22px", + "main-bg-color": "black", + "text-entry-background": "#0f0d10", + "link-bg-color": "black", + "main-link-color": "#ff9900", + "main-link-color-hover": "#d09338", + "main-visited-color": "#ffb900", + "main-fg-color": "white", + "main-bg-color-dm": "#0b0a0a", + "border-color": "#003366", + "border-width": "0", + "main-bg-color-reply": "#0f0d10", + "main-bg-color-report": "#0f0d10", + "hashtag-vertical-spacing3": "100px", + "hashtag-vertical-spacing4": "150px", + "button-background-hover": "darkblue", + "button-background": "#003366", + "button-selected": "blue", + "calendar-bg-color": "#0f0d10", + "event-background": "#555", + "border-color": "#003366", + "lines-color": "#ff9900", + "day-number": "lightblue", + "day-number2": "white", + "time-color": "#003366", + "place-color": "#003366", + "event-color": "#003366", + "title-text": "white", + "title-background": "#003366", + "quote-right-margin": "0.1em", + } + setThemeFromDict(baseDir, name, themeParams, bgParams) + + def setThemeBlue(baseDir: str): name = 'blue' removeTheme(baseDir) @@ -276,6 +337,8 @@ def setThemeNight(baseDir: str): setThemeInConfig(baseDir, name) fontStr = \ "url('./fonts/solidaric.woff2') format('woff2')" + fontStrItalic = \ + "url('./fonts/solidaric-italic.woff2') format('woff2')" themeParams = { "focus-color": "blue", "font-size-button-mobile": "36px", @@ -287,9 +350,11 @@ def setThemeNight(baseDir: str): "main-bg-color": "#0f0d10", "text-entry-background": "#0f0d10", "link-bg-color": "#0f0d10", - "main-fg-color": "#7961ab", + "main-link-color": "ff9900", + "main-link-color-hover": "#d09338", + "main-fg-color": "#a961ab", "main-bg-color-dm": "#0b0a0a", - "border-color": "#7961ab", + "border-color": "#606984", "main-bg-color-reply": "#0f0d10", "main-bg-color-report": "#0f0d10", "hashtag-vertical-spacing3": "100px", @@ -298,15 +363,18 @@ def setThemeNight(baseDir: str): "button-background": "#a961ab", "button-selected": "#86579d", "calendar-bg-color": "#0f0d10", - "lines-color": "#7961ab", - "day-number": "#7961ab", + "lines-color": "#a961ab", + "day-number": "#a961ab", "day-number2": "#555", - "time-color": "#7961ab", - "place-color": "#7961ab", - "event-color": "#7961ab", + "time-color": "#a961ab", + "place-color": "#a961ab", + "event-color": "#a961ab", "event-background": "#333", + "quote-right-margin": "0", + "line-spacing": "150%", "*font-family": "'solidaric'", - "*src": fontStr + "*src": fontStr, + "**src": fontStrItalic } bgParams = { "login": "jpg", @@ -364,6 +432,7 @@ def setThemeStarlight(baseDir: str): "place-color": "#ffc4bc", "event-color": "#ffc4bc", "image-corners": "2%", + "quote-right-margin": "0.1em", "*font-family": "'bgrove'", "*src": "url('fonts/bgrove.woff2') format('woff2')" } @@ -418,6 +487,7 @@ def setThemeHenge(baseDir: str): "event-background": "#333", "timeline-border-radius": "20px", "image-corners": "8%", + "quote-right-margin": "0.1em", "*font-family": "'bgrove'", "*src": "url('fonts/bgrove.woff2') format('woff2')" } @@ -766,8 +836,11 @@ def setThemeSolidaric(baseDir: str): "title-text": "#282c37", "title-background": "#ccc", "gallery-text-color": "black", + "quote-right-margin": "0", + "line-spacing": "150%", "*font-family": "'solidaric'", - "*src": "url('./fonts/solidaric.woff2') format('woff2')" + "*src": "url('./fonts/solidaric.woff2') format('woff2')", + "**src": "url('./fonts/solidaric-italic.woff2') format('woff2')" } bgParams = { "login": "jpg", diff --git a/translations/ar.json b/translations/ar.json index 6faa4aa4a..a27a5c6d2 100644 --- a/translations/ar.json +++ b/translations/ar.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "سياسة الوسطية أو قواعد السلوك", "Edit event": "تحرير الحدث", "Notify when posts are liked": "يخطر عندما يتم اعجاب المشاركات", - "Don't show the Like button": "لا تظهر زر أعجبني" + "Don't show the Like button": "لا تظهر زر أعجبني", + "Autogenerated Hashtags": "علامات التجزئة المُنشأة تلقائيًا", + "Autogenerated Content Warnings": "تحذيرات المحتوى المُنشأ تلقائيًا", + "Indymedia": "Indymedia" } diff --git a/translations/ca.json b/translations/ca.json index c59b79eb8..b24e0a819 100644 --- a/translations/ca.json +++ b/translations/ca.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "Política de moderació o codi de conducta", "Edit event": "Edita l’esdeveniment", "Notify when posts are liked": "Notifiqueu-ho quan us agradin les publicacions", - "Don't show the Like button": "No mostreu el botó M'agrada" + "Don't show the Like button": "No mostreu el botó M'agrada", + "Autogenerated Hashtags": "Hashtags autogenerats", + "Autogenerated Content Warnings": "Advertiments de contingut autogenerats", + "Indymedia": "Indymedia" } diff --git a/translations/cy.json b/translations/cy.json index e77c7ecea..65d011d4b 100644 --- a/translations/cy.json +++ b/translations/cy.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "Polisi cymedroli neu god ymddygiad", "Edit event": "Golygu digwyddiad", "Notify when posts are liked": "Hysbysu pryd mae swyddi'n cael eu hoffi", - "Don't show the Like button": "Peidiwch â dangos y botwm Hoffi" + "Don't show the Like button": "Peidiwch â dangos y botwm Hoffi", + "Autogenerated Hashtags": "Hashtags awtogeneiddiedig", + "Autogenerated Content Warnings": "Rhybuddion Cynnwys Autogenerated", + "Indymedia": "Indymedia" } diff --git a/translations/de.json b/translations/de.json index 957c54f6b..3ca9eee82 100644 --- a/translations/de.json +++ b/translations/de.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "Moderationsrichtlinie oder Verhaltenskodex", "Edit event": "Ereignis bearbeiten", "Notify when posts are liked": "Benachrichtigen, wenn Beiträge gefallen", - "Don't show the Like button": "Zeigen Sie nicht die Schaltfläche \"Gefällt mir\" an" + "Don't show the Like button": "Zeigen Sie nicht die Schaltfläche \"Gefällt mir\" an", + "Autogenerated Hashtags": "Automatisch generierte Hashtags", + "Autogenerated Content Warnings": "Warnungen vor automatisch generierten Inhalten", + "Indymedia": "Indymedia" } diff --git a/translations/en.json b/translations/en.json index aaa545bc7..b93dfd158 100644 --- a/translations/en.json +++ b/translations/en.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "Moderation policy or code of conduct", "Edit event": "Edit event", "Notify when posts are liked": "Notify when posts are liked", - "Don't show the Like button": "Don't show the Like button" + "Don't show the Like button": "Don't show the Like button", + "Autogenerated Hashtags": "Autogenerated Hashtags", + "Autogenerated Content Warnings": "Autogenerated Content Warnings", + "Indymedia": "Indymedia" } diff --git a/translations/es.json b/translations/es.json index 07d010a93..150aed26f 100644 --- a/translations/es.json +++ b/translations/es.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "Política de moderación o código de conducta", "Edit event": "Editar evento", "Notify when posts are liked": "Notificar cuando les gusten las publicaciones", - "Don't show the Like button": "No mostrar el botón Me gusta" + "Don't show the Like button": "No mostrar el botón Me gusta", + "Autogenerated Hashtags": "Hashtags autogenerados", + "Autogenerated Content Warnings": "Advertencias de contenido generado automáticamente", + "Indymedia": "Indymedia" } diff --git a/translations/fr.json b/translations/fr.json index 2f85eecda..09be80db8 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "Politique de modération ou code de conduite", "Edit event": "Modifier l'événement", "Notify when posts are liked": "Notifier lorsque les messages sont aimés", - "Don't show the Like button": "Ne pas afficher le bouton J'aime" + "Don't show the Like button": "Ne pas afficher le bouton J'aime", + "Autogenerated Hashtags": "Hashtags générés automatiquement", + "Autogenerated Content Warnings": "Avertissements de contenu générés automatiquement", + "Indymedia": "Indymedia" } diff --git a/translations/ga.json b/translations/ga.json index f758411f2..7434193c1 100644 --- a/translations/ga.json +++ b/translations/ga.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "Beartas modhnóireachta nó cód iompair", "Edit event": "Cuir imeacht in eagar", "Notify when posts are liked": "Cuir in iúl cathain is maith poist", - "Don't show the Like button": "Ná taispeáin an cnaipe Cosúil" + "Don't show the Like button": "Ná taispeáin an cnaipe Cosúil", + "Autogenerated Hashtags": "Hashtags uathghinte", + "Autogenerated Content Warnings": "Rabhaidh Ábhar Uathghinte", + "Indymedia": "Indymedia" } diff --git a/translations/hi.json b/translations/hi.json index 6da7225e2..59a9a1efd 100644 --- a/translations/hi.json +++ b/translations/hi.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "मॉडरेशन पॉलिसी या आचार संहिता", "Edit event": "घटना संपादित करें", "Notify when posts are liked": "पोस्ट पसंद आने पर सूचित करें", - "Don't show the Like button": "लाइक बटन न दिखाएं" + "Don't show the Like button": "लाइक बटन न दिखाएं", + "Autogenerated Hashtags": "ऑटोजेनरेटेड हैशटैग", + "Autogenerated Content Warnings": "स्वतः प्राप्त सामग्री चेतावनी", + "Indymedia": "Indymedia" } diff --git a/translations/it.json b/translations/it.json index 71952e1a6..7bc918965 100644 --- a/translations/it.json +++ b/translations/it.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "Politica di moderazione o codice di condotta", "Edit event": "Modifica evento", "Notify when posts are liked": "Avvisa quando i post sono piaciuti", - "Don't show the Like button": "Non mostrare il pulsante Mi piace" + "Don't show the Like button": "Non mostrare il pulsante Mi piace", + "Autogenerated Hashtags": "Hashtag generati automaticamente", + "Autogenerated Content Warnings": "Avvisi sui contenuti generati automaticamente", + "Indymedia": "Indymedia" } diff --git a/translations/ja.json b/translations/ja.json index 384098456..98f47d53e 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "モデレートポリシーまたは行動規範", "Edit event": "イベントを編集", "Notify when posts are liked": "投稿が高く評価されたときに通知する", - "Don't show the Like button": "「いいね!」ボタンを表示しない" + "Don't show the Like button": "「いいね!」ボタンを表示しない", + "Autogenerated Hashtags": "自動生成されたハッシュタグ", + "Autogenerated Content Warnings": "自動生成されたコンテンツの警告", + "Indymedia": "Indymedia" } diff --git a/translations/oc.json b/translations/oc.json index fae634148..aeeeac9b8 100644 --- a/translations/oc.json +++ b/translations/oc.json @@ -279,5 +279,8 @@ "Moderation policy or code of conduct": "Moderation policy or code of conduct", "Edit event": "Edit event", "Notify when posts are liked": "Notify when posts are liked", - "Don't show the Like button": "Don't show the Like button" + "Don't show the Like button": "Don't show the Like button", + "Autogenerated Hashtags": "Autogenerated Hashtags", + "Autogenerated Content Warnings": "Autogenerated Content Warnings", + "Indymedia": "Indymedia" } diff --git a/translations/pt.json b/translations/pt.json index 010df87da..c8167d622 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "Política de moderação ou código de conduta", "Edit event": "Editar evento", "Notify when posts are liked": "Notificar quando as postagens forem curtidas", - "Don't show the Like button": "Não mostrar o botão Curtir" + "Don't show the Like button": "Não mostrar o botão Curtir", + "Autogenerated Hashtags": "Hashtags autogeradas", + "Autogenerated Content Warnings": "Avisos de conteúdo gerado automaticamente", + "Indymedia": "Indymedia" } diff --git a/translations/ru.json b/translations/ru.json index f4742761f..daa47b268 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "Политика модерации или кодекс поведения", "Edit event": "Изменить мероприятие", "Notify when posts are liked": "Уведомлять, когда публикации нравятся", - "Don't show the Like button": "Не показывать кнопку \"Нравится\"" + "Don't show the Like button": "Не показывать кнопку \"Нравится\"", + "Autogenerated Hashtags": "Автоматически сгенерированные хештеги", + "Autogenerated Content Warnings": "Автоматические предупреждения о содержании", + "Indymedia": "Indymedia" } diff --git a/translations/zh.json b/translations/zh.json index 7c3fffd88..d43831e42 100644 --- a/translations/zh.json +++ b/translations/zh.json @@ -283,5 +283,8 @@ "Moderation policy or code of conduct": "审核政策或行为准则", "Edit event": "编辑活动", "Notify when posts are liked": "通知喜欢的帖子", - "Don't show the Like button": "不显示“赞”按钮" + "Don't show the Like button": "不显示“赞”按钮", + "Autogenerated Hashtags": "自动生成的标签", + "Autogenerated Content Warnings": "自动生成的内容警告", + "Indymedia": "Indymedia" } diff --git a/utils.py b/utils.py index 97fccaa8b..8a18c7534 100644 --- a/utils.py +++ b/utils.py @@ -327,9 +327,9 @@ def followPerson(baseDir: str, nickname: str, domain: str, print('DEBUG: follow of domain ' + followDomain) if ':' in domain: - handle = nickname + '@' + domain.split(':')[0].lower() + handle = nickname + '@' + domain.split(':')[0] else: - handle = nickname + '@' + domain.lower() + handle = nickname + '@' + domain if not os.path.isdir(baseDir + '/accounts/' + handle): print('WARN: account for ' + handle + ' does not exist') diff --git a/webfinger.py b/webfinger.py index c1ecdfc1c..65b5666fc 100644 --- a/webfinger.py +++ b/webfinger.py @@ -121,11 +121,11 @@ def storeWebfingerEndpoint(nickname: str, domain: str, port: int, wfSubdir = '/wfendpoints' if not os.path.isdir(baseDir + wfSubdir): os.mkdir(baseDir + wfSubdir) - filename = baseDir + wfSubdir + '/' + handle.lower() + '.json' + filename = baseDir + wfSubdir + '/' + handle + '.json' saveJson(wfJson, filename) if nickname == 'inbox': handle = originalDomain + '@' + domain - filename = baseDir + wfSubdir + '/' + handle.lower() + '.json' + filename = baseDir + wfSubdir + '/' + handle + '.json' saveJson(wfJson, filename) return True @@ -261,7 +261,7 @@ def webfingerLookup(path: str, baseDir: str, if onionDomain in handle: handle = handle.replace(onionDomain, domain) onionify = True - filename = baseDir + '/wfendpoints/' + handle.lower() + '.json' + filename = baseDir + '/wfendpoints/' + handle + '.json' if debug: print('DEBUG: WEBFINGER filename ' + filename) if not os.path.isfile(filename): @@ -339,7 +339,7 @@ def webfingerUpdate(baseDir: str, nickname: str, domain: str, if not os.path.isdir(baseDir + wfSubdir): return - filename = baseDir + wfSubdir + '/' + handle.lower() + '.json' + filename = baseDir + wfSubdir + '/' + handle + '.json' onionify = False if onionDomain: if onionDomain in handle: @@ -352,7 +352,7 @@ def webfingerUpdate(baseDir: str, nickname: str, domain: str, if not wfJson: return - actorFilename = baseDir + '/accounts/' + handle.lower() + '.json' + actorFilename = baseDir + '/accounts/' + handle + '.json' actorJson = loadJson(actorFilename) if not actorJson: return diff --git a/webinterface.py b/webinterface.py index 6a23e7aac..32ded79de 100644 --- a/webinterface.py +++ b/webinterface.py @@ -58,6 +58,7 @@ from bookmarks import bookmarkedByPerson from announce import announcedByPerson from blocking import isBlocked from blocking import isBlockedHashtag +from content import htmlReplaceEmailQuote from content import htmlReplaceQuoteMarks from content import removeTextFormatting from content import switchWords @@ -523,20 +524,20 @@ def htmlSearchSharedItems(translate: {}, break if matched: if currPage == pageNumber: - sharedItemsForm += '
' + sharedItemsForm += '
\n' sharedItemsForm += \ '' + sharedItem['displayName'] + '

\n' if sharedItem.get('imageUrl'): sharedItemsForm += \ '' + sharedItem['imageUrl'] + '">\n' sharedItemsForm += \ 'Item image' + '" alt="Item image">\n' sharedItemsForm += \ - '

' + sharedItem['summary'] + '

' + '

' + sharedItem['summary'] + '

\n' sharedItemsForm += \ '

' + translate['Type'] + \ ': ' + sharedItem['itemType'] + ' ' @@ -545,7 +546,7 @@ def htmlSearchSharedItems(translate: {}, ': ' + sharedItem['category'] + ' ' sharedItemsForm += \ '' + translate['Location'] + \ - ': ' + sharedItem['location'] + '

' + ': ' + sharedItem['location'] + '

\n' contactActor = \ httpPrefix + '://' + domainFull + \ '/users/' + contactNickname @@ -555,13 +556,13 @@ def htmlSearchSharedItems(translate: {}, sharedItem['displayName'] + \ '?mention=' + contactActor + \ '">' + translate['Contact'] + '\n' if actor.endswith('/users/' + contactNickname): sharedItemsForm += \ ' ' - sharedItemsForm += '

' + translate['Remove'] + '\n' + sharedItemsForm += '

\n' if not resultsExist and currPage > 1: postActor = \ getAltPath(actor, domainFull, @@ -571,26 +572,26 @@ def htmlSearchSharedItems(translate: {}, '
' + str(pageNumber - 1) + '">\n' sharedItemsForm += \ ' ' + 'name="actor" value="' + actor + '">\n' sharedItemsForm += \ '
' + searchStrLower + '">
\n' sharedItemsForm += \ '
' + '" type="submit" name="submitSearch">\n' sharedItemsForm += \ ' ' + translate['Page up'] + \
-                                    '' - sharedItemsForm += '
' - sharedItemsForm += '
' + '"/>\n' + sharedItemsForm += ' \n' + sharedItemsForm += '\n' resultsExist = True ctr += 1 if ctr >= resultsPerPage: @@ -604,31 +605,31 @@ def htmlSearchSharedItems(translate: {}, '
' + str(pageNumber + 1) + '">\n' sharedItemsForm += \ ' ' + 'name="actor" value="' + actor + '">\n' sharedItemsForm += \ '
' + searchStrLower + '">
\n' sharedItemsForm += \ '
' + '" type="submit" name="submitSearch">\n' sharedItemsForm += \ ' ' + translate['Page down'] + \
-                                    '' - sharedItemsForm += '
' - sharedItemsForm += '
' + '"/>\n' + sharedItemsForm += ' \n' + sharedItemsForm += '\n' break ctr = 0 if not resultsExist: sharedItemsForm += \ - '
' + translate['No results'] + '
' + '
' + translate['No results'] + '
\n' sharedItemsForm += htmlFooter() return sharedItemsForm @@ -1167,6 +1168,22 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str, with open(switchFilename, 'r') as switchfile: switchStr = switchfile.read() + autoTags = '' + autoTagsFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/autotags.txt' + if os.path.isfile(autoTagsFilename): + with open(autoTagsFilename, 'r') as autoTagsFile: + autoTags = autoTagsFile.read() + + autoCW = '' + autoCWFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/autocw.txt' + if os.path.isfile(autoCWFilename): + with open(autoCWFilename, 'r') as autoCWFile: + autoCW = autoCWFile.read() + blockedStr = '' blockedFilename = \ baseDir + '/accounts/' + \ @@ -1507,6 +1524,22 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str, ' \n' + editProfileForm += \ + '
\n' + editProfileForm += '
\n' + editProfileForm += \ + ' \n' + + editProfileForm += \ + '
\n' + editProfileForm += '
\n' + editProfileForm += \ + ' \n' + editProfileForm += \ '
\n' @@ -1880,7 +1913,7 @@ def htmlNewPost(mediaInstance: bool, translate: {}, ' ' + \ translate['this post'] + '

\n' replyStr = '' + 'name="replyTo" value="' + inReplyTo + '">\n' # if replying to a non-public post then also make # this post non-public @@ -4729,6 +4762,7 @@ def individualPostAsHtml(allowDownloads: bool, objectContent = removeTextFormatting(objectContent) objectContent = \ switchWords(baseDir, nickname, domain, objectContent) + objectContent = htmlReplaceEmailQuote(objectContent) objectContent = htmlReplaceQuoteMarks(objectContent) else: objectContent = \ @@ -6165,7 +6199,7 @@ def htmlPersonOptions(translate: {}, baseDir: str, if optionsLink: optionsLinkStr = \ ' ' + optionsLink + '">\n' cssFilename = baseDir + '/epicyon-options.css' if os.path.isfile(baseDir + '/options.css'): cssFilename = baseDir + '/options.css' @@ -6242,10 +6276,10 @@ def htmlPersonOptions(translate: {}, baseDir: str, handle = optionsNickname + '@' + optionsDomainFull petname = getPetName(baseDir, nickname, domain, handle) optionsStr += \ - translate['Petname'] + ': ' + \ - '\n' \ - '
\n' @@ -6253,24 +6287,24 @@ def htmlPersonOptions(translate: {}, baseDir: str, if receivingCalendarEvents(baseDir, nickname, domain, optionsNickname, optionsDomainFull): optionsStr += \ - ' ' + \ translate['Receive calendar events from this account'] + \ - '
\n' else: optionsStr += \ - ' ' + \ translate['Receive calendar events from this account'] + \ - '
\n' optionsStr += optionsLinkStr optionsStr += \ - ' \n' optionsStr += \ '
\n' optionsStr += \ diff --git a/website/EN/index.html b/website/EN/index.html index 712b11ea1..63a8aee3c 100644 --- a/website/EN/index.html +++ b/website/EN/index.html @@ -1159,7 +1159,7 @@

- Epicyon is written in Python with a HTML+CSS web interface and uses no javascript which makes display in a web browser very lightweight. + Epicyon is written in Python with a HTML+CSS web interface and uses no javascript which makes display in a web browser very lightweight. It can run as a Progressive Web App on mobile. Just say "no" to boring social media sites packed with generic adverts and zombified corporate influencers.

@@ -1171,6 +1171,10 @@ + + + +
various screenshots mobile screenshot
login screenemoji search

@@ -1444,7 +1448,7 @@ Forward port 443 from your internet router to your server. If you have dynamic DNS make sure its configured. Add a TLS certificate:

- certbot certonly -n --server https://acme-v01.api.letsencrypt.org/directory --standalone -d YOUR_DOMAIN --renew-by-default --agree-tos --email YOUR_EMAIL + certbot certonly -n --server https://acme-v02.api.letsencrypt.org/directory --standalone -d YOUR_DOMAIN --renew-by-default --agree-tos --email YOUR_EMAIL

Restart your web server: