Compare commits

...

335 commits

Author SHA1 Message Date
floatingghost f628483499 translation parameterisation (#165)
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#165
2022-09-12 15:47:53 +00:00
floatingghost 61c70545f0 Merge pull request 'Delete '.gitlab-ci.yml'' (#162) from norm/pleroma-fe:delete-gitlab-ci into develop
Reviewed-on: AkkomaGang/pleroma-fe#162
2022-09-11 19:17:42 +00:00
Norm ad8be6b199 Merge branch 'develop' into delete-gitlab-ci 2022-09-10 04:03:58 +00:00
Norm dd89735008 Delete '.gitlab-ci.yml'
This file obviously doesn't serve a purpose since we're not using Gitlab CI.
2022-09-09 23:05:10 +00:00
Weblate 3c780adeb2 Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 100.0% (910 of 910 strings)

Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja_PEDANTIC/
Translation: Pleroma fe/pleroma-fe
2022-09-08 10:18:49 +00:00
Weblate d813a8528a Translated using Weblate (French)
Currently translated at 92.6% (843 of 910 strings)

Translated using Weblate (French)

Currently translated at 92.6% (843 of 910 strings)

Co-authored-by: Thomate <thomas@burdick.fr>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/fr/
Translation: Pleroma fe/pleroma-fe
2022-09-08 10:18:49 +00:00
Weblate 58f6dfebe9 Translated using Weblate (English)
Currently translated at 100.0% (910 of 910 strings)

Co-authored-by: Thomate <thomas@burdick.fr>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/en/
Translation: Pleroma fe/pleroma-fe
2022-09-08 10:18:49 +00:00
FloatingGhost da4b315d69 Merge remote-tracking branch 'origin/translations' into develop 2022-09-08 11:17:40 +01:00
FloatingGhost b980d5b2ef use with-direction in edited timeago
Fixes #159
2022-09-07 12:55:03 +01:00
Weblate 9447b7eaea Merge branch 'origin/develop' into Weblate. 2022-09-06 19:25:07 +00:00
floatingghost 2da92fcd13 editing (#158)
Co-authored-by: Sean King <seanking2919@protonmail.com>
Co-authored-by: Tusooa Zhu <tusooa@kazv.moe>
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#158
2022-09-06 19:25:03 +00:00
Weblate 6aadbcecfa Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 100.0% (903 of 903 strings)

Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja_PEDANTIC/
Translation: Pleroma fe/pleroma-fe
2022-09-06 17:00:17 +00:00
floatingghost 6084cbbb00 collapse statuses with CWs by default (#157)
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#157
2022-09-06 17:00:15 +00:00
FloatingGhost bd868e47de don't use bare @ 2022-09-06 11:44:53 +01:00
FloatingGhost 1cce6969cc make linter happy 2022-09-06 11:38:23 +01:00
Weblate ebdcb31c12 Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 100.0% (903 of 903 strings)

Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja_PEDANTIC/
Translation: Pleroma fe/pleroma-fe
2022-09-06 10:35:03 +00:00
Weblate d42b207b64 Translated using Weblate (English)
Currently translated at 100.0% (903 of 903 strings)

Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/en/
Translation: Pleroma fe/pleroma-fe
2022-09-06 10:30:23 +00:00
Weblate 4f1d85b054 Translated using Weblate (French)
Currently translated at 94.9% (857 of 903 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/fr/
Translation: Pleroma fe/pleroma-fe
2022-09-05 17:24:25 +00:00
Weblate cffc073aa7 Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 90.2% (816 of 904 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja_PEDANTIC/
Translation: Pleroma fe/pleroma-fe
2022-09-05 17:24:25 +00:00
Weblate d6bbbea5dc Translated using Weblate (Slovak)
Currently translated at 41.4% (374 of 903 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/sk/
Translation: Pleroma fe/pleroma-fe
2022-09-05 17:24:25 +00:00
Weblate b5f90a96cc Translated using Weblate (Dutch)
Currently translated at 95.7% (865 of 903 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/nl/
Translation: Pleroma fe/pleroma-fe
2022-09-05 17:24:25 +00:00
Weblate 9220c3859a Translated using Weblate (Catalan)
Currently translated at 94.9% (857 of 903 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ca/
Translation: Pleroma fe/pleroma-fe
2022-09-05 17:24:25 +00:00
Weblate aecb9f6e2a Translated using Weblate (English)
Currently translated at 99.5% (900 of 904 strings)

Translated using Weblate (English)

Currently translated at 99.5% (900 of 904 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/en/
Translation: Pleroma fe/pleroma-fe
2022-09-05 17:24:25 +00:00
floatingghost ae1b6ad887 Customisation of default profile tab, clean up settings (#156)
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#156
2022-09-05 17:02:16 +00:00
floatingghost c301daa276 Merge pull request 'Jump to Top on New *only* in non-profiles' (#154) from Mergan/pleroma-fe:develop into develop
Reviewed-on: AkkomaGang/pleroma-fe#154
2022-09-05 00:31:06 +00:00
FloatingGhost 8e2a4a9f7b remove unused method 2022-09-05 01:30:32 +01:00
David 948436ad3e Jump to Top on New *only* in non-profiles 2022-09-02 17:05:42 -07:00
floatingghost 301a32991b Merge pull request 'Fix Announcements Title Style' (#152) from Mergan/pleroma-fe:develop into develop
Reviewed-on: AkkomaGang/pleroma-fe#152
2022-09-02 09:25:08 +00:00
David 91357aad88 Fix Announcements Title Style 2022-09-01 22:35:42 -07:00
FloatingGhost c97cd56439 only clear timelines on new profile 2022-08-31 23:32:02 +01:00
Weblate 49875ef601 Translated using Weblate (Dutch)
Currently translated at 96.7% (868 of 897 strings)

Co-authored-by: Fristi <fristi@subcon.town>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/nl/
Translation: Pleroma fe/pleroma-fe
2022-08-31 16:45:29 +00:00
Weblate a234cd3f00 Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 89.3% (806 of 902 strings)

Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja_PEDANTIC/
Translation: Pleroma fe/pleroma-fe
2022-08-31 16:45:29 +00:00
floatingghost 6f2058a8fc ensure timelines only start fetching on click (#150)
no more parallel fetching

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#150
2022-08-31 15:44:58 +00:00
floatingghost 064b8ba7f7 Merge pull request 'Make "Copy link" copy external URL if external post' (#144) from eris/pleroma-fe:copy-url-fix into develop
Reviewed-on: AkkomaGang/pleroma-fe#144
2022-08-31 15:43:37 +00:00
Mergan 48826ede36 Separated Posts & Replies (#145) (#148)
Co-authored-by: David <dmgf2008@hotmail.com>
Reviewed-on: AkkomaGang/pleroma-fe#148
Co-authored-by: Mergan <mergan@noreply.akkoma>
Co-committed-by: Mergan <mergan@noreply.akkoma>
2022-08-31 15:28:09 +00:00
FloatingGhost 7dc0464094 split translation language by source and dest 2022-08-30 17:46:54 +01:00
FloatingGhost 6440e51b7e allow translating to any other language 2022-08-30 15:43:57 +01:00
FloatingGhost ef50c63dc7 use supported languages from service 2022-08-30 14:37:36 +01:00
FloatingGhost 59eb434840 allow modal to expand with content 2022-08-30 10:32:34 +01:00
eris bd4e8271df Dont copy local URL if external post 2022-08-30 08:54:08 +00:00
floatingghost 08d4b3b885 Merge pull request 'allow selecting languages for translation' (#143) from translation-language-selection into develop
Reviewed-on: AkkomaGang/pleroma-fe#143
2022-08-29 21:17:41 +00:00
FloatingGhost e7c21ffbd7 allow selecting languages for translation 2022-08-29 22:16:59 +01:00
floatingghost 9b4cddd2e9 Merge pull request 'Add ability to translate statuses' (#142) from machine-translation into develop
Reviewed-on: AkkomaGang/pleroma-fe#142
2022-08-29 19:41:50 +00:00
floatingghost cd20e45157 Merge pull request 'Fix MRF transparency panel showing without transparency enabled' (#141) from eris/pleroma-fe:mrf-panel-fix into develop
Reviewed-on: AkkomaGang/pleroma-fe#141
2022-08-29 19:29:57 +00:00
FloatingGhost 48a11cb9d1 add translation options 2022-08-29 20:02:34 +01:00
eris e67d94f73d Fix MRF transparency panel showing without transparency 2022-08-28 10:09:56 +00:00
Weblate b39143413c Translated using Weblate (Catalan)
Currently translated at 100.0% (860 of 860 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: sola <spla@mastodont.cat>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ca/
Translation: Pleroma fe/pleroma-fe
2022-08-26 12:44:30 +00:00
Weblate b1460a9909 Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 86.1% (773 of 897 strings)

Translated using Weblate (Japanese (ja_PEDANTIC))

Currently translated at 86.0% (740 of 860 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja_PEDANTIC/
Translation: Pleroma fe/pleroma-fe
2022-08-26 12:44:30 +00:00
floatingghost 776bee889e Confirmation dialogs (#140)
supercedes #135

adapted from https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1431

Co-authored-by: Tusooa Zhu <tusooa@kazv.moe>
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#140
2022-08-26 11:58:33 +00:00
FloatingGhost f925fa6265 don't force white globe 2022-08-26 10:36:21 +01:00
floatingghost c1c40d72da resolve language from navigator langs (#139)
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/pleroma-fe#139
2022-08-26 09:31:25 +00:00
floatingghost df70b1e4be Merge pull request 'Potential Fix for (#113) on show new' (#136) from Mergan/pleroma-fe:pleroma-fe#113 into develop
Reviewed-on: AkkomaGang/pleroma-fe#136
2022-08-25 19:41:10 +00:00
Weblate 58614fed35 Merge branch 'origin/develop' into Weblate. 2022-08-25 16:13:05 +00:00
floatingghost 05d052212a Merge pull request 'allow instance language to take precedence over EN' (#137) from default-language into develop
Reviewed-on: AkkomaGang/pleroma-fe#137
2022-08-25 16:13:02 +00:00
FloatingGhost d4e91ef61e allow instance language to take precedence over EN 2022-08-25 17:02:55 +01:00
David bf501e36a1 Potential Fix for (#113) on show new 2022-08-24 23:53:16 -07:00
Weblate 9a37c42f06 Translated using Weblate (French)
Currently translated at 100.0% (860 of 860 strings)

Co-authored-by: Thomate <thomas@burdick.fr>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/fr/
Translation: Pleroma fe/pleroma-fe
2022-08-23 15:12:20 +00:00
Weblate f67868b9ad Translated using Weblate (Dutch)
Currently translated at 100.0% (860 of 860 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (859 of 859 strings)

Co-authored-by: Fristi <fristi@subcon.town>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/nl/
Translation: Pleroma fe/pleroma-fe
2022-08-23 15:12:19 +00:00
Weblate e678327526 Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 86.0% (740 of 860 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja_PEDANTIC/
Translation: Pleroma fe/pleroma-fe
2022-08-23 15:12:18 +00:00
Weblate e1e3b9beff Translated using Weblate (Spanish)
Currently translated at 84.1% (724 of 860 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: mint <they@mint.lgbt>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/es/
Translation: Pleroma fe/pleroma-fe
2022-08-23 15:12:18 +00:00
FloatingGhost 1f2b059320 add language tags 2022-08-23 16:12:06 +01:00
floatingghost 2d85ee6a08 use-server-side-mfm (#133)
Reviewed-on: AkkomaGang/pleroma-fe#133
2022-08-18 03:14:38 +00:00
floatingghost e61228b031 use akkoma-specific source for MFM unless asked not to (#132)
Reviewed-on: AkkomaGang/pleroma-fe#132
2022-08-17 09:34:50 +00:00
floatingghost 64fdde52f6 timeline panel nav (#131)
Reviewed-on: AkkomaGang/pleroma-fe#131
2022-08-17 08:41:36 +00:00
eris ca0b730605 Redo desktop nav, add options for site name and favicon (#83)
Reviewed-on: AkkomaGang/pleroma-fe#83
Co-authored-by: eris <femmediscord@gmail.com>
Co-committed-by: eris <femmediscord@gmail.com>
2022-08-14 23:24:59 +00:00
floatingghost 4dcca7cf53 somewhat fix scrolling behaviour (#127)
Reviewed-on: AkkomaGang/pleroma-fe#127
2022-08-11 17:25:43 +00:00
FloatingGhost cbf883ab1e fix "who reacted" emoji display
fixes #80
2022-08-11 18:14:05 +01:00
Weblate edf36d28ca Translated using Weblate (English)
Currently translated at 99.5% (851 of 855 strings)

Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/en/
Translation: Pleroma fe/pleroma-fe
2022-08-11 10:10:38 +00:00
Weblate c554ced3a4 Merge branch 'origin/develop' into Weblate. 2022-08-11 10:08:13 +00:00
floatingghost 75a3ab343b Merge pull request 'Flip order of accommodation for (#115)' (#118) from Mergan/pleroma-fe:emoji-picker into develop
Reviewed-on: AkkomaGang/pleroma-fe#118
2022-08-11 10:08:12 +00:00
Weblate 617773d287 Merge branch 'origin/develop' into Weblate. 2022-08-11 09:21:20 +00:00
FloatingGhost 9ede66fcc0 enable misskey markdown by default
fixes #67
2022-08-11 10:21:00 +01:00
Weblate f969dd6ad2 Translated using Weblate (Dutch)
Currently translated at 100.0% (855 of 855 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (853 of 853 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (852 of 852 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (852 of 852 strings)

Co-authored-by: Fristi <fristi@subcon.town>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/nl/
Translation: Pleroma fe/pleroma-fe
2022-08-11 09:04:25 +00:00
Weblate 468fe27565 Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 82.9% (709 of 855 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja_PEDANTIC/
Translation: Pleroma fe/pleroma-fe
2022-08-11 09:04:25 +00:00
Weblate 251a1b13a3 Translated using Weblate (Catalan)
Currently translated at 100.0% (855 of 855 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (855 of 855 strings)

Translated using Weblate (Catalan)

Currently translated at 96.9% (827 of 853 strings)

Translated using Weblate (Catalan)

Currently translated at 97.0% (827 of 852 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: sola <spla@mastodont.cat>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ca/
Translation: Pleroma fe/pleroma-fe
2022-08-11 09:04:25 +00:00
floatingghost 94af72fd62 Merge pull request 'Fix reply filtering on bubble timeline' (#125) from eris/pleroma-fe:bubble-filter-fix into develop
Reviewed-on: AkkomaGang/pleroma-fe#125
2022-08-11 09:04:23 +00:00
eris e5ca36a68c Fix reply filtering on bubble timeline 2022-08-10 00:30:13 +00:00
floatingghost 4d27064db3 Show bubble instances in about panel (#123)
Reviewed-on: AkkomaGang/pleroma-fe#123
2022-08-09 02:16:23 +00:00
eris d4f8934e8a New option: Pause MFM animations until status hover (#120)
Reviewed-on: AkkomaGang/pleroma-fe#120
Co-authored-by: eris <femmediscord@gmail.com>
Co-committed-by: eris <femmediscord@gmail.com>
2022-08-08 15:47:06 +00:00
floatingghost b354ad382c Merge pull request 'Don't render inline quote policy links in akkoma-fe' (#122) from eris/pleroma-fe:inline-quote-fix into develop
Reviewed-on: AkkomaGang/pleroma-fe#122
Reviewed-by: floatingghost <hannah@coffee-and-dreams.uk>
2022-08-07 20:21:04 +00:00
floatingghost 863d586664 Merge pull request 'Fix alt text line height when image doesn't load' (#121) from eris/pleroma-fe:fix-alt-text into develop
Reviewed-on: AkkomaGang/pleroma-fe#121
2022-08-07 20:03:31 +00:00
eris 92e0d885f0 Don't render inline quote policy links 2022-08-07 07:57:21 +00:00
eris 327195dc55 Fix alt text line height 2022-08-07 07:51:27 +00:00
Weblate e47d3a6d13 Translated using Weblate (Dutch)
Currently translated at 98.2% (837 of 852 strings)

Translated using Weblate (Dutch)

Currently translated at 98.1% (836 of 852 strings)

Translated using Weblate (English)

Currently translated at 99.6% (849 of 852 strings)

Co-authored-by: Fristi <fristi@subcon.town>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/en/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/nl/
Translation: Pleroma fe/pleroma-fe
2022-08-06 22:40:35 +00:00
FloatingGhost df82757376 Merge branch 'develop' of akkoma.dev:AkkomaGang/pleroma-fe into develop 2022-08-06 23:37:09 +01:00
floatingghost 59d2800051 Merge pull request 'Translated using Weblate (Dutch)' (#119) from translations into develop
Reviewed-on: AkkomaGang/pleroma-fe#119
2022-08-06 22:32:53 +00:00
Weblate eab93c1ff6 Translated using Weblate (Dutch)
Currently translated at 98.0% (835 of 852 strings)

Translated using Weblate (Dutch)

Currently translated at 98.0% (835 of 852 strings)

Translated using Weblate (Spanish)

Currently translated at 84.3% (719 of 852 strings)

Translated using Weblate (Dutch)

Currently translated at 97.7% (833 of 852 strings)

Translated using Weblate (Spanish)

Currently translated at 83.8% (714 of 852 strings)

Translated using Weblate (English)

Currently translated at 99.6% (849 of 852 strings)

Translated using Weblate (English)

Currently translated at 99.6% (849 of 852 strings)

Translated using Weblate (English)

Currently translated at 99.6% (849 of 852 strings)

Co-authored-by: Fristi <fristi@subcon.town>
Co-authored-by: Hannah Winter <konhat@hotmail.es>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Co-authored-by: aetios <dijkvanrichard+akkomatranslate@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/en/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/es/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/nl/
Translation: Pleroma fe/pleroma-fe
2022-08-06 22:32:30 +00:00
Weblate f89be95e65 Translated using Weblate (Dutch)
Currently translated at 97.7% (833 of 852 strings)

Translated using Weblate (Spanish)

Currently translated at 83.8% (714 of 852 strings)

Translated using Weblate (English)

Currently translated at 99.6% (849 of 852 strings)

Translated using Weblate (English)

Currently translated at 99.6% (849 of 852 strings)

Translated using Weblate (English)

Currently translated at 99.6% (849 of 852 strings)

Co-authored-by: Fristi <fristi@subcon.town>
Co-authored-by: Hannah Winter <konhat@hotmail.es>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Co-authored-by: aetios <dijkvanrichard+akkomatranslate@gmail.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/en/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/es/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/nl/
Translation: Pleroma fe/pleroma-fe
2022-08-06 22:28:13 +00:00
David d69df0e906 Flip order of accommodation for (#115) 2022-08-05 17:01:18 -07:00
floatingghost 5a99c54ebe Merge pull request 'Translated using Weblate (Catalan)' (#117) from translations into develop
Reviewed-on: AkkomaGang/pleroma-fe#117
2022-08-05 16:41:48 +00:00
floatingghost aa88e4cb0a Merge pull request 'document sticker setup' (#116) from document-sticker-config into develop
Reviewed-on: AkkomaGang/pleroma-fe#116
2022-08-05 15:51:53 +00:00
FloatingGhost 6ba8fcb604 document sticker setup 2022-08-05 16:51:01 +01:00
Weblate 0e1286c454 Translated using Weblate (Catalan)
Currently translated at 99.8% (851 of 852 strings)

Co-authored-by: sola <spla@mastodont.cat>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ca/
Translation: Pleroma fe/pleroma-fe
2022-08-05 11:20:05 +00:00
floatingghost 3eab17330b rework sticker picker for emoji pickerinator 5000 (#111)
Reviewed-on: AkkomaGang/pleroma-fe#111
2022-08-04 21:11:19 +00:00
floatingghost 2e92aaaaa3 Prune i18n files (#110)
Reviewed-on: AkkomaGang/pleroma-fe#110
2022-08-04 08:40:01 +00:00
FloatingGhost dfcc91c8c6 Merge remote-tracking branch 'origin/translations' into develop 2022-08-04 09:10:02 +01:00
floatingghost 2712e47bc1 Merge pull request 'Add scope locale for local only posts in status' (#106) from eris/pleroma-fe:add-local-locale into develop
Reviewed-on: AkkomaGang/pleroma-fe#106
2022-08-04 07:54:24 +00:00
floatingghost 9421aa1d39 Merge pull request 'Decode html-encoded characters inside pre tags' (#101) from sfr/pleroma-fe:mfm-htmlencode into develop
Reviewed-on: AkkomaGang/pleroma-fe#101
2022-08-04 07:52:58 +00:00
Weblate d8708f020e Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 84.9% (723 of 851 strings)

Translated using Weblate (Dutch)

Currently translated at 99.4% (846 of 851 strings)

Translated using Weblate (French)

Currently translated at 100.0% (851 of 851 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (851 of 851 strings)

Translated using Weblate (English)

Currently translated at 100.0% (851 of 851 strings)

Co-authored-by: Fristi <fristi@subcon.town>
Co-authored-by: Thomate <thomas@burdick.fr>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Co-authored-by: sola <spla@mastodont.cat>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ca/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/en/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/fr/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja_PEDANTIC/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/nl/
Translation: Pleroma fe/pleroma-fe
2022-08-03 20:57:46 +00:00
FloatingGhost 5873ee4e6c Merge remote-tracking branch 'origin/translations' into develop 2022-08-03 21:57:39 +01:00
Mergan aff6caa4c0 Emoji Pack Picker (#102)
Reviewed-on: AkkomaGang/pleroma-fe#102
Co-authored-by: Mergan <mergan@noreply.akkoma>
Co-committed-by: Mergan <mergan@noreply.akkoma>
2022-08-03 20:56:56 +00:00
floatingghost 2fe5699b8f only include direction on timestamps when required (#108)
Reviewed-on: AkkomaGang/pleroma-fe#108
2022-08-03 14:07:18 +00:00
eris e43799e3b5 Add scope locale for local only posts 2022-08-03 07:23:38 +00:00
Weblate 3a16e7401e Translated using Weblate (Dutch)
Currently translated at 99.4% (846 of 851 strings)

Translated using Weblate (French)

Currently translated at 100.0% (851 of 851 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (851 of 851 strings)

Translated using Weblate (English)

Currently translated at 100.0% (851 of 851 strings)

Co-authored-by: Fristi <fristi@subcon.town>
Co-authored-by: Thomate <thomas@burdick.fr>
Co-authored-by: sola <spla@mastodont.cat>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ca/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/en/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/fr/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/nl/
Translation: Pleroma fe/pleroma-fe
2022-08-03 07:20:10 +00:00
Sol Fisher Romanoff 6441ff45a9
Decode html-encoded characters inside pre tags 2022-08-02 12:37:18 +03:00
eris ef41494bad Remove descriptions in timeline menu, change to titles and aria-labels (#74)
Reviewed-on: AkkomaGang/pleroma-fe#74
Co-authored-by: eris <femmediscord@gmail.com>
Co-committed-by: eris <femmediscord@gmail.com>
2022-08-02 09:18:58 +00:00
floatingghost 6380e6c4c2 Merge pull request 'Align quote timestamp to right' (#64) from sfr/pleroma-fe:fix/quote-flex into develop
Reviewed-on: AkkomaGang/pleroma-fe#64
2022-08-02 09:15:34 +00:00
eris ff3421c7cb [Bug fix] Fix user avatars in mention links option (#85)
Reviewed-on: AkkomaGang/pleroma-fe#85
Co-authored-by: eris <femmediscord@gmail.com>
Co-committed-by: eris <femmediscord@gmail.com>
2022-08-02 09:15:16 +00:00
sfr 7f61beff7a a11y: draw gap around notification dot in favicon (#94)
Reviewed-on: AkkomaGang/pleroma-fe#94
Co-authored-by: sfr <sol@solfisher.com>
Co-committed-by: sfr <sol@solfisher.com>
2022-08-02 09:14:42 +00:00
floatingghost 3cc0b44cb6 Merge pull request 'Fix timeago warning applying to polls' (#100) from eris/pleroma-fe:timeago-polls-fix into develop
Reviewed-on: AkkomaGang/pleroma-fe#100
2022-08-02 08:07:25 +00:00
eris d8dd408de0 Fix timeago warning applying to polls 2022-08-02 06:12:43 +00:00
floatingghost 6f1bb99990 Merge pull request 'Fix code blocks not working in MFM' (#96) from sfr/pleroma-fe:fix/mfm into develop
Reviewed-on: AkkomaGang/pleroma-fe#96
2022-08-01 14:57:04 +00:00
Sol Fisher Romanoff b69801d33f
Fix code blocks not working in MFM 2022-08-01 16:14:14 +03:00
floatingghost 2d36b2403b run yarn deps upgrade (#95)
Reviewed-on: AkkomaGang/pleroma-fe#95
2022-08-01 12:07:39 +00:00
FloatingGhost 5bef62398a Merge remote-tracking branch 'origin/translations' into develop 2022-08-01 12:12:51 +01:00
eris 5f09326879 Mark replies to muted users for muting (#90)
Reviewed-on: AkkomaGang/pleroma-fe#90
Co-authored-by: eris <femmediscord@gmail.com>
Co-committed-by: eris <femmediscord@gmail.com>
2022-08-01 11:12:02 +00:00
Weblate 8d53d4f0a9 Translated using Weblate (Dutch)
Currently translated at 88.6% (754 of 851 strings)

Translated using Weblate (French)

Currently translated at 97.1% (827 of 851 strings)

Translated using Weblate (Dutch)

Currently translated at 86.8% (739 of 851 strings)

Translated using Weblate (French)

Currently translated at 95.6% (814 of 851 strings)

Translated using Weblate (Dutch)

Currently translated at 86.1% (732 of 850 strings)

Translated using Weblate (French)

Currently translated at 94.8% (806 of 850 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (850 of 850 strings)

Translated using Weblate (English)

Currently translated at 100.0% (850 of 850 strings)

Co-authored-by: Fristi <fristi@subcon.town>
Co-authored-by: Thomate <thomas@burdick.fr>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: eris <femmediscord@gmail.com>
Co-authored-by: sola <spla@mastodont.cat>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ca/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/en/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/fr/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/nl/
Translation: Pleroma fe/pleroma-fe
2022-08-01 11:11:26 +00:00
floatingghost 45d8747fdd Merge pull request 'Add ability to click+hold to expand collapsed notifications' (#70) from eris/pleroma-fe:click-expand-notifs into develop
Reviewed-on: AkkomaGang/pleroma-fe#70
2022-08-01 11:11:25 +00:00
sfr 974663c42d Fix broken MFM tags (#93)
Reviewed-on: AkkomaGang/pleroma-fe#93
Co-authored-by: sfr <sol@solfisher.com>
Co-committed-by: sfr <sol@solfisher.com>
2022-08-01 11:10:54 +00:00
Yukkuri f474763151 Commit list accounts state after difference in removed accounts is determined (#82)
Reviewed-on: AkkomaGang/pleroma-fe#82
Co-authored-by: Yukkuri <iamtakingiteasy@eientei.org>
Co-committed-by: Yukkuri <iamtakingiteasy@eientei.org>
2022-08-01 10:11:39 +00:00
sfr 7c675f1ded Add toggle to hide posts mentioning blocked users (#78)
Reviewed-on: AkkomaGang/pleroma-fe#78
Co-authored-by: sfr <sol@solfisher.com>
Co-committed-by: sfr <sol@solfisher.com>
2022-07-31 21:47:25 +00:00
FloatingGhost 751fd5917b Merge branch 'develop' of akkoma.dev:AkkomaGang/pleroma-fe into develop 2022-07-29 10:36:46 +01:00
floatingghost bdbd0b644e Merge pull request 'Update README.md with updated translation instructions' (#71) from norm/pleroma-fe:readme-weblate into develop
Reviewed-on: AkkomaGang/pleroma-fe#71
2022-07-29 09:34:02 +00:00
Weblate 53acda2cc7 Translated using Weblate (Catalan)
Currently translated at 100.0% (850 of 850 strings)

Translated using Weblate (English)

Currently translated at 100.0% (850 of 850 strings)

Co-authored-by: Weblate Admin <hannah.ward9001@gmail.com>
Co-authored-by: sola <spla@mastodont.cat>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ca/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/en/
Translation: Pleroma fe/pleroma-fe
2022-07-29 09:20:02 +00:00
floatingghost 50f13701be Merge pull request 'Reduce emoji size and expand on hover' (#72) from eris/pleroma-fe:hover-emojis into develop
Reviewed-on: AkkomaGang/pleroma-fe#72
2022-07-29 09:18:56 +00:00
floatingghost 1f047b3ce3 Merge pull request 'Fix tall status gradient' (#75) from eris/pleroma-fe:tall-status-fix into develop
Reviewed-on: AkkomaGang/pleroma-fe#75
2022-07-29 09:14:28 +00:00
floatingghost c6a4485a20 Merge pull request 'Better formatting for emoji react notifications' (#76) from eris/pleroma-fe:emoji-notif-fix into develop
Reviewed-on: AkkomaGang/pleroma-fe#76
2022-07-29 09:13:42 +00:00
floatingghost 7e9273aa91 Merge pull request 'Fix poll duration i18n' (#77) from norm/pleroma-fe:fix-poll-duration-i18n into develop
Reviewed-on: AkkomaGang/pleroma-fe#77
2022-07-29 09:13:16 +00:00
Tusooa Zhu bccbdd4706
Fix poll duration i18n 2022-07-29 01:37:09 -04:00
eris bd38f8e263 Better formatting for emoji react notifications 2022-07-29 03:50:36 +00:00
eris a023ee9893 Fix tall status gradient 2022-07-29 03:34:04 +00:00
Norm 38c9720c65 typo fix 2022-07-28 23:59:53 +00:00
eris 1189607f2e Reduce emoji size and expand on hover 2022-07-28 23:58:04 +00:00
Norm a142a016d9 Update README.md with updated translation instructions 2022-07-28 23:56:48 +00:00
eris 6ed6395c07 Add ability to click to expand collapsed notifications 2022-07-28 23:52:20 +00:00
sola eef99fd86b Translated using Weblate (Catalan)
Currently translated at 100.0% (850 of 850 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ca/
2022-07-27 21:25:15 +00:00
FloatingGhost 86bdbfe0c0 Merge branch 'i18n' into develop 2022-07-27 22:25:05 +01:00
floatingghost 2258174a00 weblate sync (#66)
Reviewed-on: AkkomaGang/pleroma-fe#66
2022-07-27 10:13:42 +00:00
FloatingGhost 7cec2616b4 Merge branch 'develop' into i18n 2022-07-27 11:07:24 +01:00
sola 1bd60115ce Translated using Weblate (Catalan)
Currently translated at 100.0% (850 of 850 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ca/
2022-07-27 10:00:32 +00:00
Weblate Admin f7e2b48c72 Translated using Weblate (English)
Currently translated at 100.0% (850 of 850 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/en/
2022-07-27 10:00:27 +00:00
thomate 93feda84dc More French translations (#65)
Reviewed-on: AkkomaGang/pleroma-fe#65
Co-authored-by: thomate <thomas@burdick.fr>
Co-committed-by: thomate <thomas@burdick.fr>
2022-07-27 10:00:25 +00:00
Weblate Admin 5577fc9a32 Translated using Weblate (English)
Currently translated at 100.0% (851 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/en/
2022-07-26 21:30:41 +00:00
Weblate Admin 9af2ebe485 Translated using Weblate (Chinese (Traditional))
Currently translated at 81.9% (697 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/zh_Hant/
2022-07-26 21:30:41 +00:00
Weblate Admin 7e0f13320a Translated using Weblate (Chinese (Simplified))
Currently translated at 82.0% (698 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/zh_Hans/
2022-07-26 21:30:41 +00:00
Weblate Admin 70e431f460 Translated using Weblate (Vietnamese)
Currently translated at 82.3% (701 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/vi/
2022-07-26 21:30:41 +00:00
Weblate Admin 24487f9d77 Translated using Weblate (Ukrainian)
Currently translated at 81.9% (697 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/uk/
2022-07-26 21:30:41 +00:00
Weblate Admin 584d3f4c4b Translated using Weblate (Telugu)
Currently translated at 39.8% (339 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/te/
2022-07-26 21:30:40 +00:00
Weblate Admin 61b90473ac Translated using Weblate (Slovak)
Currently translated at 46.6% (397 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/sk/
2022-07-26 21:30:40 +00:00
Weblate Admin e7d2921828 Translated using Weblate (Russian)
Currently translated at 71.9% (612 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ru/
2022-07-26 21:30:40 +00:00
Weblate Admin 68813cf4e3 Translated using Weblate (Portuguese)
Currently translated at 77.6% (661 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/pt/
2022-07-26 21:30:40 +00:00
Weblate Admin 6576042814 Translated using Weblate (Polish)
Currently translated at 81.1% (691 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/pl/
2022-07-26 21:30:39 +00:00
Weblate Admin 0421d07ca5 Translated using Weblate (Dutch)
Currently translated at 82.3% (701 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/nl/
2022-07-26 21:30:39 +00:00
Weblate Admin 7e10674eb9 Translated using Weblate (Catalan)
Currently translated at 87.1% (742 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ca/
2022-07-26 21:30:39 +00:00
Weblate Admin 0df62261f5 Translated using Weblate (Occitan)
Currently translated at 49.3% (420 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/oc/
2022-07-26 21:30:39 +00:00
Weblate Admin 28e59decab Translated using Weblate (Norwegian Bokmål)
Currently translated at 56.2% (479 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/nb_NO/
2022-07-26 21:30:39 +00:00
Weblate Admin 073f62e2ed Translated using Weblate (Korean)
Currently translated at 52.7% (449 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ko/
2022-07-26 21:30:38 +00:00
Weblate Admin 86f6bfa04f Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 85.4% (727 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ja_PEDANTIC/
2022-07-26 21:30:38 +00:00
Weblate Admin bd57be4b12 Translated using Weblate (Japanese (ja_EASY))
Currently translated at 61.4% (523 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ja_EASY/
2022-07-26 21:30:38 +00:00
Weblate Admin 7bced9da57 Translated using Weblate (Italian)
Currently translated at 82.4% (702 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/it/
2022-07-26 21:30:38 +00:00
Weblate Admin 206de567d4 Translated using Weblate (Indonesian)
Currently translated at 55.6% (474 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/id/
2022-07-26 21:30:38 +00:00
Weblate Admin f3a6c04f41 Translated using Weblate (Hebrew)
Currently translated at 37.7% (321 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/he/
2022-07-26 21:30:37 +00:00
Weblate Admin e0eb8d0342 Translated using Weblate (Irish)
Currently translated at 21.0% (179 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ga/
2022-07-26 21:30:37 +00:00
Weblate Admin 74db389f6a Translated using Weblate (French)
Currently translated at 82.6% (703 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/fr/
2022-07-26 21:30:37 +00:00
Weblate Admin 40bd0b2262 Translated using Weblate (Finnish)
Currently translated at 72.7% (619 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/fi/
2022-07-26 21:30:37 +00:00
Weblate Admin 4e2d63b15a Translated using Weblate (Persian)
Currently translated at 12.9% (110 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/fa/
2022-07-26 21:30:37 +00:00
Weblate Admin b37265f185 Translated using Weblate (Basque)
Currently translated at 65.8% (560 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/eu/
2022-07-26 21:30:36 +00:00
Weblate Admin c283924a20 Translated using Weblate (Estonian)
Currently translated at 43.4% (370 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/et/
2022-07-26 21:30:36 +00:00
Weblate Admin 554e3de73b Translated using Weblate (Spanish)
Currently translated at 82.4% (702 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/es/
2022-07-26 21:30:36 +00:00
Weblate Admin 176828a374 Translated using Weblate (Esperanto)
Currently translated at 82.4% (702 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/eo/
2022-07-26 21:30:36 +00:00
Weblate Admin 1be46da339 Translated using Weblate (German)
Currently translated at 81.9% (697 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/de/
2022-07-26 21:30:36 +00:00
Weblate Admin a072226d92 Translated using Weblate (Czech)
Currently translated at 41.4% (353 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/cs/
2022-07-26 21:30:36 +00:00
Weblate Admin d0f07d47ce Translated using Weblate (Arabic)
Currently translated at 15.0% (128 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ar/
2022-07-26 21:30:36 +00:00
Fristi 27ab013704 Translated using Weblate (Dutch)
Currently translated at 82.3% (701 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/nl/
2022-07-26 21:30:35 +00:00
sola 603d82dbe8 Translated using Weblate (Catalan)
Currently translated at 87.1% (742 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ca/
2022-07-26 21:30:35 +00:00
Weblate Admin 3b1464e29a Translated using Weblate (English)
Currently translated at 100.0% (851 of 851 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/en/
2022-07-26 21:30:33 +00:00
Weblate Admin bd63808767 Translated using Weblate (English)
Currently translated at 100.0% (852 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/en/
2022-07-26 18:36:58 +00:00
Weblate Admin 2d25fbf6a4 Translated using Weblate (Chinese (Traditional))
Currently translated at 81.9% (698 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/zh_Hant/
2022-07-26 18:36:58 +00:00
Weblate Admin b301b48571 Translated using Weblate (Chinese (Simplified))
Currently translated at 82.0% (699 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/zh_Hans/
2022-07-26 18:36:58 +00:00
Weblate Admin 16aacf55cb Translated using Weblate (Vietnamese)
Currently translated at 82.3% (702 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/vi/
2022-07-26 18:36:57 +00:00
Weblate Admin c14dbe146c Translated using Weblate (Ukrainian)
Currently translated at 81.9% (698 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/uk/
2022-07-26 18:36:57 +00:00
Weblate Admin 8361ea76d0 Translated using Weblate (Slovak)
Currently translated at 46.7% (398 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/sk/
2022-07-26 18:36:57 +00:00
Weblate Admin 80cefc20fd Translated using Weblate (Russian)
Currently translated at 71.9% (613 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ru/
2022-07-26 18:36:57 +00:00
Weblate Admin 2b99c62957 Translated using Weblate (Portuguese)
Currently translated at 77.6% (662 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/pt/
2022-07-26 18:36:57 +00:00
Weblate Admin ae16d37242 Translated using Weblate (Polish)
Currently translated at 81.2% (692 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/pl/
2022-07-26 18:36:56 +00:00
Weblate Admin 2fc2bc5590 Translated using Weblate (Dutch)
Currently translated at 81.8% (697 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/nl/
2022-07-26 18:36:56 +00:00
Weblate Admin 62c5709cc5 Translated using Weblate (Korean)
Currently translated at 52.8% (450 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ko/
2022-07-26 18:36:56 +00:00
Weblate Admin 22f21e7a83 Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 85.5% (729 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ja_PEDANTIC/
2022-07-26 18:36:56 +00:00
Weblate Admin 155a734a52 Translated using Weblate (Italian)
Currently translated at 82.5% (703 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/it/
2022-07-26 18:36:55 +00:00
Weblate Admin b996677c7e Translated using Weblate (Indonesian)
Currently translated at 55.7% (475 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/id/
2022-07-26 18:36:55 +00:00
Weblate Admin bf42249200 Translated using Weblate (French)
Currently translated at 82.7% (705 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/fr/
2022-07-26 18:36:55 +00:00
Weblate Admin c161e54e6b Translated using Weblate (Persian)
Currently translated at 13.0% (111 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/fa/
2022-07-26 18:36:54 +00:00
Weblate Admin 2ae7a4712d Translated using Weblate (Basque)
Currently translated at 65.8% (561 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/eu/
2022-07-26 18:36:54 +00:00
Weblate Admin e90c23fde2 Translated using Weblate (Spanish)
Currently translated at 82.5% (703 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/es/
2022-07-26 18:36:54 +00:00
Weblate Admin 47fd319fed Translated using Weblate (Esperanto)
Currently translated at 82.5% (703 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/eo/
2022-07-26 18:36:54 +00:00
Weblate Admin 390231246f Translated using Weblate (German)
Currently translated at 81.9% (698 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/de/
2022-07-26 18:36:53 +00:00
Weblate Admin 0c7f1475cb Translated using Weblate (Catalan)
Currently translated at 82.5% (703 of 852 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ca/
2022-07-26 18:36:53 +00:00
Weblate Admin 868dce8e3a Translated using Weblate (English)
Currently translated at 100.0% (853 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/en/
2022-07-26 18:35:57 +00:00
Weblate Admin 66f45846a6 Translated using Weblate (Chinese (Traditional))
Currently translated at 81.9% (699 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/zh_Hant/
2022-07-26 18:35:57 +00:00
Weblate Admin df158da14a Translated using Weblate (Vietnamese)
Currently translated at 82.4% (703 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/vi/
2022-07-26 18:35:57 +00:00
Weblate Admin 25e7b90926 Translated using Weblate (Ukrainian)
Currently translated at 81.9% (699 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/uk/
2022-07-26 18:35:57 +00:00
Weblate Admin b686dec2b1 Translated using Weblate (Slovak)
Currently translated at 46.7% (399 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/sk/
2022-07-26 18:35:56 +00:00
Weblate Admin 015f21a1c9 Translated using Weblate (Russian)
Currently translated at 71.9% (614 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ru/
2022-07-26 18:35:56 +00:00
Weblate Admin 6f07fc7326 Translated using Weblate (Portuguese)
Currently translated at 77.7% (663 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/pt/
2022-07-26 18:35:56 +00:00
Weblate Admin 13df926362 Translated using Weblate (Polish)
Currently translated at 81.2% (693 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/pl/
2022-07-26 18:35:56 +00:00
Weblate Admin 78c3bb168f Translated using Weblate (Dutch)
Currently translated at 81.8% (698 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/nl/
2022-07-26 18:35:55 +00:00
Weblate Admin f0ee4414f3 Translated using Weblate (Korean)
Currently translated at 52.8% (451 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ko/
2022-07-26 18:35:55 +00:00
Weblate Admin b8f6b79682 Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 85.5% (730 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ja_PEDANTIC/
2022-07-26 18:35:55 +00:00
Weblate Admin 7666a13445 Translated using Weblate (Italian)
Currently translated at 82.5% (704 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/it/
2022-07-26 18:35:55 +00:00
Weblate Admin 858df3737d Translated using Weblate (Indonesian)
Currently translated at 55.8% (476 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/id/
2022-07-26 18:35:54 +00:00
Weblate Admin a9e46b7c7f Translated using Weblate (French)
Currently translated at 82.7% (706 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/fr/
2022-07-26 18:35:54 +00:00
Weblate Admin 05ba991414 Translated using Weblate (Persian)
Currently translated at 13.1% (112 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/fa/
2022-07-26 18:35:54 +00:00
Weblate Admin 8ba5d3e739 Translated using Weblate (Basque)
Currently translated at 65.8% (562 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/eu/
2022-07-26 18:35:54 +00:00
Weblate Admin a4c7cf3b8a Translated using Weblate (Spanish)
Currently translated at 82.5% (704 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/es/
2022-07-26 18:35:53 +00:00
Weblate Admin f06ddec06b Translated using Weblate (Esperanto)
Currently translated at 82.5% (704 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/eo/
2022-07-26 18:35:53 +00:00
Weblate Admin 37ece31b94 Translated using Weblate (German)
Currently translated at 81.9% (699 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/de/
2022-07-26 18:35:53 +00:00
Weblate Admin 650898fdc4 Translated using Weblate (Catalan)
Currently translated at 82.5% (704 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ca/
2022-07-26 18:35:53 +00:00
Weblate Admin 3572d5edba Translated using Weblate (Chinese (Simplified))
Currently translated at 82.0% (700 of 853 strings)

Translation: Pleroma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/zh_Hans/
2022-07-26 18:35:53 +00:00
FloatingGhost 017197cd28 Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 85.5% (730 of 853 strings)

Translation: Akkoma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ja_PEDANTIC/
2022-07-26 18:24:45 +00:00
Weblate Admin 0c9de801f7 Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 85.4% (729 of 853 strings)

Translation: Akkoma fe/pleroma-fe
Translate-URL: http://translate.akkoma.dev/projects/akkoma-fe/pleroma-fe/ja_PEDANTIC/
2022-07-26 18:11:39 +00:00
floatingghost d85d9c26b1 Merge pull request 'Merge disabled boost and quote buttons on private posts' (#63) from sfr/pleroma-fe:hide-quote-button into develop
Reviewed-on: AkkomaGang/pleroma-fe#63
2022-07-26 13:22:09 +00:00
floatingghost 0746682763 Merge pull request 'Change quoted status link to router-link' (#60) from sfr/pleroma-fe:fix/quote-links into develop
Reviewed-on: AkkomaGang/pleroma-fe#60
2022-07-26 12:45:44 +00:00
Sol Fisher Romanoff 1201418bec
Align quote timestamp to right 2022-07-26 14:51:07 +03:00
floatingghost 07adab74ec Merge pull request 'i18n: add quote tooltip' (#62) from sfr/pleroma-fe:i18n/quote into develop
Reviewed-on: AkkomaGang/pleroma-fe#62
2022-07-26 11:44:55 +00:00
Sol Fisher Romanoff 55a840ba9d
Merge disabled boost and quote buttons on private posts 2022-07-25 21:44:38 +03:00
Sol Fisher Romanoff 0fdaca8cb2
i18n: add quote tooltip 2022-07-25 21:39:55 +03:00
Sol Fisher Romanoff 86fc043e54
Change quoted status link to router-link 2022-07-25 19:58:55 +03:00
floatingghost a2541bb4e0 Add quotes (#59)
Reviewed-on: AkkomaGang/pleroma-fe#59
2022-07-25 16:25:41 +00:00
thomate 04bb4112c0 Added some missing French translations, and fixed an adjacent bit of style that didn’t match the rest (#58)
Reviewed-on: AkkomaGang/pleroma-fe#58
Co-authored-by: thomate <thomas@burdick.fr>
Co-committed-by: thomate <thomas@burdick.fr>
2022-07-25 11:36:34 +00:00
floatingghost c8b2bf96be Merge pull request 'fix time units in poll form' (#56) from poll-times into develop
Reviewed-on: AkkomaGang/pleroma-fe#56
2022-07-23 12:47:08 +00:00
FloatingGhost 4d2cf83452 fix time units 2022-07-23 13:40:52 +01:00
floatingghost 12d7e4ea24 Merge pull request 'don't try to parse pong' (#54) from json-fix into develop
Reviewed-on: AkkomaGang/pleroma-fe#54
2022-07-22 15:09:06 +00:00
FloatingGhost 3d921c81bd don't try to parse pong 2022-07-22 16:08:29 +01:00
floatingghost e48506844d Merge pull request 'add bubble timeline' (#53) from bubble-keikaku into develop
Reviewed-on: AkkomaGang/pleroma-fe#53
2022-07-22 14:55:47 +00:00
FloatingGhost 18fc3a0ad5 add bubble timeline 2022-07-22 15:36:45 +01:00
floatingghost 75024d8501 re-add time direction (#52)
Reviewed-on: AkkomaGang/pleroma-fe#52
2022-07-21 20:56:30 +00:00
floatingghost 7a0b83cdc9 Merge pull request 'scale custom emoji with mfm' (#50) from mfm-emoji-size into develop
Reviewed-on: AkkomaGang/pleroma-fe#50
2022-07-20 15:54:57 +00:00
FloatingGhost 3302ef08ae scale custom emoji with mfm 2022-07-20 16:53:59 +01:00
floatingghost a8c4aec721 purge shout/chat (#49)
Reviewed-on: AkkomaGang/pleroma-fe#49
2022-07-20 15:36:45 +00:00
floatingghost 9a82e8b097 Merge pull request 'fix up some JP localisations' (#48) from i18n/jp-loc into develop
Reviewed-on: AkkomaGang/pleroma-fe#48
2022-07-20 13:36:23 +00:00
FloatingGhost 4714d683d3 fix up some JP localisations 2022-07-20 14:35:45 +01:00
floatingghost 137bf5557c Merge pull request '[#44] i18n/fix-format' (#45) from i18n/fix-format into develop
Reviewed-on: AkkomaGang/pleroma-fe#45
2022-07-19 10:12:36 +00:00
FloatingGhost 606e850aad lint 2022-07-19 11:02:49 +01:00
FloatingGhost eb47bb8973 remove debug log 2022-07-19 11:02:08 +01:00
FloatingGhost a32cd0a98c fix time formats 2022-07-19 11:01:24 +01:00
floatingghost cfb55594a5 Merge pull request 'Make private notes actually work' (#43) from sfr/pleroma-fe:notes into develop
Reviewed-on: AkkomaGang/pleroma-fe#43
2022-07-19 08:33:20 +00:00
Sol Fisher Romanoff 15b612bad6
Make private notes actually work 2022-07-19 08:56:14 +03:00
floatingghost fab72940c4 announcements (#42)
Reviewed-on: AkkomaGang/pleroma-fe#42
2022-07-18 13:08:50 +00:00
floatingghost 2977edc04d theme tab: don't crash out on non-colours (#41)
Reviewed-on: AkkomaGang/pleroma-fe#41
2022-07-17 13:13:51 +00:00
Norm a256931596 Update README with Akkoma stuff (#39)
Reviewed-on: AkkomaGang/pleroma-fe#39
Co-authored-by: Norm <normandy@biribiri.dev>
Co-committed-by: Norm <normandy@biribiri.dev>
2022-07-17 13:09:44 +00:00
floatingghost 29f3c8a40e Merge pull request 'Allow ctrl+enter posting when focused on alt text' (#33) from sfr/pleroma-fe:attachment-shortcut into develop
Reviewed-on: AkkomaGang/pleroma-fe#33
2022-07-17 13:09:10 +00:00
floatingghost e2d064131b Merge pull request 'add mfm cheatsheet link' (#37) from mfm-link into develop
Reviewed-on: AkkomaGang/pleroma-fe#37
2022-07-15 13:01:34 +00:00
FloatingGhost ea6f6ec82c add mfm cheatsheet link 2022-07-15 14:00:55 +01:00
floatingghost 35146e3615 Merge pull request 'deploy to a branch' (#36) from branch-artifact into develop
Reviewed-on: AkkomaGang/pleroma-fe#36
2022-07-15 10:44:00 +00:00
FloatingGhost bcd32de461 deploy to a branch 2022-07-15 11:42:56 +01:00
floatingghost 985a70c084 Merge pull request 'Add documentation builder' (#35) from stable-docs into develop
Reviewed-on: AkkomaGang/pleroma-fe#35
2022-07-15 10:21:48 +00:00
FloatingGhost 0dfde3e92c only build on develop and stable 2022-07-15 11:20:01 +01:00
FloatingGhost 361662b6cf build docs on deploy 2022-07-15 11:11:34 +01:00
FloatingGhost 494d7f49c9 add documentation builder 2022-07-15 11:07:43 +01:00
floatingghost 3ff141a122 Merge pull request 'marked-mfm fixes: spin direction, greentext' (#34) from marked-mfm into develop
Reviewed-on: AkkomaGang/pleroma-fe#34
2022-07-15 09:59:22 +00:00
FloatingGhost dba047ce34 Merge branch 'develop' into marked-mfm 2022-07-15 10:58:41 +01:00
sfr 931ed3d3c7 Use Marked and marked-mfm for MFM rendering (#29)
Reviewed-on: AkkomaGang/pleroma-fe#29
Co-authored-by: sfr <sol@solfisher.com>
Co-committed-by: sfr <sol@solfisher.com>
2022-07-15 09:57:17 +00:00
FloatingGhost d29bccb7d6 bugfix: spin css, props setting 2022-07-15 10:55:47 +01:00
Sol Fisher Romanoff d480429896
Allow ctrl+enter posting when focused on alt text 2022-07-14 22:36:14 +03:00
floatingghost 28d5a55352 Merge pull request 'Fix path to terms-of-service.html' (#32) from norm/pleroma-fe:norm-patch-1 into develop
Reviewed-on: AkkomaGang/pleroma-fe#32
2022-07-14 10:28:09 +00:00
Sol Fisher Romanoff a8f9734400
Don't greentext MFM 2022-07-13 20:59:41 +03:00
Norm 4d84fd4cf7 Fix path to terms-of-service.html 2022-07-12 20:46:08 +00:00
Sol Fisher Romanoff 8861835580
Fix CSS 2022-07-12 20:36:49 +03:00
Sol Fisher Romanoff 8c9821e444
Parse MFM options with values 2022-07-12 20:36:48 +03:00
Sol Fisher Romanoff f15465f0d3
Use media type to figure out whether to parse MFM 2022-07-12 20:36:25 +03:00
Sol Fisher Romanoff 5e18f1ceeb
Move MFM content rendering to RichContent 2022-07-12 20:36:03 +03:00
sfr 5385797ff1 Smarter scope copy (#28)
Reviewed-on: AkkomaGang/pleroma-fe#28
Co-authored-by: sfr <sol@solfisher.com>
Co-committed-by: sfr <sol@solfisher.com>
2022-07-12 09:04:28 +00:00
floatingghost 7d377b8e88 Merge pull request '[#30] increase spacing of icons' (#31) from icon-spacing into develop
Reviewed-on: AkkomaGang/pleroma-fe#31
2022-07-10 17:11:44 +00:00
FloatingGhost 67bc9e5ba3 increase spacing of icons 2022-07-10 18:05:55 +01:00
Sol Fisher Romanoff 83a3b37f1f
Use Marked and marked-mfm for MFM rendering 2022-07-09 14:34:59 +03:00
Sol Fisher Romanoff 5f9e3709b3
Update MFM css 2022-07-09 14:34:31 +03:00
floatingghost 3dac486688 Merge pull request 'refactor list search' (#27) from list-searcher into develop
Reviewed-on: AkkomaGang/pleroma-fe#27
2022-07-05 13:42:17 +00:00
FloatingGhost 88d04f0af7 also run search on checkbox update 2022-07-05 14:37:26 +01:00
FloatingGhost 617cd7d1f8 refactor list search 2022-07-05 14:28:23 +01:00
sfr 677e5325fa implement Move activities (#26)
Reviewed-on: AkkomaGang/pleroma-fe#26
Co-authored-by: sfr <sol@solfisher.com>
Co-committed-by: sfr <sol@solfisher.com>
2022-07-04 16:38:58 +00:00
floatingghost 101f15a078 Merge pull request 'fix search rendering' (#25) from mfm-search into develop
Reviewed-on: AkkomaGang/pleroma-fe#25
2022-07-01 12:50:44 +00:00
FloatingGhost 370f3b14fa fix search rendering 2022-07-01 13:50:14 +01:00
floatingghost 6e641a99a2 Show warning if a post has a subject line but is not marked as sensitive (#24)
Reviewed-on: AkkomaGang/pleroma-fe#24
2022-06-28 13:53:12 +00:00
sfr 716320de35 Respect subject lines in notifications (#23)
Reviewed-on: AkkomaGang/pleroma-fe#23
Co-authored-by: sfr <sol@solfisher.com>
Co-committed-by: sfr <sol@solfisher.com>
2022-06-27 16:15:24 +00:00
floatingghost 06343addef Merge pull request 'Render inline code correctly in MFM' (#21) from sfr/pleroma-fe:mfm-code into develop
Reviewed-on: AkkomaGang/pleroma-fe#21
2022-06-27 09:33:49 +00:00
Sol Fisher Romanoff eb69fc33f6
Render inline code correctly in MFM 2022-06-27 10:15:57 +03:00
floatingghost 9e13e5d164 Add cleaner message for rate-limited users (#19)
Reviewed-on: AkkomaGang/pleroma-fe#19
2022-06-26 19:02:49 +00:00
sfr 794906f01e Fix alt text wrap and line breaks (#14)
Reviewed-on: AkkomaGang/pleroma-fe#14
Co-authored-by: sfr <sol@solfisher.com>
Co-committed-by: sfr <sol@solfisher.com>
2022-06-26 18:36:04 +00:00
sfr 263ef17816 Use textarea instead of input for composing alt text (#15)
Reviewed-on: AkkomaGang/pleroma-fe#15
Co-authored-by: sfr <sol@solfisher.com>
Co-committed-by: sfr <sol@solfisher.com>
2022-06-26 17:51:13 +00:00
FloatingGhost 7025cb8bb0 Merge branch 'build-on-merge' into develop 2022-06-26 18:20:54 +01:00
FloatingGhost 821460075a also run build on push 2022-06-26 18:20:41 +01:00
floatingghost b87637c611 Merge pull request 'Build and release on merge into develop' (#17) from build-on-merge into develop
Reviewed-on: AkkomaGang/pleroma-fe#17
2022-06-26 17:14:52 +00:00
FloatingGhost e10588430f builds on merge into develop for ease of release 2022-06-26 18:13:57 +01:00
floatingghost dda1a2f75c Merge pull request 'Allow boosting own private posts' (#16) from sfr/pleroma-fe:boost-priv into develop
Reviewed-on: AkkomaGang/pleroma-fe#16
2022-06-26 17:11:08 +00:00
Yureka cc35202911
Allow boosting own private posts 2022-06-26 07:54:42 +03:00
floatingghost 9827c0e81e Merge pull request 'Remove auto-dismissing notifications' (#13) from sfr/pleroma-fe:develop into develop
Reviewed-on: AkkomaGang/pleroma-fe#13
2022-06-24 15:47:48 +00:00
Sol Fisher Romanoff f481cdd00b
Remove auto-dismissing notifications 2022-06-23 22:31:48 +03:00
floatingghost f0e180693e Merge pull request 'Don't show note textarea if logged out' (#12) from sfr/pleroma-fe:notes into develop
Reviewed-on: AkkomaGang/pleroma-fe#12
2022-06-23 19:09:57 +00:00
Sol Fisher Romanoff 9f9c5e01f1
Don't show note textarea if logged out 2022-06-23 20:02:47 +03:00
floatingghost c71405ccad Merge pull request 'Add private note field to user profile' (#9) from sfr/pleroma-fe:notes into develop
Reviewed-on: AkkomaGang/pleroma-fe#9
2022-06-23 14:33:18 +00:00
Sol Fisher Romanoff 231a4af754
Disable private note for self 2022-06-23 15:04:54 +03:00
Sol Fisher Romanoff 398b2624c8
Add note saving indicator 2022-06-23 15:04:19 +03:00
Sol Fisher Romanoff 4f0eabbd55
api: turn MASTODON_USER_URL into a function 2022-06-23 15:02:43 +03:00
Sol Fisher Romanoff fb2fc686b1
Add debounce to API request 2022-06-22 19:42:27 +03:00
Sol Fisher Romanoff 9923ff587b
Add private note field to user profile 2022-06-22 19:14:06 +03:00
FloatingGhost 261a9da374 remove parsing of sparkle 2022-06-22 11:26:48 +01:00
FloatingGhost ca8689fc88 bugfix: don't error if emoji not found 2022-06-20 14:55:38 +01:00
floatingghost 15ec862da0 Merge pull request 'lists' (#4) from sfr/pleroma-fe:lists into develop
Reviewed-on: http://akkoma.dev/AkkomaGang/pleroma-fe/pulls/4
2022-06-20 13:10:00 +00:00
Sol Fisher Romanoff ba4656312a
Add tests 2022-06-19 00:04:53 +03:00
Sol Fisher Romanoff a6136f6cb2
refactor: don't make API calls directly 2022-06-19 00:04:52 +03:00
Sol Fisher Romanoff 9217ca8476
Fetch all users in list before displaying 2022-06-19 00:04:52 +03:00
Sol Fisher Romanoff ab7001ed20
Use router for list timeline link 2022-06-19 00:04:52 +03:00
Sol Fisher Romanoff 023fcb506f
Lint fixes 2022-06-19 00:04:52 +03:00
Sol Fisher Romanoff a66d40e0e8
Clear list timeline on exit 2022-06-19 00:04:52 +03:00
Sol Fisher Romanoff a982af771b
Display list title 2022-06-19 00:04:52 +03:00
Sol Fisher Romanoff 92fac1cd9f
Allow removing accounts from list 2022-06-19 00:04:52 +03:00
Sol Fisher Romanoff dea803468f
Show already added accounts in search 2022-06-19 00:04:51 +03:00
Sol Fisher Romanoff d1af4bdd51
Fix account selection garbage 2022-06-19 00:04:51 +03:00
Sol Fisher Romanoff cf33b3295f
Add ability to edit and delete lists 2022-06-19 00:04:51 +03:00
Sol Fisher Romanoff bacb6c8fb3
Add list creation functionality 2022-06-19 00:04:51 +03:00
Sol Fisher Romanoff 3d18dd7e47
Make the list list page look good 2022-06-19 00:04:51 +03:00
Sol Fisher Romanoff d2c82a04d9
Add buttons to menus 2022-06-19 00:04:51 +03:00
Sol Fisher Romanoff 6e8c7460a2
Add lists listing page 2022-06-19 00:04:51 +03:00
Sol Fisher Romanoff e18e179a59
Fetch list of lists from the API 2022-06-19 00:04:50 +03:00
Sol Fisher Romanoff d5d464a289
Add list timeline rendering functionality
the list name is missing; this is because the API request to get it is different
to the API request for receiving the list timeline, and is not yet implemented.
2022-06-19 00:04:48 +03:00
floatingghost c1a8dc34da Merge pull request 'Add tests to #3' (#8) from sfr/pleroma-fe:develop into develop
Reviewed-on: http://akkoma.dev/AkkomaGang/pleroma-fe/pulls/8
2022-06-18 12:20:39 +00:00
Sol Fisher Romanoff cea08d41ee
Add tests to #3 2022-06-17 20:40:49 +03:00
floatingghost 3806f579b9 Merge pull request 'Correctly link to BE commit in version tab' (#3) from sfr/pleroma-fe:develop into develop
Reviewed-on: http://akkoma.dev/AkkomaGang/pleroma-fe/pulls/3
2022-06-17 11:56:12 +00:00
FloatingGhost 13048fce76 fix build 2022-06-17 12:23:19 +01:00
FloatingGhost a0ba907f6d build on push 2022-06-17 12:19:12 +01:00
FloatingGhost 35dfb09e6f force popover to be a bit bigger 2022-06-17 12:18:11 +01:00
floatingghost 8c9356b9fd Reuse the emoji picker for reactions (#7)
Fixes #6

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: http://akkoma.dev/AkkomaGang/pleroma-fe/pulls/7
2022-06-16 11:41:08 +00:00
FloatingGhost 52644ec5cb remove IHBA ToS 2022-06-16 11:35:25 +01:00
floatingghost a0305e90f0 fix tests and add CI (#5)
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: http://akkoma.dev/AkkomaGang/pleroma-fe/pulls/5
2022-06-15 17:22:02 +00:00
Sol Fisher Romanoff adc6b86e6b
Correctly link to BE commit in version tab 2022-06-14 22:13:51 +03:00
258 changed files with 31765 additions and 27086 deletions

1
.gitignore vendored
View file

@ -8,3 +8,4 @@ selenium-debug.log
.idea/
config/local.json
config/local.*.json
docs/site/

View file

@ -1,47 +0,0 @@
# This file is a template, and might need editing before it works on your project.
# Official framework image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/node/tags/
image: node:12
stages:
- lint
- build
- test
- deploy
lint:
stage: lint
script:
- yarn
- npm run lint
- npm run stylelint
test:
stage: test
variables:
APT_CACHE_DIR: apt-cache
script:
- mkdir -pv $APT_CACHE_DIR && apt-get -qq update
- apt install firefox-esr -y --no-install-recommends
- firefox --version
- yarn
- yarn unit
build:
stage: build
script:
- yarn
- npm run build
artifacts:
paths:
- dist/
docs-deploy:
stage: deploy
image: alpine:latest
only:
- develop@pleroma/pleroma-fe
before_script:
- apk add curl
script:
- curl -X POST -F"token=$DOCS_PIPELINE_TRIGGER" -F'ref=master' https://git.pleroma.social/api/v4/projects/673/trigger/pipeline

81
.woodpecker.yml Normal file
View file

@ -0,0 +1,81 @@
pipeline:
lint:
when:
event:
- pull_request
image: node:16
commands:
- yarn
- yarn lint
- yarn stylelint
test:
when:
event:
- pull_request
image: node:16
commands:
- apt update
- apt install firefox-esr -y --no-install-recommends
- yarn
- yarn unit
build:
when:
event:
- push
branch:
- develop
- stable
image: node:16
commands:
- yarn
- yarn build
release:
when:
event:
- push
branch:
- develop
- stable
image: node:16
secrets:
- SCW_ACCESS_KEY
- SCW_SECRET_KEY
- SCW_DEFAULT_ORGANIZATION_ID
commands:
- apt-get update && apt-get install -y rclone wget zip
- wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64
- mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli
- chmod +x scaleway-cli
- ./scaleway-cli object config install type=rclone
- zip akkoma-fe.zip -r dist
- rclone copyto akkoma-fe.zip scaleway:akkoma-updates/frontend/$CI_COMMIT_BRANCH/akkoma-fe.zip
docs:
when:
event:
- push
branch:
- develop
- stable
environment:
CI: "true"
image: python:3.10-slim
secrets:
- SCW_ACCESS_KEY
- SCW_SECRET_KEY
- SCW_DEFAULT_ORGANIZATION_ID
commands:
- apt-get update && apt-get install -y rclone wget git zip
- wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64
- mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli
- chmod +x scaleway-cli
- ./scaleway-cli object config install type=rclone
- cd docs
- pip install -r requirements.txt
- mkdocs build
- zip -r docs.zip site/*
- cd site
- rclone copy . scaleway:akkoma-docs/frontend/$CI_COMMIT_BRANCH/

View file

@ -37,6 +37,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Media modal now also displays description and counter position in gallery (i.e. 1/5)
- Ability to rearrange order of attachments when uploading
- Enabled users to zoom and pan images in media viewer with mouse and touch
- Added frontend ui for account migration
## [2.4.2] - 2022-01-09

View file

@ -10,3 +10,5 @@ Contributors of this project.
- shpuld (shpuld@shitposter.club): CSS and styling
- Vincent Guth (https://unsplash.com/photos/XrwVIFy6rTw): Background images.
- hj (hj@shigusegubu.club): Code
- Sean King (seanking@freespeechextremist.com): Code
- Tusooa Zhu (tusooa@kazv.moe): Code

View file

@ -1,16 +1,22 @@
# Pleroma-FE
> A single column frontend designed for Pleroma.
![English OK](https://img.shields.io/badge/English-OK-blueviolet) ![日本語OK](https://img.shields.io/badge/%E6%97%A5%E6%9C%AC%E8%AA%9E-OK-blueviolet)
![screenshot](/uploads/796c5ecf985ed1e2b0943ee0df131ed0/DJVqSJ0.png)
This is a fork of Pleroma-FE from the Pleroma project, with support for new Akkoma features such as:
- MFM support via [marked-mfm](https://akkoma.dev/sfr/marked-mfm)
- Custom emoji reactions
# For Translators
To translate Pleroma-FE, add your language to [src/i18n/messages.js](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/messages.js). Pleroma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js.
The [Weblate UI](https://translate.akkoma.dev/projects/akkoma/pleroma-fe/) is recommended for adding or modifying translations for Pleroma-FE.
Alternatively, edit/create `src/i18n/$LANGUAGE_CODE.json` (where `$LANGUAGE_CODE` is the [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language), then add your language to [src/i18n/messages.js](https://akkoma.dev/AkkomaGang/pleroma-fe/src/branch/develop/src/i18n/messages.js) if it doesn't already exist there.
Pleroma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js.
# FOR ADMINS
You don't need to build Pleroma-FE yourself. Those using the Pleroma backend will be able to use it out of the box.
To use Pleroma-FE in Akkoma, use the [frontend](https://docs.akkoma.dev/stable/administration/CLI_tasks/frontend/) CLI task to install Pleroma-FE, then modify your configuration as described in the [Frontend Management](https://docs.akkoma.dev/stable/configuration/frontend_management/) doc.
## Build Setup

View file

@ -30,7 +30,7 @@ module.exports = {
}
},
resolve: {
extensions: ['.js', '.jsx', '.vue'],
extensions: ['.js', '.jsx', '.vue', '.mjs'],
modules: [
path.join(__dirname, '../node_modules')
],
@ -68,6 +68,11 @@ module.exports = {
path.resolve(__dirname, '../src/i18n')
]
},
{
test: /\.mjs$/,
include: /node_modules/,
type: "javascript/auto"
},
{
test: /\.vue$/,
loader: 'vue-loader',

4
config/ihba.json Normal file
View file

@ -0,0 +1,4 @@
{
"target": "https://ihatebeinga.live",
"staticConfigPreference": false
}

View file

@ -61,7 +61,17 @@ module.exports = {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost'
}
},
'/static/stickers.json': {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost'
},
'/static/stickers': {
target,
changeOrigin: true,
cookieDomainRewrite: 'localhost'
},
},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README

7
docs/Makefile Normal file
View file

@ -0,0 +1,7 @@
all: install
pipenv run mkdocs build
install:
pipenv install
clean:
rm -rf site

10
docs/Pipfile Normal file
View file

@ -0,0 +1,10 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
mkdocs-material = "*"
markdown-include = "*"

277
docs/Pipfile.lock generated Normal file
View file

@ -0,0 +1,277 @@
{
"_meta": {
"hash": {
"sha256": "926d34630c729228bb015cb958c04f8269c57f5ca1ffc2ceab1dfd1798884772"
},
"pipfile-spec": 6,
"requires": {},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"click": {
"hashes": [
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.3"
},
"ghp-import": {
"hashes": [
"sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619",
"sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"
],
"version": "==2.1.0"
},
"importlib-metadata": {
"hashes": [
"sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670",
"sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"
],
"markers": "python_version >= '3.7'",
"version": "==4.12.0"
},
"jinja2": {
"hashes": [
"sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
"sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
],
"markers": "python_version >= '3.7'",
"version": "==3.1.2"
},
"markdown": {
"hashes": [
"sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874",
"sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"
],
"markers": "python_version >= '3.6'",
"version": "==3.3.7"
},
"markdown-include": {
"hashes": [
"sha256:6f5d680e36f7780c7f0f61dca53ca581bd50d1b56137ddcd6353efafa0c3e4a2"
],
"index": "pypi",
"version": "==0.6.0"
},
"markupsafe": {
"hashes": [
"sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003",
"sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88",
"sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5",
"sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7",
"sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a",
"sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603",
"sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1",
"sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135",
"sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247",
"sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6",
"sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601",
"sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77",
"sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02",
"sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e",
"sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63",
"sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f",
"sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980",
"sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b",
"sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812",
"sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff",
"sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96",
"sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1",
"sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925",
"sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a",
"sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6",
"sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e",
"sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f",
"sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4",
"sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f",
"sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3",
"sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c",
"sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a",
"sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417",
"sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a",
"sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a",
"sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37",
"sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452",
"sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933",
"sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a",
"sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.1"
},
"mergedeep": {
"hashes": [
"sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8",
"sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"
],
"markers": "python_version >= '3.6'",
"version": "==1.3.4"
},
"mkdocs": {
"hashes": [
"sha256:26bd2b03d739ac57a3e6eed0b7bcc86168703b719c27b99ad6ca91dc439aacde",
"sha256:b504405b04da38795fec9b2e5e28f6aa3a73bb0960cb6d5d27ead28952bd35ea"
],
"markers": "python_version >= '3.6'",
"version": "==1.3.0"
},
"mkdocs-material": {
"hashes": [
"sha256:263f2721f3abe533b61f7c8bed435a0462620912742c919821ac2d698b4bfe67",
"sha256:dc82b667d2a83f0de581b46a6d0949732ab77e7638b87ea35b770b33bc02e75a"
],
"index": "pypi",
"version": "==8.3.9"
},
"mkdocs-material-extensions": {
"hashes": [
"sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44",
"sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"
],
"markers": "python_version >= '3.6'",
"version": "==1.0.3"
},
"packaging": {
"hashes": [
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
],
"markers": "python_version >= '3.6'",
"version": "==21.3"
},
"pygments": {
"hashes": [
"sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb",
"sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"
],
"markers": "python_version >= '3.6'",
"version": "==2.12.0"
},
"pymdown-extensions": {
"hashes": [
"sha256:3ef2d998c0d5fa7eb09291926d90d69391283561cf6306f85cd588a5eb5befa0",
"sha256:ec141c0f4983755349f0c8710416348d1a13753976c028186ed14f190c8061c4"
],
"markers": "python_version >= '3.7'",
"version": "==9.5"
},
"pyparsing": {
"hashes": [
"sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb",
"sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
],
"markers": "python_full_version >= '3.6.8'",
"version": "==3.0.9"
},
"python-dateutil": {
"hashes": [
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.2"
},
"pyyaml": {
"hashes": [
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
"sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
"sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
"sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
"sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
"sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
],
"markers": "python_version >= '3.6'",
"version": "==6.0"
},
"pyyaml-env-tag": {
"hashes": [
"sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb",
"sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"
],
"markers": "python_version >= '3.6'",
"version": "==0.1"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"watchdog": {
"hashes": [
"sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412",
"sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654",
"sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306",
"sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33",
"sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd",
"sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7",
"sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892",
"sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609",
"sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6",
"sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1",
"sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591",
"sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d",
"sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d",
"sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c",
"sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3",
"sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39",
"sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213",
"sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330",
"sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428",
"sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1",
"sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846",
"sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153",
"sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3",
"sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9",
"sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658"
],
"markers": "python_version >= '3.6'",
"version": "==2.1.9"
},
"zipp": {
"hashes": [
"sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad",
"sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"
],
"markers": "python_version >= '3.7'",
"version": "==3.8.0"
}
},
"develop": {}
}

View file

@ -7,9 +7,9 @@
PleromaFE gets its configuration from several sources, in order of preference (the one above overrides ones below it)
1. `/api/statusnet/config.json` - this is generated on Backend and contains multiple things including instance name, char limit etc. It also contains FE/Client-specific data, PleromaFE uses `pleromafe` field of it. For more info on changing config on BE, look [here](../backend/configuration/cheatsheet.md#frontend_configurations)
2. `/static/config.json` - this is a static FE-provided file, containing only FE specific configuration. This file is completely optional and could be removed but is useful as a fallback if some configuration JSON property isn't present in BE-provided config. It's also a reference point to check what default configuration are and what JSON properties even exist. In local dev mode it could be used to override BE configuration, more about that in HACKING.md. File is located [here](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/static/config.json).
3. Built-in defaults. Those are hard-coded defaults that are used when `/static/config.json` is not available and BE-provided configuration JSON is missing some JSON properties. ( [Code](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/modules/instance.js) )
1. `/api/statusnet/config.json` - this is generated on Backend and contains multiple things including instance name, char limit etc. It also contains FE/Client-specific data, PleromaFE uses `pleromafe` field of it. For more info on changing config on BE, look [here](https://docs.akkoma.dev/stable/configuration/cheatsheet.md#frontend_configurations)
2. `/static/config.json` - this is a static FE-provided file, containing only FE specific configuration. This file is completely optional and could be removed but is useful as a fallback if some configuration JSON property isn't present in BE-provided config. It's also a reference point to check what default configuration are and what JSON properties even exist. In local dev mode it could be used to override BE configuration, more about that in HACKING.md. File is located [here](https://akkoma.dev/AkkomaGang/pleroma-fe/src/branch/develop/static/config.json).
3. Built-in defaults. Those are hard-coded defaults that are used when `/static/config.json` is not available and BE-provided configuration JSON is missing some JSON properties. ( [Code](https://akkoma.dev/AkkomaGang/pleroma-fe/src/branch/develop/src/modules/instance.js) )
## Instance-defaults

View file

@ -25,7 +25,7 @@ This could be a bit trickier, you basically need steps 1-4 from *develop build*
### Replacing your instance's frontend with custom FE build
This is the most easiest way to use and test FE build: you just need to copy or symlink contents of `dist` folder into backend's [static directory](../backend/configuration/static_dir.md), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder.
This is the most easiest way to use and test FE build: you just need to copy or symlink contents of `dist` folder into backend's [static directory](https://docs.akkoma.dev/stable/configuration/static_dir/), by default it is located in `instance/static`, or in `/var/lib/pleroma/static` for OTP release installations, create it if it doesn't exist already. Be aware that running `yarn build` wipes the contents of `dist` folder.
### Running production build locally or on a separate server
@ -67,7 +67,7 @@ server {
### API, Data, Operations
In 99% cases PleromaFE uses [MastoAPI](https://docs.joinmastodon.org/api/) with [Pleroma Extensions](../backend/API/differences_in_mastoapi_responses.md) to fetch the data. The rest is either QvitterAPI leftovers or pleroma-exclusive APIs. QvitterAPI doesn't exactly have documentation and uses different JSON structure and sometimes different parameters and workflows, [this](https://twitter-api.readthedocs.io/en/latest/index.html) could be a good reference though. Some pleroma-exclusive API may still be using QvitterAPI JSON structure.
In 99% cases PleromaFE uses [MastoAPI](https://docs.joinmastodon.org/api/) with [Pleroma Extensions](https://docs.akkoma.dev/stable/differences_in_mastoapi_responses.md) to fetch the data. The rest is either QvitterAPI leftovers or pleroma-exclusive APIs. QvitterAPI doesn't exactly have documentation and uses different JSON structure and sometimes different parameters and workflows, [this](https://twitter-api.readthedocs.io/en/latest/index.html) could be a good reference though. Some pleroma-exclusive API may still be using QvitterAPI JSON structure.
PleromaFE supports both formats by transforming them into internal format which is basically QvitterAPI one with some additions and renaming. All data is passed trough [Entity Normalizer](https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/src/services/entity_normalizer/entity_normalizer.service.js) which can serve as a reference of API and what's actually used, it's also a host for all the hacks and data transformation.

View file

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 491 B

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

28
docs/docs/css/extra.css Normal file
View file

@ -0,0 +1,28 @@
p, a, li, pre {
font-family: "Tiresias PCFont", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
}
code,
.codehilite pre {
font-weight: bold;
}
:root > * {
--md-primary-fg-color: #593196;
--md-accent-fg-color: #455a63;
}
@font-face {
font-family: 'Tiresias PCFont';
font-style: normal;
font-weight: 400;
src: local('Tiresias PCFont'), local('Tiresias PCFont'),
url('./fonts/Tiresias_PCfont.ttf') format('truetype')
}
@font-face {
font-family: 'Tiresias Infofont';
font-style: normal;
font-weight: 400;
src: local('Tiresias Infofont'), local('Tiresias Infofont'),
url('./fonts/Tiresias_Infofont.ttf') format('truetype')
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 B

View file

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg4485"
width="512"
height="512"
viewBox="0 0 512 512"
sodipodi:docname="pleroma_logo_vector_nobg.svg"
inkscape:version="0.92.1 r15371">
<metadata
id="metadata4491">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4489" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1680"
inkscape:window-height="997"
id="namedview4487"
showgrid="false"
inkscape:zoom="0.70710678"
inkscape:cx="26.131594"
inkscape:cy="235.37499"
inkscape:window-x="1912"
inkscape:window-y="22"
inkscape:window-maximized="1"
inkscape:current-layer="svg4485" />
<g
id="g4612">
<path
sodipodi:nodetypes="cccccc"
inkscape:connector-curvature="0"
id="path4495"
d="M 235,89 V 423 H 152 V 115 l 26,-26 z"
style="opacity:1;fill:#fba457;fill-opacity:1;stroke:#009bff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.17587938" />
<circle
r="26"
cx="178"
cy="115"
id="path4497"
style="opacity:1;fill:#fba457;fill-opacity:1;stroke:#009bff;stroke-width:0;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.17587938" />
<circle
r="26"
cx="335"
cy="230"
id="path4497-0"
style="opacity:1;fill:#fba457;fill-opacity:1;stroke:#009bff;stroke-width:0;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.17587938" />
<path
sodipodi:nodetypes="cccccc"
inkscape:connector-curvature="0"
id="path4516"
d="M 277,256 V 89 l 84,3e-6 L 361.00002,230 335,256 Z"
style="fill:#fba457;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<circle
r="26"
cx="335"
cy="397"
id="path4497-0-6"
style="opacity:1;fill:#fba457;fill-opacity:1;stroke:#009bff;stroke-width:0;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.17587938" />
<path
sodipodi:nodetypes="cccccc"
inkscape:connector-curvature="0"
id="path4516-5"
d="m 277,423 v -83 h 84 l 2e-5,57 L 335,423 Z"
style="opacity:1;fill:#fba457;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

53
docs/docs/stickers.md Normal file
View file

@ -0,0 +1,53 @@
# Adding stickers
Pleroma-fe supports stickers, which are essentially little images stored server-side
which can be selected by a user to automatically attach them to a post.
There's no explicit setting for these, they just rely on the existence of certain files.
## Initialising the sticker config file
You're probably serving pleroma-fe from your instance's `instance/static/` directory -
this directy can also override files served at a given path.
The first thing we need to do is set up our `stickers.json` file. At `instance/static/static/stickers.json`,
put a file that looks like this
```json
{
"myPack": "/static/stickers/myPack"
}
```
This file is a mapping from name to pack directory location. It says "we have a pack called myPack, look for
it at `/static/stickers/myPack`". You can add as many packs as you like in this manner.
## Creating the pack
First, create your pack directory
```bash
mkdir -p instance/static/static/stickers/myPack
```
Now you need to give it some config.
At `instance/static/static/stickers/myPack/pack.json`, put a file that looks like:
```json
{
"title": "myPack",
"author": "me for i am very cool",
"tabIcon": "tab.png",
"stickers": [
"mySticker.png"
]
}
```
This should be relatively self-explanatory, it declares a pack with a title `myPack` which has only one sticker in it.
The `tabIcon` will appear on the sticker picker itself as a representative of the pack.
You can add as many stickers as you like. They should all be in the same directory as your `pack.json`.
Now you should find that there's a sticky note icon on the emoji picker on pleroma-fe that allows you to attach stickers.

View file

@ -65,6 +65,16 @@ If you set the input-method to Markdown, and post this, it will look something l
![example_markdown](../assets/example_markdown.png)
## Misskey markdown
The akkoma version of pleroma-fe includes support for writing and rendering
misskey markdown (MFM). To write this you will need to select "MFM" from
the content type dropdown (if supported), and then you can format text
[in MFM](https://akkoma.dev/sfr/marked-mfm/src/branch/master/docs/syntax.md).
To render MFM, you will need to turn on "Render Misskey Markdown" in
the settings modal.
## Other actions
In addition to posting you can also *favorite* posts also known as *liking* them and *repeat* posts (also known as *retweeting*, *boosting* and even *reprööting*). Favoriting a post increments a counter on it, notifies the post author of your affection towards that post and also adds that post to your "favorited" posts list (in your own profile, "Favorites" tab). Reprööting a post does all that and also repeats this post to your followers and your profile page with a note "*user* repeated post".

37
docs/mkdocs.yml Normal file
View file

@ -0,0 +1,37 @@
site_name: Pleroma-FE Documentation
theme:
favicon: 'images/pleroma_logo_vector_bg_32.png'
name: 'material'
custom_dir: 'theme'
# Disable google fonts
font: false
logo: 'images/pleroma_logo_vector_nobg.svg'
features:
- tabs
palette:
primary: 'deep purple'
accent: 'blue grey'
extra_css:
- css/extra.css
repo_name: 'AkkomaGang/pleroma-fe'
repo_url: 'https://akkoma.dev/AkkomaGang/pleroma-fe'
extra:
repo_icon: gitea
markdown_extensions:
# Note/warning blocks https://squidfunk.github.io/mkdocs-material/extensions/admonition/
- admonition
- codehilite:
guess_lang: false
# Make it possible to link to every header https://squidfunk.github.io/mkdocs-material/extensions/permalinks/
- toc:
permalink: true
- pymdownx.tasklist:
custom_checkbox: true
- pymdownx.superfences
- pymdownx.tabbed
- pymdownx.details
- markdown_include.include:
base_path: docs

22
docs/requirements.txt Normal file
View file

@ -0,0 +1,22 @@
click==8.1.3
ghp-import==2.1.0
importlib-metadata==4.12.0
Jinja2==3.1.2
Markdown==3.3.7
markdown-include==0.6.0
MarkupSafe==2.1.1
mergedeep==1.3.4
mkdocs==1.3.0
mkdocs-bootswatch==1.1
mkdocs-material==8.1.8
mkdocs-material-extensions==1.0.3
packaging==21.3
Pygments==2.11.2
pymdown-extensions==9.1
pyparsing==3.0.9
python-dateutil==2.8.2
PyYAML==6.0
pyyaml_env_tag==0.1
six==1.16.0
watchdog==2.1.9
zipp==3.8.0

64
docs/theme/partials/source.html vendored Normal file
View file

@ -0,0 +1,64 @@
<!--
Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
-->
{% import "partials/language.html" as lang with context %}
<!--
Check whether the repository is hosted on one of the supported code hosting
platforms (GitHub, GitLab or Bitbucket) to show icon.
-->
{% set platform = config.extra.repo_icon or config.repo_url %}
{% if "github" in platform %}
{% set repo_type = "github" %}
{% elif "gitlab" in platform %}
{% set repo_type = "gitlab" %}
{% elif "bitbucket" in platform %}
{% set repo_type = "bitbucket" %}
{% else %}
{% set repo_type = "" %}
{% endif %}
{% if page and page.url.startswith('backend') %}
{% set repo_url = "https://git.pleroma.social/pleroma/pleroma" %}
{% set repo_name = "pleroma/pleroma" %}
{% elif page and page.url.startswith('frontend') %}
{% set repo_url = "https://git.pleroma.social/pleroma/pleroma-fe" %}
{% set repo_name = "pleroma/pleroma-fe" %}
{% else %}
{% set repo_url = config.repo_url %}
{% set repo_name = config.repo_name %}
{% endif %}
<!-- Repository containing source -->
<a href="{{ repo_url }}" title="{{ lang.t('source.link.title') }}"
class="md-source" data-md-source="{{ repo_type }}">
{% if repo_type %}
<div class="md-source__icon">
<svg viewBox="0 0 24 24" width="24" height="24">
<use xlink:href="#__{{ repo_type }}" width="24" height="24"></use>
</svg>
</div>
{% endif %}
<div class="md-source__repository">
{{ repo_name }}
</div>
</a>

View file

@ -15,6 +15,7 @@
<body class="hidden">
<noscript>To use Pleroma, please enable JavaScript.</noscript>
<div id="app"></div>
<div id="modal"></div>
<!-- built files will be auto injected -->
</body>
</html>

View file

@ -19,12 +19,12 @@
"@babel/runtime": "7.17.8",
"@chenfengyuan/vue-qrcode": "2.0.0",
"@fortawesome/fontawesome-svg-core": "1.3.0",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "^6.1.2",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/vue-fontawesome": "3.0.0-5",
"@fortawesome/vue-fontawesome": "3.0.1",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
"@vuelidate/core": "2.0.0-alpha.41",
"@vuelidate/validators": "2.0.0-alpha.27",
"@vuelidate/core": "2.0.0-alpha.42",
"@vuelidate/validators": "2.0.0-alpha.30",
"body-scroll-lock": "2.7.1",
"chromatism": "3.0.0",
"click-outside-vue3": "4.0.1",
@ -33,14 +33,15 @@
"escape-html": "1.0.3",
"js-cookie": "^3.0.1",
"localforage": "1.10.0",
"mfm-js": "^0.22.1",
"marked": "^4.0.17",
"marked-mfm": "^0.5.0",
"parse-link-header": "1.0.1",
"phoenix": "1.6.2",
"punycode.js": "2.1.0",
"qrcode": "1",
"ruffle-mirror": "2021.12.31",
"vue": "^3.2.31",
"vue-i18n": "^9.2.0-beta.34",
"vue-i18n": "^9.2.2",
"vue-router": "4.0.14",
"vue-template-compiler": "2.6.11",
"vuex": "4.0.2"
@ -55,7 +56,7 @@
"@vue/babel-helper-vue-jsx-merge-props": "1.2.1",
"@vue/babel-plugin-jsx": "1.1.1",
"@vue/compiler-sfc": "^3.1.0",
"@vue/test-utils": "2.0.0-rc.17",
"@vue/test-utils": "^2.0.2",
"autoprefixer": "6.7.7",
"babel-eslint": "7.2.3",
"babel-loader": "8.2.4",
@ -84,7 +85,7 @@
"html-webpack-plugin": "3.2.0",
"http-proxy-middleware": "0.21.0",
"inject-loader": "2.0.1",
"iso-639-1": "2.1.13",
"iso-639-1": "2.1.15",
"isparta-loader": "2.0.0",
"json-loader": "0.5.7",
"karma": "6.3.17",
@ -105,8 +106,8 @@
"ora": "0.4.1",
"postcss-loader": "3.0.0",
"raw-loader": "0.5.1",
"sass": "1.20.1",
"sass-loader": "7.2.0",
"sass": "1.53.0",
"sass-loader": "7.3.1",
"selenium-server": "2.53.1",
"semver": "5.7.1",
"serviceworker-webpack-plugin": "1.0.1",
@ -121,7 +122,7 @@
"vue-style-loader": "4.1.2",
"webpack": "4.46.0",
"webpack-dev-middleware": "3.7.3",
"webpack-hot-middleware": "2.24.3",
"webpack-hot-middleware": "2.25.1",
"webpack-merge": "0.20.0"
},
"engines": {

View file

@ -3,7 +3,6 @@ import NavPanel from './components/nav_panel/nav_panel.vue'
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
import ShoutPanel from './components/shout_panel/shout_panel.vue'
import SettingsModal from './components/settings_modal/settings_modal.vue'
import MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
@ -11,7 +10,9 @@ import MobilePostStatusButton from './components/mobile_post_status_button/mobil
import MobileNav from './components/mobile_nav/mobile_nav.vue'
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
import { mapGetters } from 'vuex'
@ -26,7 +27,6 @@ export default {
InstanceSpecificPanel,
FeaturesPanel,
WhoToFollowPanel,
ShoutPanel,
MediaModal,
SideDrawer,
MobilePostStatusButton,
@ -35,6 +35,8 @@ export default {
SettingsModal,
UserReportingModal,
PostStatusModal,
EditStatusModal,
StatusHistoryModal,
GlobalNoticeList
},
data: () => ({
@ -75,27 +77,17 @@ export default {
}
}
},
shout () { return this.$store.state.shout.joined },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
!this.$store.getters.mergedConfig.hideISP &&
this.$store.state.instance.instanceSpecificPanelContent
},
isChats () {
return this.$route.name === 'chat' || this.$route.name === 'chats'
},
newPostButtonShown () {
if (this.isChats) return false
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
shoutboxPosition () {
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false
},
hideShoutbox () {
return this.$store.getters.mergedConfig.hideShoutbox
},
editingAvailable () { return this.$store.state.instance.editingAvailable },
layoutType () { return this.$store.state.interface.layoutType },
privateMode () { return this.$store.state.instance.private },
reverseLayout () {

View file

@ -310,7 +310,6 @@ nav {
border-top-right-radius: 0;
}
.underlay,
#sidebar,
#notifs-column {
display: none;

View file

@ -33,7 +33,7 @@
<div
id="main-scroller"
class="column main"
:class="{ '-full-height': isChats }"
:class="{ '-full-height': false }"
>
<div
v-if="!currentUser"
@ -55,17 +55,13 @@
/>
</div>
<media-modal />
<shout-panel
v-if="currentUser && shout && !hideShoutbox"
:floating="true"
class="floating-shout mobile-hidden"
:class="{ '-left': shoutboxPosition }"
/>
<MobilePostStatusButton />
<UserReportingModal />
<PostStatusModal />
<EditStatusModal v-if="editingAvailable" />
<StatusHistoryModal v-if="editingAvailable" />
<SettingsModal />
<div id="modal" />
<UpdateNotification />
<GlobalNoticeList />
</div>
</template>

View file

@ -27,7 +27,6 @@ $fallback--tooltipRadius: 5px;
$fallback--avatarRadius: 4px;
$fallback--avatarAltRadius: 10px;
$fallback--attachmentRadius: 10px;
$fallback--chatMessageRadius: 10px;
$fallback--buttonShadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;

View file

@ -1,3 +1,4 @@
import Cookies from 'js-cookie'
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import vClickOutside from 'click-outside-vue3'
@ -48,6 +49,20 @@ const preloadFetch = async (request) => {
}
}
const resolveLanguage = (instanceLanguages) => {
// First language in navigator.languages that is listed as an instance language
// falls back to first instance language
const navigatorLanguages = navigator.languages.map((x) => x.split('-')[0])
for (const navLanguage of navigatorLanguages) {
if (instanceLanguages.includes(navLanguage)) {
return navLanguage
}
}
return instanceLanguages[0]
}
const getInstanceConfig = async ({ store }) => {
try {
const res = await preloadFetch('/api/v1/instance')
@ -58,6 +73,10 @@ const getInstanceConfig = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
// don't override cookie if set
if (!Cookies.get('userLanguage')) {
store.dispatch('setOption', { name: 'interfaceLanguage', value: resolveLanguage(data.languages) })
}
if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
@ -124,6 +143,11 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('hideBotIndication')
copyInstanceOption('hideUserStats')
copyInstanceOption('hideFilteredStatuses')
copyInstanceOption('hideSiteName')
copyInstanceOption('hideSiteFavicon')
copyInstanceOption('showWiderShortcuts')
copyInstanceOption('showNavShortcuts')
copyInstanceOption('showPanelNavShortcuts')
copyInstanceOption('logo')
store.dispatch('setInstanceOption', {
@ -154,6 +178,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('alwaysShowSubjectInput')
copyInstanceOption('showFeaturesPanel')
copyInstanceOption('hideSitename')
copyInstanceOption('renderMisskeyMarkdown')
copyInstanceOption('sidebarRight')
return store.dispatch('setTheme', config['theme'])
@ -247,12 +272,11 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
store.dispatch('setInstanceOption', { name: 'translationEnabled', value: features.includes('akkoma:machine_translation') })
const uploadLimits = metadata.uploadLimits
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
@ -288,6 +312,7 @@ const getNodeInfo = async ({ store }) => {
})
store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
store.dispatch('setInstanceOption', { name: 'localBubbleInstances', value: metadata.localBubbleInstances })
store.dispatch('setInstanceOption', {
name: 'federating',
value: typeof federation.enabled === 'undefined'
@ -370,16 +395,19 @@ const afterStoreSetup = async ({ store, i18n }) => {
// Start fetching things that don't need to block the UI
store.dispatch('fetchMutes')
store.dispatch('startFetchingAnnouncements')
getTOS({ store })
getStickers({ store })
store.dispatch('getSupportedTranslationlanguages')
const router = createRouter({
history: createWebHistory(),
routes: routes(store),
scrollBehavior: (to, _from, savedPosition) => {
if (to.matched.some(m => m.meta.dontScroll)) {
return false
return {}
}
return savedPosition || { left: 0, top: 0 }
}
})

View file

@ -2,12 +2,11 @@ import PublicTimeline from 'components/public_timeline/public_timeline.vue'
import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue'
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue'
import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue'
import ConversationPage from 'components/conversation-page/conversation-page.vue'
import Interactions from 'components/interactions/interactions.vue'
import DMs from 'components/dm_timeline/dm_timeline.vue'
import ChatList from 'components/chat_list/chat_list.vue'
import Chat from 'components/chat/chat.vue'
import UserProfile from 'components/user_profile/user_profile.vue'
import Search from 'components/search/search.vue'
import Registration from 'components/registration/registration.vue'
@ -16,10 +15,13 @@ import FollowRequests from 'components/follow_requests/follow_requests.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
import Notifications from 'components/notifications/notifications.vue'
import AuthForm from 'components/auth_form/auth_form.js'
import ShoutPanel from 'components/shout_panel/shout_panel.vue'
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
import About from 'components/about/about.vue'
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
import Lists from 'components/lists/lists.vue'
import ListTimeline from 'components/list_timeline/list_timeline.vue'
import ListEdit from 'components/list_edit/list_edit.vue'
import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
export default (store) => {
const validateAuthenticatedRoute = (to, from, next) => {
@ -41,6 +43,7 @@ export default (store) => {
},
{ name: 'public-external-timeline', path: '/main/all', component: PublicAndExternalTimeline },
{ name: 'public-timeline', path: '/main/public', component: PublicTimeline },
{ name: 'bubble-timeline', path: '/main/bubble', component: BubbleTimeline },
{ name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
@ -55,7 +58,7 @@ export default (store) => {
component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute
},
{ name: 'external-user-profile', path: '/users/:id', component: UserProfile },
{ name: 'external-user-profile', path: '/users/:id', component: UserProfile, meta: { dontScroll: true } },
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'registration', path: '/registration', component: Registration },
@ -64,20 +67,16 @@ export default (store) => {
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
{ name: 'notifications', path: '/:username/notifications', component: Notifications, props: () => ({ disableTeleport: true }), beforeEnter: validateAuthenticatedRoute },
{ name: 'login', path: '/login', component: AuthForm },
{ name: 'shout-panel', path: '/shout-panel', component: ShoutPanel, props: () => ({ floating: false }) },
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
{ name: 'about', path: '/about', component: About },
{ name: 'user-profile', path: '/:_(users)?/:name', component: UserProfile }
{ name: 'lists', path: '/lists', component: Lists },
{ name: 'list-timeline', path: '/lists/:id', component: ListTimeline },
{ name: 'list-edit', path: '/lists/:id/edit', component: ListEdit },
{ name: 'announcements', path: '/announcements', component: AnnouncementsPage },
{ name: 'user-profile', path: '/:_(users)?/:name', component: UserProfile, meta: { dontScroll: true } }
]
if (store.state.instance.pleromaChatMessagesAvailable) {
routes = routes.concat([
{ name: 'chat', path: '/users/:username/chats/:recipient_id', component: Chat, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute },
{ name: 'chats', path: '/users/:username/chats', component: ChatList, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute }
])
}
return routes
}

View file

@ -3,6 +3,7 @@ import FeaturesPanel from '../features_panel/features_panel.vue'
import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue'
import StaffPanel from '../staff_panel/staff_panel.vue'
import MRFTransparencyPanel from '../mrf_transparency_panel/mrf_transparency_panel.vue'
import LocalBubblePanel from '../local_bubble_panel/local_bubble_panel.vue'
const About = {
components: {
@ -10,7 +11,8 @@ const About = {
FeaturesPanel,
TermsOfServicePanel,
StaffPanel,
MRFTransparencyPanel
MRFTransparencyPanel,
LocalBubblePanel
},
computed: {
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },

View file

@ -3,6 +3,7 @@
<instance-specific-panel v-if="showInstanceSpecificPanel" />
<staff-panel />
<terms-of-service-panel />
<LocalBubblePanel />
<MRFTransparencyPanel />
<features-panel v-if="showFeaturesPanel" />
</div>

View file

@ -1,7 +1,8 @@
import { mapState } from 'vuex'
import ProgressButton from '../progress_button/progress_button.vue'
import Popover from '../popover/popover.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { mapState } from 'vuex'
import {
faEllipsisV
} from '@fortawesome/free-solid-svg-icons'
@ -15,13 +16,22 @@ const AccountActions = {
'user', 'relationship'
],
data () {
return { }
return {
showingConfirmBlock: false
}
},
components: {
ProgressButton,
Popover
Popover,
ConfirmModal
},
methods: {
showConfirmBlock () {
this.showingConfirmBlock = true
},
hideConfirmBlock () {
this.showingConfirmBlock = false
},
showRepeats () {
this.$store.dispatch('showReblogs', this.user.id)
},
@ -29,22 +39,27 @@ const AccountActions = {
this.$store.dispatch('hideReblogs', this.user.id)
},
blockUser () {
if (!this.shouldConfirmBlock) {
this.doBlockUser()
} else {
this.showConfirmBlock()
}
},
doBlockUser () {
this.$store.dispatch('blockUser', this.user.id)
this.hideConfirmBlock()
},
unblockUser () {
this.$store.dispatch('unblockUser', this.user.id)
},
reportUser () {
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
},
openChat () {
this.$router.push({
name: 'chat',
params: { username: this.$store.state.users.currentUser.screen_name, recipient_id: this.user.id }
})
}
},
computed: {
shouldConfirmBlock () {
return this.$store.getters.mergedConfig.modalOnBlock
},
...mapState({
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
})

View file

@ -48,13 +48,6 @@
>
{{ $t('user_card.report') }}
</button>
<button
v-if="pleromaChatMessagesAvailable"
class="btn button-default btn-block dropdown-item"
@click="openChat"
>
{{ $t('user_card.message') }}
</button>
</div>
</template>
<template v-slot:trigger>
@ -66,6 +59,27 @@
</button>
</template>
</Popover>
<teleport to="#modal">
<confirm-modal
v-if="showingConfirmBlock"
:title="$t('user_card.block_confirm_title')"
:confirm-text="$t('user_card.block_confirm_accept_button')"
:cancel-text="$t('user_card.block_confirm_cancel_button')"
@accepted="doBlockUser"
@cancelled="hideConfirmBlock"
>
<i18n-t
keypath="user_card.block_confirm"
tag="span"
>
<template v-slot:user>
<span
v-text="user.screen_name_ui"
/>
</template>
</i18n-t>
</confirm-modal>
</teleport>
</div>
</template>

View file

@ -0,0 +1,105 @@
import { mapState } from 'vuex'
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
import RichContent from '../rich_content/rich_content.jsx'
import localeService from '../../services/locale/locale.service.js'
const Announcement = {
components: {
AnnouncementEditor,
RichContent
},
data () {
return {
editing: false,
editedAnnouncement: {
content: '',
startsAt: undefined,
endsAt: undefined,
allDay: undefined
},
editError: ''
}
},
props: {
announcement: Object
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
}),
content () {
return this.announcement.content
},
isRead () {
return this.announcement.read
},
publishedAt () {
const time = this.announcement['published_at']
if (!time) {
return
}
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
},
startsAt () {
const time = this.announcement['starts_at']
if (!time) {
return
}
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
},
endsAt () {
const time = this.announcement['ends_at']
if (!time) {
return
}
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
},
inactive () {
return this.announcement.inactive
}
},
methods: {
markAsRead () {
if (!this.isRead) {
return this.$store.dispatch('markAnnouncementAsRead', this.announcement.id)
}
},
deleteAnnouncement () {
return this.$store.dispatch('deleteAnnouncement', this.announcement.id)
},
formatTimeOrDate (time, locale) {
const d = new Date(time)
return this.announcement['all_day'] ? d.toLocaleDateString(locale) : d.toLocaleString(locale)
},
enterEditMode () {
this.editedAnnouncement.content = this.announcement.pleroma['raw_content']
this.editedAnnouncement.startsAt = this.announcement['starts_at']
this.editedAnnouncement.endsAt = this.announcement['ends_at']
this.editedAnnouncement.allDay = this.announcement['all_day']
this.editing = true
},
submitEdit () {
this.$store.dispatch('editAnnouncement', {
id: this.announcement.id,
...this.editedAnnouncement
})
.then(() => {
this.editing = false
})
.catch(error => {
this.editError = error.error
})
},
cancelEdit () {
this.editing = false
},
clearError () {
this.editError = undefined
}
}
}
export default Announcement

View file

@ -0,0 +1,136 @@
<template>
<div class="announcement">
<div class="heading">
<h4>{{ $t('announcements.title') }}</h4>
</div>
<div class="body">
<rich-content
v-if="!editing"
:html="content"
:emoji="announcement.emojis"
:handle-links="true"
/>
<announcement-editor
v-else
:announcement="editedAnnouncement"
/>
</div>
<div class="footer">
<div
v-if="!editing"
class="times"
>
<span v-if="publishedAt">
{{ $t('announcements.published_time_display', { time: publishedAt }) }}
</span>
<span v-if="startsAt">
{{ $t('announcements.start_time_display', { time: startsAt }) }}
</span>
<span v-if="endsAt">
{{ $t('announcements.end_time_display', { time: endsAt }) }}
</span>
</div>
<div
v-if="!editing"
class="actions"
>
<button
v-if="currentUser"
class="btn button-default"
:class="{ toggled: isRead }"
:disabled="inactive"
:title="inactive ? $t('announcements.inactive_message') : ''"
@click="markAsRead"
>
{{ $t('announcements.mark_as_read_action') }}
</button>
<button
v-if="currentUser && currentUser.role === 'admin'"
class="btn button-default"
@click="enterEditMode"
>
{{ $t('announcements.edit_action') }}
</button>
<button
v-if="currentUser && currentUser.role === 'admin'"
class="btn button-default"
@click="deleteAnnouncement"
>
{{ $t('announcements.delete_action') }}
</button>
</div>
<div
v-else
class="actions"
>
<button
class="btn button-default"
@click="submitEdit"
>
{{ $t('announcements.submit_edit_action') }}
</button>
<button
class="btn button-default"
@click="cancelEdit"
>
{{ $t('announcements.cancel_edit_action') }}
</button>
<div
v-if="editing && editError"
class="alert error"
>
{{ $t('announcements.edit_error', { error }) }}
<button
class="button-unstyled"
@click="clearError"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
:title="$t('announcements.close_error')"
/>
</button>
</div>
</div>
</div>
</div>
</template>
<script src="./announcement.js"></script>
<style lang="scss">
@import "../../variables";
.announcement {
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: var(--border, $fallback--border);
border-radius: 0;
padding: var(--status-margin, $status-margin);
.heading, .body {
margin-bottom: var(--status-margin, $status-margin);
}
.footer {
display: flex;
flex-direction: column;
.times {
display: flex;
flex-direction: column;
}
}
.footer .actions {
display: flex;
flex-direction: row;
justify-content: space-evenly;
.btn {
flex: 1;
margin: 1em;
max-width: 10em;
}
}
}
</style>

View file

@ -0,0 +1,13 @@
import Checkbox from '../checkbox/checkbox.vue'
const AnnouncementEditor = {
components: {
Checkbox
},
props: {
announcement: Object,
disabled: Boolean
}
}
export default AnnouncementEditor

View file

@ -0,0 +1,60 @@
<template>
<div class="announcement-editor">
<textarea
ref="textarea"
v-model="announcement.content"
class="post-textarea"
rows="1"
cols="1"
:placeholder="$t('announcements.post_placeholder')"
:disabled="disabled"
/>
<span class="announcement-metadata">
<label for="announcement-start-time">{{ $t('announcements.start_time_prompt') }}</label>
<input
id="announcement-start-time"
v-model="announcement.startsAt"
:type="announcement.allDay ? 'date' : 'datetime-local'"
:disabled="disabled"
>
</span>
<span class="announcement-metadata">
<label for="announcement-end-time">{{ $t('announcements.end_time_prompt') }}</label>
<input
id="announcement-end-time"
v-model="announcement.endsAt"
:type="announcement.allDay ? 'date' : 'datetime-local'"
:disabled="disabled"
>
</span>
<span class="announcement-metadata">
<Checkbox
id="announcement-all-day"
v-model="announcement.allDay"
:disabled="disabled"
/>
<label for="announcement-all-day">{{ $t('announcements.all_day_prompt') }}</label>
</span>
</div>
</template>
<script src="./announcement_editor.js"></script>
<style lang="scss">
.announcement-editor {
display: flex;
align-items: stretch;
flex-direction: column;
.announcement-metadata {
margin-top: 0.5em;
}
.post-textarea {
resize: vertical;
height: 10em;
overflow: none;
box-sizing: content-box;
}
}
</style>

View file

@ -0,0 +1,55 @@
import { mapState } from 'vuex'
import Announcement from '../announcement/announcement.vue'
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
const AnnouncementsPage = {
components: {
Announcement,
AnnouncementEditor
},
data () {
return {
newAnnouncement: {
content: '',
startsAt: undefined,
endsAt: undefined,
allDay: false
},
posting: false,
error: undefined
}
},
mounted () {
this.$store.dispatch('fetchAnnouncements')
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
}),
announcements () {
return this.$store.state.announcements.announcements
}
},
methods: {
postAnnouncement () {
this.posting = true
this.$store.dispatch('postAnnouncement', this.newAnnouncement)
.then(() => {
this.newAnnouncement.content = ''
this.startsAt = undefined
this.endsAt = undefined
})
.catch(error => {
this.error = error.error
})
.finally(() => {
this.posting = false
})
},
clearError () {
this.error = undefined
}
}
}
export default AnnouncementsPage

View file

@ -0,0 +1,79 @@
<template>
<div class="panel panel-default announcements-page">
<div class="panel-heading">
<div class="title">
{{ $t('announcements.page_header') }}
</div>
</div>
<div class="panel-body">
<section
v-if="currentUser && currentUser.role === 'admin'"
>
<div class="post-form">
<div class="heading">
<h4>{{ $t('announcements.post_form_header') }}</h4>
</div>
<div class="body">
<announcement-editor
:announcement="newAnnouncement"
:disabled="posting"
/>
</div>
<div class="footer">
<button
class="btn button-default post-button"
:disabled="posting"
@click.prevent="postAnnouncement"
>
{{ $t('announcements.post_action') }}
</button>
<div
v-if="error"
class="alert error"
>
{{ $t('announcements.post_error', { error }) }}
<button
class="button-unstyled"
@click="clearError"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
:title="$t('announcements.close_error')"
/>
</button>
</div>
</div>
</div>
</section>
<section
v-for="announcement in announcements"
:key="announcement.id"
>
<announcement
:announcement="announcement"
/>
</section>
</div>
</div>
</template>
<script src="./announcements_page.js"></script>
<style lang="scss">
@import "../../variables";
.announcements-page {
.post-form {
padding: var(--status-margin, $status-margin);
.heading, .body {
margin-bottom: var(--status-margin, $status-margin);
}
.post-button {
min-width: 10em;
}
}
}
</style>

View file

@ -126,9 +126,15 @@ const Attachment = {
videoTag () {
return this.useModal ? 'button' : 'span'
},
statusForm () {
return this.$parent.$parent
},
...mapGetters(['mergedConfig'])
},
watch: {
'attachment.description' (newVal) {
this.localDescription = newVal
},
localDescription (newVal) {
this.onEdit(newVal)
}
@ -199,6 +205,29 @@ const Attachment = {
const width = image.naturalWidth
const height = image.naturalHeight
this.$emit('naturalSizeLoad', { id: this.attachment.id, width, height })
},
resize (e) {
const target = e.target || e
if (!(target instanceof window.Element)) { return }
// Reset to default height for empty form, nothing else to do here.
if (target.value === '') {
target.style.height = null
this.$emit('resize')
return
}
const paddingString = getComputedStyle(target)['padding']
// remove -px suffix
const padding = Number(paddingString.substring(0, paddingString.length - 2))
target.style.height = 'auto'
const newHeight = Math.floor(target.scrollHeight - padding * 2)
target.style.height = `${newHeight}px`
this.$emit('resize', newHeight)
},
postStatus (event) {
console.log(this.statusForm.postStatus(event, this.statusForm.newStatus))
}
}
}

View file

@ -16,7 +16,7 @@
.attachment-wrapper {
flex: 1 1 auto;
height: 100%;
height: 200px;
position: relative;
overflow: hidden;
}
@ -33,7 +33,8 @@
line-height: 1.5;
padding: 0.5em;
margin: 0;
white-space: nowrap;
white-space: pre-line;
word-break: break-word;
text-overflow: ellipsis;
overflow: hidden;
}
@ -52,6 +53,19 @@
.description-field {
flex: 1;
min-width: 0;
box-sizing: content-box;
overflow: hidden;
transition: min-height 200ms 100ms;
padding-bottom: var(--_padding);
height: calc(var(--post-line-height) * 1em);
min-height: calc(var(--post-line-height) * 1em);
resize: none;
&.scrollable-form {
overflow-y: auto;
}
}
& .placeholder-container,

View file

@ -248,14 +248,16 @@
class="description-container"
:class="{ '-static': !edit }"
>
<input
<textarea
v-if="edit"
v-model="localDescription"
type="text"
class="description-field"
:placeholder="$t('post_status.media_description')"
@keydown.enter.prevent=""
>
rows="1"
cols="1"
@keydown.ctrl.enter="postStatus()"
@input="resize"
/>
<p v-else>
{{ localDescription }}
</p>

View file

@ -0,0 +1,18 @@
import Timeline from '../timeline/timeline.vue'
const PublicTimeline = {
components: {
Timeline
},
computed: {
timeline () { return this.$store.state.statuses.timelines.bubble }
},
created () {
this.$store.dispatch('startFetchingTimeline', { timeline: 'bubble' })
},
unmounted () {
this.$store.dispatch('stopFetchingTimeline', 'bubble')
}
}
export default PublicTimeline

View file

@ -0,0 +1,9 @@
<template>
<Timeline
:title="$t('nav.bubble_timeline')"
:timeline="timeline"
:timeline-name="'bubble'"
/>
</template>
<script src="./bubble_timeline.js"></script>

View file

@ -1,346 +0,0 @@
import _ from 'lodash'
import { WSConnectionStatus } from '../../services/api/api.service.js'
import { mapGetters, mapState } from 'vuex'
import ChatMessage from '../chat_message/chat_message.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue'
import ChatTitle from '../chat_title/chat_title.vue'
import chatService from '../../services/chat_service/chat_service.js'
import { promiseInterval } from '../../services/promise_interval/promise_interval.js'
import { getScrollPosition, getNewTopPosition, isBottomedOut, isScrollable } from './chat_layout_utils.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faChevronDown,
faChevronLeft
} from '@fortawesome/free-solid-svg-icons'
import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js'
library.add(
faChevronDown,
faChevronLeft
)
const BOTTOMED_OUT_OFFSET = 10
const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 10
const SAFE_RESIZE_TIME_OFFSET = 100
const MARK_AS_READ_DELAY = 1500
const MAX_RETRIES = 10
const Chat = {
components: {
ChatMessage,
ChatTitle,
PostStatusForm
},
data () {
return {
jumpToBottomButtonVisible: false,
hoveredMessageChainId: undefined,
lastScrollPosition: {},
scrollableContainerHeight: '100%',
errorLoadingChat: false,
messageRetriers: {}
}
},
created () {
this.startFetching()
window.addEventListener('resize', this.handleResize)
},
mounted () {
window.addEventListener('scroll', this.handleScroll)
if (typeof document.hidden !== 'undefined') {
document.addEventListener('visibilitychange', this.handleVisibilityChange, false)
}
this.$nextTick(() => {
this.handleResize()
})
},
unmounted () {
window.removeEventListener('scroll', this.handleScroll)
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
this.$store.dispatch('clearCurrentChat')
},
computed: {
recipient () {
return this.currentChat && this.currentChat.account
},
recipientId () {
return this.$route.params.recipient_id
},
formPlaceholder () {
if (this.recipient) {
return this.$t('chats.message_user', { nickname: this.recipient.screen_name_ui })
} else {
return ''
}
},
chatViewItems () {
return chatService.getView(this.currentChatMessageService)
},
newMessageCount () {
return this.currentChatMessageService && this.currentChatMessageService.newMessageCount
},
streamingEnabled () {
return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED
},
...mapGetters([
'currentChat',
'currentChatMessageService',
'findOpenedChatByRecipientId',
'mergedConfig'
]),
...mapState({
backendInteractor: state => state.api.backendInteractor,
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus,
mobileLayout: state => state.interface.layoutType === 'mobile',
currentUser: state => state.users.currentUser
})
},
watch: {
chatViewItems () {
// We don't want to scroll to the bottom on a new message when the user is viewing older messages.
// Therefore we need to know whether the scroll position was at the bottom before the DOM update.
const bottomedOutBeforeUpdate = this.bottomedOut(BOTTOMED_OUT_OFFSET)
this.$nextTick(() => {
if (bottomedOutBeforeUpdate) {
this.scrollDown()
}
})
},
'$route': function () {
this.startFetching()
},
mastoUserSocketStatus (newValue) {
if (newValue === WSConnectionStatus.JOINED) {
this.fetchChat({ isFirstFetch: true })
}
}
},
methods: {
// Used to animate the avatar near the first message of the message chain when any message belonging to the chain is hovered
onMessageHover ({ isHovered, messageChainId }) {
this.hoveredMessageChainId = isHovered ? messageChainId : undefined
},
onFilesDropped () {
this.$nextTick(() => {
this.handleResize()
})
},
handleVisibilityChange () {
this.$nextTick(() => {
if (!document.hidden && this.bottomedOut(BOTTOMED_OUT_OFFSET)) {
this.scrollDown({ forceRead: true })
}
})
},
// "Sticks" scroll to bottom instead of top, helps with OSK resizing the viewport
handleResize (opts = {}) {
const { expand = false, delayed = false } = opts
if (delayed) {
setTimeout(() => {
this.handleResize({ ...opts, delayed: false })
}, SAFE_RESIZE_TIME_OFFSET)
return
}
this.$nextTick(() => {
const { offsetHeight = undefined } = getScrollPosition()
const diff = this.lastScrollPosition.offsetHeight - offsetHeight
if (diff !== 0 || (!this.bottomedOut() && expand)) {
this.$nextTick(() => {
window.scrollTo({ top: window.scrollY + diff })
})
}
this.lastScrollPosition = getScrollPosition()
})
},
scrollDown (options = {}) {
const { behavior = 'auto', forceRead = false } = options
this.$nextTick(() => {
window.scrollTo({ top: document.documentElement.scrollHeight, behavior })
})
if (forceRead) {
this.readChat()
}
},
readChat () {
if (!(this.currentChatMessageService && this.currentChatMessageService.maxId)) { return }
if (document.hidden) { return }
const lastReadId = this.currentChatMessageService.maxId
this.$store.dispatch('readChat', {
id: this.currentChat.id,
lastReadId
})
},
bottomedOut (offset) {
return isBottomedOut(offset)
},
reachedTop () {
return window.scrollY <= 0
},
cullOlderCheck () {
window.setTimeout(() => {
if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
this.$store.dispatch('cullOlderMessages', this.currentChatMessageService.chatId)
}
}, 5000)
},
handleScroll: _.throttle(function () {
if (!this.currentChat) { return }
if (this.reachedTop()) {
this.fetchChat({ maxId: this.currentChatMessageService.minId })
} else if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
this.jumpToBottomButtonVisible = false
this.cullOlderCheck()
if (this.newMessageCount > 0) {
// Use a delay before marking as read to prevent situation where new messages
// arrive just as you're leaving the view and messages that you didn't actually
// get to see get marked as read.
window.setTimeout(() => {
// Don't mark as read if the element doesn't exist, user has left chat view
if (this.$el) this.readChat()
}, MARK_AS_READ_DELAY)
}
} else {
this.jumpToBottomButtonVisible = true
}
}, 200),
handleScrollUp (positionBeforeLoading) {
const positionAfterLoading = getScrollPosition()
window.scrollTo({
top: getNewTopPosition(positionBeforeLoading, positionAfterLoading)
})
},
fetchChat ({ isFirstFetch = false, fetchLatest = false, maxId }) {
const chatMessageService = this.currentChatMessageService
if (!chatMessageService) { return }
if (fetchLatest && this.streamingEnabled) { return }
const chatId = chatMessageService.chatId
const fetchOlderMessages = !!maxId
const sinceId = fetchLatest && chatMessageService.maxId
return this.backendInteractor.chatMessages({ id: chatId, maxId, sinceId })
.then((messages) => {
// Clear the current chat in case we're recovering from a ws connection loss.
if (isFirstFetch) {
chatService.clear(chatMessageService)
}
const positionBeforeUpdate = getScrollPosition()
this.$store.dispatch('addChatMessages', { chatId, messages }).then(() => {
this.$nextTick(() => {
if (fetchOlderMessages) {
this.handleScrollUp(positionBeforeUpdate)
}
// In vertical screens, the first batch of fetched messages may not always take the
// full height of the scrollable container.
// If this is the case, we want to fetch the messages until the scrollable container
// is fully populated so that the user has the ability to scroll up and load the history.
if (!isScrollable() && messages.length > 0) {
this.fetchChat({ maxId: this.currentChatMessageService.minId })
}
})
})
})
},
async startFetching () {
let chat = this.findOpenedChatByRecipientId(this.recipientId)
if (!chat) {
try {
chat = await this.backendInteractor.getOrCreateChat({ accountId: this.recipientId })
} catch (e) {
console.error('Error creating or getting a chat', e)
this.errorLoadingChat = true
}
}
if (chat) {
this.$nextTick(() => {
this.scrollDown({ forceRead: true })
})
this.$store.dispatch('addOpenedChat', { chat })
this.doStartFetching()
}
},
doStartFetching () {
this.$store.dispatch('startFetchingCurrentChat', {
fetcher: () => promiseInterval(() => this.fetchChat({ fetchLatest: true }), 5000)
})
this.fetchChat({ isFirstFetch: true })
},
handleAttachmentPosting () {
this.$nextTick(() => {
this.handleResize()
// When the posting form size changes because of a media attachment, we need an extra resize
// to account for the potential delay in the DOM update.
this.scrollDown({ forceRead: true })
})
},
sendMessage ({ status, media, idempotencyKey }) {
const params = {
id: this.currentChat.id,
content: status,
idempotencyKey
}
if (media[0]) {
params.mediaId = media[0].id
}
const fakeMessage = buildFakeMessage({
attachments: media,
chatId: this.currentChat.id,
content: status,
userId: this.currentUser.id,
idempotencyKey
})
this.$store.dispatch('addChatMessages', {
chatId: this.currentChat.id,
messages: [fakeMessage]
}).then(() => {
this.handleAttachmentPosting()
})
return this.doSendMessage({ params, fakeMessage, retriesLeft: MAX_RETRIES })
},
doSendMessage ({ params, fakeMessage, retriesLeft = MAX_RETRIES }) {
if (retriesLeft <= 0) return
this.backendInteractor.sendChatMessage(params)
.then(data => {
this.$store.dispatch('addChatMessages', {
chatId: this.currentChat.id,
updateMaxId: false,
messages: [{ ...data, fakeId: fakeMessage.id }]
})
return data
})
.catch(error => {
console.error('Error sending message', error)
this.$store.dispatch('handleMessageError', {
chatId: this.currentChat.id,
fakeId: fakeMessage.id,
isRetry: retriesLeft !== MAX_RETRIES
})
if ((error.statusCode >= 500 && error.statusCode < 600) || error.message === 'Failed to fetch') {
this.messageRetriers[fakeMessage.id] = setTimeout(() => {
this.doSendMessage({ params, fakeMessage, retriesLeft: retriesLeft - 1 })
}, 1000 * (2 ** (MAX_RETRIES - retriesLeft)))
}
return {}
})
return Promise.resolve(fakeMessage)
},
goBack () {
this.$router.push({ name: 'chats', params: { username: this.currentUser.screen_name } })
}
}
}
export default Chat

View file

@ -1,108 +0,0 @@
.chat-view {
display: flex;
height: 100%;
.chat-view-inner {
height: auto;
width: 100%;
overflow: visible;
display: flex;
}
.chat-view-body {
box-sizing: border-box;
background-color: var(--chatBg, $fallback--bg);
display: flex;
flex-direction: column;
width: 100%;
overflow: visible;
min-height: calc(100vh - var(--navbar-height));
margin: 0 0 0 0;
border-radius: 10px 10px 0 0;
border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0;
&::after {
border-radius: 0;
}
}
.message-list {
padding: 0 0.8em;
height: 100%;
display: flex;
flex-direction: column;
justify-content: end;
}
.footer {
position: sticky;
bottom: 0;
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
z-index: 1;
}
.chat-view-heading {
grid-template-columns: auto minmax(50%, 1fr);
}
.go-back-button {
text-align: center;
line-height: 1;
height: 100%;
align-self: start;
width: var(--__panel-heading-height-inner);
}
.jump-to-bottom-button {
width: 2.5em;
height: 2.5em;
border-radius: 100%;
position: absolute;
right: 1.3em;
top: -3.2em;
background-color: $fallback--fg;
background-color: var(--btn, $fallback--fg);
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3);
z-index: 10;
transition: 0.35s all;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
opacity: 0;
visibility: hidden;
cursor: pointer;
&.visible {
opacity: 1;
visibility: visible;
}
i {
font-size: 1em;
color: $fallback--text;
color: var(--text, $fallback--text);
}
.unread-message-count {
font-size: 0.8em;
left: 50%;
margin-top: -1rem;
padding: 0.1em;
border-radius: 50px;
position: absolute;
}
.chat-loading-error {
width: 100%;
display: flex;
align-items: flex-end;
height: 100%;
.error {
width: 100%;
}
}
}
}

View file

@ -1,100 +0,0 @@
<template>
<div class="chat-view">
<div class="chat-view-inner">
<div
ref="inner"
class="panel-default panel chat-view-body"
>
<div
ref="header"
class="panel-heading -sticky chat-view-heading"
>
<button
class="button-unstyled go-back-button"
@click="goBack"
>
<FAIcon
size="lg"
icon="chevron-left"
/>
</button>
<div class="title text-center">
<ChatTitle
:user="recipient"
:with-avatar="true"
/>
</div>
</div>
<div
class="message-list"
:style="{ height: scrollableContainerHeight }"
>
<template v-if="!errorLoadingChat">
<ChatMessage
v-for="chatViewItem in chatViewItems"
:key="chatViewItem.id"
:author="recipient"
:chat-view-item="chatViewItem"
:hovered-message-chain="chatViewItem.messageChainId === hoveredMessageChainId"
@hover="onMessageHover"
/>
</template>
<div
v-else
class="chat-loading-error"
>
<div class="alert error">
{{ $t('chats.error_loading_chat') }}
</div>
</div>
</div>
<div
ref="footer"
class="panel-body footer"
>
<div
class="jump-to-bottom-button"
:class="{ 'visible': jumpToBottomButtonVisible }"
@click="scrollDown({ behavior: 'smooth' })"
>
<span>
<FAIcon icon="chevron-down" />
<div
v-if="newMessageCount"
class="badge badge-notification unread-chat-count unread-message-count"
>
{{ newMessageCount }}
</div>
</span>
</div>
<PostStatusForm
:disable-subject="true"
:disable-scope-selector="true"
:disable-notice="true"
:disable-lock-warning="true"
:disable-polls="true"
:disable-sensitivity-checkbox="true"
:disable-submit="errorLoadingChat || !currentChat"
:disable-preview="true"
:optimistic-posting="true"
:post-handler="sendMessage"
:submit-on-enter="!mobileLayout"
:preserve-focus="!mobileLayout"
:auto-focus="!mobileLayout"
:placeholder="formPlaceholder"
:file-limit="1"
max-height="160"
emoji-picker-placement="top"
@resize="handleResize"
/>
</div>
</div>
</div>
</div>
</template>
<script src="./chat.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import './chat.scss';
</style>

View file

@ -1,24 +0,0 @@
// Captures a scroll position
export const getScrollPosition = () => {
return {
scrollTop: window.scrollY,
scrollHeight: document.documentElement.scrollHeight,
offsetHeight: window.innerHeight
}
}
// A helper function that is used to keep the scroll position fixed as the new elements are added to the top
// Takes two scroll positions, before and after the update.
export const getNewTopPosition = (previousPosition, newPosition) => {
return previousPosition.scrollTop + (newPosition.scrollHeight - previousPosition.scrollHeight)
}
export const isBottomedOut = (offset = 0) => {
const scrollHeight = window.scrollY + offset
const totalHeight = document.documentElement.scrollHeight - window.innerHeight
return totalHeight <= scrollHeight
}
// Returns whether or not the scrollbar is visible.
export const isScrollable = () => {
return document.documentElement.scrollHeight > window.innerHeight
}

View file

@ -1,37 +0,0 @@
import { mapState, mapGetters } from 'vuex'
import ChatListItem from '../chat_list_item/chat_list_item.vue'
import ChatNew from '../chat_new/chat_new.vue'
import List from '../list/list.vue'
const ChatList = {
components: {
ChatListItem,
List,
ChatNew
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
}),
...mapGetters(['sortedChatList'])
},
data () {
return {
isNew: false
}
},
created () {
this.$store.dispatch('fetchChats', { latest: true })
},
methods: {
cancelNewChat () {
this.isNew = false
this.$store.dispatch('fetchChats', { latest: true })
},
newChat () {
this.isNew = true
}
}
}
export default ChatList

View file

@ -1,64 +0,0 @@
<template>
<div v-if="isNew">
<ChatNew @cancel="cancelNewChat" />
</div>
<div
v-else
class="chat-list panel panel-default"
>
<div class="panel-heading -sticky">
<span class="title">
{{ $t("chats.chats") }}
</span>
<button
class="button-default"
@click="newChat"
>
{{ $t("chats.new") }}
</button>
</div>
<div class="panel-body">
<div
v-if="sortedChatList.length > 0"
class="timeline"
>
<List :items="sortedChatList">
<template v-slot:item="{item}">
<ChatListItem
:key="item.id"
:compact="false"
:chat="item"
/>
</template>
</List>
</div>
<div
v-else
class="emtpy-chat-list-alert"
>
<span>{{ $t('chats.empty_chat_list_placeholder') }}</span>
</div>
</div>
</div>
</template>
<script src="./chat_list.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.chat-list {
min-height: 25em;
margin-bottom: 0;
}
.emtpy-chat-list-alert {
padding: 3em;
font-size: 1.2em;
display: flex;
justify-content: center;
color: $fallback--text;
color: var(--faint, $fallback--text);
}
</style>

View file

@ -1,69 +0,0 @@
import { mapState } from 'vuex'
import StatusBody from '../status_content/status_content.vue'
import fileType from 'src/services/file_type/file_type.service'
import UserAvatar from '../user_avatar/user_avatar.vue'
import AvatarList from '../avatar_list/avatar_list.vue'
import Timeago from '../timeago/timeago.vue'
import ChatTitle from '../chat_title/chat_title.vue'
const ChatListItem = {
name: 'ChatListItem',
props: [
'chat'
],
components: {
UserAvatar,
AvatarList,
Timeago,
ChatTitle,
StatusBody
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
}),
attachmentInfo () {
if (this.chat.lastMessage.attachments.length === 0) { return }
const types = this.chat.lastMessage.attachments.map(file => fileType.fileType(file.mimetype))
if (types.includes('video')) {
return this.$t('file_type.video')
} else if (types.includes('audio')) {
return this.$t('file_type.audio')
} else if (types.includes('image')) {
return this.$t('file_type.image')
} else {
return this.$t('file_type.file')
}
},
messageForStatusContent () {
const message = this.chat.lastMessage
const messageEmojis = message ? message.emojis : []
const isYou = message && message.account_id === this.currentUser.id
const content = message ? (this.attachmentInfo || message.content) : ''
const messagePreview = isYou ? `<i>${this.$t('chats.you')}</i> ${content}` : content
return {
summary: '',
emojis: messageEmojis,
raw_html: messagePreview,
text: messagePreview,
attachments: []
}
}
},
methods: {
openChat (_e) {
if (this.chat.id) {
this.$router.push({
name: 'chat',
params: {
username: this.currentUser.screen_name,
recipient_id: this.chat.account.id
}
})
}
}
}
}
export default ChatListItem

View file

@ -1,91 +0,0 @@
.chat-list-item {
display: flex;
flex-direction: row;
padding: 0.75em;
height: 5em;
overflow: hidden;
box-sizing: border-box;
cursor: pointer;
:focus {
outline: none;
}
&:hover {
background-color: var(--selectedPost, $fallback--lightBg);
box-shadow: 0 0 3px 1px rgba(0, 0, 0, 0.1);
}
.chat-list-item-left {
margin-right: 1em;
}
.chat-list-item-center {
width: 100%;
box-sizing: border-box;
overflow: hidden;
word-wrap: break-word;
}
.heading {
width: 100%;
display: inline-flex;
justify-content: space-between;
line-height: 1em;
}
.heading-right {
white-space: nowrap;
}
.name-and-account-name {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
flex-shrink: 1;
line-height: var(--post-line-height);
}
.chat-preview {
display: inline-flex;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin: 0.35em 0;
color: $fallback--text;
color: var(--faint, $fallback--text);
width: 100%;
}
a {
color: var(--faintLink, $fallback--link);
text-decoration: none;
pointer-events: none;
}
&:hover .animated.avatar {
canvas {
display: none;
}
img {
visibility: visible;
}
}
.Avatar {
border-radius: $fallback--avatarAltRadius;
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
}
.chat-preview-body {
--emoji-size: 1.4em;
}
.time-wrapper {
line-height: var(--post-line-height);
}
.chat-preview-body {
padding-right: 1em;
}
}

View file

@ -1,53 +0,0 @@
<template>
<div
class="chat-list-item"
@click.capture.prevent="openChat"
>
<div class="chat-list-item-left">
<UserAvatar
:user="chat.account"
height="48px"
width="48px"
/>
</div>
<div class="chat-list-item-center">
<div class="heading">
<span
v-if="chat.account"
class="name-and-account-name"
>
<ChatTitle
:user="chat.account"
/>
</span>
<span class="heading-right" />
<div class="time-wrapper">
<Timeago
:time="chat.updated_at"
:auto-update="60"
/>
</div>
</div>
<div class="chat-preview">
<StatusBody
class="chat-preview-body"
:status="messageForStatusContent"
:single-line="true"
/>
<div
v-if="chat.unread > 0"
class="badge badge-notification unread-chat-count"
>
{{ chat.unread }}
</div>
</div>
</div>
</div>
</template>
<script src="./chat_list_item.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import './chat_list_item.scss';
</style>

View file

@ -1,108 +0,0 @@
import { mapState, mapGetters } from 'vuex'
import Popover from '../popover/popover.vue'
import Attachment from '../attachment/attachment.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import Gallery from '../gallery/gallery.vue'
import LinkPreview from '../link-preview/link-preview.vue'
import StatusContent from '../status_content/status_content.vue'
import ChatMessageDate from '../chat_message_date/chat_message_date.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
faEllipsisH
} from '@fortawesome/free-solid-svg-icons'
library.add(
faTimes,
faEllipsisH
)
const ChatMessage = {
name: 'ChatMessage',
props: [
'author',
'edited',
'noHeading',
'chatViewItem',
'hoveredMessageChain'
],
emits: ['hover'],
components: {
Popover,
Attachment,
StatusContent,
UserAvatar,
Gallery,
LinkPreview,
ChatMessageDate
},
computed: {
// Returns HH:MM (hours and minutes) in local time.
createdAt () {
const time = this.chatViewItem.data.created_at
return time.toLocaleTimeString('en', { hour: '2-digit', minute: '2-digit', hour12: false })
},
isCurrentUser () {
return this.message.account_id === this.currentUser.id
},
message () {
return this.chatViewItem.data
},
userProfileLink () {
return generateProfileLink(this.author.id, this.author.screen_name, this.$store.state.instance.restrictedNicknames)
},
isMessage () {
return this.chatViewItem.type === 'message'
},
messageForStatusContent () {
return {
summary: '',
emojis: this.message.emojis,
raw_html: this.message.content || '',
text: this.message.content || '',
attachments: this.message.attachments
}
},
hasAttachment () {
return this.message.attachments.length > 0
},
...mapState({
betterShadow: state => state.interface.browserSupport.cssFilter,
currentUser: state => state.users.currentUser,
restrictedNicknames: state => state.instance.restrictedNicknames
}),
popoverMarginStyle () {
if (this.isCurrentUser) {
return {}
} else {
return { left: 50 }
}
},
...mapGetters(['mergedConfig', 'findUser'])
},
data () {
return {
hovered: false,
menuOpened: false
}
},
methods: {
onHover (bool) {
this.$emit('hover', { isHovered: bool, messageChainId: this.chatViewItem.messageChainId })
},
async deleteMessage () {
const confirmed = window.confirm(this.$t('chats.delete_confirm'))
if (confirmed) {
await this.$store.dispatch('deleteChatMessage', {
messageId: this.chatViewItem.data.id,
chatId: this.chatViewItem.data.chat_id
})
}
this.hovered = false
this.menuOpened = false
}
}
}
export default ChatMessage

View file

@ -1,179 +0,0 @@
@import '../../_variables.scss';
.chat-message-wrapper {
&.hovered-message-chain {
.animated.Avatar {
canvas {
display: none;
}
img {
visibility: visible;
}
}
}
.chat-message-menu {
transition: opacity 0.1s;
opacity: 0;
position: absolute;
top: -0.8em;
button {
padding-top: 0.2em;
padding-bottom: 0.2em;
}
}
.menu-icon {
cursor: pointer;
&:hover, .extra-button-popover.open & {
color: $fallback--text;
color: var(--text, $fallback--text);
}
}
.popover {
width: 12em;
}
.chat-message {
display: flex;
padding-bottom: 0.5em;
.status-body:hover {
--_still-image-img-visibility: visible;
--_still-image-canvas-visibility: hidden;
--_still-image-label-visibility: hidden;
}
}
.avatar-wrapper {
margin-right: 0.72em;
width: 32px;
}
.link-preview, .attachments {
margin-bottom: 1em;
}
.chat-message-inner {
display: flex;
flex-direction: column;
align-items: flex-start;
max-width: 80%;
min-width: 10em;
width: 100%;
&.with-media {
width: 100%;
.status {
width: 100%;
}
}
}
.status {
border-radius: $fallback--chatMessageRadius;
border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius);
display: flex;
padding: 0.75em;
}
.created-at {
position: relative;
float: right;
font-size: 0.8em;
margin: -1em 0 -0.5em 0;
font-style: italic;
opacity: 0.8;
}
.without-attachment {
.message-content {
// TODO figure out how to do it properly
.RichContent::after {
margin-right: 5.4em;
content: " ";
display: inline-block;
}
}
}
.pending {
.status-content.media-body, .created-at {
color: var(--faint);
}
}
.error {
.status-content.media-body, .created-at {
color: $fallback--cRed;
color: var(--badgeNotification, $fallback--cRed);
}
}
.incoming {
a {
color: var(--chatMessageIncomingLink, $fallback--link);
}
.status {
color: var(--chatMessageIncomingText, $fallback--text);
background-color: var(--chatMessageIncomingBg, $fallback--bg);
border: 1px solid var(--chatMessageIncomingBorder, --border);
}
.created-at {
a {
color: var(--chatMessageIncomingText, $fallback--text);
}
}
.chat-message-menu {
left: 0.4rem;
}
}
.outgoing {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: end;
justify-content: flex-end;
a {
color: var(--chatMessageOutgoingLink, $fallback--link);
}
.status {
color: var(--chatMessageOutgoingText, $fallback--text);
background-color: var(--chatMessageOutgoingBg, $fallback--lightBg);
border: 1px solid var(--chatMessageOutgoingBorder, --lightBg);
}
.chat-message-inner {
align-items: flex-end;
}
.chat-message-menu {
right: 0.4rem;
}
}
.visible {
opacity: 1;
}
}
.chat-message-date-separator {
text-align: center;
margin: 1.4em 0;
font-size: 0.9em;
user-select: none;
color: $fallback--text;
color: var(--faintedText, $fallback--text);
}

View file

@ -1,103 +0,0 @@
<template>
<div
v-if="isMessage"
class="chat-message-wrapper"
:class="{ 'hovered-message-chain': hoveredMessageChain }"
@mouseover="onHover(true)"
@mouseleave="onHover(false)"
>
<div
class="chat-message"
:class="[{ 'outgoing': isCurrentUser, 'incoming': !isCurrentUser }]"
>
<div
v-if="!isCurrentUser"
class="avatar-wrapper"
>
<router-link
v-if="chatViewItem.isHead"
:to="userProfileLink"
>
<UserAvatar
:compact="true"
:better-shadow="betterShadow"
:user="author"
/>
</router-link>
</div>
<div class="chat-message-inner">
<div
class="status-body"
:style="{ 'min-width': message.attachment ? '80%' : '' }"
>
<div
class="media status"
:class="{ 'without-attachment': !hasAttachment, 'pending': chatViewItem.data.pending, 'error': chatViewItem.data.error }"
style="position: relative"
@mouseenter="hovered = true"
@mouseleave="hovered = false"
>
<div
class="chat-message-menu"
:class="{ 'visible': hovered || menuOpened }"
>
<Popover
trigger="click"
placement="top"
:bound-to-selector="isCurrentUser ? '' : '.scrollable-message-list'"
:bound-to="{ x: 'container' }"
:margin="popoverMarginStyle"
@show="menuOpened = true"
@close="menuOpened = false"
>
<template v-slot:content>
<div class="dropdown-menu">
<button
class="button-default dropdown-item dropdown-item-icon"
@click="deleteMessage"
>
<FAIcon icon="times" /> {{ $t("chats.delete") }}
</button>
</div>
</template>
<template v-slot:trigger>
<button
class="button-default menu-icon"
:title="$t('chats.more')"
>
<FAIcon icon="ellipsis-h" />
</button>
</template>
</Popover>
</div>
<StatusContent
class="message-content"
:status="messageForStatusContent"
:full-content="true"
>
<template v-slot:footer>
<span
class="created-at"
>
{{ createdAt }}
</span>
</template>
</StatusContent>
</div>
</div>
</div>
</div>
</div>
<div
v-else
class="chat-message-date-separator"
>
<ChatMessageDate :date="chatViewItem.date" />
</div>
</template>
<script src="./chat_message.js" ></script>
<style lang="scss">
@import './chat_message.scss';
</style>

View file

@ -1,83 +0,0 @@
import { mapState, mapGetters } from 'vuex'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSearch,
faChevronLeft
} from '@fortawesome/free-solid-svg-icons'
library.add(
faSearch,
faChevronLeft
)
const chatNew = {
components: {
BasicUserCard,
UserAvatar
},
data () {
return {
suggestions: [],
userIds: [],
loading: false,
query: ''
}
},
async created () {
const { chats } = await this.backendInteractor.chats()
chats.forEach(chat => this.suggestions.push(chat.account))
},
computed: {
users () {
return this.userIds.map(userId => this.findUser(userId))
},
availableUsers () {
if (this.query.length !== 0) {
return this.users
} else {
return this.suggestions
}
},
...mapState({
currentUser: state => state.users.currentUser,
backendInteractor: state => state.api.backendInteractor
}),
...mapGetters(['findUser'])
},
methods: {
goBack () {
this.$emit('cancel')
},
goToChat (user) {
this.$router.push({ name: 'chat', params: { recipient_id: user.id } })
},
onInput () {
this.search(this.query)
},
addUser (user) {
this.selectedUserIds.push(user.id)
this.query = ''
},
removeUser (userId) {
this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId)
},
search (query) {
if (!query) {
this.loading = false
return
}
this.loading = true
this.userIds = []
this.$store.dispatch('search', { q: query, resolve: true, type: 'accounts' })
.then(data => {
this.loading = false
this.userIds = data.accounts.map(a => a.id)
})
}
}
}
export default chatNew

View file

@ -1,31 +0,0 @@
.chat-new {
.input-wrap {
display: flex;
margin: 0.7em 0.5em 0.7em 0.5em;
input {
width: 100%;
}
}
.search-icon {
margin-right: 0.3em;
}
.member-list {
padding-bottom: 0.7rem;
}
.basic-user-card:hover {
cursor: pointer;
background-color: var(--selectedPost, $fallback--lightBg);
}
.go-back-button {
text-align: center;
line-height: 1;
height: 100%;
align-self: start;
width: var(--__panel-heading-height-inner);
}
}

View file

@ -1,51 +0,0 @@
<template>
<div
class="panel-default panel chat-new"
>
<div
ref="header"
class="panel-heading"
>
<button
class="button-unstyled go-back-button"
@click="goBack"
>
<FAIcon
size="lg"
icon="chevron-left"
/>
</button>
</div>
<div class="input-wrap">
<div class="input-search">
<FAIcon
class="search-icon fa-scale-110 fa-old-padding"
icon="search"
/>
</div>
<input
ref="search"
v-model="query"
placeholder="Search people"
@input="onInput"
>
</div>
<div class="member-list">
<div
v-for="user in availableUsers"
:key="user.id"
class="member"
>
<div @click.capture.prevent="goToChat(user)">
<BasicUserCard :user="user" />
</div>
</div>
</div>
</div>
</template>
<script src="./chat_new.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import './chat_new.scss';
</style>

View file

@ -1,27 +0,0 @@
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import UserAvatar from '../user_avatar/user_avatar.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
export default {
name: 'ChatTitle',
components: {
UserAvatar,
RichContent
},
props: [
'user', 'withAvatar'
],
computed: {
title () {
return this.user ? this.user.screen_name_ui : ''
},
htmlTitle () {
return this.user ? this.user.name_html : ''
}
},
methods: {
getUserProfileLink (user) {
return generateProfileLink(user.id, user.screen_name)
}
}
}

View file

@ -1,65 +0,0 @@
<template>
<div
class="chat-title"
:title="title"
>
<router-link
v-if="withAvatar && user"
class="avatar-container"
:to="getUserProfileLink(user)"
>
<UserAvatar
class="titlebar-avatar"
:user="user"
/>
</router-link>
<RichContent
v-if="user"
class="username"
:title="'@'+(user && user.screen_name_ui)"
:html="htmlTitle"
:emoji="user.emoji || []"
/>
</div>
</template>
<script src="./chat_title.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.chat-title {
display: flex;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
--emoji-size: 14px;
.username {
max-width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
display: inline;
word-wrap: break-word;
overflow: hidden;
}
.avatar-container {
align-self: center;
line-height: 1;
}
.titlebar-avatar {
margin-right: 0.5em;
height: 1.5em;
width: 1.5em;
border-radius: $fallback--avatarAltRadius;
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
&.animated::before {
display: none;
}
}
}
</style>

View file

@ -28,7 +28,7 @@
flex: 0 0 2em;
min-width: 2em;
align-self: center;
height: 100%;
height: 2em;
}
}
.computedIndicator,

View file

@ -0,0 +1,37 @@
import DialogModal from '../dialog_modal/dialog_modal.vue'
/**
* This component emits the following events:
* cancelled, emitted when the action should not be performed;
* accepted, emitted when the action should be performed;
*
* The caller should close this dialog after receiving any of the two events.
*/
const ConfirmModal = {
components: {
DialogModal
},
props: {
title: {
type: String
},
cancelText: {
type: String
},
confirmText: {
type: String
}
},
computed: {
},
methods: {
onCancel () {
this.$emit('cancelled')
},
onAccept () {
this.$emit('accepted')
}
}
}
export default ConfirmModal

View file

@ -0,0 +1,39 @@
<template>
<dialog-modal
v-body-scroll-lock="true"
class="confirm-modal"
:on-cancel="onCancel"
>
<template #header>
<span v-text="title" />
</template>
<slot />
<template #footer>
<button
class="btn button-default"
@click.prevent="onCancel"
v-text="cancelText"
/>
<button
class="btn button-default button-positive"
@click.prevent="onAccept"
v-text="confirmText"
/>
</template>
</dialog-modal>
</template>
<style lang="scss" scoped>
@import '../../_variables';
.confirm-modal {
.button-positive {
border: 3px solid var(--accent, $fallback--link);
border-radius: var(--btnRadius, $fallback--btnRadius);
}
}
</style>
<script src="./confirm_modal.js"></script>

View file

@ -1,6 +1,8 @@
import { reduce, filter, findIndex, clone, get } from 'lodash'
import Status from '../status/status.vue'
import ThreadTree from '../thread_tree/thread_tree.vue'
import { WSConnectionStatus } from '../../services/api/api.service.js'
import { mapGetters, mapState } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -77,6 +79,9 @@ const conversation = {
const maxDepth = this.$store.getters.mergedConfig.maxDepthInThread - 2
return maxDepth >= 1 ? maxDepth : 1
},
streamingEnabled () {
return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED
},
displayStyle () {
return this.$store.getters.mergedConfig.conversationDisplay
},
@ -339,7 +344,11 @@ const conversation = {
},
maybeHighlight () {
return this.isExpanded ? this.highlight : null
}
},
...mapGetters(['mergedConfig']),
...mapState({
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus
})
},
components: {
Status,
@ -395,6 +404,11 @@ const conversation = {
setHighlight (id) {
if (!id) return
this.highlight = id
if (!this.streamingEnabled) {
this.$store.dispatch('fetchStatus', id)
}
this.$store.dispatch('fetchFavsAndRepeats', id)
this.$store.dispatch('fetchEmojiReactionsBy', id)
},

View file

@ -1,16 +1,21 @@
import SearchBar from 'components/search_bar/search_bar.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSignInAlt,
faSignOutAlt,
faHome,
faComments,
faBell,
faUserPlus,
faBullhorn,
faSearch,
faTachometerAlt,
faCog,
faGlobe,
faBolt,
faUsers,
faCommentMedical,
faBookmark,
faInfoCircle
} from '@fortawesome/free-solid-svg-icons'
@ -19,18 +24,23 @@ library.add(
faSignOutAlt,
faHome,
faComments,
faBell,
faUserPlus,
faBullhorn,
faSearch,
faTachometerAlt,
faCog,
faGlobe,
faBolt,
faUsers,
faCommentMedical,
faBookmark,
faInfoCircle
)
export default {
components: {
SearchBar
SearchBar,
ConfirmModal
},
data: () => ({
searchBarHidden: true,
@ -40,7 +50,8 @@ export default {
window.CSS.supports('-moz-mask-size', 'contain') ||
window.CSS.supports('-ms-mask-size', 'contain') ||
window.CSS.supports('-o-mask-size', 'contain')
)
),
showingConfirmLogout: false
}),
computed: {
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
@ -65,20 +76,34 @@ export default {
})
},
logo () { return this.$store.state.instance.logo },
mergedConfig () {
return this.$store.getters.mergedConfig
},
sitename () { return this.$store.state.instance.name },
showNavShortcuts () {
return this.mergedConfig.showNavShortcuts
},
showWiderShortcuts () {
return this.mergedConfig.showWiderShortcuts
},
hideSiteFavicon () {
return this.mergedConfig.hideSiteFavicon
},
hideSiteName () {
return this.mergedConfig.hideSiteName
},
hideSitename () { return this.$store.state.instance.hideSitename },
logoLeft () { return this.$store.state.instance.logoLeft },
currentUser () { return this.$store.state.users.currentUser },
privateMode () { return this.$store.state.instance.private }
privateMode () { return this.$store.state.instance.private },
shouldConfirmLogout () {
return this.$store.getters.mergedConfig.modalOnLogout
}
},
methods: {
scrollToTop () {
window.scrollTo(0, 0)
},
logout () {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
},
onSearchBarToggled (hidden) {
this.searchBarHidden = hidden
},

View file

@ -3,6 +3,10 @@
.DesktopNav {
width: 100%;
input {
color: var(--inputTopbarText, var(--inputText));
}
a {
color: var(--topBarLink, $fallback--link);
}
@ -11,16 +15,16 @@
display: grid;
grid-template-rows: var(--navbar-height);
grid-template-columns: 2fr auto 2fr;
grid-template-areas: "sitename logo actions";
grid-template-areas: "nav-left logo actions";
box-sizing: border-box;
padding: 0 1.2em;
margin: auto;
max-width: 980px;
max-width: 1110px;
}
&.-logoLeft .inner-nav {
grid-template-columns: auto 2fr 2fr;
grid-template-areas: "logo sitename actions";
grid-template-areas: "logo nav-left actions";
}
.button-default {
@ -85,14 +89,43 @@
height: 100%;
text-align: center;
&.router-link-active {
font-size: 1.2em;
margin-top: 0.05em;
.svg-inline--fa {
font-weight: bolder;
color: $fallback--text;
color: var(--selectedMenuText, $fallback--text);
--lightText: var(--selectedMenuLightText, $fallback--lightText);
}
}
.svg-inline--fa {
color: $fallback--link;
color: var(--topBarLink, $fallback--link);
}
}
.sitename {
grid-area: sitename;
.-wide {
.nav-icon {
margin-left: 0.7em;
}
}
.left {
padding-left: 5px;
display: flex;
}
.nav-left-wrapper {
grid-area: nav-left;
.favicon {
height: 28px;
vertical-align: middle;
padding-right: 5px;
}
}
.actions {

View file

@ -5,16 +5,79 @@
:class="{ '-logoLeft': logoLeft }"
@click="scrollToTop()"
>
<div class="inner-nav">
<div class="item sitename">
<div
class="inner-nav"
:class="{ '-wide': showWiderShortcuts }"
>
<div class="item nav-left-wrapper">
<router-link
v-if="!hideSitename"
class="site-name"
class="site-brand"
:to="{ name: 'root' }"
active-class="home"
>
{{ sitename }}
<img
v-if="!hideSiteFavicon"
class="favicon"
src="/favicon.png"
>
<span
v-if="!hideSiteName"
class="site-name"
>
{{ sitename }}
</span>
</router-link>
<div
v-if="(currentUser || !privateMode) && showNavShortcuts"
class="nav-items left"
>
<router-link
v-if="currentUser"
:to="{ name: 'friends' }"
class="nav-icon"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="home"
:title="$t('nav.home_timeline')"
/>
</router-link>
<router-link
:to="{ name: 'public-timeline' }"
class="nav-icon"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="users"
:title="$t('nav.public_tl')"
/>
</router-link>
<router-link
v-if="currentUser"
:to="{ name: 'bubble-timeline' }"
class="nav-icon"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="circle"
:title="$t('nav.bubble_timeline')"
/>
</router-link>
<router-link
:to="{ name: 'public-external-timeline' }"
class="nav-icon"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="globe"
:title="$t('nav.twkn')"
/>
</router-link>
</div>
</div>
<router-link
class="logo"
@ -36,6 +99,47 @@
@toggled="onSearchBarToggled"
@click.stop
/>
<div
v-if="(currentUser || !privateMode) && showNavShortcuts"
class="nav-items right"
>
<router-link
v-if="currentUser"
class="nav-icon"
:to="{ name: 'interactions', params: { username: currentUser.screen_name } }"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="bolt"
:title="$t('nav.interactions')"
/>
</router-link>
<router-link
v-if="currentUser"
:to="{ name: 'lists' }"
class="nav-icon"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="list"
:title="$t('nav.lists')"
/>
</router-link>
<router-link
v-if="currentUser"
:to="{ name: 'bookmarks' }"
class="nav-icon"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="bookmark"
:title="$t('nav.bookmarks')"
/>
</router-link>
</div>
<button
class="button-unstyled nav-icon"
@click.stop="openSettingsModal"
@ -61,20 +165,20 @@
:title="$t('nav.administration')"
/>
</a>
<button
v-if="currentUser"
class="button-unstyled nav-icon"
@click.prevent="logout"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="sign-out-alt"
:title="$t('login.logout')"
/>
</button>
</div>
</div>
<teleport to="#modal">
<confirm-modal
v-if="showingConfirmLogout"
:title="$t('login.logout_confirm_title')"
:confirm-text="$t('login.logout_confirm_accept_button')"
:cancel-text="$t('login.logout_confirm_cancel_button')"
@accepted="doLogout"
@cancelled="hideConfirmLogout"
>
{{ $t('login.logout_confirm') }}
</confirm-modal>
</teleport>
</nav>
</template>
<script src="./desktop_nav.js"></script>

View file

@ -39,7 +39,7 @@
right: 0;
top: 0;
background: rgba(27,31,35,.5);
z-index: 99;
z-index: 2000;
}
}
@ -51,9 +51,10 @@
margin: 15vh auto;
position: fixed;
transform: translateX(-50%);
z-index: 999;
z-index: 2001;
cursor: default;
display: block;
width: max-content;
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);

View file

@ -0,0 +1,75 @@
import PostStatusForm from '../post_status_form/post_status_form.vue'
import Modal from '../modal/modal.vue'
import statusPosterService from '../../services/status_poster/status_poster.service.js'
import get from 'lodash/get'
const EditStatusModal = {
components: {
PostStatusForm,
Modal
},
data () {
return {
resettingForm: false
}
},
computed: {
isLoggedIn () {
return !!this.$store.state.users.currentUser
},
modalActivated () {
return this.$store.state.editStatus.modalActivated
},
isFormVisible () {
return this.isLoggedIn && !this.resettingForm && this.modalActivated
},
params () {
return this.$store.state.editStatus.params || {}
}
},
watch: {
params (newVal, oldVal) {
if (get(newVal, 'statusId') !== get(oldVal, 'statusId')) {
this.resettingForm = true
this.$nextTick(() => {
this.resettingForm = false
})
}
},
isFormVisible (val) {
if (val) {
this.$nextTick(() => this.$el && this.$el.querySelector('textarea').focus())
}
}
},
methods: {
doEditStatus ({ status, spoilerText, sensitive, media, contentType, poll }) {
const params = {
store: this.$store,
statusId: this.$store.state.editStatus.params.statusId,
status,
spoilerText,
sensitive,
poll,
media,
contentType
}
return statusPosterService.editStatus(params)
.then((data) => {
return data
})
.catch((err) => {
console.error('Error editing status', err)
return {
error: err.message
}
})
},
closeModal () {
this.$store.dispatch('closeEditStatusModal')
}
}
}
export default EditStatusModal

View file

@ -0,0 +1,48 @@
<template>
<Modal
v-if="isFormVisible"
class="edit-form-modal-view"
@backdropClicked="closeModal"
>
<div class="edit-form-modal-panel panel">
<div class="panel-heading">
{{ $t('post_status.edit_status') }}
</div>
<PostStatusForm
class="panel-body"
v-bind="params"
@posted="closeModal"
:disablePolls="true"
:disableVisibilitySelector="true"
:post-handler="doEditStatus"
/>
</div>
</Modal>
</template>
<script src="./edit_status_modal.js"></script>
<style lang="scss">
.modal-view.edit-form-modal-view {
align-items: flex-start;
}
.edit-form-modal-panel {
flex-shrink: 0;
margin-top: 25%;
margin-bottom: 2em;
width: 100%;
max-width: 700px;
@media (orientation: landscape) {
margin-top: 8%;
}
.form-bottom-left {
max-width: 6.5em;
.emoji-icon {
justify-content: right;
}
}
}
</style>

View file

@ -491,11 +491,10 @@ const EmojiInput = {
},
setPlacement (container, target, offsetBottom) {
if (!container || !target) return
target.style.top = offsetBottom + 'px'
target.style.bottom = 'auto'
if (this.placement === 'top' || (this.placement === 'auto' && this.overflowsBottom(container))) {
if (this.placement === 'bottom' || (this.placement === 'auto' && !this.overflowsBottom(container))) {
target.style.top = offsetBottom + 'px'
target.style.bottom = 'auto'
} else {
target.style.top = 'auto'
target.style.bottom = this.input.offsetHeight + 'px'
}

View file

@ -6,6 +6,7 @@ import {
faStickyNote,
faSmileBeam
} from '@fortawesome/free-solid-svg-icons'
import { trim, escapeRegExp, startCase } from 'lodash'
library.add(
faBoxOpen,
@ -20,23 +21,6 @@ const LOAD_EMOJI_BY = 60
// When to start loading new batch emoji, in pixels
const LOAD_EMOJI_MARGIN = 64
const filterByKeyword = (list, keyword = '') => {
if (keyword === '') return list
const keywordLowercase = keyword.toLowerCase()
let orderedEmojiList = []
for (const emoji of list) {
const indexOfKeyword = emoji.displayText.toLowerCase().indexOf(keywordLowercase)
if (indexOfKeyword > -1) {
if (!Array.isArray(orderedEmojiList[indexOfKeyword])) {
orderedEmojiList[indexOfKeyword] = []
}
orderedEmojiList[indexOfKeyword].push(emoji)
}
}
return orderedEmojiList.flat()
}
const EmojiPicker = {
props: {
enableStickerPicker: {
@ -48,7 +32,7 @@ const EmojiPicker = {
data () {
return {
keyword: '',
activeGroup: 'custom',
activeGroup: 'standard',
showingStickers: false,
groupsScrolledClass: 'scrolled-top',
keepOpen: false,
@ -79,13 +63,8 @@ const EmojiPicker = {
this.triggerLoadMore(target)
},
highlight (key) {
const ref = this.$refs['group-' + key]
const top = ref.offsetTop
this.setShowStickers(false)
this.activeGroup = key
this.$nextTick(() => {
this.$refs['emoji-groups'].scrollTop = top + 1
})
},
updateScrolledClass (target) {
if (target.scrollTop <= 5) {
@ -154,6 +133,13 @@ const EmojiPicker = {
},
setShowStickers (value) {
this.showingStickers = value
},
filterByKeyword (list) {
if (this.keyword === '') return list
const regex = new RegExp(escapeRegExp(trim(this.keyword)), 'i')
return list.filter(emoji => {
return regex.test(emoji.displayText)
})
}
},
watch: {
@ -174,9 +160,8 @@ const EmojiPicker = {
return 0
},
filteredEmoji () {
return filterByKeyword(
this.$store.state.instance.customEmoji || [],
this.keyword
return this.filterByKeyword(
this.$store.state.instance.customEmoji || []
)
},
customEmojiBuffer () {
@ -184,28 +169,53 @@ const EmojiPicker = {
},
emojis () {
const standardEmojis = this.$store.state.instance.emoji || []
const customEmojis = this.customEmojiBuffer
const customEmojis = this.sortedEmoji
const emojiPacks = []
customEmojis.forEach((pack, id) => {
emojiPacks.push({
id: id.replace(/^pack:/, ''),
text: startCase(id.replace(/^pack:/, '')),
first: pack[0],
emojis: this.filterByKeyword(pack)
})
})
return [
{
id: 'custom',
text: this.$t('emoji.custom'),
icon: 'smile-beam',
emojis: customEmojis
},
{
id: 'standard',
text: this.$t('emoji.unicode'),
icon: 'box-open',
emojis: filterByKeyword(standardEmojis, this.keyword)
first: {
imageUrl: '',
replacement: '🥴'
},
emojis: this.filterByKeyword(standardEmojis)
}
]
].concat(emojiPacks)
},
sortedEmoji () {
const customEmojis = this.$store.state.instance.customEmoji || []
const sortedEmojiGroups = new Map()
customEmojis.forEach((emoji) => {
if (!sortedEmojiGroups.has(emoji.tags[0])) {
sortedEmojiGroups.set(emoji.tags[0], [emoji])
} else {
sortedEmojiGroups.get(emoji.tags[0]).push(emoji)
}
})
return new Map([...sortedEmojiGroups.entries()].sort())
},
emojisView () {
return this.emojis.filter(value => value.emojis.length > 0)
if (this.keyword === '') {
return this.emojis.filter(pack => {
return pack.id === this.activeGroup
})
} else {
return this.emojis.filter(pack => {
return pack.emojis.length > 0
})
}
},
stickerPickerEnabled () {
return (this.$store.state.instance.stickers || []).length !== 0
return (this.$store.state.instance.stickers || []).length !== 0 && this.enableStickerPicker
}
}
}

View file

@ -35,9 +35,8 @@
}
.heading {
display: flex;
height: 32px;
padding: 10px 7px 5px;
margin-top: 10px;
height: 4.8em;
}
.content {
@ -65,15 +64,34 @@
.additional-tabs,
.emoji-tabs {
position: absolute;
display: block;
min-width: 0;
flex-basis: auto;
flex-shrink: 1;
flex-wrap: nowrap;
overflow: auto;
width: 100%;
white-space: nowrap;
&-item {
padding: 0 7px;
vertical-align: top;
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: .4em;
cursor: pointer;
font-size: 1.85em;
img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
span {
font-size: 1.9em;
}
&.disabled {
opacity: 0.5;
@ -88,6 +106,9 @@
color: var(--lightText, $fallback--lightText);
}
}
.fa-sticky-note {
font-size: 2em;
}
}
}

View file

@ -13,18 +13,15 @@
:title="group.text"
@click.prevent="highlight(group.id)"
>
<FAIcon
:icon="group.icon"
fixed-width
/>
<span v-if="!group.first.imageUrl">{{ group.first.replacement }}</span>
<img
v-else
:src="group.first.imageUrl"
>
</span>
</span>
<span
v-if="stickerPickerEnabled"
class="additional-tabs"
>
<span
class="stickers-tab-icon additional-tabs-item"
v-if="stickerPickerEnabled"
class="stickers-tab-icon emoji-tabs-item"
:class="{active: showingStickers}"
:title="$t('emoji.stickers')"
@click.prevent="toggleStickers"
@ -47,6 +44,7 @@
type="text"
class="form-control"
:placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false"
>
</div>
<div

View file

@ -27,7 +27,11 @@ const EmojiReactions = {
},
accountsForEmoji () {
return this.status.emoji_reactions.reduce((acc, reaction) => {
acc[reaction.name] = reaction.accounts || []
if (reaction.url) {
acc[reaction.url] = reaction.accounts || []
} else {
acc[reaction.name] = reaction.accounts || []
}
return acc
}, {})
},

View file

@ -2,8 +2,8 @@
<div class="emoji-reactions">
<UserListPopover
v-for="(reaction) in emojiReactions"
:key="reaction.name"
:users="accountsForEmoji[reaction.name]"
:key="reaction.url || reaction.name"
:users="accountsForEmoji[reaction.url || reaction.name]"
>
<button
class="emoji-reaction btn button-default"

View file

@ -1,4 +1,5 @@
import Popover from '../popover/popover.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisH,
@ -6,7 +7,8 @@ import {
faEyeSlash,
faThumbtack,
faShareAlt,
faExternalLinkAlt
faExternalLinkAlt,
faHistory
} from '@fortawesome/free-solid-svg-icons'
import {
faBookmark as faBookmarkReg,
@ -21,19 +23,49 @@ library.add(
faThumbtack,
faShareAlt,
faExternalLinkAlt,
faFlag
faFlag,
faHistory
)
const ExtraButtons = {
props: [ 'status' ],
components: { Popover },
props: ['status'],
components: {
Popover,
ConfirmModal
},
data () {
return {
expanded: false,
showingDeleteDialog: false
}
},
methods: {
deleteStatus () {
const confirmed = window.confirm(this.$t('status.delete_confirm'))
if (confirmed) {
this.$store.dispatch('deleteStatus', { id: this.status.id })
if (this.shouldConfirmDelete) {
this.showDeleteStatusConfirmDialog()
} else {
this.doDeleteStatus()
}
},
doDeleteStatus () {
this.$store.dispatch('deleteStatus', { id: this.status.id })
this.hideDeleteStatusConfirmDialog()
},
showDeleteStatusConfirmDialog () {
this.showingDeleteDialog = true
},
hideDeleteStatusConfirmDialog () {
this.showingDeleteDialog = false
},
translateStatus () {
if (this.noTranslationTargetSet) {
this.$store.dispatch('pushGlobalNotice', { messageKey: 'toast.no_translation_target_set', level: 'info' })
}
const translateTo = this.$store.getters.mergedConfig.translationLanguage || this.$store.state.instance.interfaceLanguage
this.$store.dispatch('translateStatus', { id: this.status.id, language: translateTo })
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
},
pinStatus () {
this.$store.dispatch('pinStatus', this.status.id)
.then(() => this.$emit('onSuccess'))
@ -71,6 +103,25 @@ const ExtraButtons = {
},
reportStatus () {
this.$store.dispatch('openUserReportingModal', { userId: this.status.user.id, statusIds: [this.status.id] })
},
editStatus () {
this.$store.dispatch('fetchStatusSource', { id: this.status.id })
.then(data => this.$store.dispatch('openEditStatusModal', {
statusId: this.status.id,
subject: data.spoiler_text,
statusText: data.text,
statusIsSensitive: this.status.nsfw,
statusPoll: this.status.poll,
statusFiles: [...this.status.attachments],
visibility: this.status.visibility,
statusContentType: data.content_type
}))
},
showStatusHistory () {
const originalStatus = { ...this.status }
const stripFieldsList = ['attachments', 'created_at', 'emojis', 'text', 'raw_html', 'nsfw', 'poll', 'summary', 'summary_raw_html']
stripFieldsList.forEach(p => delete originalStatus[p])
this.$store.dispatch('openStatusHistoryModal', originalStatus)
}
},
computed: {
@ -89,9 +140,26 @@ const ExtraButtons = {
canMute () {
return !!this.currentUser
},
canTranslate () {
return this.$store.state.instance.translationEnabled === true
},
noTranslationTargetSet () {
return this.$store.getters.mergedConfig.translationLanguage === undefined
},
statusLink () {
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
}
if (this.status.is_local) {
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
} else {
return this.status.external_url
}
},
shouldConfirmDelete () {
return this.$store.getters.mergedConfig.modalOnDelete
},
isEdited () {
return this.status.edited_at !== null
},
editingAvailable () { return this.$store.state.instance.editingAvailable }
}
}

View file

@ -73,6 +73,28 @@
icon="bookmark"
/><span>{{ $t("status.unbookmark") }}</span>
</button>
<button
v-if="ownStatus && editingAvailable"
class="button-default dropdown-item dropdown-item-icon"
@click.prevent="editStatus"
@click="close"
>
<FAIcon
fixed-width
icon="pen"
/><span>{{ $t("status.edit") }}</span>
</button>
<button
v-if="isEdited && editingAvailable"
class="button-default dropdown-item dropdown-item-icon"
@click.prevent="showStatusHistory"
@click="close"
>
<FAIcon
fixed-width
icon="history"
/><span>{{ $t("status.edit_history") }}</span>
</button>
<button
v-if="canDelete"
class="button-default dropdown-item dropdown-item-icon"
@ -116,6 +138,27 @@
:icon="['far', 'flag']"
/><span>{{ $t("user_card.report") }}</span>
</button>
<button
v-if="canTranslate"
class="button-default dropdown-item dropdown-item-icon"
@click.prevent="translateStatus"
@click="close"
>
<FAIcon
fixed-width
icon="globe"
/><span>{{ $t("status.translate") }}</span>
<template v-if="noTranslationTargetSet">
<span class="dropdown-item-icon__badge warning">
<FAIcon
fixed-width
icon="exclamation-triangle"
name="test"
/>
</span>
</template>
</button>
</div>
</template>
<template v-slot:trigger>
@ -125,6 +168,18 @@
icon="ellipsis-h"
/>
</button>
<teleport to="#modal">
<ConfirmModal
v-if="showingDeleteDialog"
:title="$t('status.delete_confirm_title')"
:cancel-text="$t('status.delete_confirm_cancel_button')"
:confirm-text="$t('status.delete_confirm_accept_button')"
@cancelled="hideDeleteStatusConfirmDialog"
@accepted="doDeleteStatus"
>
{{ $t('status.delete_confirm') }}
</ConfirmModal>
</teleport>
</template>
</Popover>
</template>

View file

@ -2,9 +2,6 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for
const FeaturesPanel = {
computed: {
shout: function () { return this.$store.state.instance.shoutAvailable },
pleromaChatMessages: function () { return this.$store.state.instance.pleromaChatMessagesAvailable },
gopher: function () { return this.$store.state.instance.gopherAvailable },
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable },
minimalScopesMode: function () { return this.$store.state.instance.minimalScopesMode },

View file

@ -8,15 +8,6 @@
</div>
<div class="panel-body features-panel">
<ul>
<li v-if="shout">
{{ $t('features_panel.shout') }}
</li>
<li v-if="pleromaChatMessages">
{{ $t('features_panel.pleroma_chat_messages') }}
</li>
<li v-if="gopher">
{{ $t('features_panel.gopher') }}
</li>
<li v-if="whoToFollow">
{{ $t('features_panel.who_to_follow') }}
</li>

View file

@ -1,12 +1,20 @@
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
export default {
props: ['relationship', 'user', 'labelFollowing', 'buttonClass'],
components: {
ConfirmModal
},
data () {
return {
inProgress: false
inProgress: false,
showingConfirmUnfollow: false
}
},
computed: {
shouldConfirmUnfollow () {
return this.$store.getters.mergedConfig.modalOnUnfollow
},
isPressed () {
return this.inProgress || this.relationship.following
},
@ -35,6 +43,12 @@ export default {
}
},
methods: {
showConfirmUnfollow () {
this.showingConfirmUnfollow = true
},
hideConfirmUnfollow () {
this.showingConfirmUnfollow = false
},
onClick () {
this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow()
},
@ -45,12 +59,21 @@ export default {
})
},
unfollow () {
if (this.shouldConfirmUnfollow) {
this.showConfirmUnfollow()
} else {
this.doUnfollow()
}
},
doUnfollow () {
const store = this.$store
this.inProgress = true
requestUnfollow(this.relationship.id, store).then(() => {
this.inProgress = false
store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id })
})
this.hideConfirmUnfollow()
}
}
}

View file

@ -7,6 +7,27 @@
@click="onClick"
>
{{ label }}
<teleport to="#modal">
<confirm-modal
v-if="showingConfirmUnfollow"
:title="$t('user_card.unfollow_confirm_title')"
:confirm-text="$t('user_card.unfollow_confirm_accept_button')"
:cancel-text="$t('user_card.unfollow_confirm_cancel_button')"
@accepted="doUnfollow"
@cancelled="hideConfirmUnfollow"
>
<i18n-t
keypath="user_card.unfollow_confirm"
tag="span"
>
<template #user>
<span
v-text="user.screen_name_ui"
/>
</template>
</i18n-t>
</confirm-modal>
</teleport>
</button>
</template>

View file

@ -1,10 +1,18 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
const FollowRequestCard = {
props: ['user'],
components: {
BasicUserCard
BasicUserCard,
ConfirmModal
},
data () {
return {
showingApproveConfirmDialog: false,
showingDenyConfirmDialog: false
}
},
methods: {
findFollowRequestNotificationId () {
@ -13,7 +21,26 @@ const FollowRequestCard = {
)
return notif && notif.id
},
showApproveConfirmDialog () {
this.showingApproveConfirmDialog = true
},
hideApproveConfirmDialog () {
this.showingApproveConfirmDialog = false
},
showDenyConfirmDialog () {
this.showingDenyConfirmDialog = true
},
hideDenyConfirmDialog () {
this.showingDenyConfirmDialog = false
},
approveUser () {
if (this.shouldConfirmApprove) {
this.showApproveConfirmDialog()
} else {
this.doApprove()
}
},
doApprove () {
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
@ -25,14 +52,34 @@ const FollowRequestCard = {
notification.type = 'follow'
}
})
this.hideApproveConfirmDialog()
},
denyUser () {
if (this.shouldConfirmDeny) {
this.showDenyConfirmDialog()
} else {
this.doDeny()
}
},
doDeny () {
const notifId = this.findFollowRequestNotificationId()
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
this.$store.dispatch('removeFollowRequest', this.user)
})
this.hideDenyConfirmDialog()
}
},
computed: {
mergedConfig () {
return this.$store.getters.mergedConfig
},
shouldConfirmApprove () {
return this.mergedConfig.modalOnApproveFollow
},
shouldConfirmDeny () {
return this.mergedConfig.modalOnDenyFollow
}
}
}

View file

@ -14,6 +14,28 @@
{{ $t('user_card.deny') }}
</button>
</div>
<teleport to="#modal">
<confirm-modal
v-if="showingApproveConfirmDialog"
:title="$t('user_card.approve_confirm_title')"
:confirm-text="$t('user_card.approve_confirm_accept_button')"
:cancel-text="$t('user_card.approve_confirm_cancel_button')"
@accepted="doApprove"
@cancelled="hideApproveConfirmDialog"
>
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
</confirm-modal>
<confirm-modal
v-if="showingDenyConfirmDialog"
:title="$t('user_card.deny_confirm_title')"
:confirm-text="$t('user_card.deny_confirm_accept_button')"
:cancel-text="$t('user_card.deny_confirm_cancel_button')"
@accepted="doDeny"
@cancelled="hideDenyConfirmDialog"
>
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
</confirm-modal>
</teleport>
</basic-user-card>
</template>

View file

@ -171,7 +171,6 @@
.gallery-item {
margin: 0;
height: 200px;
}
}
}

View file

@ -1,5 +1,10 @@
<template>
<div>
<FAIcon
v-if="globeIcon"
icon="globe"
/>
{{ ' ' }}
<label for="interface-language-switcher">
{{ promptText }}
</label>
@ -39,6 +44,10 @@ export default {
setLanguage: {
type: Function,
required: true
},
globeIcon: {
type: Boolean,
default: true
}
},
computed: {

View file

@ -0,0 +1,16 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisH
} from '@fortawesome/free-solid-svg-icons'
library.add(
faEllipsisH
)
const ListCard = {
props: [
'list'
]
}
export default ListCard

View file

@ -0,0 +1,51 @@
<template>
<div class="list-card">
<router-link
:to="{ name: 'list-timeline', params: { id: list.id } }"
class="list-name"
>
{{ list.title }}
</router-link>
<router-link
:to="{ name: 'list-edit', params: { id: list.id } }"
class="button-list-edit"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="ellipsis-h"
/>
</router-link>
</div>
</template>
<script src="./list_card.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.list-card {
display: flex;
}
.list-name,
.button-list-edit {
margin: 0;
padding: 1em;
color: $fallback--link;
color: var(--link, $fallback--link);
&:hover {
background-color: $fallback--lightBg;
background-color: var(--selectedMenu, $fallback--lightBg);
color: $fallback--link;
color: var(--selectedMenuText, $fallback--link);
--faint: var(--selectedMenuFaintText, $fallback--faint);
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
--lightText: var(--selectedMenuLightText, $fallback--lightText);
}
}
.list-name {
flex-grow: 1;
}
</style>

View file

@ -0,0 +1,91 @@
import { mapState, mapGetters } from 'vuex'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import ListUserSearch from '../list_user_search/list_user_search.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSearch,
faChevronLeft
} from '@fortawesome/free-solid-svg-icons'
library.add(
faSearch,
faChevronLeft
)
const ListNew = {
components: {
BasicUserCard,
UserAvatar,
ListUserSearch
},
data () {
return {
title: '',
userIds: [],
selectedUserIds: []
}
},
created () {
this.$store.dispatch('fetchList', { id: this.id })
.then(() => { this.title = this.findListTitle(this.id) })
this.$store.dispatch('fetchListAccounts', { id: this.id })
.then(() => {
this.selectedUserIds = this.findListAccounts(this.id)
this.selectedUserIds.forEach(userId => {
this.$store.dispatch('fetchUserIfMissing', userId)
})
})
},
computed: {
id () {
return this.$route.params.id
},
users () {
return this.userIds.map(userId => this.findUser(userId))
},
selectedUsers () {
return this.selectedUserIds.map(userId => this.findUser(userId)).filter(user => user)
},
...mapState({
currentUser: state => state.users.currentUser
}),
...mapGetters(['findUser', 'findListTitle', 'findListAccounts'])
},
methods: {
onInput () {
this.search(this.query)
},
selectUser (user) {
if (this.selectedUserIds.includes(user.id)) {
this.removeUser(user.id)
} else {
this.addUser(user)
}
},
isSelected (user) {
return this.selectedUserIds.includes(user.id)
},
addUser (user) {
this.selectedUserIds.push(user.id)
},
removeUser (userId) {
this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId)
},
onResults (results) {
this.userIds = results
},
updateList () {
this.$store.dispatch('setList', { id: this.id, title: this.title })
this.$store.dispatch('setListAccounts', { id: this.id, accountIds: this.selectedUserIds })
this.$router.push({ name: 'list-timeline', params: { id: this.id } })
},
deleteList () {
this.$store.dispatch('deleteList', { id: this.id })
this.$router.push({ name: 'lists' })
}
}
}
export default ListNew

Some files were not shown because too many files have changed in this diff Show more