diff --git a/daemon.py b/daemon.py index 3008469f4..cb1c75e2d 100644 --- a/daemon.py +++ b/daemon.py @@ -4050,6 +4050,7 @@ class PubServer(BaseHTTPRequestHandler): postBytesStr = postBytes.decode('utf-8') redirectPath = '' checkNameAndBio = False + onFinalWelcomeScreen = False if 'name="previewAvatar"' in postBytesStr: redirectPath = '/welcome_profile' elif 'name="initialWelcomeScreen"' in postBytesStr: @@ -4061,6 +4062,7 @@ class PubServer(BaseHTTPRequestHandler): redirectPath = '/' + self.server.defaultTimeline welcomeScreenIsComplete(self.server.baseDir, nickname, self.server.domain) + onFinalWelcomeScreen = True # extract all of the text fields into a dict fields = \ @@ -4717,15 +4719,20 @@ class PubServer(BaseHTTPRequestHandler): nickname, domain) # approve followers - approveFollowers = False - if fields.get('approveFollowers'): - if fields['approveFollowers'] == 'on': - approveFollowers = True - if approveFollowers != \ - actorJson['manuallyApprovesFollowers']: - actorJson['manuallyApprovesFollowers'] = \ - approveFollowers + if onFinalWelcomeScreen: + # Default setting created via the welcome screen + actorJson['manuallyApprovesFollowers'] = True actorChanged = True + else: + approveFollowers = False + if fields.get('approveFollowers'): + if fields['approveFollowers'] == 'on': + approveFollowers = True + if approveFollowers != \ + actorJson['manuallyApprovesFollowers']: + actorJson['manuallyApprovesFollowers'] = \ + approveFollowers + actorChanged = True # remove a custom font if fields.get('removeCustomFont'): @@ -4773,15 +4780,22 @@ class PubServer(BaseHTTPRequestHandler): baseDir + '/accounts/' + \ nickname + '@' + domain + \ '/.followDMs' - followDMsActive = False - if fields.get('followDMs'): - if fields['followDMs'] == 'on': - followDMsActive = True - with open(followDMsFilename, 'w+') as fFile: - fFile.write('\n') - if not followDMsActive: - if os.path.isfile(followDMsFilename): - os.remove(followDMsFilename) + if onFinalWelcomeScreen: + # initial default setting created via + # the welcome screen + with open(followDMsFilename, 'w+') as fFile: + fFile.write('\n') + actorChanged = True + else: + followDMsActive = False + if fields.get('followDMs'): + if fields['followDMs'] == 'on': + followDMsActive = True + with open(followDMsFilename, 'w+') as fFile: + fFile.write('\n') + if not followDMsActive: + if os.path.isfile(followDMsFilename): + os.remove(followDMsFilename) # remove Twitter retweets removeTwitterFilename = \ @@ -4822,16 +4836,22 @@ class PubServer(BaseHTTPRequestHandler): os.remove(hideLikeButtonFile) # notify about new Likes - notifyLikesActive = False - if fields.get('notifyLikes'): - if fields['notifyLikes'] == 'on' and \ - not hideLikeButtonActive: - notifyLikesActive = True - with open(notifyLikesFilename, 'w+') as rFile: - rFile.write('\n') - if not notifyLikesActive: - if os.path.isfile(notifyLikesFilename): - os.remove(notifyLikesFilename) + if onFinalWelcomeScreen: + # default setting from welcome screen + with open(notifyLikesFilename, 'w+') as rFile: + rFile.write('\n') + actorChanged = True + else: + notifyLikesActive = False + if fields.get('notifyLikes'): + if fields['notifyLikes'] == 'on' and \ + not hideLikeButtonActive: + notifyLikesActive = True + with open(notifyLikesFilename, 'w+') as rFile: + rFile.write('\n') + if not notifyLikesActive: + if os.path.isfile(notifyLikesFilename): + os.remove(notifyLikesFilename) # this account is a bot if fields.get('isBot'): @@ -5751,6 +5771,52 @@ class PubServer(BaseHTTPRequestHandler): return self._404() + def _showHelpScreenImage(self, callingDomain: str, path: str, + baseDir: str, + GETstartTime, GETtimings: {}) -> None: + """Shows a help screen image + """ + if not path.endswith('.jpg') and \ + not path.endswith('.png') and \ + not path.endswith('.webp') and \ + not path.endswith('.avif') and \ + not path.endswith('.gif'): + return + mediaStr = path.split('/helpimages/')[1] + if '/' not in mediaStr: + if not self.server.themeName: + theme = 'default' + else: + theme = self.server.themeName + iconFilename = mediaStr + else: + theme = mediaStr.split('/')[0] + iconFilename = mediaStr.split('/')[1] + mediaFilename = \ + baseDir + '/theme/' + theme + '/helpimages/' + iconFilename + # if there is no theme-specific help image then use the default one + if not os.path.isfile(mediaFilename): + mediaFilename = \ + baseDir + '/theme/default/helpimages/' + iconFilename + if self._etag_exists(mediaFilename): + # The file has not changed + self._304() + return + if os.path.isfile(mediaFilename): + with open(mediaFilename, 'rb') as avFile: + mediaBinary = avFile.read() + mimeType = mediaFileMimeType(mediaFilename) + self._set_headers_etag(mediaFilename, + mimeType, + mediaBinary, None, + self.server.domainFull) + self._write(mediaBinary) + self._benchmarkGETtimings(GETstartTime, GETtimings, + 'show files done', + 'help image shown') + return + self._404() + def _showCachedAvatar(self, callingDomain: str, path: str, baseDir: str, GETstartTime, GETtimings: {}) -> None: @@ -9669,7 +9735,7 @@ class PubServer(BaseHTTPRequestHandler): """ imageExtensions = getImageExtensions() for ext in imageExtensions: - for bg in ('follow', 'options', 'login'): + for bg in ('follow', 'options', 'login', 'welcome'): # follow screen background image if path.endswith('/' + bg + '-background.' + ext): bgFilename = \ @@ -9714,41 +9780,45 @@ class PubServer(BaseHTTPRequestHandler): GETstartTime, GETtimings: {}) -> bool: """Show a shared item image """ - if self._pathIsImage(path): - mediaStr = path.split('/sharefiles/')[1] - mediaFilename = \ - baseDir + '/sharefiles/' + mediaStr - if os.path.isfile(mediaFilename): - if self._etag_exists(mediaFilename): - # The file has not changed - self._304() - return True + if not self._pathIsImage(path): + self._404() + return True - mediaFileType = 'png' - if mediaFilename.endswith('.png'): - mediaFileType = 'png' - elif mediaFilename.endswith('.jpg'): - mediaFileType = 'jpeg' - elif mediaFilename.endswith('.webp'): - mediaFileType = 'webp' - elif mediaFilename.endswith('.avif'): - mediaFileType = 'avif' - elif mediaFilename.endswith('.svg'): - mediaFileType = 'svg+xml' - else: - mediaFileType = 'gif' - with open(mediaFilename, 'rb') as avFile: - mediaBinary = avFile.read() - self._set_headers_etag(mediaFilename, - 'image/' + mediaFileType, - mediaBinary, None, - self.server.domainFull) - self._write(mediaBinary) - self._benchmarkGETtimings(GETstartTime, GETtimings, - 'show media done', - 'share files shown') - return True - self._404() + mediaStr = path.split('/sharefiles/')[1] + mediaFilename = \ + baseDir + '/sharefiles/' + mediaStr + if not os.path.isfile(mediaFilename): + self._404() + return True + + if self._etag_exists(mediaFilename): + # The file has not changed + self._304() + return True + + mediaFileType = 'png' + if mediaFilename.endswith('.png'): + mediaFileType = 'png' + elif mediaFilename.endswith('.jpg'): + mediaFileType = 'jpeg' + elif mediaFilename.endswith('.webp'): + mediaFileType = 'webp' + elif mediaFilename.endswith('.avif'): + mediaFileType = 'avif' + elif mediaFilename.endswith('.svg'): + mediaFileType = 'svg+xml' + else: + mediaFileType = 'gif' + with open(mediaFilename, 'rb') as avFile: + mediaBinary = avFile.read() + self._set_headers_etag(mediaFilename, + 'image/' + mediaFileType, + mediaBinary, None, + self.server.domainFull) + self._write(mediaBinary) + self._benchmarkGETtimings(GETstartTime, GETtimings, + 'show media done', + 'share files shown') return True def _showAvatarOrBanner(self, callingDomain: str, path: str, @@ -9756,59 +9826,62 @@ class PubServer(BaseHTTPRequestHandler): GETstartTime, GETtimings: {}) -> bool: """Shows an avatar or banner or profile background image """ - if '/users/' in path: - if self._pathIsImage(path): - avatarStr = path.split('/users/')[1] - if '/' in avatarStr and '.temp.' not in path: - avatarNickname = avatarStr.split('/')[0] - avatarFile = avatarStr.split('/')[1] - avatarFileExt = avatarFile.split('.')[-1] - # remove any numbers, eg. avatar123.png becomes avatar.png - if avatarFile.startswith('avatar'): - avatarFile = 'avatar.' + avatarFileExt - elif avatarFile.startswith('banner'): - avatarFile = 'banner.' + avatarFileExt - elif avatarFile.startswith('search_banner'): - avatarFile = 'search_banner.' + avatarFileExt - elif avatarFile.startswith('image'): - avatarFile = 'image.' + avatarFileExt - elif avatarFile.startswith('left_col_image'): - avatarFile = 'left_col_image.' + avatarFileExt - elif avatarFile.startswith('right_col_image'): - avatarFile = 'right_col_image.' + avatarFileExt - avatarFilename = \ - baseDir + '/accounts/' + \ - avatarNickname + '@' + domain + '/' + avatarFile - if os.path.isfile(avatarFilename): - if self._etag_exists(avatarFilename): - # The file has not changed - self._304() - return True - mediaImageType = 'png' - if avatarFile.endswith('.png'): - mediaImageType = 'png' - elif avatarFile.endswith('.jpg'): - mediaImageType = 'jpeg' - elif avatarFile.endswith('.gif'): - mediaImageType = 'gif' - elif avatarFile.endswith('.avif'): - mediaImageType = 'avif' - elif avatarFile.endswith('.svg'): - mediaImageType = 'svg+xml' - else: - mediaImageType = 'webp' - with open(avatarFilename, 'rb') as avFile: - mediaBinary = avFile.read() - self._set_headers_etag(avatarFilename, - 'image/' + mediaImageType, - mediaBinary, None, - self.server.domainFull) - self._write(mediaBinary) - self._benchmarkGETtimings(GETstartTime, GETtimings, - 'icon shown done', - 'avatar background shown') - return True - return False + if '/users/' not in path: + return False + if not self._pathIsImage(path): + return False + avatarStr = path.split('/users/')[1] + if not ('/' in avatarStr and '.temp.' not in path): + return False + avatarNickname = avatarStr.split('/')[0] + avatarFile = avatarStr.split('/')[1] + avatarFileExt = avatarFile.split('.')[-1] + # remove any numbers, eg. avatar123.png becomes avatar.png + if avatarFile.startswith('avatar'): + avatarFile = 'avatar.' + avatarFileExt + elif avatarFile.startswith('banner'): + avatarFile = 'banner.' + avatarFileExt + elif avatarFile.startswith('search_banner'): + avatarFile = 'search_banner.' + avatarFileExt + elif avatarFile.startswith('image'): + avatarFile = 'image.' + avatarFileExt + elif avatarFile.startswith('left_col_image'): + avatarFile = 'left_col_image.' + avatarFileExt + elif avatarFile.startswith('right_col_image'): + avatarFile = 'right_col_image.' + avatarFileExt + avatarFilename = \ + baseDir + '/accounts/' + \ + avatarNickname + '@' + domain + '/' + avatarFile + if not os.path.isfile(avatarFilename): + return False + if self._etag_exists(avatarFilename): + # The file has not changed + self._304() + return True + mediaImageType = 'png' + if avatarFile.endswith('.png'): + mediaImageType = 'png' + elif avatarFile.endswith('.jpg'): + mediaImageType = 'jpeg' + elif avatarFile.endswith('.gif'): + mediaImageType = 'gif' + elif avatarFile.endswith('.avif'): + mediaImageType = 'avif' + elif avatarFile.endswith('.svg'): + mediaImageType = 'svg+xml' + else: + mediaImageType = 'webp' + with open(avatarFilename, 'rb') as avFile: + mediaBinary = avFile.read() + self._set_headers_etag(avatarFilename, + 'image/' + mediaImageType, + mediaBinary, None, + self.server.domainFull) + self._write(mediaBinary) + self._benchmarkGETtimings(GETstartTime, GETtimings, + 'icon shown done', + 'avatar background shown') + return True def _confirmDeleteEvent(self, callingDomain: str, path: str, baseDir: str, httpPrefix: str, cookie: str, @@ -11042,6 +11115,14 @@ class PubServer(BaseHTTPRequestHandler): GETstartTime, GETtimings) return + # help screen images + # Note that this comes before the busy flag to avoid conflicts + if self.path.startswith('/helpimages/'): + self._showHelpScreenImage(callingDomain, self.path, + self.server.baseDir, + GETstartTime, GETtimings) + return + self._benchmarkGETtimings(GETstartTime, GETtimings, 'show files done', 'icon shown done') diff --git a/defaultwelcome/help_dm_en.md b/defaultwelcome/help_dm_en.md new file mode 100644 index 000000000..2f58489bf --- /dev/null +++ b/defaultwelcome/help_dm_en.md @@ -0,0 +1,3 @@ +Direct messages will appear here, as a chronological timeline. + +To avoid spam and improve security, by default you will only be able to receive direct messages *from people that you're following*. You can turn this off within your profile settings if you need to, by selecting the top **banner** and then the **edit** icon. diff --git a/defaultwelcome/help_inbox_en.md b/defaultwelcome/help_inbox_en.md new file mode 100644 index 000000000..82b95bb05 --- /dev/null +++ b/defaultwelcome/help_inbox_en.md @@ -0,0 +1,19 @@ +Incoming posts will appear here, as a chronological timeline. If you send any posts they will also appear here. + +### The top banner +At the top of the screen you can select the **banner** to switch to your profile, and edit it or log out. + +### Timeline buttons and icons +The **buttons** below the top banner allow you to select different timelines. There are also **icons** on the right to **search**, view your **calendar** or create **new posts**. + +The **show/hide** icon allows more timeline buttons to be shown, along with moderator controls. + +### Left column +Here you can add **useful links**. This only appears on desktop displays or devices with larger screens. It is similar to a *blogroll*. You can only add or edit links if you have an **administrator** or **editor** role. + +If you are on mobile then use the **links icon** at the top to read news. + +### Right column +RSS feeds can be added in the right column, known as the *newswire*. This only appears on desktop displays or devices with larger screens. You can only add or edit feeds if you have an **administrator** or **editor** role, and incoming feed items can also be moderated. + +If you are on mobile then use the **newswire icon** at the top to read news. diff --git a/defaultwelcome/help_outbox_en.md b/defaultwelcome/help_outbox_en.md new file mode 100644 index 000000000..7651570d6 --- /dev/null +++ b/defaultwelcome/help_outbox_en.md @@ -0,0 +1 @@ +Your sent posts will appear here, as a cronological timeline. diff --git a/defaultwelcome/help_tlbookmarks_en.md b/defaultwelcome/help_tlbookmarks_en.md new file mode 100644 index 000000000..829c860dc --- /dev/null +++ b/defaultwelcome/help_tlbookmarks_en.md @@ -0,0 +1 @@ +Any bookmarked posts appear here. diff --git a/defaultwelcome/help_tlmedia_en.md b/defaultwelcome/help_tlmedia_en.md new file mode 100644 index 000000000..b5daee390 --- /dev/null +++ b/defaultwelcome/help_tlmedia_en.md @@ -0,0 +1 @@ +Any incoming posts which contain **images**, **video** or **audio** files will appear here, together with their descriptions. diff --git a/defaultwelcome/help_tlshares_en.md b/defaultwelcome/help_tlshares_en.md new file mode 100644 index 000000000..e984d407c --- /dev/null +++ b/defaultwelcome/help_tlshares_en.md @@ -0,0 +1,6 @@ +### Shared items +These are typically physical objects or local services, exchanged or given away without use of money. + +For example, you may want to share **equipment** between members of a sports team on the same instance, share any surplus **clothing**, share **gadgets** which you are no longer using, or share plants and gardening **tools** between people using the same growing space. + +To avoid spam, shared items are not federated via ActivityPub and are local to members on the same instance. diff --git a/defaultwelcome/welcome_ar.md b/defaultwelcome/welcome_ar.md index 8e88deb0a..8d7170c4e 100644 --- a/defaultwelcome/welcome_ar.md +++ b/defaultwelcome/welcome_ar.md @@ -1,3 +1,4 @@ +![مرحبا الصورة](/helpimages/welcome.jpg) ### مرحبًا بكم في INSTANCE هذا خادم ActivityPub مصمم للاستضافة الذاتية السهلة لعدد قليل من الأشخاص على أنظمة منخفضة الطاقة ، مثل أجهزة الكمبيوتر ذات اللوحة الواحدة أو أجهزة الكمبيوتر المحمولة القديمة. diff --git a/defaultwelcome/welcome_ca.md b/defaultwelcome/welcome_ca.md index 0721e7e14..ada6129ed 100644 --- a/defaultwelcome/welcome_ca.md +++ b/defaultwelcome/welcome_ca.md @@ -1,3 +1,4 @@ +![Imatge de benvinguda](/helpimages/welcome.jpg) ### Benvingut a INSTANCE Es tracta d’un servidor ActivityPub dissenyat per allotjar fàcilment algunes persones en sistemes de poca potència, com ara ordinadors de placa única o portàtils antics. diff --git a/defaultwelcome/welcome_cy.md b/defaultwelcome/welcome_cy.md index 7596a7927..cb4530faa 100644 --- a/defaultwelcome/welcome_cy.md +++ b/defaultwelcome/welcome_cy.md @@ -1,3 +1,4 @@ +![Delwedd groeso](/helpimages/welcome.jpg) ### Croeso i INSTANCE Gweinydd ActivityPub yw hwn sydd wedi'i gynllunio ar gyfer hunan-letya ychydig o bobl ar systemau pŵer isel yn hawdd, fel cyfrifiaduron bwrdd sengl neu hen gliniaduron. diff --git a/defaultwelcome/welcome_de.md b/defaultwelcome/welcome_de.md index f1dedb95b..c6f8e1ac7 100644 --- a/defaultwelcome/welcome_de.md +++ b/defaultwelcome/welcome_de.md @@ -1,3 +1,4 @@ +![Willkommensbild](/helpimages/welcome.jpg) ### Willkommen bei INSTANCE Dies ist ein ActivityPub-Server, der für das einfache Selbsthosting einiger weniger Personen auf Systemen mit geringem Stromverbrauch wie Single-Board-Computern oder alten Laptops entwickelt wurde. diff --git a/defaultwelcome/welcome_en.md b/defaultwelcome/welcome_en.md index 5d942a9a5..1d1e8935f 100644 --- a/defaultwelcome/welcome_en.md +++ b/defaultwelcome/welcome_en.md @@ -1,3 +1,4 @@ +![Welcome image](/helpimages/welcome.jpg) ### Welcome to INSTANCE This is an ActivityPub server designed for easy self-hosting of a few people on low power systems, such as single board computers or old laptops. diff --git a/defaultwelcome/welcome_es.md b/defaultwelcome/welcome_es.md index f9ba98454..0c1474dc1 100644 --- a/defaultwelcome/welcome_es.md +++ b/defaultwelcome/welcome_es.md @@ -1,3 +1,4 @@ +![Imagen de bienvenida](/helpimages/welcome.jpg) ### Bienvenido a INSTANCE Este es un servidor ActivityPub diseñado para el autohospedaje sencillo de algunas personas en sistemas de bajo consumo de energía, como computadoras de placa única o laptops antiguas. diff --git a/defaultwelcome/welcome_fr.md b/defaultwelcome/welcome_fr.md index 9fd72437e..7f6610630 100644 --- a/defaultwelcome/welcome_fr.md +++ b/defaultwelcome/welcome_fr.md @@ -1,3 +1,4 @@ +![Image de bienvenue](/helpimages/welcome.jpg) ### Bienvenue à INSTANCE Il s'agit d'un serveur ActivityPub conçu pour l'auto-hébergement facile de quelques personnes sur des systèmes à faible consommation d'énergie, tels que des ordinateurs monocarte ou d'anciens ordinateurs portables. diff --git a/defaultwelcome/welcome_ga.md b/defaultwelcome/welcome_ga.md index c9a11c680..91dc36d64 100644 --- a/defaultwelcome/welcome_ga.md +++ b/defaultwelcome/welcome_ga.md @@ -1,3 +1,4 @@ +![Íomhá fáilte](/helpimages/welcome.jpg) ### Fáilte go INSTANCE Is freastalaí ActivityPub é seo atá deartha chun féin-óstáil éasca a dhéanamh ar chúpla duine ar chórais ísealchumhachta, mar ríomhairí boird aonair nó sean ríomhairí glúine. diff --git a/defaultwelcome/welcome_hi.md b/defaultwelcome/welcome_hi.md index 13923d095..fcffdfabd 100644 --- a/defaultwelcome/welcome_hi.md +++ b/defaultwelcome/welcome_hi.md @@ -1,3 +1,4 @@ +![स्वागत है छवि](/helpimages/welcome.jpg) ### INSTANCE पर आपका स्वागत है यह एक एक्टिविटीपब सर्वर है जो कम पावर सिस्टम पर सिंगल बोर्ड कंप्यूटर या पुराने लैपटॉप जैसे कुछ लोगों की आसान सेल्फ-होस्टिंग के लिए बनाया गया है। diff --git a/defaultwelcome/welcome_it.md b/defaultwelcome/welcome_it.md index 498caf115..b74a1e053 100644 --- a/defaultwelcome/welcome_it.md +++ b/defaultwelcome/welcome_it.md @@ -1,3 +1,4 @@ +![Immagine di benvenuto](/helpimages/welcome.jpg) ### Benvenuto in INSTANCE Questo è un server ActivityPub progettato per un facile self-hosting di poche persone su sistemi a basso consumo, come computer a scheda singola o vecchi laptop. diff --git a/defaultwelcome/welcome_ja.md b/defaultwelcome/welcome_ja.md index 7e21a1821..dd26aa2ba 100644 --- a/defaultwelcome/welcome_ja.md +++ b/defaultwelcome/welcome_ja.md @@ -1,3 +1,4 @@ +![ウェルカムイメージ](/helpimages/welcome.jpg) ### INSTANCEへようこそ これは、シングルボードコンピューターや古いラップトップなどの低電力システムで数人を簡単にセルフホスティングするために設計されたActivityPubサーバーです。 diff --git a/defaultwelcome/welcome_oc.md b/defaultwelcome/welcome_oc.md index 2a90b3a52..520d07387 100644 --- a/defaultwelcome/welcome_oc.md +++ b/defaultwelcome/welcome_oc.md @@ -1,4 +1,5 @@ -# Welcome +![Welcome image](/helpimages/welcome.jpg) +### Welcome Epicyon is an ActivityPub server designed for easy self-hosting of a few people on low power systems, such as single board computers or old laptops. Run your own social network presence the way you want to, and say goodbye to Big Tech. diff --git a/defaultwelcome/welcome_pt.md b/defaultwelcome/welcome_pt.md index f302f5aec..e0193f6a2 100644 --- a/defaultwelcome/welcome_pt.md +++ b/defaultwelcome/welcome_pt.md @@ -1,4 +1,5 @@ -# Bem-vindo a INSTANCE +![Imagem de boas-vindas](/helpimages/welcome.jpg) +### Bem-vindo a INSTANCE Este é um servidor ActivityPub projetado para fácil auto-hospedagem de algumas pessoas em sistemas de baixo consumo de energia, como computadores de placa única ou laptops antigos. Administre sua própria presença na rede social do jeito que você quiser e diga adeus à Big Tech. diff --git a/defaultwelcome/welcome_ru.md b/defaultwelcome/welcome_ru.md index 65c82400d..857581ef4 100644 --- a/defaultwelcome/welcome_ru.md +++ b/defaultwelcome/welcome_ru.md @@ -1,3 +1,4 @@ +![Приветственное изображение](/helpimages/welcome.jpg) ### Добро пожаловать в INSTANCE Это сервер ActivityPub, предназначенный для простого самостоятельного размещения нескольких человек в системах с низким энергопотреблением, таких как одноплатные компьютеры или старые ноутбуки. diff --git a/defaultwelcome/welcome_zh.md b/defaultwelcome/welcome_zh.md index 978734e94..1f9a62db6 100644 --- a/defaultwelcome/welcome_zh.md +++ b/defaultwelcome/welcome_zh.md @@ -1,3 +1,4 @@ +![欢迎图片](/helpimages/welcome.jpg) ### 欢迎来到INSTANCE 这是一个ActivityPub服务器,设计用于在低功耗系统(例如单板计算机或旧笔记本电脑)上轻松实现一些人的自我托管。 diff --git a/epicyon-profile.css b/epicyon-profile.css index 384c60893..6ef938ec7 100644 --- a/epicyon-profile.css +++ b/epicyon-profile.css @@ -426,6 +426,10 @@ a:focus { background-color: var(--timeline-posts-background-color); } +.container img.markdownImage { + width: 100%; +} + .container img.timelineicon:hover { filter: brightness(var(--icon-brightness-change)); } diff --git a/epicyon-welcome.css b/epicyon-welcome.css index 21a2ec8a1..89ccadb2c 100644 --- a/epicyon-welcome.css +++ b/epicyon-welcome.css @@ -149,6 +149,10 @@ img.avatar { width: var(--welcome-avatar-width); } +.container img.markdownImage { + width: 100%; +} + .container.next { float: right; } @@ -192,7 +196,7 @@ span.psw { margin: 8px 0; border: none; cursor: pointer; - width: var(--welcome-button-width); + width: var(--welcome-button-width); font-size: var(--welcome-font-size); font-family: Arial, Helvetica, sans-serif; } @@ -232,7 +236,7 @@ span.psw { margin: 8px 0; border: none; cursor: pointer; - width: var(--welcome-button-width); + width: var(--welcome-button-width); font-size: var(--welcome-font-size-mobile); font-family: Arial, Helvetica, sans-serif; } diff --git a/tests.py b/tests.py index ddab940d4..e4dace257 100644 --- a/tests.py +++ b/tests.py @@ -3288,6 +3288,18 @@ def testMarkdownToHtml(): markdown = 'This is just plain text' assert markdownToHtml(markdown) == markdown + markdown = 'This is a quotation:\n' + \ + '> Some quote or other' + assert markdownToHtml(markdown) == 'This is a quotation:
' + \ + '
Some quote or other
' + + markdown = 'This is a multi-line quotation:\n' + \ + '> The first line\n' + \ + '> The second line' + assert markdownToHtml(markdown) == \ + 'This is a multi-line quotation:
' + \ + '
The first line The second line
' + markdown = 'This is **bold**' assert markdownToHtml(markdown) == 'This is bold' @@ -3306,14 +3318,16 @@ def testMarkdownToHtml(): markdown = \ 'This is [a link](https://something.somewhere) to something.\n' + \ - 'And [something else](https://cat.pic).' + 'And [something else](https://cat.pic).\n' + \ + 'Or ![pounce](/cat.jpg).' assert markdownToHtml(markdown) == \ 'This is ' + \ 'a link to something.
' + \ 'And ' + \ - 'something else.' + 'something else.
' + \ + 'Or pounce.' def runAllTests(): diff --git a/theme/debian/helpimages/welcome.jpg b/theme/debian/helpimages/welcome.jpg new file mode 100644 index 000000000..2dca20d97 Binary files /dev/null and b/theme/debian/helpimages/welcome.jpg differ diff --git a/theme/default/helpimages/welcome.jpg b/theme/default/helpimages/welcome.jpg new file mode 100644 index 000000000..77e97a3a6 Binary files /dev/null and b/theme/default/helpimages/welcome.jpg differ diff --git a/theme/hacker/helpimages/welcome.jpg b/theme/hacker/helpimages/welcome.jpg new file mode 100644 index 000000000..688320ac4 Binary files /dev/null and b/theme/hacker/helpimages/welcome.jpg differ diff --git a/theme/henge/helpimages/welcome.jpg b/theme/henge/helpimages/welcome.jpg new file mode 100644 index 000000000..6a417ba6b Binary files /dev/null and b/theme/henge/helpimages/welcome.jpg differ diff --git a/theme/light/helpimages/welcome.jpg b/theme/light/helpimages/welcome.jpg new file mode 100644 index 000000000..327cf7adf Binary files /dev/null and b/theme/light/helpimages/welcome.jpg differ diff --git a/theme/night/helpimages/welcome.jpg b/theme/night/helpimages/welcome.jpg new file mode 100644 index 000000000..e11b8089c Binary files /dev/null and b/theme/night/helpimages/welcome.jpg differ diff --git a/theme/pixel/helpimages/welcome.jpg b/theme/pixel/helpimages/welcome.jpg new file mode 100644 index 000000000..0a5c37cfc Binary files /dev/null and b/theme/pixel/helpimages/welcome.jpg differ diff --git a/theme/purple/helpimages/welcome.jpg b/theme/purple/helpimages/welcome.jpg new file mode 100644 index 000000000..328cfa37e Binary files /dev/null and b/theme/purple/helpimages/welcome.jpg differ diff --git a/theme/solidaric/helpimages/welcome.jpg b/theme/solidaric/helpimages/welcome.jpg new file mode 100644 index 000000000..02e4eb006 Binary files /dev/null and b/theme/solidaric/helpimages/welcome.jpg differ diff --git a/theme/starlight/helpimages/welcome.jpg b/theme/starlight/helpimages/welcome.jpg new file mode 100644 index 000000000..970e080d3 Binary files /dev/null and b/theme/starlight/helpimages/welcome.jpg differ diff --git a/theme/zen/helpimages/welcome.jpg b/theme/zen/helpimages/welcome.jpg new file mode 100644 index 000000000..b12f111c4 Binary files /dev/null and b/theme/zen/helpimages/welcome.jpg differ diff --git a/utils.py b/utils.py index 25499145e..b0594c7ae 100644 --- a/utils.py +++ b/utils.py @@ -1274,7 +1274,7 @@ def _isReservedName(nickname: str) -> bool: 'accounts', 'channels', 'profile', 'u', 'updates', 'repeat', 'announce', 'shares', 'fonts', 'icons', 'avatars', - 'welcome') + 'welcome', 'helpimages') if nickname in reservedNames: return True return False diff --git a/webapp_timeline.py b/webapp_timeline.py index 1c3cf7f63..cf0e17719 100644 --- a/webapp_timeline.py +++ b/webapp_timeline.py @@ -8,12 +8,15 @@ __status__ = "Production" import os import time +from shutil import copyfile +from utils import dangerousMarkup from utils import getConfigParam from utils import getFullDomain from utils import isEditor from utils import removeIdEnding from follow import followerApprovalActive from person import isPersonSnoozed +from webapp_utils import markdownToHtml from webapp_utils import htmlKeyboardNavigation from webapp_utils import htmlHideFromScreenReader from webapp_utils import htmlPostSeparator @@ -42,6 +45,42 @@ def _logTimelineTiming(enableTimingLog: bool, timelineStartTime, boxName + ' ' + debugId + ' = ' + str(timeDiff)) +def _getHelpForTimeline(baseDir: str, boxName: str) -> str: + """Shows help text for the given timeline + """ + # get the filename for help for this timeline + helpFilename = baseDir + '/accounts/help_' + boxName + '.md' + if not os.path.isfile(helpFilename): + language = \ + getConfigParam(baseDir, 'language') + if not language: + language = 'en' + defaultFilename = \ + baseDir + '/defaultwelcome/' + \ + 'help_' + boxName + '_' + language + '.md' + if not os.path.isfile(defaultFilename): + defaultFilename = \ + baseDir + '/defaultwelcome/help_' + boxName + '_en.md' + if os.path.isfile(defaultFilename): + copyfile(defaultFilename, helpFilename) + + # show help text + if os.path.isfile(helpFilename): + instanceTitle = \ + getConfigParam(baseDir, 'instanceTitle') + if not instanceTitle: + instanceTitle = 'Epicyon' + with open(helpFilename, 'r') as helpFile: + helpText = helpFile.read() + if dangerousMarkup(helpText, False): + return '' + helpText = helpText.replace('INSTANCE', instanceTitle) + return '
\n' + \ + markdownToHtml(helpText) + '\n' + \ + '
\n' + return '' + + def htmlTimeline(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, @@ -698,6 +737,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str, translate['Page down'] + '">\n' + \ ' \n' tlStr += textModeSeparator + elif itemCtr == 0: + tlStr += _getHelpForTimeline(baseDir, boxName) # end of timeline-posts tlStr += ' \n' @@ -788,6 +829,7 @@ def _htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int, ' \n' separatorStr = htmlPostSeparator(baseDir, None) + ctr = 0 for published, item in sharesJson.items(): showContactButton = False if item['actor'] != actor: @@ -799,6 +841,10 @@ def _htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int, htmlIndividualShare(actor, item, translate, showContactButton, showRemoveButton) timelineStr += separatorStr + ctr += 1 + + if ctr == 0: + timelineStr += _getHelpForTimeline(baseDir, 'tlshares') if not lastPage: timelineStr += \ diff --git a/webapp_utils.py b/webapp_utils.py index 5c3983bc2..a0f7d0076 100644 --- a/webapp_utils.py +++ b/webapp_utils.py @@ -66,31 +66,88 @@ def _markdownEmphasisHtml(markdown: str) -> str: return markdown -def markdownToHtml(markdown: str) -> str: - """Converts markdown formatted text to html +def _markdownReplaceQuotes(markdown: str) -> str: + """Replaces > quotes with html blockquote + """ + if '> ' not in markdown: + return markdown + lines = markdown.split('\n') + result = '' + prevQuoteLine = None + for line in lines: + if '> ' not in line: + result += line + '\n' + prevQuoteLine = None + continue + lineStr = line.strip() + if not lineStr.startswith('> '): + result += line + '\n' + prevQuoteLine = None + continue + lineStr = lineStr.replace('> ', '', 1).strip() + if prevQuoteLine: + newPrevLine = prevQuoteLine.replace('\n', '') + result = result.replace(prevQuoteLine, newPrevLine) + ' ' + lineStr += '\n' + else: + lineStr = '
' + lineStr + '
\n' + result += lineStr + prevQuoteLine = lineStr + + if '\n' in result: + result = result.replace('\n', '') + + if result.endswith('\n') and \ + not markdown.endswith('\n'): + result = result[:len(result) - 1] + return result + + +def _markdownReplaceLinks(markdown: str, images=False) -> str: + """Replaces markdown links with html + Optionally replace image links """ - markdown = _markdownEmphasisHtml(markdown) - # replace markdown style links with html links replaceLinks = {} text = markdown - while '[' in text: + startChars = '[' + if images: + startChars = '![' + while startChars in text: if ')' not in text: break - text = text.split('[', 1)[1] - markdownLink = '[' + text.split(')')[0] + ')' + text = text.split(startChars, 1)[1] + markdownLink = startChars + text.split(')')[0] + ')' if ']' not in markdownLink or \ '(' not in markdownLink: text = text.split(')', 1)[1] continue - replaceLinks[markdownLink] = \ - '' + \ - markdownLink.split('[')[1].split(']')[0] + \ - '' + if not images: + replaceLinks[markdownLink] = \ + '' + \ + markdownLink.split(startChars)[1].split(']')[0] + \ + '' + else: + replaceLinks[markdownLink] = \ + '' + \
+                markdownLink.split(startChars)[1].split(']')[0] + \
+                '' text = text.split(')', 1)[1] for mdLink, htmlLink in replaceLinks.items(): markdown = markdown.replace(mdLink, htmlLink) + return markdown + + +def markdownToHtml(markdown: str) -> str: + """Converts markdown formatted text to html + """ + markdown = _markdownReplaceQuotes(markdown) + markdown = _markdownEmphasisHtml(markdown) + markdown = _markdownReplaceLinks(markdown, True) + markdown = _markdownReplaceLinks(markdown) # replace headers linesList = markdown.split('\n')