Compare commits

...

662 Commits

Author SHA1 Message Date
Shpuld Shpludson b225c3578f Merge branch 'rc/2.1.2' into 'master'
Merge 2.1.2 to MASTER

See merge request pleroma/pleroma-fe!1236
2020-09-17 17:26:22 +00:00
Shpuld Shpludson 30efbaab34 Merge branch 'master' into 'rc/2.1.2'
# Conflicts:
#   CHANGELOG.md
2020-09-17 17:17:21 +00:00
Shpuld Shpuldson 8ca0586c0f update changelog for 2.1.2 2020-09-17 20:15:08 +03:00
lain ee620f6028 Merge branch 'fix/use-shoutbox-name-in-sidedrawer' into 'develop'
change side drawer to use shoutbox name - fix #956

Closes #956

See merge request pleroma/pleroma-fe!1235
2020-09-16 09:22:05 +00:00
Shpuld Shpuldson 20d33c2fbc change icon to a megaphone 2020-09-16 11:53:54 +03:00
Shpuld Shpuldson 1b811d8b93 change side drawer to use shoutbox name 2020-09-16 11:29:10 +03:00
Shpuld Shpuldson d6fe4f6a4f update changelog 2020-09-16 10:27:31 +03:00
Shpuld Shpludson 38189ee838 Merge branch 'fix-chat-message-list-fetch' into 'develop'
Fix chat messages being missed sometimes when the streaming is disabled and the messages are sent by both participants simultaneously

Closes #957

See merge request pleroma/pleroma-fe!1233
2020-09-16 07:13:31 +00:00
Shpuld Shpludson c00c20a31f Merge branch 'fix-chat-list-order' into 'develop'
Fix the chat list order update

Closes #955

See merge request pleroma/pleroma-fe!1234
2020-09-16 06:59:04 +00:00
eugenijm 8c4514013d Fix chat messages being missed when the streaming is disabled and the messages are sent by both participants simultaneously 2020-09-16 09:21:24 +03:00
eugenijm 92caaa2d7f Fix the chat list order and last message timestamp updates 2020-09-16 05:10:09 +03:00
Shpuld Shpludson 679a1f5e1c Merge branch 'fix/fix-vue-errors' into 'develop'
fix vue warnings and errors

See merge request pleroma/pleroma-fe!1230
2020-09-09 10:21:54 +00:00
Shpuld Shpuldson afb2241a5b change a eslint disable to nextline only 2020-09-08 16:29:10 +03:00
Shpuld Shpludson 938887ef91 Merge branch 'rc/2.1.1' into 'master'
Update master with 2.1.1

See merge request pleroma/pleroma-fe!1231
2020-09-08 09:46:19 +00:00
Shpuld Shpludson f31bc5310e Merge branch 'master' into 'rc/2.1.1'
# Conflicts:
#   CHANGELOG.md
2020-09-08 09:35:36 +00:00
Shpuld Shpuldson 925bf5b5a4 update changelog with 2.1.1 2020-09-08 12:30:45 +03:00
Shpuld Shpuldson 8975589bca fix vue warnings and errors 2020-09-08 10:44:08 +03:00
Shpuld Shpludson da63894072 Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1226
2020-09-07 08:11:54 +00:00
tarteka 26c42484f0 Translated using Weblate (Basque)
Currently translated at 77.4% (513 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/eu/
2020-09-07 08:01:49 +00:00
tarteka 002cd61353 Translated using Weblate (Spanish)
Currently translated at 100.0% (662 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/es/
2020-09-07 08:01:49 +00:00
Anonymous a0aa45d328 Translated using Weblate (Spanish)
Currently translated at 83.2% (551 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/es/
2020-09-07 08:01:48 +00:00
tarteka 31a9da05ff Translated using Weblate (Spanish)
Currently translated at 83.2% (551 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/es/
2020-09-07 08:01:48 +00:00
Shpuld Shpludson 8a0f280cd8 Merge branch 'chat-desktop-notifications-fix' into 'develop'
Do not show desktop notifications for your own chat messages

Closes #951

See merge request pleroma/pleroma-fe!1225
2020-09-07 08:01:06 +00:00
eugenijm b8afdb4c02 Do not show desktop notifications for your own chat messages 2020-09-04 23:11:10 +03:00
Shpuld Shpludson a73b09c732 Merge branch 'fix/locked-account-link-fix' into 'develop'
Open profile tab from 'locked' link in FO-post warning - Fix #945

Closes #945

See merge request pleroma/pleroma-fe!1223
2020-09-04 08:11:31 +00:00
lain 4adccf2216 Merge branch 'fix/autocomplete-fixes' into 'develop'
Rewrite word split for autocomplete, fix #930 fix #681

Closes #681 and #930

See merge request pleroma/pleroma-fe!1218
2020-09-03 14:08:36 +00:00
Shpuld Shpuldson f281663b49 Add hacky functionality to open specific settings tabs 2020-09-03 15:45:13 +03:00
Shpuld Shpludson 02ea173947 Merge branch 'fix/password-reset-behavior' into 'develop'
Password reset no longer informs user of errors or account existence

See merge request pleroma/pleroma-fe!1221
2020-09-02 14:18:35 +00:00
Mark Felder 5809f33c86 Password reset no longer informs user of errors or account existence 2020-09-01 16:48:51 -05:00
Shpuld Shpludson 5b7decea3d Merge branch 'develop' into 'fix/autocomplete-fixes'
# Conflicts:
#   CHANGELOG.md
2020-08-31 13:58:02 +00:00
HJ acc45b49a5 Merge branch 'fix/hide-polls-in-collapsed-posts' into 'develop'
hide poll when subject collapsed, fix #683

Closes #683

See merge request pleroma/pleroma-fe!1220
2020-08-31 12:30:29 +00:00
Shpuld Shpuldson ca0ce902ea add changelog entry 2020-08-31 11:54:27 +03:00
Shpuld Shpuldson b5d15eddcf hide poll when subject collapsed, but show poll icon 2020-08-31 11:50:26 +03:00
Shpuld Shpuldson 4da248f8bc update changelog for autocomplete fixes 2020-08-28 12:35:02 +03:00
Shpuld Shpuldson c01a3720ac Merge branch 'develop' into fix/autocomplete-fixes 2020-08-28 12:34:20 +03:00
Shpuld Shpludson 39f8587171 Merge branch 'chore/fix-changelog' into 'develop'
Chore/fix-changelog

See merge request pleroma/pleroma-fe!1219
2020-08-28 09:33:45 +00:00
Shpuld Shpuldson 82b872df44 update changelog with 2.1.0, fix Add -> Added in older releases 2020-08-28 12:24:48 +03:00
Shpuld Shpuldson 0347d79bda Rewrite word split imperatively for control 2020-08-28 12:02:52 +03:00
Shpuld Shpludson e768ec1fca Merge branch '2.1.0-rc0' into 'master'
2.1.0 into master

See merge request pleroma/pleroma-fe!1217
2020-08-27 14:45:03 +00:00
Shpuld Shpuldson d09f43ba7a Merge branch '2.1.0-rc0' of git.pleroma.social:pleroma/pleroma-fe into 2.1.0-rc0 2020-08-27 17:13:43 +03:00
Shpuld Shpuldson 748c4d8c71 fix boomarks mistake in changelog 2020-08-27 17:13:14 +03:00
Shpuld Shpludson c7ddfefe34 Merge branch 'master' into '2.1.0-rc0'
# Conflicts:
#   CHANGELOG.md
#   src/services/api/api.service.js
2020-08-27 13:59:56 +00:00
Shpuld Shpuldson c0205d582a add missing release to changelog to fix conflicts 2020-08-27 16:56:47 +03:00
Shpuld Shpuldson 4ac882a3b0 add back unreleased/patch 2020-08-27 16:51:16 +03:00
Shpuld Shpuldson bdbc4b27b6 set 2.1.0 date and remove rc from changelog 2020-08-27 16:49:01 +03:00
Shpuld Shpuldson f5e4ad601a wip start 2020-08-27 16:46:23 +03:00
Shpuld Shpuldson 42598fc675 change changelog 2020-08-26 14:28:43 +03:00
lain 65ac71139e Merge branch 'fix/long-poll-words' into 'develop'
fix long poll labels overflowing

See merge request pleroma/pleroma-fe!1216
2020-08-25 09:32:11 +00:00
Shpuld Shpuldson c9ea2db69d fix long poll labels overflowing 2020-08-25 12:17:42 +03:00
lain ff328e7c55 Merge branch 'fix/user-card-avatar-too-small' into 'develop'
Fix some Avatar related css bugs

See merge request pleroma/pleroma-fe!1215
2020-08-25 08:50:28 +00:00
Shpuld Shpuldson f0ac40a428 change more animated gif avatars to work properly 2020-08-25 11:40:02 +03:00
Shpuld Shpuldson 06159101cf fix avatar overwrite classes in usercard 2020-08-25 11:33:38 +03:00
lain 0ed5d40ad7 Merge branch 'fix/fix-tests' into 'develop'
Fix tests

See merge request pleroma/pleroma-fe!1214
2020-08-19 13:28:57 +00:00
Shpuld Shpuldson a0c17e1fd0 fix tests by removing only and adding empty func for notification tests 2020-08-19 16:19:36 +03:00
Shpuld Shpludson 7599244968 Merge branch 'support-thumbnails' into 'develop'
EntityNormalizer: Normalize thumbnail url.

See merge request pleroma/pleroma-fe!1213
2020-08-19 13:01:33 +00:00
lain c649f28faf EntityNormalizer: Normalize thumbnail url. 2020-08-19 14:54:00 +02:00
lain 45add8f5bc Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1208
2020-08-19 12:09:38 +00:00
Ben Is a26fc61efa Translated using Weblate (Italian)
Currently translated at 100.0% (663 of 663 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-08-19 12:00:19 +00:00
lain 54b7e986a7 Merge branch 'fix/tag-timeline-title' into 'develop'
Fix #928 show tag name in title for tag timelines

Closes #928

See merge request pleroma/pleroma-fe!1212
2020-08-19 12:00:12 +00:00
HJ ddeeb240ac Merge branch 'css-fixes' into 'develop'
Css fixes

See merge request pleroma/pleroma-fe!1211
2020-08-19 10:20:38 +00:00
Shpuld Shpuldson 54b619dfec show tag name in title for tag timelines 2020-08-19 13:06:46 +03:00
Henry Jameson 800047b81f fixed 2020-08-19 12:14:00 +03:00
Henry Jameson d7c42f1469 lint 2020-08-19 00:38:54 +03:00
Henry Jameson 61966d290a fix lain's bug 2020-08-19 00:22:08 +03:00
Henry Jameson 36c9368ae5 fix reply popovers being too slim 2020-08-19 00:18:52 +03:00
Henry Jameson 815f230ac7 fixed some issues related to user avatar 2020-08-18 23:57:42 +03:00
Henry Jameson 7de78b1401 revert change to form resize logic 2020-08-18 23:50:23 +03:00
Henry Jameson a6223cec0c Fix last line having slightly cut-off low-hanging parts of characters 2020-08-18 00:52:51 +03:00
Henry Jameson ecbbec7b97 work around incorrect styles ordering in status popover 2020-08-18 00:26:44 +03:00
Henry Jameson 30219098a6 fix issues with reply-link 2020-08-18 00:26:31 +03:00
Henry Jameson 2fa050b163 Fix muted notifications 2020-08-18 00:26:02 +03:00
lain 1a8840c7c3 Merge branch 'fix/treat-field-name-as-text' into 'develop'
treat field name as text

See merge request pleroma/pleroma-fe!1210
2020-08-13 14:01:30 +00:00
Shpuld Shpuldson ae4dc3e439 treat field name as text 2020-08-13 16:29:12 +03:00
HJ b9d1ecec44 Merge branch 'stylelint-rscss-1' into 'develop'
Stylelint + RSCSS initial approach

See merge request pleroma/pleroma-fe!1205
2020-08-06 13:38:07 +00:00
lain 3e6e6096bf Merge branch 'feat/separate-timeline-navigation-from-navpanel' into 'develop'
Separate timeline navigation from navpanel

See merge request pleroma/pleroma-fe!1172
2020-08-06 10:17:44 +00:00
Shpuld Shpludson 9a10ad3826 Merge branch 'mobile-setting-modal-fix' into 'develop'
Mobile setting modal behavior fixes

Closes #908

See merge request pleroma/pleroma-fe!1200
2020-08-06 09:53:24 +00:00
Shpuld Shpludson 7bd6846703 Merge branch 'chat-list-single-line-fix' into 'develop'
Fix single line status content mode in the chat list

See merge request pleroma/pleroma-fe!1202
2020-08-06 09:46:19 +00:00
Henry Jameson 0feab1d97c more broken reply improvements 2020-08-04 19:41:03 +03:00
Henry Jameson a962ca6065 lint 2020-08-04 19:34:18 +03:00
Henry Jameson 0d02b04726 bring back red stripe 2020-08-04 19:32:47 +03:00
Henry Jameson 10bb2b288d remove unnecessary border-radius 2020-08-04 19:26:07 +03:00
Henry Jameson 24fd9372ce i am an idiot sandwich 2020-08-04 19:18:13 +03:00
Henry Jameson dce057f24e lint 2020-08-04 19:16:55 +03:00
Henry Jameson 839681bbc7 add stylelint to CI/npm, only one file for now. 2020-08-04 19:12:13 +03:00
Henry Jameson 2a1c9e094c more fixes 2020-08-04 19:08:49 +03:00
Henry Jameson 4c8e9bc0c2 Merge remote-tracking branch 'origin/develop' into stylelint-rscss-1
* origin/develop:
  Let's serve the README image from GitLab
2020-08-04 18:01:09 +03:00
feld b3f3e87411 Merge branch 'chore/readme-image-link' into 'develop'
Let's serve the README image from GitLab

See merge request pleroma/pleroma-fe!1206
2020-07-28 21:18:05 +00:00
Mark Felder fbdcec1a22 Let's serve the README image from GitLab 2020-07-28 16:08:36 -05:00
Shpuld Shpuldson e57562959f fix oops 2020-07-28 09:41:00 +03:00
Shpuld Shpuldson e86c5ea1fa fix non-timeline routes breaking current/previous timeline 2020-07-28 09:40:04 +03:00
Henry Jameson 5cf5325de2 bump node to 10 for stylint 2020-07-28 02:03:41 +03:00
Henry Jameson 7870b3f7ec neater way to do hover thing with still image 2020-07-28 01:54:40 +03:00
Henry Jameson 65b6e23c00 refactor status 2020-07-28 01:27:11 +03:00
Henry Jameson 61d78ff11b separate status scss into another file 2020-07-27 22:49:57 +03:00
HJ f293dc39dc Merge branch 'fix/preview-styles-polish' into 'develop'
polish preview styles a bit

See merge request pleroma/pleroma-fe!1186
2020-07-27 18:42:43 +00:00
HJ bcde140753 Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1201
2020-07-27 18:34:12 +00:00
Ben Is 28cafdd1bb Translated using Weblate (Italian)
Currently translated at 100.0% (662 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-07-27 14:45:21 +00:00
Tirifto 023d1c01bd Translated using Weblate (Esperanto)
Currently translated at 97.5% (646 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/eo/
2020-07-27 14:45:21 +00:00
Tirifto 536d035913 Translated using Weblate (Esperanto)
Currently translated at 86.7% (574 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/eo/
2020-07-27 14:45:21 +00:00
Karol Kosek 1c44376cc8 Translated using Weblate (Polish)
Currently translated at 95.6% (633 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/pl/
2020-07-27 14:45:21 +00:00
Tirifto 6e651c7693 Translated using Weblate (Esperanto)
Currently translated at 77.4% (513 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/eo/
2020-07-27 14:45:21 +00:00
Tirifto b73c62b17a Translated using Weblate (Esperanto)
Currently translated at 77.4% (513 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/eo/
2020-07-27 14:45:21 +00:00
lain 5be55b6162 Merge branch 'fix/poll-option-delete' into 'develop'
Fix #903: update poll to post-status-form on deleting option

Closes #903

See merge request pleroma/pleroma-fe!1204
2020-07-27 14:45:15 +00:00
Shpuld Shpuldson f9e4afa3e9 update poll to post status form on deleting option 2020-07-24 17:24:34 +03:00
HJ 9726edd8c0 Merge branch 'chat-to-shoutbox' into 'develop'
ChatPanel: Rename `chat` to `shoutbox`.

See merge request pleroma/pleroma-fe!1203
2020-07-23 15:49:03 +00:00
Lain Soykaf 96929d9930 ChatPanel: Rename chat to shoutbox.
To remove confusion with the new ChatMessages.
2020-07-23 17:31:35 +02:00
eugenijm 222ad2dd50 Make the single line mode is consistent with status-content line height 2020-07-23 16:39:01 +03:00
eugenijm 77d65d6cec Ensures the minimized modal is always 50px above the mobile browser bottom bar regardless of whether or not it is visible. 2020-07-23 16:36:21 +03:00
lain 0ea23a03ce Merge branch 'feat/idempotency' into 'develop'
Status posting Idempotency

See merge request pleroma/pleroma-fe!1194
2020-07-23 13:17:44 +00:00
Shpuld Shpuldson fdbacba36a resume last lasttime instead of always friends/public 2020-07-23 15:09:32 +03:00
eugenijm 2298ad0011 Use bock-scroll-lock directive for the settings modal 2020-07-23 12:15:44 +03:00
eugenijm 61dd1a3b49 Add body 100% width for the preview, refactor the modalActivated watcher, use body scroll lock for the setting tab content 2020-07-23 09:53:51 +03:00
eugenijm dac075c61a Fix mobile setting modal behavior: ensure the mobile browser address bar doesn't overlap the modal top panel. 2020-07-23 09:53:51 +03:00
Shpuld Shpuldson 0546326b22 update icon to hopefully look fine on both browsers 2020-07-22 09:43:22 +03:00
HJ 25a015b471 Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1190
2020-07-21 13:10:08 +00:00
Karol Kosek c7966a2028 Translated using Weblate (Polish)
Currently translated at 92.5% (613 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/pl/
2020-07-21 09:11:21 +00:00
Karol Kosek 537c1fd817 Translated using Weblate (Polish)
Currently translated at 92.4% (612 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/pl/
2020-07-21 09:11:21 +00:00
Tirifto dee8f7febe Translated using Weblate (Esperanto)
Currently translated at 72.6% (481 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/eo/
2020-07-21 09:11:21 +00:00
Tirifto 51fd4bd5ba Translated using Weblate (Esperanto)
Currently translated at 68.2% (452 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/eo/
2020-07-21 09:11:21 +00:00
Tirifto 6e49afcac4 Translated using Weblate (Esperanto)
Currently translated at 60.1% (398 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/eo/
2020-07-21 09:11:21 +00:00
Tirifto 70129c934c Translated using Weblate (Esperanto)
Currently translated at 60.1% (398 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/eo/
2020-07-21 09:11:21 +00:00
Ben Is 9db096fa69 Translated using Weblate (Japanese)
Currently translated at 81.8% (542 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ja_EASY/
2020-07-21 09:11:21 +00:00
Ben Is 18e2396da4 Translated using Weblate (Hebrew)
Currently translated at 48.7% (323 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/he/
2020-07-21 09:11:21 +00:00
Ben Is 89379b10cc Translated using Weblate (Irish)
Currently translated at 29.4% (195 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ga/
2020-07-21 09:11:21 +00:00
Haelwenn (lanodan) Monnier 2fe8b3937b Translated using Weblate (French)
Currently translated at 89.8% (595 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/fr/
2020-07-21 09:11:21 +00:00
Ben Is 83962f90c8 Translated using Weblate (Basque)
Currently translated at 77.3% (512 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/eu/
2020-07-21 09:11:21 +00:00
Ben Is 98d2160303 Translated using Weblate (Esperanto)
Currently translated at 49.5% (328 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/eo/
2020-07-21 09:11:21 +00:00
Ben Is bfb768cdde Translated using Weblate (German)
Currently translated at 68.7% (455 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-07-21 09:11:21 +00:00
Ben Is c0144b5187 Translated using Weblate (Czech)
Currently translated at 55.1% (365 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/cs/
2020-07-21 09:11:21 +00:00
Ben Is 10fce88f37 Translated using Weblate (Catalan)
Currently translated at 29.0% (192 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ca/
2020-07-21 09:11:21 +00:00
Ben Is 8f61892833 Translated using Weblate (Italian)
Currently translated at 100.0% (662 of 662 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-07-21 09:11:21 +00:00
Karol Kosek 9ff59ad113 Translated using Weblate (Polish)
Currently translated at 92.1% (614 of 666 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/pl/
2020-07-21 09:11:21 +00:00
Shpuld Shpludson f0e296296c Merge branch 'feat/show-fav-rt-lists-on-hover' into 'develop'
Feat/show fav rt lists on hover

See merge request pleroma/pleroma-fe!1196
2020-07-21 09:11:11 +00:00
Shpuld Shpludson 4528c5c982 Merge branch 'feat/more-title-attributes-in-status-header' into 'develop'
Add title attributes to more things in status header

See merge request pleroma/pleroma-fe!1195
2020-07-20 14:37:21 +00:00
Shpuld Shpludson a06ab08f96 Merge branch 'desktop-notifications-for-chat' into 'develop'
Desktop chat notifications

Closes #893

See merge request pleroma/pleroma-fe!1185
2020-07-20 14:34:50 +00:00
Shpuld Shpludson e0add7a44e Merge branch 'features/favicons' into 'develop'
status.vue: Add support for favicons

See merge request pleroma/pleroma-fe!1076
2020-07-20 14:06:16 +00:00
Shpuld Shpludson 92377beab1 Merge branch 'fix-notification-polling' into 'develop'
Fix the notifications polling when all the latest notifications are unread

Closes #896

See merge request pleroma/pleroma-fe!1198
2020-07-20 08:22:51 +00:00
eugenijm 5b9d22e2c5 Fix the notifications polling when all the latest notifications are unread 2020-07-17 18:27:47 +03:00
lain 067caacb29 Merge branch 'fix/reply-filtering-limit-to-friends-public' into 'develop'
Fix #898 and #900 Limit reply filtering to home and public timelines

Closes #900 and #898

See merge request pleroma/pleroma-fe!1197
2020-07-17 10:36:06 +00:00
Shpuld Shpuldson f28ba3dfdb Limit reply filtering to home and public 2020-07-17 09:33:18 +03:00
Shpuld Shpuldson caae2668ce add 16 users cap to user list popover 2020-07-16 18:08:00 +03:00
Shpuld Shpuldson 85e09d81c7 update changelog 2020-07-16 17:47:26 +03:00
Shpuld Shpuldson 8e0414dc7c change class names to not have reacts in them 2020-07-16 17:45:07 +03:00
Shpuld Shpuldson 6079301ec4 Move user list from reactions to its own component, make favs and rts use it 2020-07-16 17:42:16 +03:00
Shpuld Shpuldson 717adf22cc add title to user card account name too 2020-07-16 11:10:45 +03:00
Shpuld Shpuldson 476954295e add title attributes to more things in status header 2020-07-16 11:00:18 +03:00
Shpuld Shpuldson bca77ef97f update changelog 2020-07-16 10:50:03 +03:00
Shpuld Shpuldson 0c7c24d3d1 make idempotency watch entire status 2020-07-16 10:18:18 +03:00
Shpuld Shpludson 950ae6d89a Merge branch 'reactions-search' into 'develop'
make reactions-search case-insensitive

See merge request pleroma/pleroma-fe!1189
2020-07-16 06:04:49 +00:00
Shpuld Shpludson 0478e82bab Merge branch 'refactor/notification_settings_more' into 'develop'
Improve layout of the Notifications Filters section

See merge request pleroma/pleroma-fe!1192
2020-07-15 15:06:29 +00:00
Mark Felder 149941828f Improve layout of the Notifications Filters section 2020-07-15 09:56:46 -05:00
feld 1454d33e53 Merge branch 'refactor/notification_settings' into 'develop'
Updated Notification Settings API use

See merge request pleroma/pleroma-fe!1124
2020-07-15 14:29:37 +00:00
Mark Felder d4dfc5dcdf JSON and the Quest For Punishing Trailing Commas 2020-07-15 09:02:42 -05:00
Mark Felder 49aaf9dc57 Merge branch 'develop' into refactor/notification_settings 2020-07-15 09:01:40 -05:00
HJ fd94dba6d0 Merge branch 'fix/stabilize-input-bux' into 'develop'
fix: stabilize post status form input box

See merge request pleroma/pleroma-fe!1191
2020-07-15 13:49:21 +00:00
Shpuld Shpuldson d85904f42f add extra sanity stuff to make input boxes more stable 2020-07-15 16:36:06 +03:00
Shpuld Shpuldson 89a677f5e8 add basic idempotency support 2020-07-15 16:19:57 +03:00
Shpuld Shpuldson 51b235f7aa adjust icon use 2020-07-15 12:26:14 +03:00
Shpuld Shpuldson 3cac26a2ac Merge branch 'develop' into fix/preview-styles-polish 2020-07-15 08:30:32 +03:00
Dym Sohin 00d662c4a0 make reactions-search case-insensitive 2020-07-14 19:47:02 +00:00
Shpuld Shpludson 297a0c1f7f Merge branch 'fix-chrome69' into 'develop'
Fix chrome 69 + some theme data related to chats

Closes #890

See merge request pleroma/pleroma-fe!1183
2020-07-14 13:13:23 +00:00
HJ 5af0949491 Apply suggestion to src/services/theme_data/theme_data.service.js 2020-07-14 13:04:00 +00:00
HJ 6fe0751037 Merge branch 'cleanup/remove-stupid-optns' into 'develop'
Cleanup/remove stupid options

See merge request pleroma/pleroma-fe!1187
2020-07-14 12:47:10 +00:00
Shpuld Shpuldson 97a01b057d mention removed options in changelog 2020-07-14 15:35:39 +03:00
Shpuld Shpuldson 89d1fb5cf4 remove remaining autoload i18n 2020-07-14 15:15:02 +03:00
Shpuld Shpuldson b8c1b25397 remove most of reply preview and autoload related stuff 2020-07-14 15:08:04 +03:00
Shpuld Shpuldson 7859c5fe4b polish preview styles a bit 2020-07-14 14:43:26 +03:00
Shpuld Shpuldson 3598360ce5 fix missed conflict 2020-07-14 11:54:03 +03:00
Shpuld Shpuldson cac1418aff fix merge conflicts 2020-07-14 11:44:06 +03:00
Mark Felder 8fcd37a3b9 Remove i18n entries that are deprecated or need to be retranslated 2020-07-13 13:29:44 -05:00
Mark Felder da94935aaa Merge branch 'develop' into refactor/notification_settings 2020-07-13 13:25:23 -05:00
eugenijm 54dea24bb8 Do not display desktop chat notification when the chat is focused 2020-07-13 15:48:19 +03:00
eugenijm 2c35afeebf Add desktop chat notifications 2020-07-13 06:27:51 +03:00
Henry Jameson 40e3595ad3 fix chrome69 2020-07-12 16:21:43 +03:00
HJ 3e09a708f6 Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1182
2020-07-12 10:56:37 +00:00
Ben Is cf0ee51821 Translated using Weblate (Italian)
Currently translated at 100.0% (667 of 667 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-07-12 10:47:02 +00:00
HJ 4cdfe4235d Merge branch 'feat/add-you-to-last-message' into 'develop'
fix #894 add simple You: to chat list last message

Closes #894

See merge request pleroma/pleroma-fe!1181
2020-07-12 10:46:52 +00:00
Shpuld Shpuldson 4a6300bbee add simple You: to chat list last message 2020-07-12 11:11:12 +03:00
HJ 0c5e4f89e3 Merge branch 'develop' into 'develop'
fixed checkbox using emoji instead of dingbat

See merge request pleroma/pleroma-fe!1180
2020-07-11 11:48:05 +00:00
Dym Sohin a754e89eed fixed checkbox using emoji instead of dingbat 2020-07-11 06:39:44 +00:00
Shpuld Shpludson 7d5d3e4d7b Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1174
2020-07-10 11:07:02 +00:00
Ben Is b32394c2ef Translated using Weblate (Italian)
Currently translated at 100.0% (645 of 645 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-07-10 10:56:44 +00:00
Shpuld Shpuldson dca13d9a3b Translated using Weblate (Finnish)
Currently translated at 100.0% (645 of 645 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/fi/
2020-07-10 10:56:44 +00:00
Ben Is 053af7b385 Translated using Weblate (Italian)
Currently translated at 100.0% (641 of 641 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-07-10 10:56:44 +00:00
Ben Is 8606cedfb5 Translated using Weblate (Italian)
Currently translated at 93.9% (602 of 641 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-07-10 10:56:44 +00:00
lain 4c59a17499 Merge branch 'fix/ctrl-enter-before-chat-loading-breaks' into 'develop'
fix #891 Fix early send killing the chat

Closes #891

See merge request pleroma/pleroma-fe!1178
2020-07-10 10:56:39 +00:00
lain fd37957abd Merge branch 'feat/add-chat-notification-dot-mobilenav' into 'develop'
add simple red dot for chat notifs on mobile

See merge request pleroma/pleroma-fe!1177
2020-07-10 10:55:39 +00:00
Shpuld Shpuldson 5734dd05a0 change misnamed member field check to actually disable sending 2020-07-10 13:48:37 +03:00
Shpuld Shpuldson 0038a9794b add simple red dot for chat notifs 2020-07-10 13:21:42 +03:00
HJ c1a20079be Merge branch 'direct-conversations' into 'develop'
Chats

Closes #201

See merge request pleroma/pleroma-fe!1019
2020-07-10 09:04:45 +00:00
Shpuld Shpuldson 78ec84477e fix pixel offset hack in panel heading 2020-07-09 12:49:16 +03:00
Shpuld Shpludson d2f0e4e7d5 Merge branch '888-fix-utf8-base64-decoding' into 'develop'
After Store: Correctly decode UTF-8 encoded base64 strings.

Closes #888

See merge request pleroma/pleroma-fe!1176
2020-07-09 09:29:20 +00:00
lain 905de12980 After Store: Correctly decode UTF-8 encoded base64 strings. 2020-07-09 11:20:07 +02:00
HJ 2b3c4ccfae Merge branch 'fix/notifications-jumping-based-on-time' into 'develop'
Fix notifications (favs and rts) jumping based on timeago width

See merge request pleroma/pleroma-fe!1175
2020-07-08 12:57:31 +00:00
Eugenij b756c83e8d Apply suggestion to src/components/chat_list/chat_list.vue 2020-07-08 15:21:31 +03:00
eugenijm 3b2dfcaf5c Add the single-line prop to StatusContent and use it for chat list items 2020-07-08 15:21:31 +03:00
eugenijm fc865d3a12 Remove direct style manipulations in favor of classes 2020-07-08 15:21:31 +03:00
eugenijm ed7310c04b Undo the promise rejection on the json parser error in promisedRequest
to keep the existing behavior in case some parts of the code rely on it
and to limit the overall scope of the changes.
2020-07-08 15:21:31 +03:00
eugenijm 18a1f5d62a Add the empty chat list placeholder.
Do not use full height when displaying the chat list.
Remove an unsued chat action.
2020-07-08 15:21:31 +03:00
eugenijm 45901c8da6 Disable status preview in the chat posting form 2020-07-08 15:21:31 +03:00
eugenijm f05f832bff Address feedback
Use more specific css rules for the emoji dimensions in the chat list status preview.

Use more round em value for chat list item height.
Add global html overflow and height for smoother chat navigation in
the desktop Safari.

Use offsetHeight instad of a computed style when setting the window height on resize.

Remove margin-bottom from the last message to avoid occasional layout shift in the desktop Safari

Use break-word to prevent chat message text overflow

Resize and scroll the textarea when inserting a new line on ctrl+enter

Remove fade transition on route change

Ensure proper border radius at the bottom of the chat, remove unused border-radius

Prevent the chat header "jumping" on the avatar load.
2020-07-08 15:21:31 +03:00
eugenijm aa2cf51c05 Add Chats 2020-07-08 15:21:31 +03:00
Shpuld Shpuldson 60921be5a0 give notification timeago a min width 2020-07-08 14:11:42 +03:00
Shpuld Shpludson a0ddcbdf5b Merge branch 'patch-1' into 'develop'
corrected tos.html location

See merge request pleroma/pleroma-fe!1173
2020-07-08 10:18:28 +00:00
lain 4f96418143 Merge branch 'wyatt777/pleroma-fe-issue-353' into 'develop'
Allow remove of banner, avatar images issue #353 v2

See merge request pleroma/pleroma-fe!1156
2020-07-08 10:11:17 +00:00
Shpuld Shpludson eea002e6f5 streamline profile image api, update reset ui for all profile images to match avatar, remove unnecessary stuff 2020-07-08 10:11:17 +00:00
Shpuld Shpuldson 5186929efa add hack to not overlap menu with timeline header 2020-07-08 10:42:40 +03:00
Dym Sohin 1767ee01d0 corrected tos.html location 2020-07-07 19:34:45 +00:00
Shpuld Shpuldson cde6acdbbd update navigation, rename timeline link in nav panel to timelines 2020-07-07 18:20:37 +03:00
Shpuld Shpuldson ddde05771f update with bookmarks, clean up some code 2020-07-07 17:34:35 +03:00
Shpuld Shpludson 7206fee437 Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1169
2020-07-07 13:03:12 +00:00
Ben Is 0ab6d92ab8 Translated using Weblate (Italian)
Currently translated at 85.2% (545 of 639 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-07-07 12:53:55 +00:00
Ben Is a6606df01a Translated using Weblate (Chinese (Simplified))
Currently translated at 88.2% (564 of 639 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/zh_Hans/
2020-07-07 12:53:55 +00:00
Ben Is 5b48954644 Translated using Weblate (Italian)
Currently translated at 75.1% (480 of 639 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-07-07 12:53:55 +00:00
Ben Is e9be206a65 Translated using Weblate (Finnish)
Currently translated at 95.4% (610 of 639 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/fi/
2020-07-07 12:53:55 +00:00
Shpuld Shpludson 8f87a36a5a Merge branch '884-about-panel-optimizations' into 'develop'
StaffPanel: Move staff loading to panel creation.

Closes #884

See merge request pleroma/pleroma-fe!1171
2020-07-07 12:53:49 +00:00
Shpuld Shpludson 7d881d97fe Apply suggestion to src/components/staff_panel/staff_panel.js 2020-07-07 12:44:29 +00:00
lain 431b3f527d StaffPanel: Move staff loading to panel creation. 2020-07-07 14:39:43 +02:00
Shpuld Shpludson ec26ff04ce Merge branch 'xenofem/pleroma-fe-rebased-image-description-authoring' into 'develop'
media description authoring v3

See merge request pleroma/pleroma-fe!1161
2020-07-07 12:14:58 +00:00
Shpuld Shpludson 0fe5d4cf10 Merge branch 'develop' into 'xenofem/pleroma-fe-rebased-image-description-authoring'
# Conflicts:
#   src/components/post_status_form/post_status_form.js
2020-07-07 12:05:30 +00:00
Shpuld Shpludson bbb977a044 Merge branch '885-fix-preloading-html' into 'develop'
After Store: Correctly handle preloaded HTML

Closes #885

See merge request pleroma/pleroma-fe!1170
2020-07-07 11:59:36 +00:00
lain 7c9ba8995c Apply suggestion to src/boot/after_store.js 2020-07-07 11:47:01 +00:00
Shpuld Shpuldson f7f8181dcf fix preview opening automatically 2020-07-07 14:46:53 +03:00
lain 1f97f36e1c After Store: Correctly handle preloaded HTML 2020-07-07 13:17:25 +02:00
Shpuld Shpuldson 97c62587d0 remove console log 2020-07-07 11:34:40 +03:00
Shpuld Shpuldson 84d728b970 fix issue on posting another status 2020-07-07 11:31:24 +03:00
Shpuld Shpuldson e3991db5c4 fix conflicts and add error handling to media descriptions 2020-07-07 10:01:37 +03:00
Shpuld Shpuldson f4bdbf7691 remove unnecessary code 2020-07-07 09:08:50 +03:00
Shpuld Shpuldson b4709f93d4 add more ways to set description 2020-07-07 09:07:20 +03:00
Shpuld Shpuldson d55c09df18 fix edge case of videos and unknown files together 2020-07-06 15:06:13 +03:00
Shpuld Shpuldson 87b5f828d7 fix conflicts, use file icon instead of link, add support for audio and file icon in cw'd posts 2020-07-06 14:01:03 +03:00
Shpuld Shpuldson 4d25be7cb3 fix modal types check being broken 2020-07-06 13:42:33 +03:00
Shpuld Shpludson 9ccc6174a7 Merge branch 'feat/rich-text-preview' into 'develop'
Status preview #459

See merge request pleroma/pleroma-fe!1159
2020-07-06 10:17:26 +00:00
Shpuld Shpuldson ada5a3806b don't close preview on post, move visibility notices above the preview where they belong 2020-07-06 11:36:35 +03:00
Shpuld Shpuldson 7bdc3d6ce9 remove contenttype check from content type watcher 2020-07-06 11:12:33 +03:00
Shpuld Shpuldson f254a847d2 move translation strings to correct place, translate error message 2020-07-06 10:53:03 +03:00
Shpuld Shpuldson 105254d73d Merge branch 'feat/rich-text-preview' of git.pleroma.social:pleroma/pleroma-fe into feat/rich-text-preview 2020-07-06 10:46:23 +03:00
Shpuld Shpuldson afdc3f96f0 fix conflicts, make subject update the preview 2020-07-06 10:45:47 +03:00
Shpuld Shpludson b761bcf333 Merge branch 'fix/remove-extra-notifications-fetch' into 'develop'
Fix: notifications fetcher double fetching on every tick

See merge request pleroma/pleroma-fe!1164
2020-07-05 07:03:27 +00:00
Shpuld Shpludson 9178908c1e Apply suggestion to src/components/notifications/notifications.js 2020-07-05 06:54:12 +00:00
Shpuld Shpludson af4a26b588 Merge branch 'feat/allow-use-without-cookies' into 'develop'
Fix #815: Allow use without cookies, Add a global notice popup system

Closes #815

See merge request pleroma/pleroma-fe!1166
2020-07-05 06:53:29 +00:00
Shpuld Shpludson acc3b083aa Merge branch 'feat/parent-visible-support' into 'develop'
#874 Add strike-through to "Reply to" when parent_visible is set to false

See merge request pleroma/pleroma-fe!1162
2020-07-05 06:15:35 +00:00
HJ 5b8875e3d8 Merge branch 'fix-emoji-panel-height' into 'develop'
Fix emoji panel for settings modal

See merge request pleroma/pleroma-fe!1168
2020-07-04 23:53:39 +00:00
HJ b448871d23 Merge branch 'fix/allow-over-scrolling-on-bottom-mobile' into 'develop'
Fix #854: allow overscrolling enough to not have FAB block interactables

Closes #854

See merge request pleroma/pleroma-fe!1165
2020-07-04 20:41:54 +00:00
kPherox 9b40cf43d8 fix height for emoji panel of settings modal 2020-07-04 18:42:15 +09:00
Shpuld Shpludson 76de4a3132 Merge branch 'bookmarks' into 'develop'
Bookmarks

Closes #472

See merge request pleroma/pleroma-fe!903
2020-07-03 19:45:49 +00:00
Eugenij de291e2e33 Add bookmarks
Co-authored-by: jared <jaredrmain@gmail.com>
2020-07-03 19:45:49 +00:00
Shpuld Shpuldson 77bb31b474 remove extra computed from nav panel 2020-07-03 13:26:11 +03:00
lain 7bd89b579f Merge branch 'fix_BE_1586_provide_index_md' into 'develop'
FE part of BE issue 1586 provide index md

See merge request pleroma/pleroma-fe!1154
2020-07-03 10:17:42 +00:00
Ilja 08b593746f FE part of BE issue 1586 provide index md
* I added an index.md which will be the landing page for the docs. It has an explanation of Pleroma-FE from the user point of view
* See also BE MR: https://git.pleroma.social/pleroma/pleroma/-/merge_requests/2669
* And issue: https://git.pleroma.social/pleroma/pleroma/-/issues/1586
2020-07-03 10:17:42 +00:00
Shpuld Shpuldson ebe885953c change more opinionated translation 2020-07-03 12:59:30 +03:00
Shpuld Shpuldson 21d1f557f6 change styles for the dropdown, make things work nicely on mobile 2020-07-03 12:56:31 +03:00
Shpuld Shpuldson 961099d34a basic version done, needs cleanup from nav panel styles 2020-07-02 18:03:02 +03:00
Shpuld Shpuldson 9cac5d94dd change alert popup alpha 2020-07-02 15:17:58 +03:00
Shpuld Shpuldson 150516df4e Merge branch 'xenofem/pleroma-fe-rebased-image-description-authoring' of git.pleroma.social:pleroma/pleroma-fe into xenofem/pleroma-fe-rebased-image-description-authoring 2020-07-02 14:31:07 +03:00
Shpuld Shpuldson b8ed904c0c change px to em 2020-07-02 14:28:35 +03:00
Shpuld Shpludson e1cf6bd41c Merge branch 'fix/follow-requests-usercard-crash' into 'develop'
Fix #877 Follow request user card crash

Closes #877

See merge request pleroma/pleroma-fe!1167
2020-07-02 09:28:57 +00:00
Shpuld Shpuldson f0668c9ff8 add follow request users to store 2020-07-02 12:19:33 +03:00
Shpuld Shpuldson 685ab4f33e make the addNotice dispatch return the notice 2020-07-02 10:46:43 +03:00
Shpuld Shpuldson 1293bec77e change storage error one-off into a global notice system 2020-07-02 10:40:41 +03:00
Shpuld Shpludson c0fb35b106 Merge branch 'develop' into 'xenofem/pleroma-fe-rebased-image-description-authoring'
# Conflicts:
#   CHANGELOG.md
2020-07-02 04:50:03 +00:00
Shpuld Shpuldson 0997e5ff66 remove accidental log 2020-07-01 19:25:31 +03:00
Shpuld Shpuldson 15d492ace4 revert accidental change in instance.js 2020-07-01 19:24:17 +03:00
Shpuld Shpuldson 43b7a5d9b3 update the message and changelog 2020-07-01 19:22:39 +03:00
Shpuld Shpuldson d30b0b28c9 catch localforage error and let the application work, add an alert for user to dismiss 2020-07-01 19:15:28 +03:00
Shpuld Shpuldson 3ebd4e4429 document the 'mark-as-read-detection' system 2020-07-01 17:55:42 +03:00
Shpuld Shpuldson ca997f45e8 allow overscrolling enough to not have FAB block interactables 2020-07-01 15:56:45 +03:00
lain beb160bd53 Merge branch 'fix/use-backend-reply-filtering' into 'develop'
Make use of backend reply filtering

See merge request pleroma/pleroma-fe!1163
2020-07-01 12:46:42 +00:00
Shpuld Shpuldson a3e370e9f8 add initial fetching back in a more streamlined way 2020-07-01 15:19:45 +03:00
Shpuld Shpuldson 62d0bc47b3 remove unnecessary fetchAndUpdate, change notifications fetcher to not double fetch 2020-07-01 14:15:04 +03:00
Shpuld Shpuldson 38d8526660 change Show New text to Reload when flushing 2020-06-30 17:37:36 +03:00
Shpuld Shpuldson ea09bbecf8 Make use of backend reply filtering 2020-06-30 17:02:38 +03:00
Shpuld Shpuldson 3a79918b89 update changelog for reply-to strikethrough 2020-06-30 15:23:47 +03:00
Shpuld Shpuldson ee1364a167 add no-statusId support for status popover 2020-06-30 15:15:27 +03:00
Shpuld Shpuldson 6529f9fa34 add strikethrough when parent isn't visible 2020-06-30 15:04:16 +03:00
Shpuld Shpuldson 0ca3a6e63d fix gallery-videos being weirdly offset 2020-06-30 11:02:10 +03:00
Shpuld Shpludson a290f53694 Merge branch 'develop' into 'feat/rich-text-preview'
# Conflicts:
#   src/i18n/en.json
2020-06-29 13:20:53 +00:00
Shpuld Shpuldson a09709f71d update changelog with media-descriptions and the fixes 2020-06-29 15:37:46 +03:00
Shpuld Shpuldson 79011e02ef Merge branch 'develop' into xenofem/pleroma-fe-rebased-image-description-authoring 2020-06-29 15:19:41 +03:00
Shpuld Shpuldson 8c2335f84f conflict whem merging xenofem changes 2020-06-29 15:03:51 +03:00
Shpuld Shpuldson 96d2c86d3b change placeholders to use descriptions, use icons with placeholders, change uploads to use attachment component 2020-06-29 14:48:22 +03:00
lain 82944f862d Merge branch '812-subject-separation' into 'develop'
StatusContent: Better separate subject from status content.

Closes #812

See merge request pleroma/pleroma-fe!1150
2020-06-29 07:53:12 +00:00
xenofem 529d72c297 suppress enter key on media description input field 2020-06-28 16:05:22 -04:00
Shpuld Shpuldson 61f34ff361 remove panel-footer in userpanel, simplify preview header, fix word-wrap in preview 2020-06-28 16:40:39 +03:00
Shpuld Shpuldson ed5b36f751 make line sizes match for errors/loading and statuses, make X hitbox bigger, remove attachments, add shorter custom message for empty status preview. fix auto update triggering 2020-06-28 15:43:08 +03:00
Shpuld Shpuldson 3c47036101 add automatic updating 2020-06-28 15:14:01 +03:00
lain d0c9aef668 Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1158
2020-06-28 12:00:23 +00:00
Shpuld Shpuldson 0287d5c07e update changelog with status previews 2020-06-28 13:29:07 +03:00
Shpuld Shpuldson 6e2de20367 show preview with plaintext too 2020-06-28 13:17:04 +03:00
Shpuld Shpuldson cecf3d4f89 remove unnecessary type=button 2020-06-28 13:03:25 +03:00
Shpuld Shpuldson 9d6f233202 fix preview attachments getting wrong styles 2020-06-28 12:48:17 +03:00
Shpuld Shpuldson 3d5da3caf9 update styles in preview heading 2020-06-28 12:35:05 +03:00
Shpuld Shpuldson 223fabfe90 add rich text preview 2020-06-28 12:16:41 +03:00
xenofem 03aa1f3154 fix vue style warnings 2020-06-28 02:31:57 -04:00
Fristi 195e83d0c8 Translated using Weblate (Dutch)
Currently translated at 100.0% (626 of 626 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
2020-06-28 06:24:57 +00:00
xenofem 1ff55e17b1 fix invalid end tag on input element 2020-06-28 02:16:58 -04:00
William Pitcock 2c6ec37e6a media description support 2020-06-28 02:10:06 -04:00
lain 391f796cb4 Merge branch 'feat/preload-data-from-html' into 'develop'
Add basic preloading for nodeinfo/config

See merge request pleroma/pleroma-fe!1155
2020-06-27 10:21:06 +00:00
Shpuld Shpuldson 46cf50a4d6 rename variable requestJson when it's not actually json 2020-06-27 12:59:24 +03:00
Shpuld Shpuldson a8cb5e71d9 don't block ui with stickers or tos 2020-06-27 12:32:01 +03:00
Shpuld Shpuldson 5ab62c4cb8 make use of api/v1/instance 2020-06-27 12:26:19 +03:00
Shpuld Shpludson 4d52b8bf56 Merge branch 'remove-twitterapi-config' into 'develop'
Remove StatusNet config endpoint usage

See merge request pleroma/pleroma-fe!1084
2020-06-27 09:19:43 +00:00
Shpuld Shpuldson 58f9c1cc49 fix conflict in after_store 2020-06-27 12:10:02 +03:00
Shpuld Shpuldson 3113d904b9 update changelog with profile fields 2020-06-27 10:34:28 +03:00
Shpuld Shpludson c658f57abb Merge branch 'iss-149/profile-fields-setting' into 'develop'
Profile fields setting

See merge request pleroma/pleroma-fe!997
2020-06-27 07:32:59 +00:00
Shpuld Shpludson 8150899a73 Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1152
2020-06-27 07:21:32 +00:00
translate 4528b31256 Translated using Weblate (Italian)
Currently translated at 72.7% (452 of 621 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-06-27 07:21:32 +00:00
Shpuld Shpludson ea0a12f604 Merge branch 'develop' into 'iss-149/profile-fields-setting'
# Conflicts:
#   src/components/settings_modal/tabs/profile_tab.vue
2020-06-27 07:19:49 +00:00
Mark Felder 2eda3d687e Sync up key names properly 2020-06-26 17:02:17 -05:00
Mark Felder 6606b57f44 Update english i18n 2020-06-26 16:59:22 -05:00
Mark Felder caf9a2a8f6 Revert i18n to develop 2020-06-26 16:56:57 -05:00
Mark Felder 5321415641 Update notification filters to reflect new settings 2020-06-26 16:56:31 -05:00
Shpuld Shpuldson 8c3106c588 Change the show/hide strings about, remove subjected status toggle when 'collapse' option not used 2020-06-26 18:20:32 +03:00
Shpuld Shpuldson a2c5175d14 add basic preloading for nodeinfo/config 2020-06-26 14:47:02 +03:00
Shpuld Shpuldson 44edb730c1 rip restyle subject, fix some issues with long subject 2020-06-26 14:07:39 +03:00
Mark Felder 8e1e7144fd Change notification settings keys to new names, remove the defunct non_follows key. 2020-06-25 15:34:48 -05:00
Mark Felder 199fc9351d Merge branch 'develop' into refactor/notification_settings 2020-06-25 15:28:17 -05:00
lain 504d48fc5a After Store: Remove last statusnet vestiges. 2020-06-24 17:54:35 +02:00
lain 143da55c56 Merge branch 'develop' of git.pleroma.social:pleroma/pleroma-fe into remove-twitterapi-config 2020-06-24 17:50:05 +02:00
lain bbb91d8ae3 Merge branch 'feature/bot-flag' into 'develop'
Add 'Bot' settings option and badge

Closes #832

See merge request pleroma/pleroma-fe!1153
2020-06-22 12:51:51 +00:00
Sergey Suprunenko 2b6d16900b Use more correct class name for user badges 2020-06-21 15:36:20 +02:00
Sergey Suprunenko c04f8b2ccb Show a 'bot' badge on the service account page 2020-06-21 15:06:07 +02:00
Sergey Suprunenko 2451956d83 Add 'bot' flag to Profile settings 2020-06-21 14:59:05 +02:00
lain 1afa0f0044 Merge branch 'notification-unification-experiments' into 'develop'
Notification unification / WebPush i18n.

See merge request pleroma/pleroma-fe!1142
2020-06-19 13:34:04 +00:00
lain ed908e0195 Changelog: Add info about push notifications. 2020-06-19 15:25:57 +02:00
lain aa725010c5 ServiceWorker: Use clearer variable names 2020-06-19 15:24:06 +02:00
HJ 267102809d Merge branch 'fix-username-emoji' into 'develop'
fix huge emoji in usernames

See merge request pleroma/pleroma-fe!1151
2020-06-19 13:15:22 +00:00
Henry Jameson 5aa65e32ef fix huge emoji in usernames 2020-06-19 16:09:44 +03:00
kPherox bad3dacfac implement user suggestor for profile fields 2020-06-19 21:18:36 +09:00
kPherox fd2157efe4 Fix emoji input error 2020-06-19 20:51:53 +09:00
lain 4da0a0c0bf StatusContent: Fix greentext. 2020-06-19 12:49:42 +02:00
lain a79bad5cdb StatusContent: Better separate subject from status content. 2020-06-19 12:46:48 +02:00
Shpuld Shpludson 95f5c7fff6 Merge branch 'iss-149/profile-fields-display' into 'develop'
Display profile fields

See merge request pleroma/pleroma-fe!1004
2020-06-19 08:04:54 +00:00
Shpuld Shpuldson c4340ad467 move favicon 2020-06-18 17:09:30 +03:00
Shpuld Shpuldson 8a9654b511 Merge branch 'develop' into features/favicons 2020-06-18 16:47:37 +03:00
Shpuld Shpuldson 6d2befa452 move favicon to name 2020-06-18 16:47:32 +03:00
Shpuld Shpludson 6343ee929c Merge branch '589-poll-options' into 'develop'
Polls: Construct an html field during normalization.

Closes #589

See merge request pleroma/pleroma-fe!1146
2020-06-18 13:33:00 +00:00
lain b0b33be9dd Poll, StatusContent: Move emoji css to status body. 2020-06-18 14:41:40 +02:00
lain 2ea5bff3a7 Merge branch 'feat/tweak-autocomplete-search' into 'develop'
Autocomplete tweaks: remove search blocker, fix debounce params #869

See merge request pleroma/pleroma-fe!1149
2020-06-18 11:29:50 +00:00
Shpuld Shpuldson 4e0b4427a9 remove recentqueries thing 2020-06-18 14:19:21 +03:00
kPherox eed58a7b97 fix width of long field value 2020-06-18 20:13:44 +09:00
kPherox bb513dd961 change key-value to look like one 2020-06-18 20:08:00 +09:00
Shpuld Shpuldson f1175b4e9b revert import style 2020-06-18 13:41:04 +03:00
Shpuld Shpuldson fa7bcb74ce return the correct promise type from action 2020-06-18 13:00:09 +03:00
Shpuld Shpuldson abbad84b00 update changelog 2020-06-18 12:56:22 +03:00
Shpuld Shpuldson 9c884fef11 remove search blocker, fix debounce params 2020-06-18 12:29:13 +03:00
Shpuld Shpludson ebf4321e64 Merge branch 'fix/popovers-cutting-off-in-notifications' into 'develop'
Fix popovers cutting off in notifications #809

Closes #809

See merge request pleroma/pleroma-fe!1147
2020-06-18 06:08:07 +00:00
Shpuld Shpuldson 632f62ee0f resume the previous emoji size for now 2020-06-17 18:34:24 +03:00
Shpuld Shpuldson 580fcd3ad9 restyle the fields 2020-06-17 18:26:06 +03:00
Shpuld Shpuldson f8cf92a01f Merge branch 'develop' into kPherox/pleroma-fe-iss-149/profile-fields-display 2020-06-17 11:23:32 +03:00
Shpuld Shpludson 24e47eb604 Update popover.js 2020-06-16 15:12:44 +00:00
Shpuld Shpludson 84a69f9502 Merge branch 'develop' into 'fix/popovers-cutting-off-in-notifications'
# Conflicts:
#   CHANGELOG.md
2020-06-16 14:51:12 +00:00
HJ 57626c125d Merge branch 'fix/disable-subject-when-posting' into 'develop'
disable subject field when posting #868

See merge request pleroma/pleroma-fe!1148
2020-06-16 14:43:48 +00:00
Shpuld Shpuldson c18a8ab308 update changelog 2020-06-16 17:35:12 +03:00
Shpuld Shpuldson fc6ca20a42 disable subject field when posting 2020-06-16 17:34:22 +03:00
Shpuld Shpuldson 5a8a428c15 remove log whoops 2020-06-16 17:30:56 +03:00
Shpuld Shpuldson 5cf1574fdb fix popovers cutting off in notifications, port popover changes from chats mr 2020-06-16 17:20:36 +03:00
lain 14540e2a07 Service Worker: Extract messages to own module. 2020-06-16 13:27:58 +02:00
lain 8427c5a067 Changelog: Add info about poll option emoji. 2020-06-15 09:59:39 +02:00
HJ 8a15900c81 Merge branch 'themes-fixes' into 'develop'
Several small fixes

Closes #866 and #864

See merge request pleroma/pleroma-fe!1145
2020-06-14 13:38:24 +00:00
Henry Jameson c0a4752bd5 changelog 2020-06-14 16:14:32 +03:00
lain 72ee51c85c Apply suggestion to src/services/entity_normalizer/entity_normalizer.service.js 2020-06-14 13:09:14 +00:00
lain d41c9a717c Polls: Construct an html field during normalization. 2020-06-14 13:16:08 +02:00
Henry Jameson ac270d7060 fix #866 2020-06-13 14:42:53 +03:00
Henry Jameson 8cd5041663 add support for defining the greentext 2020-06-13 13:25:56 +03:00
Henry Jameson e4b18deece fix redmond themes 2020-06-13 13:16:53 +03:00
Henry Jameson c13d5b4f1b Fix faint links in notifications 2020-06-13 13:09:55 +03:00
lain 1e57adf6d4 Linting + docs 2020-06-13 11:53:16 +02:00
lain 9bfb3754c1 ServiceWorker: Use loader to only notification messages.
This keeps the translation size very small and makes it easy to
integrate all the languages, as dynamically loading them isn't
easy in the service worker.
2020-06-13 11:48:02 +02:00
lain 7de56a8063 Merge branch 'chore/improve-default-tos' into 'develop'
update terms of service instructions

See merge request pleroma/pleroma-fe!986
2020-06-12 14:26:40 +00:00
lain fd109fa355 Merge branch 'develop' into 'chore/improve-default-tos'
# Conflicts:
#   static/terms-of-service.html
2020-06-12 14:17:56 +00:00
lain 33c1adbef4 Merge branch 'develop' of git.pleroma.social:pleroma/pleroma-fe into notification-unification-experiments 2020-06-12 12:58:01 +02:00
Shpuld Shpludson 48365819d1 Merge branch 'depatchou' into 'develop'
Remove pizza

See merge request pleroma/pleroma-fe!1143
2020-06-11 17:48:15 +00:00
lain 17b6396333 Update lockfile 2020-06-11 19:11:56 +02:00
lain 0c36486299 API: Remove fetch polyfill
All browser except IE have supported this for longer than
Pleroma even exists.
2020-06-11 18:49:39 +02:00
lain 178ff1672d PersistedState: Replace object-path with lodash function
We were loading that one anyway.
2020-06-11 18:44:45 +02:00
lain 8d7d4980b9 StatusParser: Remove unused removeAttachmentLinks.
Brings down the vendor by over 200kb
2020-06-11 18:39:19 +02:00
lain dc7489fd8b Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1140
2020-06-11 14:00:24 +00:00
lain 98819ae32c NotificationUtils: Add tag to notifications. 2020-06-11 15:49:39 +02:00
lain e2ca107e01 ServiceWorker: Don't show message via sw if a client is active. 2020-06-11 15:26:52 +02:00
lain 5b0190bef5 ServiceWorker: Grab the notification and display it with i18n. 2020-06-11 15:22:58 +02:00
lain a52a393266 NotificationUtils: Extract preparation of notification object. 2020-06-11 15:22:31 +02:00
Ben Is c59c0ec4be Translated using Weblate (Italian)
Currently translated at 72.6% (451 of 621 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-06-11 12:48:44 +00:00
lain 4acac3c38c Merge branch 'fix/attachment-sizing-cover-fix' into 'develop'
Fix attachment cover/contain again

See merge request pleroma/pleroma-fe!1141
2020-06-11 12:48:38 +00:00
Shpuld Shpuldson 14348d8ddf fix the contain option 2020-06-11 14:27:36 +03:00
Shpuld Shpuldson 2b6a6fe7ea make object-fit cover work like it should 2020-06-11 13:44:32 +03:00
Shpuld Shpludson 4e3ce18703 Merge branch 'feature/autocomplete-domain-mutes' into 'develop'
Autocomplete domain mutes from list of known instances

Closes #824

See merge request pleroma/pleroma-fe!1103
2020-06-10 11:18:30 +00:00
lain 23b7424913 Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1121
2020-06-10 11:13:29 +00:00
Ben Is 7b9fa32585 Translated using Weblate (Italian)
Currently translated at 68.5% (426 of 621 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-06-10 10:27:55 +00:00
hj ff2cd3d672 Translated using Weblate (Russian)
Currently translated at 60.0% (373 of 621 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
2020-06-10 10:27:55 +00:00
Shpuld Shpludson 02ba992476 Merge branch 'feat/drag-and-drop-improovements' into 'develop'
Add visual indication for drag&dropping files, make dropzone bigger

See merge request pleroma/pleroma-fe!1137
2020-06-10 10:27:48 +00:00
Shpuld Shpuldson ea2b2a35bb add fade-in fade-out 2020-06-10 12:41:02 +03:00
Shpuld Shpuldson 855b8d4dad fix conflict 2020-06-10 11:52:46 +03:00
Shpuld Shpuldson d3187720c5 remove useless captures 2020-06-10 11:49:04 +03:00
Shpuld Shpuldson 1308fdd3bc update changelog 2020-06-10 11:29:28 +03:00
lain 4f66221adc Merge branch 'half-a-ctrl-press' into 'develop'
Attempt to fix that elusive bug with post being sent on copypaste

See merge request pleroma/pleroma-fe!1138
2020-06-10 08:27:01 +00:00
Henry Jameson cad42eec2a changelog 2020-06-10 11:20:40 +03:00
Henry Jameson 3cfdfec72d attempt to fix that one bug with submitting on copy-pasting 2020-06-10 11:18:07 +03:00
Shpuld Shpuldson cd9d732afa add better visual indication for dropping files, make dropzone bigger 2020-06-10 11:01:38 +03:00
kPherox cc4691a4ef Fix merging array field for users 2020-06-10 03:48:49 +09:00
kPherox de9cc033df Add i18n en 2020-06-10 03:48:41 +09:00
kPherox 54fdc22001 Add profile fields form 2020-06-10 03:37:58 +09:00
HJ aa125072b3 Merge branch 'async-language-loading' into 'develop'
Messages: Load languages asynchronously.

See merge request pleroma/pleroma-fe!1136
2020-06-09 09:31:33 +00:00
lain 44db3af0db Messages: DRY things up a bit. 2020-06-09 09:27:57 +02:00
Shpuld Shpludson 951f25707a Merge branch '572-multiple-file-drag-and-drop' into 'develop'
MediaUpload: Allow drag-and-drop of multiple files at once

Closes #572

See merge request pleroma/pleroma-fe!1135
2020-06-08 17:31:40 +00:00
lain d8211392c4 Apply suggestion to CHANGELOG.md 2020-06-08 17:24:08 +00:00
lain d5ec269d88 Update changelog. 2020-06-08 19:14:13 +02:00
lain fbc4889ece Linting. 2020-06-08 18:26:16 +02:00
lain e45f7fe877 MediaUpload: Correctly handle multiple uploads. 2020-06-08 18:22:17 +02:00
lain 99eaec8547 Messages: Load languages asynchronously.
Reduces the size of the initial app bundle by about half.
2020-06-08 17:22:07 +02:00
lain e6a27bcaca MediaUpload: Allow drag-and-drop of multiple files at once 2020-06-08 13:30:16 +02:00
Sergey Suprunenko 1007039478 Autocomplete domain mutes from list of known instances 2020-06-08 13:17:18 +02:00
HJ acbef1ebdc Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Weblate pleroma pleroma fe

See merge request pleroma/pleroma-fe!1134
2020-06-07 15:21:05 +00:00
Henry Jameson 1e554ee287 indent 2 2020-06-07 18:19:34 +03:00
HJ 66b4121dad Merge branch 'fix-cropped-button-shadow' into 'develop'
Fix the cropped button shadow in 2FA settings

See merge request pleroma/pleroma-fe!1133
2020-06-07 13:52:52 +00:00
HJ a5de8db579 Merge branch 'settings-modal' into 'develop'
Settings modal

See merge request pleroma/pleroma-fe!1118
2020-06-06 21:24:47 +00:00
Henry Jameson 68482fd3a6 move helpers out of tabs directory 2020-06-07 00:15:10 +03:00
Henry Jameson 9e3e6b0c69 Merge remote-tracking branch 'origin/develop' into settings-modal
* origin/develop: (22 commits)
  changelog
  alignment fixes
  Update CHANGELOG.md
  StillImage: Make it work properly in both firefox and chrome.
  ReactButton: Change the combined emoji (heart) to a simple one.
  Linting fixes.
  Settings: Keep a local version of the mutedWordsString
  MediaModal: Close on browser navigation events.
  StatusContent: Try to get hashtag from dataset first.
  Docs: Change wrong documentation.
  EntityNormalizerSpec: More fixes.
  EntityNormalizerSpec: Test new behavior.
  EntityNormalizer: Add colons to emoji alt text.
  fixed case in class name
  The sidebarRight option wasn't being read
  Use consistent naming for Pleroma-FE in README
  Remove mention of GNU Social
  lint
  multiple fixes
  fix non-mention notifs
  ...
2020-06-07 00:10:21 +03:00
Karol Kosek 03952832b4 Fix the cropped button shadow in 2FA settings 2020-06-06 22:43:56 +02:00
HJ e47d0f2103 Merge branch 'fix-reprooted-muted-posts' into 'develop'
Fixed some issues with muting

See merge request pleroma/pleroma-fe!1120
2020-06-06 20:31:43 +00:00
Henry Jameson 7d695fc8f2 changelog 2020-06-06 23:22:58 +03:00
Henry Jameson ebf2ce84fd alignment fixes 2020-06-06 23:08:52 +03:00
Ben Is 8b43893c47 Translated using Weblate (Italian)
Currently translated at 65.7% (403 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-06-05 16:13:45 +00:00
Evert Prants 419fdd58a5 Translated using Weblate (Estonian)
Currently translated at 61.5% (377 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/et/
2020-06-05 16:13:45 +00:00
Anonymous 1c4be95384 Translated using Weblate (French)
Currently translated at 99.8% (612 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/fr/
2020-06-05 16:13:45 +00:00
Haelwenn (lanodan) Monnier 49d0a05644 Translated using Weblate (French)
Currently translated at 99.8% (612 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/fr/
2020-06-05 16:13:45 +00:00
Evert Prants f5c4d7d989 Translated using Weblate (Estonian)
Currently translated at 54.3% (333 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/et/
2020-06-05 16:13:45 +00:00
Fristi 790825257f Translated using Weblate (Dutch)
Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
2020-06-05 16:13:45 +00:00
Anonymous 654105d5c1 Translated using Weblate (Dutch)
Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
2020-06-05 16:13:45 +00:00
Ben Is a0d935649b Translated using Weblate (Italian)
Currently translated at 65.0% (399 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-06-05 16:13:45 +00:00
Ben Is e1475d7f57 Translated using Weblate (English)
Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/en/
2020-06-05 16:13:45 +00:00
Ben Is 5048f203a3 Translated using Weblate (Italian)
Currently translated at 64.1% (393 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-06-05 16:13:45 +00:00
Chuculate 18997c2d07 Translated using Weblate (Spanish)
Currently translated at 84.3% (517 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/es/
2020-06-05 16:13:45 +00:00
Ben Is e0e1fe5e20 Translated using Weblate (Italian)
Currently translated at 51.8% (318 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-06-05 16:13:45 +00:00
lain 0a3069aec0 Update CHANGELOG.md 2020-06-05 16:13:41 +00:00
Shpuld Shpludson f6d5922e65 Merge branch '851-fix-squishy-images' into 'develop'
StillImage: Make it work properly in both firefox and chrome.

Closes #851

See merge request pleroma/pleroma-fe!1132
2020-06-05 16:06:19 +00:00
lain 2aabd8ed1a StillImage: Make it work properly in both firefox and chrome. 2020-06-05 17:55:19 +02:00
Shpuld Shpludson 63f237d736 Merge branch '848-change-emoji-reaction-defaults' into 'develop'
ReactButton: Change the combined emoji (heart) to a simple one.

Closes #848

See merge request pleroma/pleroma-fe!1131
2020-06-05 15:36:15 +00:00
lain ba4c189c53 ReactButton: Change the combined emoji (heart) to a simple one. 2020-06-05 17:26:13 +02:00
HJ 0bf2b372d0 Merge branch '790-unable-to-filter' into 'develop'
Settings: Keep a local version of the mutedWordsString

Closes #790

See merge request pleroma/pleroma-fe!1130
2020-06-05 12:03:46 +00:00
lain c4c5568b98 Linting fixes. 2020-06-05 13:51:47 +02:00
lain 282e10e3be Settings: Keep a local version of the mutedWordsString
Without this it was impossible to use newlines
2020-06-05 13:42:39 +02:00
HJ 585bb5727a Merge branch '593-doc-change' into 'develop'
Docs: Change wrong documentation.

Closes #593

See merge request pleroma/pleroma-fe!1127
2020-06-04 16:07:13 +00:00
HJ c678756eba Merge branch '748-hashtag-link-fixes' into 'develop'
StatusContent: Try to get hashtag from dataset first.

Closes #748

See merge request pleroma/pleroma-fe!1128
2020-06-04 16:06:33 +00:00
HJ a6f73d7da5 Merge branch '788-hide-media-viewer-on-navigation' into 'develop'
MediaModal: Close on browser navigation events.

Closes #788

See merge request pleroma/pleroma-fe!1129
2020-06-04 16:00:53 +00:00
HJ 7f755878d5 Merge branch '802-emoji-alt' into 'develop'
EntityNormalizer: Add colons to emoji alt text.

Closes #802

See merge request pleroma/pleroma-fe!1126
2020-06-04 15:59:29 +00:00
lain cb99dc2b27 MediaModal: Close on browser navigation events. 2020-06-04 16:30:28 +02:00
lain d872d55832 StatusContent: Try to get hashtag from dataset first. 2020-06-04 15:50:44 +02:00
lain c0497b6f1f Docs: Change wrong documentation. 2020-06-04 15:34:29 +02:00
lain 05167f202f EntityNormalizerSpec: More fixes. 2020-06-04 15:31:52 +02:00
lain c2dfe1f6cc EntityNormalizerSpec: Test new behavior. 2020-06-04 15:25:00 +02:00
lain f197a2aa39 EntityNormalizer: Add colons to emoji alt text.
This makes it possible to copy them and still have them work.
2020-06-04 15:12:03 +02:00
lain 5ac2c365a7 Merge branch 'update-readme' into 'develop'
Update readme

See merge request pleroma/pleroma-fe!1123
2020-06-02 14:42:10 +00:00
Henry Jameson de3a376beb fixed case in class name 2020-06-02 01:16:10 +03:00
Henry Jameson 4fae2f8ea8 track if settings modal has been opened once 2020-06-02 01:10:52 +03:00
Henry Jameson 94436c1f85 fixed tab not hiding 2020-05-29 13:39:30 +03:00
Henry Jameson 9a20a90932 fixed the remaining issues 2020-05-28 21:28:01 +03:00
HJ f288178b67 Merge branch 'fix/sidebarRight_option' into 'develop'
The sidebarRight option wasn't being read

Closes #858

See merge request pleroma/pleroma-fe!1125
2020-05-28 17:36:05 +00:00
Mark Felder 5f41ee8864 The sidebarRight option wasn't being read 2020-05-28 12:28:32 -05:00
Mark Felder b8350423d9 Updated Notification Settings API use 2020-05-28 10:57:02 -05:00
Francis Dinh 3111fc5dbc Use consistent naming for Pleroma-FE in README 2020-05-28 05:14:11 -04:00
Francis Dinh 6ecd1dd15b Remove mention of GNU Social
Pleroma-FE has not supported GNU Social for a while now. Plus the
wiki page for setting it up has been deleted.
2020-05-28 05:14:01 -04:00
Henry Jameson 5ffcddd3b9 fixes. sorry for bad commit message i'm tired 2020-05-27 03:32:57 +03:00
Henry Jameson 3938ccb8e7 lint 2020-05-27 00:02:36 +03:00
Henry Jameson a8e013bd65 Move modal frame parts away from modal-content into modal, improve error handling 2020-05-26 23:58:55 +03:00
Henry Jameson 5187b37aca moved multiChoiceProperties where it fits better 2020-05-26 22:50:37 +03:00
Henry Jameson 1f205b87ac lint 2020-05-26 01:06:34 +03:00
Henry Jameson 9d09e4090f multiple fixes 2020-05-26 01:01:25 +03:00
Henry Jameson 83e5ee5494 fix non-mention notifs 2020-05-26 00:22:15 +03:00
Henry Jameson b5c1d074f8 fix reprööted posts not being muted properly. fix muted posts making
desktop notifications
2020-05-25 23:38:31 +03:00
Henry Jameson a14635f4f2 oops 2020-05-25 19:57:32 +03:00
Henry Jameson dc8f78e84d fix for a previous fix 2020-05-25 17:22:57 +03:00
Henry Jameson 101f657d36 improve big spinner visibility 2020-05-25 17:21:05 +03:00
Henry Jameson ccdbba2348 Change flex for tab switcher lines to always have some small space reserved 2020-05-25 17:18:57 +03:00
Henry Jameson c0fe39af7a increase gap between buttons in titlebars 2020-05-25 17:15:02 +03:00
Henry Jameson 0eea5c6b80 fix icon changing color 2020-05-25 17:11:30 +03:00
Henry Jameson 500511b415 fix sidebar not closing in mobile 2020-05-25 17:05:30 +03:00
Henry Jameson 534e1fef88 lint 2020-05-25 16:56:32 +03:00
Henry Jameson 5d1b539da2 lint 2020-05-25 16:48:23 +03:00
Henry Jameson 097216c49e remove timeout, was meant for testing 2020-05-25 16:32:32 +03:00
Henry Jameson bb418bf155 fix tests. user-profile didn't have tab-switcher compnent imported!! 2020-05-25 16:23:14 +03:00
Henry Jameson 0286e1024c fix cursor on desktop, add modal link on mobile 2020-05-25 16:16:30 +03:00
Henry Jameson 7951192cd9 Improve settings-modal async loading, update vue to 2.6.11 to be able
to use Vue.observable, to implmement resettable async component
2020-05-25 16:11:05 +03:00
Henry Jameson a6ca923a76 icons update 2020-05-25 16:10:43 +03:00
Henry Jameson 79c03984bc scroll to top when switching tabs 2020-05-25 14:16:03 +03:00
Henry Jameson 6a4ad1fe62 added "settings saved" notice back 2020-05-25 14:04:36 +03:00
HJ cf3fbdd610 Merge branch 'fix/favorites-timeline-unsupported-with-muted' into 'develop'
Removed `with_muted` param usage for user favorites timeline endpoint

See merge request pleroma/pleroma-fe!1119
2020-05-25 10:48:45 +00:00
Ivan Tashkinov 5235e7ea1e Removed with_muted param usage for user favorites timeline endpoint (it only supports pagination params). 2020-05-25 08:48:44 +03:00
Henry Jameson e7ba4255bb eslint --fix + small fix 2020-05-25 03:43:55 +03:00
Henry Jameson bcea2e4d12 cleanup 2020-05-25 03:30:14 +03:00
Henry Jameson 138ec85003 Merge remote-tracking branch 'origin/develop' into settings-modal
* origin/develop: (95 commits)
  Translated using Weblate (Italian)
  Translated using Weblate (Chinese (Simplified))
  Translated using Weblate (Russian)
  Translated using Weblate (Polish)
  Translated using Weblate (Dutch)
  Translated using Weblate (German)
  Translated using Weblate (German)
  Translated using Weblate (German)
  Translated using Weblate (German)
  Translated using Weblate (German)
  Translated using Weblate (German)
  Translated using Weblate (German)
  Translated using Weblate (German)
  Translated using Weblate (German)
  Translated using Weblate (German)
  Translated using Weblate (German)
  Translated using Weblate (German)
  Translated using Weblate (German)
  Translated using Weblate (German)
  Translated using Weblate (German)
  ...
2020-05-25 03:29:48 +03:00
Henry Jameson 1e606d2f26 split modal's content into another component, add lazy loading 2020-05-25 03:14:41 +03:00
Henry Jameson a872c53472 misc fixes 2020-05-24 02:29:09 +03:00
Henry Jameson ab74cd4972 Multiple fixes for CSS, added proper auth checking 2020-05-24 02:06:55 +03:00
HJ 1ae8935977 Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1115
2020-05-22 14:52:31 +00:00
Ben Is 306ed13087 Translated using Weblate (Italian)
Currently translated at 42.2% (259 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-05-20 17:26:11 +00:00
Master Sparker e300f08e09 Translated using Weblate (Chinese (Simplified))
Currently translated at 92.0% (564 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/zh_Hans/
2020-05-20 17:26:11 +00:00
rinpatch 866fe78bba Translated using Weblate (Russian)
Currently translated at 61.1% (375 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
2020-05-20 17:26:11 +00:00
Jędrzej Tomaszewski 0a2aeb5d1a Translated using Weblate (Polish)
Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/pl/
2020-05-20 17:26:11 +00:00
Fristi b4f8d91905 Translated using Weblate (Dutch)
Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
2020-05-20 17:26:11 +00:00
Anonymous 7f9eb23759 Translated using Weblate (German)
Currently translated at 75.2% (461 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Toromino 1478ff7fc7 Translated using Weblate (German)
Currently translated at 75.2% (461 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Anonymous 91da315af9 Translated using Weblate (German)
Currently translated at 74.0% (454 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Toromino 9ede7db8df Translated using Weblate (German)
Currently translated at 74.0% (454 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Anonymous 3e260661d0 Translated using Weblate (German)
Currently translated at 73.0% (448 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Toromino 53b2ae0f73 Translated using Weblate (German)
Currently translated at 73.0% (448 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Anonymous c8cf939cdd Translated using Weblate (German)
Currently translated at 71.4% (438 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Toromino 9202eeef4b Translated using Weblate (German)
Currently translated at 71.4% (438 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Toromino 495b3d819c Translated using Weblate (German)
Currently translated at 71.2% (437 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Anonymous c1834b3156 Translated using Weblate (German)
Currently translated at 71.2% (437 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Anonymous 7bd0f1a5f1 Translated using Weblate (German)
Currently translated at 70.3% (431 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Toromino f141fa2a66 Translated using Weblate (German)
Currently translated at 70.3% (431 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Anonymous e7fd793438 Translated using Weblate (German)
Currently translated at 69.0% (423 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Toromino 9e43a83c48 Translated using Weblate (German)
Currently translated at 69.0% (423 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Anonymous 1179a6687a Translated using Weblate (German)
Currently translated at 68.8% (422 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Toromino f91bb4dc7c Translated using Weblate (German)
Currently translated at 68.8% (422 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Anonymous f22d3c18cf Translated using Weblate (German)
Currently translated at 68.6% (421 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Toromino 14ac5a5fdb Translated using Weblate (German)
Currently translated at 68.6% (421 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Anonymous 9374002de0 Translated using Weblate (German)
Currently translated at 68.5% (420 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Toromino ab4790034b Translated using Weblate (German)
Currently translated at 68.5% (420 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Toromino 9e306efe2e Translated using Weblate (German)
Currently translated at 68.3% (419 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Anonymous b994bde3e2 Translated using Weblate (German)
Currently translated at 68.3% (419 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-20 17:26:11 +00:00
Ben Is 00b4b1f4f6 Translated using Weblate (Italian)
Currently translated at 40.6% (249 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-05-20 17:26:11 +00:00
Fristi a62d504fab Translated using Weblate (Dutch)
Currently translated at 78.1% (479 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
2020-05-20 17:26:11 +00:00
Ben Is 7f7919bf6f Translated using Weblate (Italian)
Currently translated at 36.2% (222 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-05-20 17:26:11 +00:00
Fristi 3cd6bc9cfb Translated using Weblate (Dutch)
Currently translated at 77.9% (478 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
2020-05-20 17:26:11 +00:00
feld 24901ed98f Merge branch 'chore/update-docs' into 'develop'
Document the sidebarRight feature

See merge request pleroma/pleroma-fe!1117
2020-05-20 17:26:04 +00:00
Mark Felder 4aaa3f87d8 Document the sidebarRight feature 2020-05-20 12:16:43 -05:00
Shpuld Shpludson a8e9c05f09 Merge branch 'feat/sidebar-alignment' into 'develop'
[#765] Permit sidebar alignment with instance configuration option

See merge request pleroma/pleroma-fe!1106
2020-05-20 15:42:42 +00:00
Shpuld Shpludson 0b44a5b601 Merge branch 'fix/its-me-elements' into 'develop'
Show "it's you" label and hide follow btn for current user

Closes #839 and #827

See merge request pleroma/pleroma-fe!1111
2020-05-20 14:48:52 +00:00
Shpuld Shpludson d53a4a754f Merge branch 'chore/remote-unused-option' into 'develop'
Remove unused noAttachmentLinks option

See merge request pleroma/pleroma-fe!1109
2020-05-20 14:42:29 +00:00
lain bc9ed36424 Merge branch 'feat/add-embed-relationships' into 'develop'
Add `with_relationships` query param

Closes #838

See merge request pleroma/pleroma-fe!1107
2020-05-17 07:27:07 +00:00
Mark Felder 2135dc8510 Merge branch 'develop' into feat/sidebar-alignment 2020-05-15 12:26:38 -05:00
rinpatch 671ecfb152 Merge branch 'resolve-weblate-merge-conflicts' into 'develop'
Resolve weblate merge conflicts

See merge request pleroma/pleroma-fe!1114
2020-05-15 05:02:47 +00:00
rinpatch ca52a7321d Merge remote-tracking branch 'weblate/develop' into resolve-weblate-merge-conflicts 2020-05-14 22:53:08 +03:00
Anonymous 8db7cba9e1 Translated using Weblate (English)
Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/en/
2020-05-14 18:13:04 +00:00
Ben Is 7a0f27f3b0 Translated using Weblate (Italian)
Currently translated at 35.0% (215 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-05-14 18:13:03 +00:00
Fristi 9c62b90212 Translated using Weblate (Dutch)
Currently translated at 77.8% (477 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
2020-05-14 18:13:00 +00:00
HJ 2eea36f623 Merge branch 'i18n/update-delete-description' into 'develop'
Clarify that we only delete data, not the account

See merge request pleroma/pleroma-fe!1102
2020-05-14 18:12:53 +00:00
Mark Felder 840c6f40b6 Merge branch 'develop' into chore/remote-unused-option 2020-05-14 12:22:28 -05:00
Fristi f2bcaec8dc Translated using Weblate (Dutch)
Currently translated at 77.8% (477 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
2020-05-14 16:48:25 +00:00
Fristi 9d2935e72f Translated using Weblate (Dutch)
Currently translated at 77.6% (476 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/nl/
2020-05-14 11:06:22 +00:00
Sergey Suprunenko db8dd1223c Make a condition clear and unambiguous 2020-05-14 09:27:58 +02:00
Sergey Suprunenko e50857d0ff Show "it's you" label and hide follow btn for current user 2020-05-14 09:25:07 +02:00
HJ dbf9285ed7 Merge branch 'chore/update-config-docs' into 'develop'
Update settings documentation and sample static/config.json

See merge request pleroma/pleroma-fe!1110
2020-05-13 22:31:48 +00:00
Mark Felder 229bf79d90 name setting should be with apiConfig section 2020-05-13 17:20:35 -05:00
Mark Felder 79c53b849e registrationOpen is not an FE setting 2020-05-13 16:46:58 -05:00
Mark Felder 03318b64aa Really alpha sort this 2020-05-13 15:48:21 -05:00
Mark Felder 17b65e06be Merge branch 'develop' into chore/update-config-docs 2020-05-13 15:43:26 -05:00
Mark Felder e80fa3ff6d Split apiConfig options from static/config.json options;
Move safeDM to nasty section, alpha sort things
2020-05-13 15:40:13 -05:00
Mark Felder d91f5189ef Config setting is actually called disableChat 2020-05-13 15:33:01 -05:00
Shpuld Shpludson cc9dd28e6b Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1108
2020-05-13 20:32:10 +00:00
Mark Felder 0ebf1bebb4 SafeDM is enabled by default and won't take effect if BE not enabled anyway. 2020-05-13 15:32:02 -05:00
Mark Felder 55fe6e4703 Alpha sort indirect section 2020-05-13 15:30:54 -05:00
Mark Felder cad40133d2 Alpha sort docs, add missing options 2020-05-13 15:29:38 -05:00
rinpatch 346f3a285a Translated using Weblate (Russian)
Currently translated at 61.0% (374 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
2020-05-13 20:08:00 +00:00
Ben Is 1f53f0a844 Translated using Weblate (Italian)
Currently translated at 28.3% (174 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-05-13 20:07:59 +00:00
Shpuld Shpuldson fc12f44f7a Translated using Weblate (Finnish)
Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/fi/
2020-05-13 20:07:58 +00:00
Mark Felder 0ef5965b3b Add missing settings: disableChat, greentext, hideFilteredStatuses, hideMutedPosts, hidePostStats, hideSitename 2020-05-13 14:52:48 -05:00
Mark Felder 1db2fc3f41 alpha sort 2020-05-13 14:52:21 -05:00
Mark Felder 62e0fda597 loginMethod was missing 2020-05-13 14:46:31 -05:00
Mark Felder a4a25105ba Separate the user configurable section 2020-05-13 14:43:36 -05:00
Mark Felder 98d332793c alpha sort 2020-05-13 14:40:46 -05:00
Mark Felder eea6d772ad Also remove from docs 2020-05-13 14:31:00 -05:00
Mark Felder c2bba3f5ad Remove unused noAttachmentLinks option 2020-05-13 14:28:35 -05:00
Michał Sidor bc215d70f5 Translated using Weblate (Polish)
Currently translated at 100.0% (613 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/pl/
2020-05-13 18:41:14 +00:00
rinpatch 8ac78e92f8 Translated using Weblate (Russian)
Currently translated at 59.3% (364 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
2020-05-13 18:41:13 +00:00
Ben Is 21ecca4c62 Translated using Weblate (Italian)
Currently translated at 28.3% (174 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-05-13 18:41:11 +00:00
Toro Mino a26731de30 Translated using Weblate (German)
Currently translated at 66.7% (409 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/de/
2020-05-13 18:41:10 +00:00
Shpuld Shpuldson c360a23845 Translated using Weblate (Finnish)
Currently translated at 78.7% (483 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/fi/
2020-05-13 18:41:07 +00:00
Anonymous 433d64827d Translated using Weblate (Italian)
Currently translated at 27.5% (169 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2020-05-13 17:41:40 +00:00
rinpatch 625d9b7320 Translated using Weblate (Russian)
Currently translated at 54.4% (334 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
2020-05-13 17:17:13 +00:00
hj 1a944efa33 Translated using Weblate (Russian)
Currently translated at 54.4% (334 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
2020-05-13 17:17:12 +00:00
Alibek Omarov fa403bbcbd Translated using Weblate (Russian)
Currently translated at 54.4% (334 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
2020-05-13 17:17:11 +00:00
Shpuld Shpuldson 9c7cb3a954 remove search1 with_relationships 2020-05-13 18:04:30 +03:00
Shpuld Shpuldson 355281081a don't send undefined 2020-05-13 17:53:43 +03:00
Shpuld Shpuldson 8e39971098 add with_relationships where necessary 2020-05-13 17:48:31 +03:00
Mark Felder bc5005b3dd Permit sidebar alignment with instance configuration option 2020-05-12 13:59:52 -05:00
Shpuld Shpludson 5d49edc823 Merge branch 'rc/2.0.5' into 'master'
Update MASTER for 2.0.5 patch

See merge request pleroma/pleroma-fe!1105
2020-05-12 17:36:05 +00:00
Shpuld Shpuldson 0bc0a8d5f5 update changelog for 2.0.5 2020-05-12 20:27:37 +03:00
Shpuld Shpuldson 726d5279c1 Revert "remove with_move param"
This reverts commit 02c8a9e314.
2020-05-12 20:04:00 +03:00
Egor 26bcfea727 Translated using Weblate (Russian)
Currently translated at 53.8% (330 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ru/
2020-05-12 15:52:53 +00:00
Haelwenn (lanodan) Monnier c412856716 Translated using Weblate (French)
Currently translated at 97.3% (597 of 613 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: http://translate.pleroma.social/projects/pleroma/pleroma-fe/fr/
2020-05-12 08:31:01 +00:00
Henry Jameson bcebec478e moved stuff from settings, cleaned up naming for tabs, added close and peek 2020-05-10 06:46:06 +03:00
Mark Felder 9180fdb492 Clarify that we only delete data, not the account 2020-05-08 15:56:53 -05:00
Shpuld Shpuldson 7a0e554daf update changelog 2020-05-08 13:25:18 +03:00
Shpuld Shpludson 47c56ffa1c Merge branch 'feat/relationship-refactor' into 'develop'
Refactor: make relationships separate from users

Closes #819

See merge request pleroma/pleroma-fe!1091
2020-05-08 09:14:26 +00:00
Shpuld Shpludson 1186205583 Merge branch 'develop' into 'feat/relationship-refactor'
# Conflicts:
#   src/components/notification/notification.js
2020-05-08 08:35:30 +00:00
Shpuld Shpludson 921eedfd84 Merge branch 'feat/status-content-separation' into 'develop'
Refactor: Separate status content from status

See merge request pleroma/pleroma-fe!1092
2020-05-08 08:33:55 +00:00
Shpuld Shpludson ec7bee6bd6 Merge branch 'feature/copy-link' into 'develop'
Copy status link to clipboard

See merge request pleroma/pleroma-fe!1085
2020-05-08 08:33:20 +00:00
Shpuld Shpludson f4676e17d3 Merge branch 'develop' into 'feature/copy-link'
# Conflicts:
#   static/fontello.json
2020-05-08 08:20:03 +00:00
Shpuld Shpuldson ddc3b86d24 fix popover not closing on pressing the buttons 2020-05-08 10:46:00 +03:00
Mark Felder 41fc26869f Correctly resolve the URI of the server 2020-05-07 16:33:21 -05:00
Shpuld Shpuldson 93baa8b664 fix gap when not logged in 2020-05-08 00:10:49 +03:00
Shpuld Shpludson f40de85b92 Merge branch 'fix/signup-without-email' into 'develop'
Permit signup without email

See merge request pleroma/pleroma-fe!1072
2020-05-07 21:04:59 +00:00
Shpuld Shpludson 2893834095 Merge branch 'feat/notification-privacy' into 'develop'
Add notification privacy option to user settings

See merge request pleroma/pleroma-fe!1100
2020-05-07 20:34:27 +00:00
Mark Felder b3003d4e8d Add notification privacy option to user settings 2020-05-06 11:46:40 -05:00
Shpuld Shpuldson f7f8a579fa make email validation conditional work 2020-05-04 12:56:39 +03:00
Shpuld Shpuldson 101eebb40b Merge branch 'develop' into fix/signup-without-email 2020-05-04 11:05:05 +03:00
Henry Jameson 2e35289c33 initial work on settings modal 2020-05-03 17:36:12 +03:00
Shpuld Shpludson a0f780c455 Merge branch 'rc/2.0.3' into 'master'
Update MASTER with 2.0.3 for real

See merge request pleroma/pleroma-fe!1099
2020-05-02 14:17:27 +00:00
Shpuld Shpludson 9b349b4019 Merge branch 'rc/2.0.3' into 'develop'
update MASTER for 2.0.3

See merge request pleroma/pleroma-fe!1098
2020-05-02 13:43:22 +00:00
Shpuld Shpludson 2618c1b702 Update CHANGELOG.md 2020-05-02 16:21:46 +03:00
Shpuld Shpuldson b095d2e17e don't dismiss a rejected follow request on server 2020-05-02 16:21:46 +03:00
Shpuld Shpuldson 20b53d58b7 mark single notifs as seen properly on server 2020-05-02 16:21:46 +03:00
Shpuld Shpuldson 36dcfa8cc1 follow request bugfixes, wrong text, notifs not being marked as read, approving from follow request view 2020-05-02 16:21:46 +03:00
eugenijm ab3c0e8512 Add support for follow request notifications 2020-05-02 16:21:46 +03:00
Shpuld Shpludson 4d1a674634 Update CHANGELOG.md 2020-05-02 16:21:46 +03:00
Shpuld Shpludson 372eb723db Update CHANGELOG.md 2020-05-02 16:21:46 +03:00
xenofem fe4282f44b Prioritize custom emoji a lot and boost exact matches to the top 2020-05-02 16:21:46 +03:00
xenofem aef03d53b2 Allow emoji suggestions based on a match anywhere in the emoji name, but improve sorting 2020-05-02 16:21:46 +03:00
xenofem eae0bce320 Refactor status showing/hiding code for better handling of edge cases and easier comprehension 2020-05-02 16:21:46 +03:00
Karol Kosek c4d1c2131c Fix user names with the RTL char in notifications 2020-05-02 16:21:46 +03:00
Egor Kislitsyn 0ba34eeca5 Fix pagination 2020-05-02 16:21:46 +03:00
Shpuld Shpludson 068abb4d26 Update CHANGELOG.md 2020-05-02 13:05:45 +00:00
Shpuld Shpludson 5f90b6a384 Merge branch 'fix/follow-request-notification-bugfixes' into 'develop'
Fix remaining follow request notif problems

Closes #823

See merge request pleroma/pleroma-fe!1096
2020-05-02 11:19:05 +00:00
Shpuld Shpuldson 92ccaa97bb don't dismiss a rejected follow request on server 2020-05-02 11:51:39 +03:00
Shpuld Shpuldson 75519223f9 mark single notifs as seen properly on server 2020-05-02 10:52:57 +03:00
Shpuld Shpuldson 406fdd8ede follow request bugfixes, wrong text, notifs not being marked as read, approving from follow request view 2020-05-02 10:19:47 +03:00
Shpuld Shpludson c67e9daf06 Merge branch 'follow-request-notification' into 'develop'
Add support for follow request notifications

Closes #823 and #822

See merge request pleroma/pleroma-fe!1093
2020-05-01 20:24:25 +00:00
Shpuld Shpludson af3e69743e Update CHANGELOG.md 2020-05-01 19:28:26 +00:00
Shpuld Shpludson 790b8555dc Merge branch 'fix/remove-with_move-param' into 'develop'
Remove with_move param

See merge request pleroma/pleroma-fe!1094
2020-05-01 14:41:00 +00:00
Shpuld Shpuldson 02c8a9e314 remove with_move param 2020-05-01 17:26:07 +03:00
eugenijm 01b07f01e9 Add support for follow request notifications 2020-04-30 09:58:55 +03:00
Shpuld Shpuldson 7a25160ddc Separate status content from status like in direct conversations mr 2020-04-27 12:53:04 +03:00
Shpuld Shpludson dea7e2f6ac Update CHANGELOG.md 2020-04-27 08:09:31 +00:00
Shpuld Shpludson fe16b6259e Merge branch 'feature-emoji-length-sorting' into 'develop'
Allow emoji suggestions based on a match anywhere in the emoji name, but improve sorting

Closes #135

See merge request pleroma/pleroma-fe!1061
2020-04-27 08:01:57 +00:00
Shpuld Shpludson d32159d69d Merge branch 'fix/status-showing-hiding' into 'develop'
Refactor status showing/hiding code for better handling of edge cases and easier comprehension

Closes #779

See merge request pleroma/pleroma-fe!1073
2020-04-27 07:47:02 +00:00
Shpuld Shpuldson 8b1aa593a4 fix status mutes 2020-04-27 10:06:17 +03:00
Shpuld Shpuldson af9492977a add back mute prediction, add getter for relationships 2020-04-24 18:53:17 +03:00
Shpuld Shpuldson f6fce92cf7 last lint warning 2020-04-23 14:48:15 +03:00
Shpuld Shpuldson c476193fd9 minor lint fixes 2020-04-23 14:44:55 +03:00
Shpuld Shpuldson ca00e93b60 minor fixes 2020-04-23 14:27:27 +03:00
Shpuld Shpuldson 99d8e16e4d remove with_relationships as it doesn't help 2020-04-23 14:11:48 +03:00
Shpuld Shpuldson ce0a1e7ad1 add back missing catch 2020-04-23 14:08:33 +03:00
Shpuld Shpuldson cda298c822 remove unused mutation and test for it 2020-04-23 11:17:52 +03:00
Shpuld Shpuldson aa56147322 fix follow 2020-04-22 15:06:10 +03:00
Shpuld Shpuldson 576ad9750b make tests not fail 2020-04-22 00:07:01 +03:00
Shpuld Shpuldson 4b7007bc7d fix mistakes 2020-04-21 23:56:48 +03:00
Shpuld Shpuldson 6bb75a3a6d make relationships separate from users 2020-04-21 23:27:51 +03:00
HJ 023dec1953 Merge branch 'notification-rtl' into 'develop'
Fix user names with the RTL char in notifications

See merge request pleroma/pleroma-fe!1090
2020-04-19 01:59:19 +00:00
Karol Kosek 2fbb94fe5c Fix user names with the RTL char in notifications 2020-04-18 19:45:24 +02:00
Shpuld Shpludson ac9985aedb Merge branch 'fix-pagination' into 'develop'
Fix pagination

See merge request pleroma/pleroma-fe!1089
2020-04-14 16:57:42 +00:00
Egor Kislitsyn 18fa338d43 Fix pagination 2020-04-13 15:26:55 +04:00
Mark Felder 8c5946b728 Add button in 3dot menu to copy status link to clipboard 2020-03-30 12:39:28 -05:00
rinpatch 7dfa734665 after_store: Remove most of StatusNet config usage
Information under `pleromafe` key is equivalent to the one under
`pleroma_fe` key in  `/api/pleroma/frontend_configurations`.

All other information had equivalents in `/nodeinfo/2.0.json`, except
`textlimit` and `vapidPublicKey`.
2020-03-25 20:06:48 +03:00
Haelwenn (lanodan) Monnier 88fbbb5e9b status.vue: Add support for favicons
Put in replacement to the external icon when possible
2020-03-01 09:47:21 +01:00
xenofem 7fa5eb07dd Refactor status showing/hiding code for better handling of edge cases and easier comprehension 2020-02-24 18:10:15 -05:00
Mark Felder 39e3917118 Remove unneccessary nested 2020-02-24 11:23:16 -06:00
Mark Felder 86561592d0 First attempt at not requiring email address for registration 2020-02-24 11:19:00 -06:00
kPherox 064b59812c Change to use tags removed fields instead of raw fields 2020-02-19 21:00:50 +09:00
kPherox da55b0d435 Add fields_text for tooltip 2020-02-19 20:57:58 +09:00
xenofem 02864bc07b Prioritize custom emoji a lot and boost exact matches to the top 2020-02-10 09:32:07 -05:00
xenofem 44dea9f364 Allow emoji suggestions based on a match anywhere in the emoji name, but improve sorting 2020-02-09 17:39:07 -05:00
kPherox bfdf7c876c Change field name to right justify 2019-11-20 19:26:04 +09:00
kPherox a15baa8962 Use fields_html only 2019-11-20 19:26:04 +09:00
kPherox d2e334daa3 Add tooltip 2019-11-20 19:26:02 +09:00
kPherox 87940ead8a Change css selectors to classname from elementname 2019-11-20 18:59:37 +09:00
kPherox 004827a7f1 Change profile fields design to horizontal 2019-11-20 00:19:47 +09:00
kPherox 2bc549a66a Fix emoji size 2019-11-20 00:19:47 +09:00
kPherox ca4d5950d0 Display user profile fields 2019-11-20 00:19:47 +09:00
Ariadne Conill 1946661911 update terms of service instructions 2019-11-09 00:27:09 -06:00
238 changed files with 15589 additions and 5822 deletions
+2 -1
View File
@@ -1,7 +1,7 @@
# This file is a template, and might need editing before it works on your project. # 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: # Official framework image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/node/tags/ # https://hub.docker.com/r/library/node/tags/
image: node:8 image: node:10
stages: stages:
- lint - lint
@@ -14,6 +14,7 @@ lint:
script: script:
- yarn - yarn
- npm run lint - npm run lint
- npm run stylelint
test: test:
stage: test stage: test
+19
View File
@@ -0,0 +1,19 @@
{
"extends": [
"stylelint-rscss/config",
"stylelint-config-recommended",
"stylelint-config-standard"
],
"rules": {
"declaration-no-important": true,
"rscss/no-descendant-combinator": false,
"rscss/class-format": [
true,
{
"component": "pascal-case",
"variant": "^-[a-z]\\w+",
"element": "^[a-z]\\w+"
}
]
}
}
+84
View File
@@ -4,6 +4,88 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
## [Unreleased patch]
## [2.1.2] - 2020-09-17
### Fixed
- Fixed chats list not updating its order when new messages come in
- Fixed chat messages sometimes getting lost when you receive a message at the same time
## [2.1.1] - 2020-09-08
### Changed
- Polls will be hidden with status content if "Collapse posts with subjects" is enabled and the post is collapsed.
### Fixed
- Autocomplete won't stop at the second @, so it'll still work with "@lain@l" and not start over.
- Fixed weird autocomplete behavior when you write ":custom_emoji: ?"
## [2.1.0] - 2020-08-28
### Added
- Autocomplete domains from list of known instances
- 'Bot' settings option and badge
- Added profile meta data fields that can be set in profile settings
- Added option to reset avatar/banner in profile settings
- Descriptions can be set on uploaded files before posting
- Added status preview option to preview your statuses before posting
- When a post is a reply to an unavailable post, the 'Reply to'-text has a strike-through style
- Added ability to see all favoriting or repeating users when hovering the number on highlighted statuses
- Bookmarks
### Changed
- Change heart to thumbs up in reaction picker
- Close the media modal on navigation events
- Add colons to the emoji alt text, to make them copyable
- Add better visual indication for drag-and-drop for files
- When disabling attachments, the placeholder links now show an icon and the description instead of just IMAGE or VIDEO etc
- Remove unnecessary options for 'automatic loading when loading older' and 'reply previews'
- Greentext now has separate color slot for it
- Removed the use of with_move parameters when fetching notifications
- Push notifications now are the same as normal notfication, and are localized.
- Updated Notification Settings to match new BE API
### Fixed
- Custom Emoji will display in poll options now.
- Status ellipsis menu closes properly when selecting certain options
- Cropped images look correct in Chrome
- Newlines in the muted words settings work again
- Clicking on non-latin hashtags won't open a new window
- Uploading and drag-dropping multiple files works correctly now.
- Subject field now appears disabled when posting
- Fix status ellipsis menu being cut off in notifications column
- Fixed autocomplete sometimes not returning the right user when there's already some results
- Videos and audio and misc files show description as alt/title properly now
- Clicking on non-image/video files no longer opens an empty modal
- Audio files can now be played back in the frontend with hidden attachments
- Videos are not cropped awkwardly in the uploads section anymore
- Reply filtering options in Settings -> Filtering now work again using filtering on server
- Don't show just blank-screen when cookies are disabled
- Add status idempotency to prevent accidental double posting when posting returns an error
- Weird bug related to post being sent seemingly after pasting with keyboard (hopefully)
- Multiple issues with muted statuses/notifications
## [2.0.5] - 2020-05-12
### Added
- Added private notifications option for push notifications
- 'Copy link' button for statuses (in the ellipsis menu)
### Changed
- Registration page no longer requires email if the server is configured not to require it
### Fixed
- Status ellipsis menu closes properly when selecting certain options
## [2.0.3] - 2020-05-02
### Fixed
- Show more/less works correctly with auto-collapsed subjects and long posts
- RTL characters won't look messed up in notifications
### Changed
- Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach:
### Added
- Follow request notification support
## [2.0.2] - 2020-04-08 ## [2.0.2] - 2020-04-08
### Fixed ### Fixed
@@ -54,7 +136,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Ability to change user's email - Ability to change user's email
- About page - About page
- Added remote user redirect - Added remote user redirect
### Changed ### Changed
- changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes - changed the way fading effects for user profile/long statuses works, now uses css-mask instead of gradient background hacks which weren't exactly compatible with semi-transparent themes
### Fixed ### Fixed
- improved hotkey behavior on autocomplete popup - improved hotkey behavior on autocomplete popup
+3 -4
View File
@@ -1,8 +1,8 @@
# pleroma_fe # Pleroma-FE
> A single column frontend for both Pleroma and GS servers. > A single column frontend designed for Pleroma.
![screenshot](https://i.imgur.com/DJVqSJ0.png) ![screenshot](/uploads/796c5ecf985ed1e2b0943ee0df131ed0/DJVqSJ0.png)
# For Translators # For Translators
@@ -11,7 +11,6 @@ To translate Pleroma-FE, add your language to [src/i18n/messages.js](https://git
# FOR ADMINS # 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. You don't need to build Pleroma-FE yourself. Those using the Pleroma backend will be able to use it out of the box.
For the GNU social backend, check out https://git.pleroma.social/pleroma/pleroma-fe/wikis/dual-boot-with-qvitter to see how to run Pleroma-FE and Qvitter at the same time.
## Build Setup ## Build Setup
+53 -43
View File
@@ -19,32 +19,69 @@ There's currently no mechanism for user-settings synchronization across several
## Options ## Options
### `theme` ### `alwaysShowSubjectInput`
Default theme used for new users. De-facto instance-default, user can change theme. `true` - will always show subject line input, `false` - only show when it's not empty (i.e. replying). To hide subject line input completely, set it to `false` and `subjectLineBehavior` to `"noop"`
### `background` ### `background`
Default image background. Be aware of using too big images as they may take longer to load. Currently image is fitted with `background-size: cover` which means "scaled and cropped", currently left-aligned. De-facto instance default, user can choose their own background, if they remove their own background, instance default will be used instead. Default image background. Be aware of using too big images as they may take longer to load. Currently image is fitted with `background-size: cover` which means "scaled and cropped", currently left-aligned. De-facto instance default, user can choose their own background, if they remove their own background, instance default will be used instead.
### `collapseMessageWithSubject`
Collapse post content when post has a subject line (content warning). Instance-default.
### `disableChat`
hides the chat (TODO: even if it's enabled on backend)
### `greentext`
Changes lines prefixed with the `>` character to have a green text color
### `hideFilteredStatuses`
Removes filtered statuses from timelines.
### `hideMutedPosts`
Removes muted statuses from timelines.
### `hidePostStats`
Hide repeats/favorites counters for posts.
### `hideSitename`
Hide instance name in header.
### `hideUserStats`
Hide followers/friends counters for users.
### `loginMethod`
`"password"` - show simple password field
`"token"` - show button to log in with external method (will redirect to login form, more details in BE documentation)
### `logo`, `logoMask`, `logoMargin` ### `logo`, `logoMask`, `logoMargin`
Instance `logo`, could be any image, including svg. By default it assumes logo used will be monochrome-with-alpha one, this is done to be compatible with both light and dark themes, so that white logo designed with dark theme in mind won't be invisible over light theme, this is done via [CSS3 Masking](https://www.html5rocks.com/en/tutorials/masking/adobe/). Basically - it will take alpha channel of the image and fill non-transparent areas of it with solid color. If you really want colorful logo - it can be done by setting `logoMask` to `false`. Instance `logo`, could be any image, including svg. By default it assumes logo used will be monochrome-with-alpha one, this is done to be compatible with both light and dark themes, so that white logo designed with dark theme in mind won't be invisible over light theme, this is done via [CSS3 Masking](https://www.html5rocks.com/en/tutorials/masking/adobe/). Basically - it will take alpha channel of the image and fill non-transparent areas of it with solid color. If you really want colorful logo - it can be done by setting `logoMask` to `false`.
`logoMargin` allows you to adjust vertical margins between logo boundary and navbar borders. The idea is that to have logo's image without any extra margins and instead adjust them to your need in layout. `logoMargin` allows you to adjust vertical margins between logo boundary and navbar borders. The idea is that to have logo's image without any extra margins and instead adjust them to your need in layout.
### `minimalScopesMode`
Limit scope selection to *Direct*, *User default* and *Scope of post replying to*. This also makes it impossible to reply to a DM with a non-DM post from PleromaFE.
### `nsfwCensorImage`
Use custom image for NSFW'd images
### `postContentType`
Default post formatting option (markdown/bbcode/plaintext/etc...)
### `redirectRootNoLogin`, `redirectRootLogin` ### `redirectRootNoLogin`, `redirectRootLogin`
These two settings should point to where FE should redirect visitor when they login/open up website root These two settings should point to where FE should redirect visitor when they login/open up website root
### `chatDisabled` ### `scopeCopy`
hides the chat (TODO: even if it's enabled on backend) Copy post scope (visibility) when replying to a post. Instance-default.
### `sidebarRight`
Change alignment of sidebar and panels to the right. Defaults to `false`.
### `showFeaturesPanel`
Show panel showcasing instance features/settings to logged-out visitors
### `showInstanceSpecificPanel` ### `showInstanceSpecificPanel`
This allows you to include arbitrary HTML content in a panel below navigation menu. PleromaFE looks for an html page `instance/panel.html`, by default it's not provided in FE, but BE bundles some [default one](https://git.pleroma.social/pleroma/pleroma/blob/develop/priv/static/instance/panel.html). De-facto instance-defaults, since user can hide instance-specific panel. This allows you to include arbitrary HTML content in a panel below navigation menu. PleromaFE looks for an html page `instance/panel.html`, by default it's not provided in FE, but BE bundles some [default one](https://git.pleroma.social/pleroma/pleroma/blob/develop/priv/static/instance/panel.html). De-facto instance-defaults, since user can hide instance-specific panel.
### `collapseMessageWithSubject`
Collapse post content when post has a subject line (content warning). Instance-default.
### `scopeCopy`
Copy post scope (visibility) when replying to a post. Instance-default.
### `subjectLineBehavior` ### `subjectLineBehavior`
How to handle subject line (CW) when replying to a post. How to handle subject line (CW) when replying to a post.
* `"email"` - like EMail - prepend `re: ` to subject line if it doesn't already start with it. * `"email"` - like EMail - prepend `re: ` to subject line if it doesn't already start with it.
@@ -52,39 +89,22 @@ How to handle subject line (CW) when replying to a post.
* `"noop"` - do not copy * `"noop"` - do not copy
Instance-default. Instance-default.
### `postContentType` ### `theme`
Default post formatting option (markdown/bbcode/plaintext/etc...) Default theme used for new users. De-facto instance-default, user can change theme.
### `alwaysShowSubjectInput`
`true` - will always show subject line input, `false` - only show when it's not empty (i.e. replying). To hide subject line input completely, set it to `false` and `subjectLineBehavior` to `"noop"`
### `hidePostStats` and `hideUserStats`
Hide counters for posts and users respectively, i.e. hiding repeats/favorites counts for posts, hiding followers/friends counts for users. This is just cosmetic and aimed to ease pressure and bias imposed by stat numbers of people and/or posts. (as an example: so that people care less about how many followers someone has since they can't see that info)
### `loginMethod`
`"password"` - show simple password field
`"token"` - show button to log in with external method (will redirect to login form, more details in BE documentation)
### `webPushNotifications` ### `webPushNotifications`
Enables [PushAPI](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) - based notifications for users. Instance-default. Enables [PushAPI](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) - based notifications for users. Instance-default.
### `noAttachmentLinks`
**TODO Currently doesn't seem to be doing anything code-wise**, but implication is to disable adding links for attachments, which looks nicer but breaks compatibility with old GNU/Social servers.
### `nsfwCensorImage`
Use custom image for NSFW'd images
### `showFeaturesPanel`
Show panel showcasing instance features/settings to logged-out visitors
### `hideSitename`
Hide instance name in header
## Indirect configuration ## Indirect configuration
Some features are configured depending on how backend is configured. In general the approach is "if backend allows it there's no need to hide it, if backend doesn't allow it there's no need to show it. Some features are configured depending on how backend is configured. In general the approach is "if backend allows it there's no need to hide it, if backend doesn't allow it there's no need to show it.
### Chat ### Chat
**TODO somewhat broken, see: chatDisabled** chat can be disabled by disabling it in backend **TODO somewhat broken, see: disableChat** chat can be disabled by disabling it in backend
### Private Mode
If the `private` instance setting is enabled in the backend, features that are not accessible without authentication, such as the timelines and search will be disabled for unauthenticated users.
### Rich text formatting in post formatting ### Rich text formatting in post formatting
Rich text formatting options are displayed depending on how many formatting options are enabled on backend, if you don't want your users to use rich text at all you can only allow "text/plain" one, frontend then will only display post text format as a label instead of dropdown (just so that users know for example if you only allow Markdown, only BBCode or only Plain text) Rich text formatting options are displayed depending on how many formatting options are enabled on backend, if you don't want your users to use rich text at all you can only allow "text/plain" one, frontend then will only display post text format as a label instead of dropdown (just so that users know for example if you only allow Markdown, only BBCode or only Plain text)
@@ -92,13 +112,3 @@ Rich text formatting options are displayed depending on how many formatting opti
### Who to follow ### Who to follow
This is a panel intended for users to find people to follow based on randomness or on post contents. Being potentially privacy unfriendly feature it needs to be enabled and configured in backend to be enabled. This is a panel intended for users to find people to follow based on randomness or on post contents. Being potentially privacy unfriendly feature it needs to be enabled and configured in backend to be enabled.
### Safe DM message display
Setting this will change the warning text that is displayed for direct messages.
ATTENTION: If you actually want the behavior to change. You will need to set the appropriate option at the backend. See the backend documentation for information about that.
DO NOT activate this without checking the backend configuration first!
### Private Mode
If the `private` instance setting is enabled in the backend, features that are not accessible without authentication, such as the timelines and search will be disabled for unauthenticated users.
+1 -3
View File
@@ -8,8 +8,6 @@
> >
> --Catbag > --Catbag
Pleroma-FE user interface is modeled after Qvitter which is modeled after older Twitter design. It provides a simple 2-column interface for microblogging. While being simple by default it also provides many powerful customization options.
## Posting, reading, basic functions. ## Posting, reading, basic functions.
After registering and logging in you're presented with your timeline in right column and new post form with timeline list and notifications in the left column. After registering and logging in you're presented with your timeline in right column and new post form with timeline list and notifications in the left column.
@@ -33,7 +31,7 @@ will become
Note that you can only use emoji defined on your instance, you cannot "copy" someone else's emoji, and will have to ask your administrator to copy emoji from other instance to yours. Note that you can only use emoji defined on your instance, you cannot "copy" someone else's emoji, and will have to ask your administrator to copy emoji from other instance to yours.
Lastly, there's two convenience options for emoji: an emoji picker (smiley face to the right of "submit" button) and autocomplete suggestions - when you start typing :shortcode: it will automatically try to suggest you emoj and complete the shortcode for you if you select one. **Note** that if emoji doesn't show up in suggestions nor in emoji picker it means there's no such emoji on your instance, if shortcode doesn't match any defined emoji it will appear as text. Lastly, there's two convenience options for emoji: an emoji picker (smiley face to the right of "submit" button) and autocomplete suggestions - when you start typing :shortcode: it will automatically try to suggest you emoj and complete the shortcode for you if you select one. **Note** that if emoji doesn't show up in suggestions nor in emoji picker it means there's no such emoji on your instance, if shortcode doesn't match any defined emoji it will appear as text.
* **Attachments** are fairly simple - you can attach any file to a post as long as the file is within maximum size limits. If you're uploading explicit material you can mark all of your attachments as sensitive (or add `#nsfw` tag) - it will hide the images and videos behind a warning so that it won't be displayed instantly. * **Attachments** are fairly simple - you can attach any file to a post as long as the file is within maximum size limits. If you're uploading explicit material you can mark all of your attachments as sensitive (or add `#nsfw` tag) - it will hide the images and videos behind a warning so that it won't be displayed instantly.
* **Subject line** also known as **CW** (Content Warning) could be used as a header to the post and/or to warn others about contents of the post having something that might upset somebody or something among those lines. Several applications allow to hide post content leaving only subject line visible. As a side-effect using subject line will also mark your images as sensitive (see above). * **Subject line** also known as **CW** (Content Warning) could be used as a header to the post and/or to warn others about contents of the post having something that might upset somebody or something among those lines. Several applications allow to hide post content leaving only subject line visible. Using a subject line will not mark your images as sensitive, you will have to do that explicitly (see above).
* **Visiblity scope** controls who will be able to see your posts. There are four scopes available: * **Visiblity scope** controls who will be able to see your posts. There are four scopes available:
1. `Public`: This is the default, and some fediverse software like GNU Social only supports this. This means that your post is accessible by anyone and will be shown in the public timelines. 1. `Public`: This is the default, and some fediverse software like GNU Social only supports this. This means that your post is accessible by anyone and will be shown in the public timelines.
+8
View File
@@ -0,0 +1,8 @@
# Introduction to Pleroma-FE
## What is Pleroma-FE?
Pleroma-FE is the default user-facing frontend for Pleroma. It's user interface is modeled after Qvitter which is modeled after an older Twitter design. It provides a simple 2-column interface for microblogging. While being simple by default it also provides many powerful customization options.
## How can I use it?
If your instance uses Pleroma-FE, you can acces it by going to your instance (e.g. <https://pleroma.soykaf.com>). You can read more about it's basic functionality in the [Pleroma-FE User Guide](./USER_GUIDE.md). We also have [a guide for administrators](./CONFIGURATION.md) and for [hackers/contributors](./HACKING.md).
+9 -7
View File
@@ -11,6 +11,7 @@
"unit:watch": "karma start test/unit/karma.conf.js --single-run=false", "unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
"e2e": "node test/e2e/runner.js", "e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e", "test": "npm run unit && npm run e2e",
"stylelint": "npx stylelint src/components/status/status.scss",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs", "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs" "lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
}, },
@@ -22,21 +23,18 @@
"cropperjs": "^1.4.3", "cropperjs": "^1.4.3",
"diff": "^3.0.1", "diff": "^3.0.1",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"karma-mocha-reporter": "^2.2.1",
"localforage": "^1.5.0", "localforage": "^1.5.0",
"object-path": "^0.11.3", "parse-link-header": "^1.0.1",
"phoenix": "^1.3.0", "phoenix": "^1.3.0",
"portal-vue": "^2.1.4", "portal-vue": "^2.1.4",
"sanitize-html": "^1.13.0",
"v-click-outside": "^2.1.1", "v-click-outside": "^2.1.1",
"vue": "^2.5.13", "vue": "^2.6.11",
"vue-chat-scroll": "^1.2.1", "vue-chat-scroll": "^1.2.1",
"vue-i18n": "^7.3.2", "vue-i18n": "^7.3.2",
"vue-router": "^3.0.1", "vue-router": "^3.0.1",
"vue-template-compiler": "^2.3.4", "vue-template-compiler": "^2.6.11",
"vuelidate": "^0.7.4", "vuelidate": "^0.7.4",
"vuex": "^3.0.1", "vuex": "^3.0.1"
"whatwg-fetch": "^2.0.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.7.5", "@babel/core": "^7.7.5",
@@ -82,6 +80,7 @@
"karma-coverage": "^1.1.1", "karma-coverage": "^1.1.1",
"karma-firefox-launcher": "^1.1.0", "karma-firefox-launcher": "^1.1.0",
"karma-mocha": "^1.2.0", "karma-mocha": "^1.2.0",
"karma-mocha-reporter": "^2.2.1",
"karma-sinon-chai": "^2.0.2", "karma-sinon-chai": "^2.0.2",
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.26", "karma-spec-reporter": "0.0.26",
@@ -103,6 +102,9 @@
"shelljs": "^0.7.4", "shelljs": "^0.7.4",
"sinon": "^2.1.0", "sinon": "^2.1.0",
"sinon-chai": "^2.8.0", "sinon-chai": "^2.8.0",
"stylelint": "^13.6.1",
"stylelint-config-standard": "^20.0.0",
"stylelint-rscss": "^0.4.0",
"url-loader": "^1.1.2", "url-loader": "^1.1.2",
"vue-loader": "^14.0.0", "vue-loader": "^14.0.0",
"vue-style-loader": "^4.0.0", "vue-style-loader": "^4.0.0",
+19 -4
View File
@@ -6,13 +6,15 @@ import InstanceSpecificPanel from './components/instance_specific_panel/instance
import FeaturesPanel from './components/features_panel/features_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 WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
import ChatPanel from './components/chat_panel/chat_panel.vue' import ChatPanel from './components/chat_panel/chat_panel.vue'
import SettingsModal from './components/settings_modal/settings_modal.vue'
import MediaModal from './components/media_modal/media_modal.vue' import MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue' import SideDrawer from './components/side_drawer/side_drawer.vue'
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue' import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
import MobileNav from './components/mobile_nav/mobile_nav.vue' import MobileNav from './components/mobile_nav/mobile_nav.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue' import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
import PostStatusModal from './components/post_status_modal/post_status_modal.vue' import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
import { windowWidth } from './services/window_utils/window_utils' import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
export default { export default {
name: 'app', name: 'app',
@@ -29,8 +31,10 @@ export default {
SideDrawer, SideDrawer,
MobilePostStatusButton, MobilePostStatusButton,
MobileNav, MobileNav,
SettingsModal,
UserReportingModal, UserReportingModal,
PostStatusModal PostStatusModal,
GlobalNoticeList
}, },
data: () => ({ data: () => ({
mobileActivePanel: 'timeline', mobileActivePanel: 'timeline',
@@ -45,7 +49,8 @@ export default {
}), }),
created () { created () {
// Load the locale from the storage // Load the locale from the storage
this.$i18n.locale = this.$store.getters.mergedConfig.interfaceLanguage const val = this.$store.getters.mergedConfig.interfaceLanguage
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
window.addEventListener('resize', this.updateMobileState) window.addEventListener('resize', this.updateMobileState)
}, },
destroyed () { destroyed () {
@@ -99,7 +104,12 @@ export default {
}, },
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
isMobileLayout () { return this.$store.state.interface.mobileLayout }, isMobileLayout () { return this.$store.state.interface.mobileLayout },
privateMode () { return this.$store.state.instance.private } privateMode () { return this.$store.state.instance.private },
sidebarAlign () {
return {
'order': this.$store.state.instance.sidebarRight ? 99 : 0
}
}
}, },
methods: { methods: {
scrollToTop () { scrollToTop () {
@@ -112,12 +122,17 @@ export default {
onSearchBarToggled (hidden) { onSearchBarToggled (hidden) {
this.searchBarHidden = hidden this.searchBarHidden = hidden
}, },
openSettingsModal () {
this.$store.dispatch('openSettingsModal')
},
updateMobileState () { updateMobileState () {
const mobileLayout = windowWidth() <= 800 const mobileLayout = windowWidth() <= 800
const layoutHeight = windowHeight()
const changed = mobileLayout !== this.isMobileLayout const changed = mobileLayout !== this.isMobileLayout
if (changed) { if (changed) {
this.$store.dispatch('setMobileLayout', mobileLayout) this.$store.dispatch('setMobileLayout', mobileLayout)
} }
this.$store.dispatch('setLayoutHeight', layoutHeight)
} }
} }
} }
+53 -45
View File
@@ -47,6 +47,7 @@ html {
} }
body { body {
overscroll-behavior-y: none;
font-family: sans-serif; font-family: sans-serif;
font-family: var(--interfaceFont, sans-serif); font-family: var(--interfaceFont, sans-serif);
margin: 0; margin: 0;
@@ -319,7 +320,7 @@ option {
i[class*=icon-] { i[class*=icon-] {
color: $fallback--icon; color: $fallback--icon;
color: var(--icon, $fallback--icon) color: var(--icon, $fallback--icon);
} }
.btn-block { .btn-block {
@@ -566,7 +567,7 @@ main-router {
min-height: 0; min-height: 0;
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
margin-left: .25em; margin-left: .5em;
min-width: 1px; min-width: 1px;
align-self: stretch; align-self: stretch;
} }
@@ -858,53 +859,12 @@ nav {
display: block; display: block;
margin-right: 0.8em; margin-right: 0.8em;
} }
}
.setting-item { .main {
border-bottom: 2px solid var(--fg, $fallback--fg); margin-bottom: 7em;
margin: 1em 1em 1.4em;
padding-bottom: 1.4em;
> div {
margin-bottom: .5em;
&:last-child {
margin-bottom: 0;
} }
} }
&:last-child {
border-bottom: none;
padding-bottom: 0;
margin-bottom: 1em;
}
select {
min-width: 10em;
}
textarea {
width: 100%;
max-width: 100%;
height: 100px;
}
.unavailable,
.unavailable i {
color: var(--cRed, $fallback--cRed);
color: $fallback--cRed;
}
.btn {
min-height: 28px;
min-width: 10em;
padding: 0 2em;
}
.number-input {
max-width: 6em;
}
}
.select-multiple { .select-multiple {
display: flex; display: flex;
.option-list { .option-list {
@@ -969,3 +929,51 @@ nav {
background-color: $fallback--fg; background-color: $fallback--fg;
background-color: var(--panel, $fallback--fg); background-color: var(--panel, $fallback--fg);
} }
.unread-chat-count {
font-size: 0.9em;
font-weight: bolder;
font-style: normal;
position: absolute;
right: 0.6rem;
padding: 0 0.3em;
min-width: 1.3rem;
min-height: 1.3rem;
max-height: 1.3rem;
line-height: 1.3rem;
}
.chat-layout {
// Needed for smoother chat navigation in the desktop Safari (otherwise the chat layout "jumps" as the chat opens).
overflow: hidden;
height: 100%;
// Ensures the fixed position of the mobile browser bars on scroll up / down events.
// Prevents the mobile browser bars from overlapping or hiding the message posting form.
@media all and (max-width: 800px) {
body {
height: 100%;
}
#app {
height: 100%;
overflow: hidden;
min-height: auto;
}
#app_bg_wrapper {
overflow: hidden;
}
.main {
overflow: hidden;
height: 100%;
}
#content {
padding-top: 0;
height: 100%;
overflow: visible;
}
}
}
+11 -6
View File
@@ -46,15 +46,16 @@
@toggled="onSearchBarToggled" @toggled="onSearchBarToggled"
@click.stop.native @click.stop.native
/> />
<router-link <a
href="#"
class="mobile-hidden" class="mobile-hidden"
:to="{ name: 'settings'}" @click.stop="openSettingsModal"
> >
<i <i
class="button-icon icon-cog nav-icon" class="button-icon icon-cog nav-icon"
:title="$t('nav.preferences')" :title="$t('nav.preferences')"
/> />
</router-link> </a>
<a <a
v-if="currentUser && currentUser.role === 'admin'" v-if="currentUser && currentUser.role === 'admin'"
href="/pleroma/admin/#/login-pleroma" href="/pleroma/admin/#/login-pleroma"
@@ -76,11 +77,15 @@
</div> </div>
</div> </div>
</nav> </nav>
<div class="app-bg-wrapper app-container-wrapper" />
<div <div
id="content" id="content"
class="container underlay" class="container underlay"
> >
<div class="sidebar-flexer mobile-hidden"> <div
class="sidebar-flexer mobile-hidden"
:style="sidebarAlign"
>
<div class="sidebar-bounds"> <div class="sidebar-bounds">
<div class="sidebar-scroller"> <div class="sidebar-scroller">
<div class="sidebar"> <div class="sidebar">
@@ -108,9 +113,7 @@
{{ $t("login.hint") }} {{ $t("login.hint") }}
</router-link> </router-link>
</div> </div>
<transition name="fade">
<router-view /> <router-view />
</transition>
</div> </div>
<media-modal /> <media-modal />
</div> </div>
@@ -122,7 +125,9 @@
<MobilePostStatusButton /> <MobilePostStatusButton />
<UserReportingModal /> <UserReportingModal />
<PostStatusModal /> <PostStatusModal />
<SettingsModal />
<portal-target name="modal" /> <portal-target name="modal" />
<GlobalNoticeList />
</div> </div>
</template> </template>
+1
View File
@@ -27,5 +27,6 @@ $fallback--tooltipRadius: 5px;
$fallback--avatarRadius: 4px; $fallback--avatarRadius: 4px;
$fallback--avatarAltRadius: 10px; $fallback--avatarAltRadius: 10px;
$fallback--attachmentRadius: 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; $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;
+85 -28
View File
@@ -8,38 +8,72 @@ import backendInteractorService from '../services/backend_interactor_service/bac
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
import { applyTheme } from '../services/style_setter/style_setter.js' import { applyTheme } from '../services/style_setter/style_setter.js'
const getStatusnetConfig = async ({ store }) => { let staticInitialResults = null
const parsedInitialResults = () => {
if (!document.getElementById('initial-results')) {
return null
}
if (!staticInitialResults) {
staticInitialResults = JSON.parse(document.getElementById('initial-results').textContent)
}
return staticInitialResults
}
const decodeUTF8Base64 = (data) => {
const rawData = atob(data)
const array = Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)))
const text = new TextDecoder().decode(array)
return text
}
const preloadFetch = async (request) => {
const data = parsedInitialResults()
if (!data || !data[request]) {
return window.fetch(request)
}
const decoded = decodeUTF8Base64(data[request])
const requestData = JSON.parse(decoded)
return {
ok: true,
json: () => requestData,
text: () => requestData
}
}
const getInstanceConfig = async ({ store }) => {
try { try {
const res = await window.fetch('/api/statusnet/config.json') const res = await preloadFetch('/api/v1/instance')
if (res.ok) { if (res.ok) {
const data = await res.json() const data = await res.json()
const { name, closed: registrationClosed, textlimit, uploadlimit, server, vapidPublicKey, safeDMMentionsEnabled } = data.site const textlimit = data.max_toot_chars
const vapidPublicKey = data.pleroma.vapid_public_key
store.dispatch('setInstanceOption', { name: 'name', value: name }) store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: (registrationClosed === '0') })
store.dispatch('setInstanceOption', { name: 'textlimit', value: parseInt(textlimit) })
store.dispatch('setInstanceOption', { name: 'server', value: server })
store.dispatch('setInstanceOption', { name: 'safeDM', value: safeDMMentionsEnabled !== '0' })
// TODO: default values for this stuff, added if to not make it break on
// my dev config out of the box.
if (uploadlimit) {
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadlimit.uploadlimit) })
store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadlimit.avatarlimit) })
store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadlimit.backgroundlimit) })
store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadlimit.bannerlimit) })
}
if (vapidPublicKey) { if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
} }
return data.site.pleromafe
} else { } else {
throw (res) throw (res)
} }
} catch (error) { } catch (error) {
console.error('Could not load statusnet config, potentially fatal') console.error('Could not load instance config, potentially fatal')
console.error(error)
}
}
const getBackendProvidedConfig = async ({ store }) => {
try {
const res = await window.fetch('/api/pleroma/frontend_configurations')
if (res.ok) {
const data = await res.json()
return data.pleroma_fe
} else {
throw (res)
}
} catch (error) {
console.error('Could not load backend-provided frontend config, potentially fatal')
console.error(error) console.error(error)
} }
} }
@@ -108,9 +142,9 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('subjectLineBehavior') copyInstanceOption('subjectLineBehavior')
copyInstanceOption('postContentType') copyInstanceOption('postContentType')
copyInstanceOption('alwaysShowSubjectInput') copyInstanceOption('alwaysShowSubjectInput')
copyInstanceOption('noAttachmentLinks')
copyInstanceOption('showFeaturesPanel') copyInstanceOption('showFeaturesPanel')
copyInstanceOption('hideSitename') copyInstanceOption('hideSitename')
copyInstanceOption('sidebarRight')
return store.dispatch('setTheme', config['theme']) return store.dispatch('setTheme', config['theme'])
} }
@@ -132,7 +166,7 @@ const getTOS = async ({ store }) => {
const getInstancePanel = async ({ store }) => { const getInstancePanel = async ({ store }) => {
try { try {
const res = await window.fetch('/instance/panel.html') const res = await preloadFetch('/instance/panel.html')
if (res.ok) { if (res.ok) {
const html = await res.text() const html = await res.text()
store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html }) store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html })
@@ -189,24 +223,34 @@ const getAppSecret = async ({ store }) => {
const resolveStaffAccounts = ({ store, accounts }) => { const resolveStaffAccounts = ({ store, accounts }) => {
const nicknames = accounts.map(uri => uri.split('/').pop()) const nicknames = accounts.map(uri => uri.split('/').pop())
nicknames.map(nickname => store.dispatch('fetchUser', nickname))
store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames }) store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
} }
const getNodeInfo = async ({ store }) => { const getNodeInfo = async ({ store }) => {
try { try {
const res = await window.fetch('/nodeinfo/2.0.json') const res = await preloadFetch('/nodeinfo/2.0.json')
if (res.ok) { if (res.ok) {
const data = await res.json() const data = await res.json()
const metadata = data.metadata const metadata = data.metadata
const features = metadata.features const features = metadata.features
store.dispatch('setInstanceOption', { name: 'name', value: metadata.nodeName })
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') }) 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: 'chatAvailable', value: features.includes('chat') }) store.dispatch('setInstanceOption', { name: 'chatAvailable', 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: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') }) store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits }) store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
const uploadLimits = metadata.uploadLimits
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadLimits.avatar) })
store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadLimits.background) })
store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadLimits.banner) })
store.dispatch('setInstanceOption', { name: 'fieldsLimits', value: metadata.fieldsLimits })
store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats }) store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
@@ -241,6 +285,9 @@ const getNodeInfo = async ({ store }) => {
: federation.enabled : federation.enabled
}) })
const accountActivationRequired = metadata.accountActivationRequired
store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired })
const accounts = metadata.staffAccounts const accounts = metadata.staffAccounts
resolveStaffAccounts({ store, accounts }) resolveStaffAccounts({ store, accounts })
} else { } else {
@@ -254,7 +301,7 @@ const getNodeInfo = async ({ store }) => {
const setConfig = async ({ store }) => { const setConfig = async ({ store }) => {
// apiConfig, staticConfig // apiConfig, staticConfig
const configInfos = await Promise.all([getStatusnetConfig({ store }), getStaticConfig()]) const configInfos = await Promise.all([getBackendProvidedConfig({ store }), getStaticConfig()])
const apiConfig = configInfos[0] const apiConfig = configInfos[0]
const staticConfig = configInfos[1] const staticConfig = configInfos[1]
@@ -277,6 +324,11 @@ const checkOAuthToken = async ({ store }) => {
const afterStoreSetup = async ({ store, i18n }) => { const afterStoreSetup = async ({ store, i18n }) => {
const width = windowWidth() const width = windowWidth()
store.dispatch('setMobileLayout', width <= 800) store.dispatch('setMobileLayout', width <= 800)
const overrides = window.___pleromafe_dev_overrides || {}
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
store.dispatch('setInstanceOption', { name: 'server', value: server })
await setConfig({ store }) await setConfig({ store })
const { customTheme, customThemeSource } = store.state.config const { customTheme, customThemeSource } = store.state.config
@@ -296,14 +348,19 @@ const afterStoreSetup = async ({ store, i18n }) => {
} }
// Now we can try getting the server settings and logging in // Now we can try getting the server settings and logging in
// Most of these are preloaded into the index.html so blocking is minimized
await Promise.all([ await Promise.all([
checkOAuthToken({ store }), checkOAuthToken({ store }),
getTOS({ store }),
getInstancePanel({ store }), getInstancePanel({ store }),
getStickers({ store }), getNodeInfo({ store }),
getNodeInfo({ store }) getInstanceConfig({ store })
]) ])
// Start fetching things that don't need to block the UI
store.dispatch('fetchMutes')
getTOS({ store })
getStickers({ store })
const router = new VueRouter({ const router = new VueRouter({
mode: 'history', mode: 'history',
routes: routes(store), routes: routes(store),
+15 -6
View File
@@ -2,15 +2,16 @@ import PublicTimeline from 'components/public_timeline/public_timeline.vue'
import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_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 FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
import TagTimeline from 'components/tag_timeline/tag_timeline.vue' import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue'
import ConversationPage from 'components/conversation-page/conversation-page.vue' import ConversationPage from 'components/conversation-page/conversation-page.vue'
import Interactions from 'components/interactions/interactions.vue' import Interactions from 'components/interactions/interactions.vue'
import DMs from 'components/dm_timeline/dm_timeline.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 UserProfile from 'components/user_profile/user_profile.vue'
import Search from 'components/search/search.vue' import Search from 'components/search/search.vue'
import Settings from 'components/settings/settings.vue'
import Registration from 'components/registration/registration.vue' import Registration from 'components/registration/registration.vue'
import PasswordReset from 'components/password_reset/password_reset.vue' import PasswordReset from 'components/password_reset/password_reset.vue'
import UserSettings from 'components/user_settings/user_settings.vue'
import FollowRequests from 'components/follow_requests/follow_requests.vue' import FollowRequests from 'components/follow_requests/follow_requests.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue' import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
import Notifications from 'components/notifications/notifications.vue' import Notifications from 'components/notifications/notifications.vue'
@@ -29,7 +30,7 @@ export default (store) => {
} }
} }
return [ let routes = [
{ name: 'root', { name: 'root',
path: '/', path: '/',
redirect: _to => { redirect: _to => {
@@ -42,6 +43,7 @@ export default (store) => {
{ name: 'public-timeline', path: '/main/public', component: PublicTimeline }, { name: 'public-timeline', path: '/main/public', component: PublicTimeline },
{ name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute }, { name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline }, { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
{ name: 'remote-user-profile-acct', { name: 'remote-user-profile-acct',
path: '/remote-users/(@?):username([^/@]+)@:hostname([^/@]+)', path: '/remote-users/(@?):username([^/@]+)@:hostname([^/@]+)',
@@ -56,19 +58,26 @@ export default (store) => {
{ name: 'external-user-profile', path: '/users/:id', component: UserProfile }, { name: 'external-user-profile', path: '/users/:id', component: UserProfile },
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute }, { name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute }, { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'settings', path: '/settings', component: Settings },
{ name: 'registration', path: '/registration', component: Registration }, { name: 'registration', path: '/registration', component: Registration },
{ name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true }, { name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true },
{ name: 'registration-token', path: '/registration/:token', component: Registration }, { name: 'registration-token', path: '/registration/:token', component: Registration },
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute }, { name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
{ name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute },
{ name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute }, { name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute },
{ name: 'login', path: '/login', component: AuthForm }, { name: 'login', path: '/login', component: AuthForm },
{ name: 'chat', path: '/chat', component: ChatPanel, props: () => ({ floating: false }) }, { name: 'chat-panel', path: '/chat-panel', component: ChatPanel, props: () => ({ floating: false }) },
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) }, { 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: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute }, { name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
{ name: 'about', path: '/about', component: About }, { name: 'about', path: '/about', component: About },
{ name: 'user-profile', path: '/(users/)?:name', component: UserProfile } { name: 'user-profile', path: '/(users/)?:name', component: UserProfile }
] ]
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
} }
@@ -1,9 +1,10 @@
import { mapState } from 'vuex'
import ProgressButton from '../progress_button/progress_button.vue' import ProgressButton from '../progress_button/progress_button.vue'
import Popover from '../popover/popover.vue' import Popover from '../popover/popover.vue'
const AccountActions = { const AccountActions = {
props: [ props: [
'user' 'user', 'relationship'
], ],
data () { data () {
return { } return { }
@@ -27,7 +28,18 @@ const AccountActions = {
}, },
reportUser () { reportUser () {
this.$store.dispatch('openUserReportingModal', this.user.id) this.$store.dispatch('openUserReportingModal', this.user.id)
},
openChat () {
this.$router.push({
name: 'chat',
params: { recipient_id: this.user.id }
})
} }
},
computed: {
...mapState({
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
})
} }
} }
@@ -3,22 +3,23 @@
<Popover <Popover
trigger="click" trigger="click"
placement="bottom" placement="bottom"
:bound-to="{ x: 'container' }"
> >
<div <div
slot="content" slot="content"
class="account-tools-popover" class="account-tools-popover"
> >
<div class="dropdown-menu"> <div class="dropdown-menu">
<template v-if="user.following"> <template v-if="relationship.following">
<button <button
v-if="user.showing_reblogs" v-if="relationship.showing_reblogs"
class="btn btn-default dropdown-item" class="btn btn-default dropdown-item"
@click="hideRepeats" @click="hideRepeats"
> >
{{ $t('user_card.hide_repeats') }} {{ $t('user_card.hide_repeats') }}
</button> </button>
<button <button
v-if="!user.showing_reblogs" v-if="!relationship.showing_reblogs"
class="btn btn-default dropdown-item" class="btn btn-default dropdown-item"
@click="showRepeats" @click="showRepeats"
> >
@@ -30,7 +31,7 @@
/> />
</template> </template>
<button <button
v-if="user.statusnet_blocking" v-if="relationship.blocking"
class="btn btn-default btn-block dropdown-item" class="btn btn-default btn-block dropdown-item"
@click="unblockUser" @click="unblockUser"
> >
@@ -49,6 +50,13 @@
> >
{{ $t('user_card.report') }} {{ $t('user_card.report') }}
</button> </button>
<button
v-if="pleromaChatMessagesAvailable"
class="btn btn-default btn-block dropdown-item"
@click="openChat"
>
{{ $t('user_card.message') }}
</button>
</div> </div>
</div> </div>
<div <div
@@ -0,0 +1,41 @@
<template>
<div class="async-component-error">
<div>
<h4>
{{ $t('general.generic_error') }}
</h4>
<p>
{{ $t('general.error_retry') }}
</p>
<button
class="btn"
@click="retry"
>
{{ $t('general.retry') }}
</button>
</div>
</div>
</template>
<script>
export default {
methods: {
retry () {
this.$emit('resetAsyncComponent')
}
}
}
</script>
<style lang="scss">
.async-component-error {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
.btn {
margin: .5em;
padding: .5em 2em;
}
}
</style>
+23 -9
View File
@@ -8,7 +8,6 @@ const Attachment = {
props: [ props: [
'attachment', 'attachment',
'nsfw', 'nsfw',
'statusId',
'size', 'size',
'allowPlay', 'allowPlay',
'setMedia', 'setMedia',
@@ -30,9 +29,21 @@ const Attachment = {
VideoAttachment VideoAttachment
}, },
computed: { computed: {
usePlaceHolder () { usePlaceholder () {
return this.size === 'hide' || this.type === 'unknown' return this.size === 'hide' || this.type === 'unknown'
}, },
placeholderName () {
if (this.attachment.description === '' || !this.attachment.description) {
return this.type.toUpperCase()
}
return this.attachment.description
},
placeholderIconClass () {
if (this.type === 'image') return 'icon-picture'
if (this.type === 'video') return 'icon-video'
if (this.type === 'audio') return 'icon-music'
return 'icon-doc'
},
referrerpolicy () { referrerpolicy () {
return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer' return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer'
}, },
@@ -49,7 +60,15 @@ const Attachment = {
return this.size === 'small' return this.size === 'small'
}, },
fullwidth () { fullwidth () {
return this.type === 'html' || this.type === 'audio' if (this.size === 'hide') return false
return this.type === 'html' || this.type === 'audio' || this.type === 'unknown'
},
useModal () {
const modalTypes = this.size === 'hide' ? ['image', 'video', 'audio']
: this.mergedConfig.playVideosInModal
? ['image', 'video']
: ['image']
return modalTypes.includes(this.type)
}, },
...mapGetters(['mergedConfig']) ...mapGetters(['mergedConfig'])
}, },
@@ -60,12 +79,7 @@ const Attachment = {
} }
}, },
openModal (event) { openModal (event) {
const modalTypes = this.mergedConfig.playVideosInModal if (this.useModal) {
? ['image', 'video']
: ['image']
if (fileTypeService.fileMatchesSomeType(modalTypes, this.attachment) ||
this.usePlaceHolder
) {
event.stopPropagation() event.stopPropagation()
event.preventDefault() event.preventDefault()
this.setMedia() this.setMedia()
+22 -13
View File
@@ -1,6 +1,7 @@
<template> <template>
<div <div
v-if="usePlaceHolder" v-if="usePlaceholder"
:class="{ 'fullwidth': fullwidth }"
@click="openModal" @click="openModal"
> >
<a <a
@@ -8,8 +9,11 @@
class="placeholder" class="placeholder"
target="_blank" target="_blank"
:href="attachment.url" :href="attachment.url"
:alt="attachment.description"
:title="attachment.description"
> >
[{{ nsfw ? "NSFW/" : "" }}{{ type.toUpperCase() }}] <span :class="placeholderIconClass" />
<b>{{ nsfw ? "NSFW / " : "" }}</b>{{ placeholderName }}
</a> </a>
</div> </div>
<div <div
@@ -22,6 +26,8 @@
v-if="hidden" v-if="hidden"
class="image-attachment" class="image-attachment"
:href="attachment.url" :href="attachment.url"
:alt="attachment.description"
:title="attachment.description"
@click.prevent="toggleHidden" @click.prevent="toggleHidden"
> >
<img <img
@@ -51,14 +57,15 @@
:class="{'hidden': hidden && preloadImage }" :class="{'hidden': hidden && preloadImage }"
:href="attachment.url" :href="attachment.url"
target="_blank" target="_blank"
:title="attachment.description"
@click="openModal" @click="openModal"
> >
<StillImage <StillImage
class="image"
:referrerpolicy="referrerpolicy" :referrerpolicy="referrerpolicy"
:mimetype="attachment.mimetype" :mimetype="attachment.mimetype"
:src="attachment.large_thumb_url || attachment.url" :src="attachment.large_thumb_url || attachment.url"
:image-load-handler="onImageLoad" :image-load-handler="onImageLoad"
:alt="attachment.description"
/> />
</a> </a>
@@ -83,6 +90,8 @@
<audio <audio
v-if="type === 'audio'" v-if="type === 'audio'"
:src="attachment.url" :src="attachment.url"
:alt="attachment.description"
:title="attachment.description"
controls controls
/> />
@@ -116,22 +125,19 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
.attachment.media-upload-container { .non-gallery {
flex: 0 0 auto;
max-height: 200px;
max-width: 100%; max-width: 100%;
display: flex;
align-items: center;
video {
max-width: 100%;
}
} }
.placeholder { .placeholder {
margin-right: 8px; display: inline-block;
margin-bottom: 4px; padding: 0.3em 1em 0.3em 0;
color: $fallback--link; color: $fallback--link;
color: var(--postLink, $fallback--link); color: var(--postLink, $fallback--link);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 100%;
} }
.nsfw-placeholder { .nsfw-placeholder {
@@ -276,8 +282,11 @@
} }
.image-attachment { .image-attachment {
&,
& .image {
width: 100%; width: 100%;
height: 100%; height: 100%;
}
&.hidden { &.hidden {
display: none; display: none;
@@ -12,7 +12,7 @@
class="basic-user-card-expanded-content" class="basic-user-card-expanded-content"
> >
<UserCard <UserCard
:user="user" :user-id="user.id"
:rounded="true" :rounded="true"
:bordered="true" :bordered="true"
/> />
+4 -1
View File
@@ -11,8 +11,11 @@ const BlockCard = {
user () { user () {
return this.$store.getters.findUser(this.userId) return this.$store.getters.findUser(this.userId)
}, },
relationship () {
return this.$store.getters.relationship(this.userId)
},
blocked () { blocked () {
return this.user.statusnet_blocking return this.relationship.blocking
} }
}, },
components: { components: {
@@ -0,0 +1,17 @@
import Timeline from '../timeline/timeline.vue'
const Bookmarks = {
computed: {
timeline () {
return this.$store.state.statuses.timelines.bookmarks
}
},
components: {
Timeline
},
destroyed () {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
}
}
export default Bookmarks
@@ -0,0 +1,9 @@
<template>
<Timeline
:title="$t('nav.bookmarks')"
:timeline="timeline"
:timeline-name="'bookmarks'"
/>
</template>
<script src="./bookmark_timeline.js"></script>
+337
View File
@@ -0,0 +1,337 @@
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 { getScrollPosition, getNewTopPosition, isBottomedOut, scrollableContainerHeight } from './chat_layout_utils.js'
const BOTTOMED_OUT_OFFSET = 10
const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 150
const SAFE_RESIZE_TIME_OFFSET = 100
const Chat = {
components: {
ChatMessage,
ChatTitle,
PostStatusForm
},
data () {
return {
jumpToBottomButtonVisible: false,
hoveredMessageChainId: undefined,
lastScrollPosition: {},
scrollableContainerHeight: '100%',
errorLoadingChat: false
}
},
created () {
this.startFetching()
window.addEventListener('resize', this.handleLayoutChange)
},
mounted () {
window.addEventListener('scroll', this.handleScroll)
if (typeof document.hidden !== 'undefined') {
document.addEventListener('visibilitychange', this.handleVisibilityChange, false)
}
this.$nextTick(() => {
this.updateScrollableContainerHeight()
this.handleResize()
})
this.setChatLayout()
},
destroyed () {
window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleLayoutChange)
this.unsetChatLayout()
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 })
} 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.mobileLayout,
layoutHeight: state => state.interface.layoutHeight,
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({ forceRead: !document.hidden })
}
})
},
'$route': function () {
this.startFetching()
},
layoutHeight () {
this.handleResize({ expand: true })
},
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()
this.updateScrollableContainerHeight()
})
},
handleVisibilityChange () {
this.$nextTick(() => {
if (!document.hidden && this.bottomedOut(BOTTOMED_OUT_OFFSET)) {
this.scrollDown({ forceRead: true })
}
})
},
setChatLayout () {
// This is a hacky way to adjust the global layout to the mobile chat (without modifying the rest of the app).
// This layout prevents empty spaces from being visible at the bottom
// of the chat on iOS Safari (`safe-area-inset`) when
// - the on-screen keyboard appears and the user starts typing
// - the user selects the text inside the input area
// - the user selects and deletes the text that is multiple lines long
// TODO: unify the chat layout with the global layout.
let html = document.querySelector('html')
if (html) {
html.classList.add('chat-layout')
}
this.$nextTick(() => {
this.updateScrollableContainerHeight()
})
},
unsetChatLayout () {
let html = document.querySelector('html')
if (html) {
html.classList.remove('chat-layout')
}
},
handleLayoutChange () {
this.$nextTick(() => {
this.updateScrollableContainerHeight()
this.scrollDown()
})
},
// Ensures the proper position of the posting form in the mobile layout (the mobile browser panel does not overlap or hide it)
updateScrollableContainerHeight () {
const header = this.$refs.header
const footer = this.$refs.footer
const inner = this.mobileLayout ? window.document.body : this.$refs.inner
this.scrollableContainerHeight = scrollableContainerHeight(inner, header, footer) + 'px'
},
// Preserves the scroll position when OSK appears or the posting form changes its height.
handleResize (opts = {}) {
const { expand = false, delayed = false } = opts
if (delayed) {
setTimeout(() => {
this.handleResize({ ...opts, delayed: false })
}, SAFE_RESIZE_TIME_OFFSET)
return
}
this.$nextTick(() => {
this.updateScrollableContainerHeight()
const { offsetHeight = undefined } = this.lastScrollPosition
this.lastScrollPosition = getScrollPosition(this.$refs.scrollable)
const diff = this.lastScrollPosition.offsetHeight - offsetHeight
if (diff < 0 || (!this.bottomedOut() && expand)) {
this.$nextTick(() => {
this.updateScrollableContainerHeight()
this.$refs.scrollable.scrollTo({
top: this.$refs.scrollable.scrollTop - diff,
left: 0
})
})
}
})
},
scrollDown (options = {}) {
const { behavior = 'auto', forceRead = false } = options
const scrollable = this.$refs.scrollable
if (!scrollable) { return }
this.$nextTick(() => {
scrollable.scrollTo({ top: scrollable.scrollHeight, left: 0, behavior })
})
if (forceRead || this.newMessageCount > 0) {
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(this.$refs.scrollable, offset)
},
reachedTop () {
const scrollable = this.$refs.scrollable
return scrollable && scrollable.scrollTop <= 0
},
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
if (this.newMessageCount > 0) {
this.readChat()
}
} else {
this.jumpToBottomButtonVisible = true
}
}, 100),
handleScrollUp (positionBeforeLoading) {
const positionAfterLoading = getScrollPosition(this.$refs.scrollable)
this.$refs.scrollable.scrollTo({
top: getNewTopPosition(positionBeforeLoading, positionAfterLoading),
left: 0
})
},
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
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.$refs.scrollable)
this.$store.dispatch('addChatMessages', { chatId, messages }).then(() => {
this.$nextTick(() => {
if (fetchOlderMessages) {
this.handleScrollUp(positionBeforeUpdate)
}
if (isFirstFetch) {
this.updateScrollableContainerHeight()
}
})
})
})
},
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: () => setInterval(() => this.fetchChat({ fetchLatest: true }), 5000)
})
this.fetchChat({ isFirstFetch: true })
},
sendMessage ({ status, media }) {
const params = {
id: this.currentChat.id,
content: status
}
if (media[0]) {
params.mediaId = media[0].id
}
return this.backendInteractor.sendChatMessage(params)
.then(data => {
this.$store.dispatch('addChatMessages', {
chatId: this.currentChat.id,
messages: [data],
updateMaxId: false
}).then(() => {
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.
setTimeout(() => {
this.updateScrollableContainerHeight()
}, SAFE_RESIZE_TIME_OFFSET)
this.scrollDown({ forceRead: true })
})
})
return data
})
.catch(error => {
console.error('Error sending message', error)
return {
error: this.$t('chats.error_sending_message')
}
})
},
goBack () {
this.$router.push({ name: 'chats', params: { username: this.currentUser.screen_name } })
}
}
}
export default Chat
+162
View File
@@ -0,0 +1,162 @@
.chat-view {
display: flex;
height: calc(100vh - 60px);
width: 100%;
.chat-title {
// prevents chat header jumping on when the user avatar loads
height: 28px;
}
.chat-view-inner {
height: auto;
width: 100%;
overflow: visible;
display: flex;
margin: 0.5em 0.5em 0 0.5em;
}
.chat-view-body {
background-color: var(--chatBg, $fallback--bg);
display: flex;
flex-direction: column;
width: 100%;
overflow: visible;
min-height: 100%;
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;
}
}
.scrollable-message-list {
padding: 0 0.8em;
height: 100%;
overflow-y: scroll;
overflow-x: hidden;
display: flex;
flex-direction: column;
}
.footer {
position: sticky;
bottom: 0;
}
.chat-view-heading {
align-items: center;
justify-content: space-between;
top: 50px;
display: flex;
z-index: 2;
position: sticky;
overflow: hidden;
}
.go-back-button {
cursor: pointer;
margin-right: 1.4em;
i {
display: flex;
align-items: center;
}
}
.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: 0px 1px 1px rgba(0, 0, 0, 0.3), 0px 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%;
transform: translate(-50%, 0);
border-radius: 100%;
margin-top: -1rem;
padding: 0;
}
.chat-loading-error {
width: 100%;
display: flex;
align-items: flex-end;
height: 100%;
.error {
width: 100%;
}
}
}
@media all and (max-width: 800px) {
height: 100%;
overflow: hidden;
.chat-view-inner {
overflow: hidden;
height: 100%;
margin-top: 0;
margin-left: 0;
margin-right: 0;
}
.chat-view-body {
display: flex;
min-height: auto;
overflow: hidden;
height: 100%;
margin: 0;
border-radius: 0;
}
.chat-view-heading {
position: static;
z-index: 9999;
top: 0;
margin-top: 0;
border-radius: 0;
}
.scrollable-message-list {
display: unset;
overflow-y: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
.footer {
position: sticky;
bottom: auto;
}
}
}
+100
View File
@@ -0,0 +1,100 @@
<template>
<div class="chat-view">
<div class="chat-view-inner">
<div
id="nav"
ref="inner"
class="panel-default panel chat-view-body"
>
<div
ref="header"
class="panel-heading chat-view-heading mobile-hidden"
>
<a
class="go-back-button"
@click="goBack"
>
<i class="button-icon icon-left-open" />
</a>
<div class="title text-center">
<ChatTitle
:user="recipient"
:with-avatar="true"
/>
</div>
</div>
<template>
<div
ref="scrollable"
class="scrollable-message-list"
:style="{ height: scrollableContainerHeight }"
@scroll="handleScroll"
>
<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' })"
>
<i class="icon-down-open">
<div
v-if="newMessageCount"
class="badge badge-notification unread-chat-count unread-message-count"
>
{{ newMessageCount }}
</div>
</i>
</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"
: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>
</template>
</div>
</div>
</div>
</template>
<script src="./chat.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import './chat.scss';
</style>
+26
View File
@@ -0,0 +1,26 @@
// Captures a scroll position
export const getScrollPosition = (el) => {
return {
scrollTop: el.scrollTop,
scrollHeight: el.scrollHeight,
offsetHeight: el.offsetHeight
}
}
// 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 = (el, offset = 0) => {
if (!el) { return }
const scrollHeight = el.scrollTop + offset
const totalHeight = el.scrollHeight - el.offsetHeight
return totalHeight <= scrollHeight
}
// Height of the scrollable container. The dynamic height is needed to ensure the mobile browser panel doesn't overlap or hide the posting form.
export const scrollableContainerHeight = (inner, header, footer) => {
return inner.offsetHeight - header.clientHeight - footer.clientHeight
}
+37
View File
@@ -0,0 +1,37 @@
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
+64
View File
@@ -0,0 +1,64 @@
<template>
<div v-if="isNew">
<ChatNew @cancel="cancelNewChat" />
</div>
<div
v-else
class="chat-list panel panel-default"
>
<div class="panel-heading">
<span class="title">
{{ $t("chats.chats") }}
</span>
<button @click="newChat">
{{ $t("chats.new") }}
</button>
</div>
<div class="panel-body">
<div
v-if="sortedChatList.length > 0"
class="timeline"
>
<List :items="sortedChatList">
<template
slot="item"
slot-scope="{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>
@@ -0,0 +1,67 @@
import { mapState } from 'vuex'
import StatusContent 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,
StatusContent
},
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 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: '',
statusnet_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
@@ -0,0 +1,94 @@
.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: 1.4em;
}
.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);
}
.StatusContent {
img.emoji {
width: 1.4em;
height: 1.4em;
}
}
.time-wrapper {
line-height: 1.4em;
}
.single-line {
padding-right: 1em;
}
}
@@ -0,0 +1,52 @@
<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>
<div class="chat-preview">
<StatusContent
:status="messageForStatusContent"
:single-line="true"
/>
<div
v-if="chat.unread > 0"
class="badge badge-notification unread-chat-count"
>
{{ chat.unread }}
</div>
</div>
</div>
<div class="time-wrapper">
<Timeago
:time="chat.updated_at"
:auto-update="60"
/>
</div>
</div>
</template>
<script src="./chat_list_item.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import './chat_list_item.scss';
</style>
@@ -0,0 +1,96 @@
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'
const ChatMessage = {
name: 'ChatMessage',
props: [
'author',
'edited',
'noHeading',
'chatViewItem',
'hoveredMessageChain'
],
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: '',
statusnet_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
@@ -0,0 +1,164 @@
@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;
}
}
.icon-ellipsis {
cursor: pointer;
&:hover, .extra-button-popover.open & {
color: $fallback--text;
color: var(--text, $fallback--text);
}
border-radius: $fallback--chatMessageRadius;
border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius);
}
.popover {
width: 12em;
}
.chat-message {
display: flex;
padding-bottom: 0.5em;
}
.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%;
.gallery-row {
overflow: hidden;
}
.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 {
.status-content {
&::after {
margin-right: 5.4em;
content: " ";
display: inline-block;
}
}
}
.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);
}
@@ -0,0 +1,99 @@
<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 }"
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"
>
<div slot="content">
<div class="dropdown-menu">
<button
class="dropdown-item dropdown-item-icon"
@click="deleteMessage"
>
<i class="icon-cancel" /> {{ $t("chats.delete") }}
</button>
</div>
</div>
<button
slot="trigger"
:title="$t('chats.more')"
>
<i class="icon-ellipsis" />
</button>
</Popover>
</div>
<StatusContent
:status="messageForStatusContent"
:full-content="true"
>
<span
slot="footer"
class="created-at"
>
{{ createdAt }}
</span>
</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>
@@ -0,0 +1,24 @@
<template>
<time>
{{ displayDate }}
</time>
</template>
<script>
export default {
name: 'Timeago',
props: ['date'],
computed: {
displayDate () {
const today = new Date()
today.setHours(0, 0, 0, 0)
if (this.date.getTime() === today.getTime()) {
return this.$t('display_date.today')
} else {
return this.date.toLocaleDateString('en', { day: 'numeric', month: 'long' })
}
}
}
}
</script>
+73
View File
@@ -0,0 +1,73 @@
import { mapState, mapGetters } from 'vuex'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
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
+29
View File
@@ -0,0 +1,29 @@
.chat-new {
.input-wrap {
display: flex;
margin: 0.7em 0.5em 0.7em 0.5em;
input {
width: 100%;
}
}
.icon-search {
font-size: 1.5em;
float: right;
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 {
cursor: pointer;
}
}
+46
View File
@@ -0,0 +1,46 @@
<template>
<div
id="nav"
class="panel-default panel chat-new"
>
<div
ref="header"
class="panel-heading"
>
<a
class="go-back-button"
@click="goBack"
>
<i class="button-icon icon-left-open" />
</a>
</div>
<div class="input-wrap">
<div class="input-search">
<i class="button-icon 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>
+5 -3
View File
@@ -10,7 +10,7 @@
@click.stop.prevent="togglePanel" @click.stop.prevent="togglePanel"
> >
<div class="title"> <div class="title">
<span>{{ $t('chat.title') }}</span> <span>{{ $t('shoutbox.title') }}</span>
<i <i
v-if="floating" v-if="floating"
class="icon-cancel" class="icon-cancel"
@@ -63,8 +63,8 @@
@click.stop.prevent="togglePanel" @click.stop.prevent="togglePanel"
> >
<div class="title"> <div class="title">
<i class="icon-comment-empty" /> <i class="icon-megaphone" />
{{ $t('chat.title') }} {{ $t('shoutbox.title') }}
</div> </div>
</div> </div>
</div> </div>
@@ -84,6 +84,7 @@
max-width: 25em; max-width: 25em;
} }
.chat-panel {
.chat-heading { .chat-heading {
cursor: pointer; cursor: pointer;
.icon-comment-empty { .icon-comment-empty {
@@ -134,4 +135,5 @@
justify-content: space-between; justify-content: space-between;
} }
} }
}
</style> </style>
+26
View File
@@ -0,0 +1,26 @@
import Vue from 'vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import UserAvatar from '../user_avatar/user_avatar.vue'
export default Vue.component('chat-title', {
name: 'ChatTitle',
components: {
UserAvatar
},
props: [
'user', 'withAvatar'
],
computed: {
title () {
return this.user ? this.user.screen_name : ''
},
htmlTitle () {
return this.user ? this.user.name_html : ''
}
},
methods: {
getUserProfileLink (user) {
return generateProfileLink(user.id, user.screen_name)
}
}
})
+67
View File
@@ -0,0 +1,67 @@
<template>
<!-- eslint-disable vue/no-v-html -->
<div
class="chat-title"
:title="title"
>
<router-link
v-if="withAvatar && user"
:to="getUserProfileLink(user)"
>
<UserAvatar
:user="user"
width="23px"
height="23px"
/>
</router-link>
<span
class="username"
v-html="htmlTitle"
/>
</div>
<!-- eslint-enable vue/no-v-html -->
</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;
align-items: center;
.username {
max-width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
display: inline;
word-wrap: break-word;
overflow: hidden;
text-overflow: ellipsis;
.emoji {
width: 14px;
height: 14px;
vertical-align: middle;
object-fit: contain
}
}
.Avatar {
width: 23px;
height: 23px;
margin-right: 0.5em;
border-radius: $fallback--avatarAltRadius;
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
&.animated::before {
display: none;
}
}
}
</style>
+1 -1
View File
@@ -52,7 +52,7 @@ export default {
right: 0; right: 0;
top: 0; top: 0;
display: block; display: block;
content: ''; content: '';
transition: color 200ms; transition: color 200ms;
width: 1.1em; width: 1.1em;
height: 1.1em; height: 1.1em;
@@ -39,13 +39,16 @@
export default { export default {
props: { props: {
large: { large: {
required: false required: false,
type: Boolean,
default: false
}, },
// TODO: Make theme switcher compute theme initially so that contrast // TODO: Make theme switcher compute theme initially so that contrast
// component won't be called without contrast data // component won't be called without contrast data
contrast: { contrast: {
required: false, required: false,
type: Object type: Object,
default: () => ({})
} }
}, },
computed: { computed: {
+20 -7
View File
@@ -1,7 +1,7 @@
<template> <template>
<div <div
class="timeline panel-default" class="Conversation"
:class="[isExpanded ? 'panel' : 'panel-disabled']" :class="{ '-expanded' : isExpanded, 'panel' : isExpanded }"
> >
<div <div
v-if="isExpanded" v-if="isExpanded"
@@ -28,7 +28,7 @@
:replies="getReplies(status.id)" :replies="getReplies(status.id)"
:in-profile="inProfile" :in-profile="inProfile"
:profile-user-id="profileUserId" :profile-user-id="profileUserId"
class="status-fadein panel-body" class="conversation-status status-fadein panel-body"
@goto="setHighlight" @goto="setHighlight"
@toggleExpanded="toggleExpanded" @toggleExpanded="toggleExpanded"
/> />
@@ -40,15 +40,28 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.timeline { .Conversation {
.panel-disabled { .conversation-status {
.status-el {
border-left: none; border-left: none;
border-bottom-width: 1px; border-bottom-width: 1px;
border-bottom-style: solid; border-bottom-style: solid;
border-color: var(--border, $fallback--border); border-bottom-color: var(--border, $fallback--border);
border-radius: 0; border-radius: 0;
} }
&.-expanded {
.conversation-status {
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
border-left: 4px solid $fallback--cRed;
border-left: 4px solid var(--cRed, $fallback--cRed);
}
.conversation-status:last-child {
border-bottom: none;
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
}
} }
} }
</style> </style>
@@ -5,9 +5,20 @@ const DomainMuteCard = {
components: { components: {
ProgressButton ProgressButton
}, },
computed: {
user () {
return this.$store.state.users.currentUser
},
muted () {
return this.user.domainMutes.includes(this.domain)
}
},
methods: { methods: {
unmuteDomain () { unmuteDomain () {
return this.$store.dispatch('unmuteDomain', this.domain) return this.$store.dispatch('unmuteDomain', this.domain)
},
muteDomain () {
return this.$store.dispatch('muteDomain', this.domain)
} }
} }
} }
@@ -4,6 +4,7 @@
{{ domain }} {{ domain }}
</div> </div>
<ProgressButton <ProgressButton
v-if="muted"
:click="unmuteDomain" :click="unmuteDomain"
class="btn btn-default" class="btn btn-default"
> >
@@ -12,6 +13,16 @@
{{ $t('domain_mute_card.unmute_progress') }} {{ $t('domain_mute_card.unmute_progress') }}
</template> </template>
</ProgressButton> </ProgressButton>
<ProgressButton
v-else
:click="muteDomain"
class="btn btn-default"
>
{{ $t('domain_mute_card.mute') }}
<template slot="progress">
{{ $t('domain_mute_card.mute_progress') }}
</template>
</ProgressButton>
</div> </div>
</template> </template>
@@ -34,5 +45,9 @@
button { button {
width: 10em; width: 10em;
} }
.autosuggest-results & {
padding-left: 1em;
}
} }
</style> </style>
+53 -7
View File
@@ -79,6 +79,20 @@ const EmojiInput = {
required: false, required: false,
type: Boolean, type: Boolean,
default: false default: false
},
placement: {
/**
* Forces the panel to take a specific position relative to the input element.
* The 'auto' placement chooses either bottom or top depending on which has the available space (when both have available space, bottom is preferred).
*/
required: false,
type: String, // 'auto', 'top', 'bottom'
default: 'auto'
},
newlineOnCtrlEnter: {
required: false,
type: Boolean,
default: false
} }
}, },
data () { data () {
@@ -162,6 +176,11 @@ const EmojiInput = {
input.elm.removeEventListener('input', this.onInput) input.elm.removeEventListener('input', this.onInput)
} }
}, },
watch: {
showSuggestions: function (newValue) {
this.$emit('shown', newValue)
}
},
methods: { methods: {
triggerShowPicker () { triggerShowPicker () {
this.showPicker = true this.showPicker = true
@@ -190,7 +209,7 @@ const EmojiInput = {
this.$emit('input', newValue) this.$emit('input', newValue)
this.caret = 0 this.caret = 0
}, },
insert ({ insertion, keepOpen }) { insert ({ insertion, keepOpen, surroundingSpace = true }) {
const before = this.value.substring(0, this.caret) || '' const before = this.value.substring(0, this.caret) || ''
const after = this.value.substring(this.caret) || '' const after = this.value.substring(this.caret) || ''
@@ -209,8 +228,8 @@ const EmojiInput = {
* them, masto seem to be rendering :emoji::emoji: correctly now so why not * them, masto seem to be rendering :emoji::emoji: correctly now so why not
*/ */
const isSpaceRegex = /\s/ const isSpaceRegex = /\s/
const spaceBefore = !isSpaceRegex.exec(before.slice(-1)) && before.length && this.padEmoji > 0 ? ' ' : '' const spaceBefore = (surroundingSpace && !isSpaceRegex.exec(before.slice(-1)) && before.length && this.padEmoji > 0) ? ' ' : ''
const spaceAfter = !isSpaceRegex.exec(after[0]) && this.padEmoji ? ' ' : '' const spaceAfter = (surroundingSpace && !isSpaceRegex.exec(after[0]) && this.padEmoji) ? ' ' : ''
const newValue = [ const newValue = [
before, before,
@@ -367,6 +386,18 @@ const EmojiInput = {
}, },
onKeyDown (e) { onKeyDown (e) {
const { ctrlKey, shiftKey, key } = e const { ctrlKey, shiftKey, key } = e
if (this.newlineOnCtrlEnter && ctrlKey && key === 'Enter') {
this.insert({ insertion: '\n', surroundingSpace: false })
// Ensure only one new line is added on macos
e.stopPropagation()
e.preventDefault()
// Scroll the input element to the position of the cursor
this.$nextTick(() => {
this.input.elm.blur()
this.input.elm.focus()
})
}
// Disable suggestions hotkeys if suggestions are hidden // Disable suggestions hotkeys if suggestions are hidden
if (!this.temporarilyHideSuggestions) { if (!this.temporarilyHideSuggestions) {
if (key === 'Tab') { if (key === 'Tab') {
@@ -425,14 +456,29 @@ const EmojiInput = {
this.caret = selectionStart this.caret = selectionStart
}, },
resize () { resize () {
const { panel, picker } = this.$refs const panel = this.$refs.panel
if (!panel) return if (!panel) return
const picker = this.$refs.picker.$el
const panelBody = this.$refs['panel-body']
const { offsetHeight, offsetTop } = this.input.elm const { offsetHeight, offsetTop } = this.input.elm
const offsetBottom = offsetTop + offsetHeight const offsetBottom = offsetTop + offsetHeight
panel.style.top = offsetBottom + 'px' this.setPlacement(panelBody, panel, offsetBottom)
picker.$el.style.top = offsetBottom + 'px' this.setPlacement(picker, picker, offsetBottom)
picker.$el.style.bottom = 'auto' },
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))) {
target.style.top = 'auto'
target.style.bottom = this.input.elm.offsetHeight + 'px'
}
},
overflowsBottom (el) {
return el.getBoundingClientRect().bottom > window.innerHeight
} }
} }
} }
+4 -1
View File
@@ -29,7 +29,10 @@
class="autocomplete-panel" class="autocomplete-panel"
:class="{ hide: !showSuggestions }" :class="{ hide: !showSuggestions }"
> >
<div class="autocomplete-panel-body"> <div
ref="panel-body"
class="autocomplete-panel-body"
>
<div <div
v-for="(suggestion, index) in suggestions" v-for="(suggestion, index) in suggestions"
:key="index" :key="index"
+21 -9
View File
@@ -13,7 +13,7 @@ import { debounce } from 'lodash'
const debounceUserSearch = debounce((data, input) => { const debounceUserSearch = debounce((data, input) => {
data.updateUsersList(input) data.updateUsersList(input)
}, 500, { leading: true, trailing: false }) }, 500)
export default data => input => { export default data => input => {
const firstChar = input[0] const firstChar = input[0]
@@ -29,17 +29,29 @@ export default data => input => {
export const suggestEmoji = emojis => input => { export const suggestEmoji = emojis => input => {
const noPrefix = input.toLowerCase().substr(1) const noPrefix = input.toLowerCase().substr(1)
return emojis return emojis
.filter(({ displayText }) => displayText.toLowerCase().startsWith(noPrefix)) .filter(({ displayText }) => displayText.toLowerCase().match(noPrefix))
.sort((a, b) => { .sort((a, b) => {
let aScore = 0 let aScore = 0
let bScore = 0 let bScore = 0
// Make custom emojis a priority // An exact match always wins
aScore += a.imageUrl ? 10 : 0 aScore += a.displayText.toLowerCase() === noPrefix ? 200 : 0
bScore += b.imageUrl ? 10 : 0 bScore += b.displayText.toLowerCase() === noPrefix ? 200 : 0
// Sort alphabetically // Prioritize custom emoji a lot
const alphabetically = a.displayText > b.displayText ? 1 : -1 aScore += a.imageUrl ? 100 : 0
bScore += b.imageUrl ? 100 : 0
// Prioritize prefix matches somewhat
aScore += a.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
bScore += b.displayText.toLowerCase().startsWith(noPrefix) ? 10 : 0
// Sort by length
aScore -= a.displayText.length
bScore -= b.displayText.length
// Break ties alphabetically
const alphabetically = a.displayText > b.displayText ? 0.5 : -0.5
return bScore - aScore + alphabetically return bScore - aScore + alphabetically
}) })
@@ -85,8 +97,8 @@ export const suggestUsers = data => input => {
replacement: '@' + screen_name + ' ' replacement: '@' + screen_name + ' '
})) }))
// BE search users if there are no matches // BE search users to get more comprehensive results
if (newUsers.length === 0 && data.updateUsersList) { if (data.updateUsersList) {
debounceUserSearch(data, noPrefix) debounceUserSearch(data, noPrefix)
} }
return newUsers return newUsers
@@ -1,5 +1,5 @@
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
import Popover from '../popover/popover.vue' import UserListPopover from '../user_list_popover/user_list_popover.vue'
const EMOJI_REACTION_COUNT_CUTOFF = 12 const EMOJI_REACTION_COUNT_CUTOFF = 12
@@ -7,7 +7,7 @@ const EmojiReactions = {
name: 'EmojiReactions', name: 'EmojiReactions',
components: { components: {
UserAvatar, UserAvatar,
Popover UserListPopover
}, },
props: ['status'], props: ['status'],
data: () => ({ data: () => ({
@@ -1,44 +1,11 @@
<template> <template>
<div class="emoji-reactions"> <div class="emoji-reactions">
<Popover <UserListPopover
v-for="(reaction) in emojiReactions" v-for="(reaction) in emojiReactions"
:key="reaction.name" :key="reaction.name"
trigger="hover" :users="accountsForEmoji[reaction.name]"
placement="top"
:offset="{ y: 5 }"
> >
<div
slot="content"
class="reacted-users"
>
<div v-if="accountsForEmoji[reaction.name].length">
<div
v-for="(account) in accountsForEmoji[reaction.name]"
:key="account.id"
class="reacted-user"
>
<UserAvatar
:user="account"
class="avatar-small"
:compact="true"
/>
<div class="reacted-user-names">
<!-- eslint-disable vue/no-v-html -->
<span
class="reacted-user-name"
v-html="account.name_html"
/>
<!-- eslint-enable vue/no-v-html -->
<span class="reacted-user-screen-name">{{ account.screen_name }}</span>
</div>
</div>
</div>
<div v-else>
<i class="icon-spin4 animate-spin" />
</div>
</div>
<button <button
slot="trigger"
class="emoji-reaction btn btn-default" class="emoji-reaction btn btn-default"
:class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }" :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
@click="emojiOnClick(reaction.name, $event)" @click="emojiOnClick(reaction.name, $event)"
@@ -47,7 +14,7 @@
<span class="reaction-emoji">{{ reaction.name }}</span> <span class="reaction-emoji">{{ reaction.name }}</span>
<span>{{ reaction.count }}</span> <span>{{ reaction.count }}</span>
</button> </button>
</Popover> </UserListPopover>
<a <a
v-if="tooManyReactions" v-if="tooManyReactions"
class="emoji-reaction-expand faint" class="emoji-reaction-expand faint"
@@ -69,32 +36,6 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
.reacted-users {
padding: 0.5em;
}
.reacted-user {
padding: 0.25em;
display: flex;
flex-direction: row;
.reacted-user-names {
display: flex;
flex-direction: column;
margin-left: 0.5em;
min-width: 5em;
img {
width: 1em;
height: 1em;
}
}
.reacted-user-screen-name {
font-size: 9px;
}
}
.emoji-reaction { .emoji-reaction {
padding: 0 0.5em; padding: 0 0.5em;
margin-right: 0.5em; margin-right: 0.5em;
@@ -29,6 +29,21 @@ const ExtraButtons = {
this.$store.dispatch('unmuteConversation', this.status.id) this.$store.dispatch('unmuteConversation', this.status.id)
.then(() => this.$emit('onSuccess')) .then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error)) .catch(err => this.$emit('onError', err.error.error))
},
copyLink () {
navigator.clipboard.writeText(this.statusLink)
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
},
bookmarkStatus () {
this.$store.dispatch('bookmark', { id: this.status.id })
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
},
unbookmarkStatus () {
this.$store.dispatch('unbookmark', { id: this.status.id })
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
} }
}, },
computed: { computed: {
@@ -46,6 +61,9 @@ const ExtraButtons = {
}, },
canMute () { canMute () {
return !!this.currentUser return !!this.currentUser
},
statusLink () {
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
} }
} }
} }
+31 -5
View File
@@ -1,11 +1,14 @@
<template> <template>
<Popover <Popover
v-if="canDelete || canMute || canPin"
trigger="click" trigger="click"
placement="top" placement="top"
class="extra-button-popover" class="extra-button-popover"
:bound-to="{ x: 'container' }"
>
<div
slot="content"
slot-scope="{close}"
> >
<div slot="content">
<div class="dropdown-menu"> <div class="dropdown-menu">
<button <button
v-if="canMute && !status.thread_muted" v-if="canMute && !status.thread_muted"
@@ -23,28 +26,51 @@
</button> </button>
<button <button
v-if="!status.pinned && canPin" v-if="!status.pinned && canPin"
v-close-popover
class="dropdown-item dropdown-item-icon" class="dropdown-item dropdown-item-icon"
@click.prevent="pinStatus" @click.prevent="pinStatus"
@click="close"
> >
<i class="icon-pin" /><span>{{ $t("status.pin") }}</span> <i class="icon-pin" /><span>{{ $t("status.pin") }}</span>
</button> </button>
<button <button
v-if="status.pinned && canPin" v-if="status.pinned && canPin"
v-close-popover
class="dropdown-item dropdown-item-icon" class="dropdown-item dropdown-item-icon"
@click.prevent="unpinStatus" @click.prevent="unpinStatus"
@click="close"
> >
<i class="icon-pin" /><span>{{ $t("status.unpin") }}</span> <i class="icon-pin" /><span>{{ $t("status.unpin") }}</span>
</button> </button>
<button
v-if="!status.bookmarked"
class="dropdown-item dropdown-item-icon"
@click.prevent="bookmarkStatus"
@click="close"
>
<i class="icon-bookmark-empty" /><span>{{ $t("status.bookmark") }}</span>
</button>
<button
v-if="status.bookmarked"
class="dropdown-item dropdown-item-icon"
@click.prevent="unbookmarkStatus"
@click="close"
>
<i class="icon-bookmark" /><span>{{ $t("status.unbookmark") }}</span>
</button>
<button <button
v-if="canDelete" v-if="canDelete"
v-close-popover
class="dropdown-item dropdown-item-icon" class="dropdown-item dropdown-item-icon"
@click.prevent="deleteStatus" @click.prevent="deleteStatus"
@click="close"
> >
<i class="icon-cancel" /><span>{{ $t("status.delete") }}</span> <i class="icon-cancel" /><span>{{ $t("status.delete") }}</span>
</button> </button>
<button
class="dropdown-item dropdown-item-icon"
@click.prevent="copyLink"
@click="close"
>
<i class="icon-share" /><span>{{ $t("status.copy_link") }}</span>
</button>
</div> </div>
</div> </div>
<i <i
@@ -1,6 +1,7 @@
const FeaturesPanel = { const FeaturesPanel = {
computed: { computed: {
chat: function () { return this.$store.state.instance.chatAvailable }, chat: function () { return this.$store.state.instance.chatAvailable },
pleromaChatMessages: function () { return this.$store.state.instance.pleromaChatMessagesAvailable },
gopher: function () { return this.$store.state.instance.gopherAvailable }, gopher: function () { return this.$store.state.instance.gopherAvailable },
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled }, whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable }, mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable },
@@ -11,6 +11,9 @@
<li v-if="chat"> <li v-if="chat">
{{ $t('features_panel.chat') }} {{ $t('features_panel.chat') }}
</li> </li>
<li v-if="pleromaChatMessages">
{{ $t('features_panel.pleroma_chat_messages') }}
</li>
<li v-if="gopher"> <li v-if="gopher">
{{ $t('features_panel.gopher') }} {{ $t('features_panel.gopher') }}
</li> </li>
+10 -10
View File
@@ -1,6 +1,6 @@
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
export default { export default {
props: ['user', 'labelFollowing', 'buttonClass'], props: ['relationship', 'labelFollowing', 'buttonClass'],
data () { data () {
return { return {
inProgress: false inProgress: false
@@ -8,12 +8,12 @@ export default {
}, },
computed: { computed: {
isPressed () { isPressed () {
return this.inProgress || this.user.following return this.inProgress || this.relationship.following
}, },
title () { title () {
if (this.inProgress || this.user.following) { if (this.inProgress || this.relationship.following) {
return this.$t('user_card.follow_unfollow') return this.$t('user_card.follow_unfollow')
} else if (this.user.requested) { } else if (this.relationship.requested) {
return this.$t('user_card.follow_again') return this.$t('user_card.follow_again')
} else { } else {
return this.$t('user_card.follow') return this.$t('user_card.follow')
@@ -22,9 +22,9 @@ export default {
label () { label () {
if (this.inProgress) { if (this.inProgress) {
return this.$t('user_card.follow_progress') return this.$t('user_card.follow_progress')
} else if (this.user.following) { } else if (this.relationship.following) {
return this.labelFollowing || this.$t('user_card.following') return this.labelFollowing || this.$t('user_card.following')
} else if (this.user.requested) { } else if (this.relationship.requested) {
return this.$t('user_card.follow_sent') return this.$t('user_card.follow_sent')
} else { } else {
return this.$t('user_card.follow') return this.$t('user_card.follow')
@@ -33,20 +33,20 @@ export default {
}, },
methods: { methods: {
onClick () { onClick () {
this.user.following ? this.unfollow() : this.follow() this.relationship.following ? this.unfollow() : this.follow()
}, },
follow () { follow () {
this.inProgress = true this.inProgress = true
requestFollow(this.user, this.$store).then(() => { requestFollow(this.relationship.id, this.$store).then(() => {
this.inProgress = false this.inProgress = false
}) })
}, },
unfollow () { unfollow () {
const store = this.$store const store = this.$store
this.inProgress = true this.inProgress = true
requestUnfollow(this.user, store).then(() => { requestUnfollow(this.relationship.id, store).then(() => {
this.inProgress = false this.inProgress = false
store.commit('removeStatus', { timeline: 'friends', userId: this.user.id }) store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id })
}) })
} }
} }
@@ -18,6 +18,9 @@ const FollowCard = {
}, },
loggedIn () { loggedIn () {
return this.$store.state.users.currentUser return this.$store.state.users.currentUser
},
relationship () {
return this.$store.getters.relationship(this.user.id)
} }
} }
} }
+5 -5
View File
@@ -2,24 +2,24 @@
<basic-user-card :user="user"> <basic-user-card :user="user">
<div class="follow-card-content-container"> <div class="follow-card-content-container">
<span <span
v-if="!noFollowsYou && user.follows_you" v-if="isMe || (!noFollowsYou && relationship.followed_by)"
class="faint" class="faint"
> >
{{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }} {{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }}
</span> </span>
<template v-if="!loggedIn"> <template v-if="!loggedIn">
<div <div
v-if="!user.following" v-if="!relationship.following"
class="follow-card-follow-button" class="follow-card-follow-button"
> >
<RemoteFollow :user="user" /> <RemoteFollow :user="user" />
</div> </div>
</template> </template>
<template v-else> <template v-else-if="!isMe">
<FollowButton <FollowButton
:user="user" :relationship="relationship"
class="follow-card-follow-button"
:label-following="$t('user_card.follow_unfollow')" :label-following="$t('user_card.follow_unfollow')"
class="follow-card-follow-button"
/> />
</template> </template>
</div> </div>
@@ -1,4 +1,5 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue' import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
const FollowRequestCard = { const FollowRequestCard = {
props: ['user'], props: ['user'],
@@ -6,13 +7,32 @@ const FollowRequestCard = {
BasicUserCard BasicUserCard
}, },
methods: { methods: {
findFollowRequestNotificationId () {
const notif = notificationsFromStore(this.$store).find(
(notif) => notif.from_profile.id === this.user.id && notif.type === 'follow_request'
)
return notif && notif.id
},
approveUser () { approveUser () {
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('removeFollowRequest', this.user)
const notifId = this.findFollowRequestNotificationId()
this.$store.dispatch('markSingleNotificationAsSeen', { id: notifId })
this.$store.dispatch('updateNotification', {
id: notifId,
updater: notification => {
notification.type = 'follow'
}
})
}, },
denyUser () { denyUser () {
const notifId = this.findFollowRequestNotificationId()
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id }) this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('removeFollowRequest', this.user)
})
} }
} }
} }
+2 -3
View File
@@ -50,9 +50,7 @@
align-content: stretch; align-content: stretch;
} }
// FIXME: specificity problem with this and .attachments.attachment .gallery-row-inner .attachment {
// we shouldn't have the need for .image here
.attachment.image {
margin: 0 0.5em 0 0; margin: 0 0.5em 0 0;
flex-grow: 1; flex-grow: 1;
height: 100%; height: 100%;
@@ -78,6 +76,7 @@
video, video,
canvas { canvas {
object-fit: contain; object-fit: contain;
height: 100%;
} }
} }
@@ -0,0 +1,15 @@
const GlobalNoticeList = {
computed: {
notices () {
return this.$store.state.interface.globalNotices
}
},
methods: {
closeNotice (notice) {
this.$store.dispatch('removeGlobalNotice', notice)
}
}
}
export default GlobalNoticeList
@@ -0,0 +1,77 @@
<template>
<div class="global-notice-list">
<div
v-for="(notice, index) in notices"
:key="index"
class="alert global-notice"
:class="{ ['global-' + notice.level]: true }"
>
<div class="notice-message">
{{ $t(notice.messageKey, notice.messageArgs) }}
</div>
<i
class="button-icon icon-cancel"
@click="closeNotice(notice)"
/>
</div>
</div>
</template>
<script src="./global_notice_list.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.global-notice-list {
position: fixed;
top: 50px;
width: 100%;
pointer-events: none;
z-index: 1001;
display: flex;
flex-direction: column;
align-items: center;
.global-notice {
pointer-events: auto;
text-align: center;
width: 40em;
max-width: calc(100% - 3em);
display: flex;
padding-left: 1.5em;
line-height: 2em;
.notice-message {
flex: 1 1 100%;
}
i {
flex: 0 0;
width: 1.5em;
cursor: pointer;
}
}
.global-error {
background-color: var(--alertPopupError, $fallback--cRed);
color: var(--alertPopupErrorText, $fallback--text);
i {
color: var(--alertPopupErrorText, $fallback--text);
}
}
.global-warning {
background-color: var(--alertPopupWarning, $fallback--cOrange);
color: var(--alertPopupWarningText, $fallback--text);
i {
color: var(--alertPopupWarningText, $fallback--text);
}
}
.global-info {
background-color: var(--alertPopupNeutral, $fallback--fg);
color: var(--alertPopupNeutralText, $fallback--text);
i {
color: var(--alertPopupNeutralText, $fallback--text);
}
}
}
</style>
@@ -32,7 +32,7 @@ import _ from 'lodash'
export default { export default {
computed: { computed: {
languageCodes () { languageCodes () {
return Object.keys(languagesObject) return languagesObject.languages
}, },
languageNames () { languageNames () {
@@ -43,7 +43,6 @@ export default {
get: function () { return this.$store.getters.mergedConfig.interfaceLanguage }, get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
set: function (val) { set: function (val) {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
this.$i18n.locale = val
} }
} }
}, },
@@ -84,10 +84,12 @@ const MediaModal = {
} }
}, },
mounted () { mounted () {
window.addEventListener('popstate', this.hide)
document.addEventListener('keyup', this.handleKeyupEvent) document.addEventListener('keyup', this.handleKeyupEvent)
document.addEventListener('keydown', this.handleKeydownEvent) document.addEventListener('keydown', this.handleKeydownEvent)
}, },
destroyed () { destroyed () {
window.removeEventListener('popstate', this.hide)
document.removeEventListener('keyup', this.handleKeyupEvent) document.removeEventListener('keyup', this.handleKeyupEvent)
document.removeEventListener('keydown', this.handleKeydownEvent) document.removeEventListener('keydown', this.handleKeydownEvent)
} }
@@ -8,6 +8,8 @@
v-if="type === 'image'" v-if="type === 'image'"
class="modal-image" class="modal-image"
:src="currentMedia.url" :src="currentMedia.url"
:alt="currentMedia.description"
:title="currentMedia.description"
@touchstart.stop="mediaTouchStart" @touchstart.stop="mediaTouchStart"
@touchmove.stop="mediaTouchMove" @touchmove.stop="mediaTouchMove"
@click="hide" @click="hide"
@@ -18,6 +20,14 @@
:attachment="currentMedia" :attachment="currentMedia"
:controls="true" :controls="true"
/> />
<audio
v-if="type === 'audio'"
class="modal-image"
:src="currentMedia.url"
:alt="currentMedia.description"
:title="currentMedia.description"
controls
/>
<button <button
v-if="canNavigate" v-if="canNavigate"
:title="$t('media_modal.previous')" :title="$t('media_modal.previous')"
+21 -21
View File
@@ -5,10 +5,15 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for
const mediaUpload = { const mediaUpload = {
data () { data () {
return { return {
uploading: false, uploadCount: 0,
uploadReady: true uploadReady: true
} }
}, },
computed: {
uploading () {
return this.uploadCount > 0
}
},
methods: { methods: {
uploadFile (file) { uploadFile (file) {
const self = this const self = this
@@ -23,29 +28,21 @@ const mediaUpload = {
formData.append('file', file) formData.append('file', file)
self.$emit('uploading') self.$emit('uploading')
self.uploading = true self.uploadCount++
statusPosterService.uploadMedia({ store, formData }) statusPosterService.uploadMedia({ store, formData })
.then((fileData) => { .then((fileData) => {
self.$emit('uploaded', fileData) self.$emit('uploaded', fileData)
self.uploading = false self.decreaseUploadCount()
}, (error) => { // eslint-disable-line handle-callback-err }, (error) => { // eslint-disable-line handle-callback-err
self.$emit('upload-failed', 'default') self.$emit('upload-failed', 'default')
self.uploading = false self.decreaseUploadCount()
}) })
}, },
fileDrop (e) { decreaseUploadCount () {
if (e.dataTransfer.files.length > 0) { this.uploadCount--
e.preventDefault() // allow dropping text like before if (this.uploadCount === 0) {
this.uploadFile(e.dataTransfer.files[0]) this.$emit('all-uploaded')
}
},
fileDrag (e) {
let types = e.dataTransfer.types
if (types.contains('Files')) {
e.dataTransfer.dropEffect = 'copy'
} else {
e.dataTransfer.dropEffect = 'none'
} }
}, },
clearFile () { clearFile () {
@@ -54,20 +51,23 @@ const mediaUpload = {
this.uploadReady = true this.uploadReady = true
}) })
}, },
change ({ target }) { multiUpload (files) {
for (var i = 0; i < target.files.length; i++) { for (const file of files) {
let file = target.files[i]
this.uploadFile(file) this.uploadFile(file)
} }
},
change ({ target }) {
this.multiUpload(target.files)
} }
}, },
props: [ props: [
'dropFiles' 'dropFiles',
'disabled'
], ],
watch: { watch: {
'dropFiles': function (fileInfos) { 'dropFiles': function (fileInfos) {
if (!this.uploading) { if (!this.uploading) {
this.uploadFile(fileInfos[0]) this.multiUpload(fileInfos)
} }
} }
} }
+4 -3
View File
@@ -1,9 +1,7 @@
<template> <template>
<div <div
class="media-upload" class="media-upload"
@drop.prevent :class="{ disabled: disabled }"
@dragover.prevent="fileDrag"
@drop="fileDrop"
> >
<label <label
class="label" class="label"
@@ -19,6 +17,7 @@
/> />
<input <input
v-if="uploadReady" v-if="uploadReady"
:disabled="disabled"
type="file" type="file"
style="position: fixed; top: -100em" style="position: fixed; top: -100em"
multiple="true" multiple="true"
@@ -31,6 +30,8 @@
<script src="./media_upload.js" ></script> <script src="./media_upload.js" ></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss';
.media-upload { .media-upload {
.label { .label {
display: inline-block; display: inline-block;
+7 -2
View File
@@ -2,6 +2,7 @@ import SideDrawer from '../side_drawer/side_drawer.vue'
import Notifications from '../notifications/notifications.vue' import Notifications from '../notifications/notifications.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils' import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service' import GestureService from '../../services/gesture_service/gesture_service'
import { mapGetters } from 'vuex'
const MobileNav = { const MobileNav = {
components: { components: {
@@ -30,7 +31,11 @@ const MobileNav = {
return this.unseenNotifications.length return this.unseenNotifications.length
}, },
hideSitename () { return this.$store.state.instance.hideSitename }, hideSitename () { return this.$store.state.instance.hideSitename },
sitename () { return this.$store.state.instance.name } sitename () { return this.$store.state.instance.name },
isChat () {
return this.$route.name === 'chat'
},
...mapGetters(['unreadChatCount'])
}, },
methods: { methods: {
toggleMobileSidebar () { toggleMobileSidebar () {
@@ -64,7 +69,7 @@ const MobileNav = {
this.$refs.notifications.markAsSeen() this.$refs.notifications.markAsSeen()
}, },
onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) { onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) {
if (this.$store.getters.mergedConfig.autoLoad && scrollTop + clientHeight >= scrollHeight) { if (scrollTop + clientHeight >= scrollHeight) {
this.$refs.notifications.fetchOlderNotifications() this.$refs.notifications.fetchOlderNotifications()
} }
} }
+5
View File
@@ -3,6 +3,7 @@
<nav <nav
id="nav" id="nav"
class="nav-bar container" class="nav-bar container"
:class="{ 'mobile-hidden': isChat }"
> >
<div <div
class="mobile-inner-nav" class="mobile-inner-nav"
@@ -15,6 +16,10 @@
@click.stop.prevent="toggleMobileSidebar()" @click.stop.prevent="toggleMobileSidebar()"
> >
<i class="button-icon icon-menu" /> <i class="button-icon icon-menu" />
<div
v-if="unreadChatCount"
class="alert-dot"
/>
</a> </a>
<router-link <router-link
v-if="!hideSitename" v-if="!hideSitename"
@@ -1,5 +1,10 @@
import { debounce } from 'lodash' import { debounce } from 'lodash'
const HIDDEN_FOR_PAGES = new Set([
'chats',
'chat'
])
const MobilePostStatusButton = { const MobilePostStatusButton = {
data () { data () {
return { return {
@@ -27,6 +32,8 @@ const MobilePostStatusButton = {
return !!this.$store.state.users.currentUser return !!this.$store.state.users.currentUser
}, },
isHidden () { isHidden () {
if (HIDDEN_FOR_PAGES.has(this.$route.name)) { return true }
return this.autohideFloatingPostButton && (this.hidden || this.inputActive) return this.autohideFloatingPostButton && (this.hidden || this.inputActive)
}, },
autohideFloatingPostButton () { autohideFloatingPostButton () {
+27 -4
View File
@@ -1,8 +1,9 @@
<template> <template>
<div <div
v-show="isOpen" v-show="isOpen"
v-body-scroll-lock="isOpen" v-body-scroll-lock="isOpen && !noBackground"
class="modal-view" class="modal-view"
:class="classes"
@click.self="$emit('backdropClicked')" @click.self="$emit('backdropClicked')"
> >
<slot /> <slot />
@@ -15,6 +16,18 @@ export default {
isOpen: { isOpen: {
type: Boolean, type: Boolean,
default: true default: true
},
noBackground: {
type: Boolean,
default: false
}
},
computed: {
classes () {
return {
'modal-background': !this.noBackground,
'open': this.isOpen
}
} }
} }
} }
@@ -32,12 +45,22 @@ export default {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
overflow: auto; overflow: auto;
pointer-events: none;
animation-duration: 0.2s; animation-duration: 0.2s;
background-color: rgba(0, 0, 0, 0.5);
animation-name: modal-background-fadein; animation-name: modal-background-fadein;
body:not(.scroll-locked) & {
opacity: 0; opacity: 0;
> * {
pointer-events: initial;
}
&.modal-background {
pointer-events: initial;
background-color: rgba(0, 0, 0, 0.5);
}
&.open {
opacity: 1;
} }
} }
+6 -3
View File
@@ -11,8 +11,11 @@ const MuteCard = {
user () { user () {
return this.$store.getters.findUser(this.userId) return this.$store.getters.findUser(this.userId)
}, },
relationship () {
return this.$store.getters.relationship(this.userId)
},
muted () { muted () {
return this.user.muted return this.relationship.muting
} }
}, },
components: { components: {
@@ -21,13 +24,13 @@ const MuteCard = {
methods: { methods: {
unmuteUser () { unmuteUser () {
this.progress = true this.progress = true
this.$store.dispatch('unmuteUser', this.user.id).then(() => { this.$store.dispatch('unmuteUser', this.userId).then(() => {
this.progress = false this.progress = false
}) })
}, },
muteUser () { muteUser () {
this.progress = true this.progress = true
this.$store.dispatch('muteUser', this.user.id).then(() => { this.$store.dispatch('muteUser', this.userId).then(() => {
this.progress = false this.progress = false
}) })
} }
+18 -5
View File
@@ -1,4 +1,5 @@
import { mapState } from 'vuex' import { timelineNames } from '../timeline_menu/timeline_menu.js'
import { mapState, mapGetters } from 'vuex'
const NavPanel = { const NavPanel = {
created () { created () {
@@ -6,13 +7,25 @@ const NavPanel = {
this.$store.dispatch('startFetchingFollowRequests') this.$store.dispatch('startFetchingFollowRequests')
} }
}, },
computed: mapState({ computed: {
onTimelineRoute () {
return !!timelineNames()[this.$route.name]
},
timelinesRoute () {
if (this.$store.state.interface.lastTimeline) {
return this.$store.state.interface.lastTimeline
}
return this.currentUser ? 'friends' : 'public-timeline'
},
...mapState({
currentUser: state => state.users.currentUser, currentUser: state => state.users.currentUser,
chat: state => state.chat.channel,
followRequestCount: state => state.api.followRequests.length, followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private, privateMode: state => state.instance.private,
federating: state => state.instance.federating federating: state => state.instance.federating,
}) pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
}),
...mapGetters(['unreadChatCount'])
}
} }
export default NavPanel export default NavPanel
+15 -16
View File
@@ -2,9 +2,12 @@
<div class="nav-panel"> <div class="nav-panel">
<div class="panel panel-default"> <div class="panel panel-default">
<ul> <ul>
<li v-if="currentUser"> <li v-if="currentUser || !privateMode">
<router-link :to="{ name: 'friends' }"> <router-link
<i class="button-icon icon-home-2" /> {{ $t("nav.timeline") }} :to="{ name: timelinesRoute }"
:class="onTimelineRoute && 'router-link-active'"
>
<i class="button-icon icon-home-2" /> {{ $t("nav.timelines") }}
</router-link> </router-link>
</li> </li>
<li v-if="currentUser"> <li v-if="currentUser">
@@ -12,9 +15,15 @@
<i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }} <i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }}
</router-link> </router-link>
</li> </li>
<li v-if="currentUser"> <li v-if="currentUser && pleromaChatMessagesAvailable">
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }"> <router-link :to="{ name: 'chats', params: { username: currentUser.screen_name } }">
<i class="button-icon icon-mail-alt" /> {{ $t("nav.dms") }} <div
v-if="unreadChatCount"
class="badge badge-notification unread-chat-count"
>
{{ unreadChatCount }}
</div>
<i class="button-icon icon-chat" /> {{ $t("nav.chats") }}
</router-link> </router-link>
</li> </li>
<li v-if="currentUser && currentUser.locked"> <li v-if="currentUser && currentUser.locked">
@@ -28,16 +37,6 @@
</span> </span>
</router-link> </router-link>
</li> </li>
<li v-if="currentUser || !privateMode">
<router-link :to="{ name: 'public-timeline' }">
<i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
</router-link>
</li>
<li v-if="federating && (currentUser || !privateMode)">
<router-link :to="{ name: 'public-external-timeline' }">
<i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
</router-link>
</li>
<li> <li>
<router-link :to="{ name: 'about' }"> <router-link :to="{ name: 'about' }">
<i class="button-icon icon-info-circled" /> {{ $t("nav.about") }} <i class="button-icon icon-info-circled" /> {{ $t("nav.about") }}
+32 -4
View File
@@ -1,7 +1,10 @@
import StatusContent from '../status_content/status_content.vue'
import { mapState } from 'vuex'
import Status from '../status/status.vue' import Status from '../status/status.vue'
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
import UserCard from '../user_card/user_card.vue' import UserCard from '../user_card/user_card.vue'
import Timeago from '../timeago/timeago.vue' import Timeago from '../timeago/timeago.vue'
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@@ -15,10 +18,11 @@ const Notification = {
}, },
props: [ 'notification' ], props: [ 'notification' ],
components: { components: {
Status, StatusContent,
UserAvatar, UserAvatar,
UserCard, UserCard,
Timeago Timeago,
Status
}, },
methods: { methods: {
toggleUserExpanded () { toggleUserExpanded () {
@@ -32,6 +36,24 @@ const Notification = {
}, },
toggleMute () { toggleMute () {
this.unmuted = !this.unmuted this.unmuted = !this.unmuted
},
approveUser () {
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id })
this.$store.dispatch('updateNotification', {
id: this.notification.id,
updater: notification => {
notification.type = 'follow'
}
})
},
denyUser () {
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id })
this.$store.dispatch('removeFollowRequest', this.user)
})
} }
}, },
computed: { computed: {
@@ -56,8 +78,14 @@ const Notification = {
return this.generateUserProfileLink(this.targetUser) return this.generateUserProfileLink(this.targetUser)
}, },
needMute () { needMute () {
return this.user.muted return this.$store.getters.relationship(this.user.id).muting
} },
isStatusNotification () {
return isStatusNotification(this.notification.type)
},
...mapState({
currentUser: state => state.users.currentUser
})
} }
} }
@@ -0,0 +1,52 @@
// TODO Copypaste from Status, should unify it somehow
.Notification {
&.-muted {
padding: 0.25em 0.6em;
height: 1.2em;
line-height: 1.2em;
text-overflow: ellipsis;
overflow: hidden;
display: flex;
flex-wrap: nowrap;
& .status-username,
& .mute-thread,
& .mute-words {
word-wrap: normal;
word-break: normal;
white-space: nowrap;
}
& .status-username,
& .mute-words {
text-overflow: ellipsis;
overflow: hidden;
}
.status-username {
font-weight: normal;
flex: 0 1 auto;
margin-right: 0.2em;
font-size: smaller;
}
.mute-thread {
flex: 0 0 auto;
}
.mute-words {
flex: 1 0 5em;
margin-left: 0.2em;
&::before {
content: ' ';
}
}
.unmute {
flex: 0 0 auto;
margin-left: auto;
display: block;
}
}
}
+42 -21
View File
@@ -7,7 +7,7 @@
<div v-else> <div v-else>
<div <div
v-if="needMute && !unmuted" v-if="needMute && !unmuted"
class="container muted" class="Notification container -muted"
> >
<small> <small>
<router-link :to="userProfileLink"> <router-link :to="userProfileLink">
@@ -40,14 +40,14 @@
<div class="notification-right"> <div class="notification-right">
<UserCard <UserCard
v-if="userExpanded" v-if="userExpanded"
:user="getUser(notification)" :user-id="getUser(notification).id"
:rounded="true" :rounded="true"
:bordered="true" :bordered="true"
/> />
<span class="notification-details"> <span class="notification-details">
<div class="name-and-action"> <div class="name-and-action">
<!-- eslint-disable vue/no-v-html --> <!-- eslint-disable vue/no-v-html -->
<span <bdi
v-if="!!notification.from_profile.name_html" v-if="!!notification.from_profile.name_html"
class="username" class="username"
:title="'@'+notification.from_profile.screen_name" :title="'@'+notification.from_profile.screen_name"
@@ -74,6 +74,10 @@
<i class="fa icon-user-plus lit" /> <i class="fa icon-user-plus lit" />
<small>{{ $t('notifications.followed_you') }}</small> <small>{{ $t('notifications.followed_you') }}</small>
</span> </span>
<span v-if="notification.type === 'follow_request'">
<i class="fa icon-user lit" />
<small>{{ $t('notifications.follow_request') }}</small>
</span>
<span v-if="notification.type === 'move'"> <span v-if="notification.type === 'move'">
<i class="fa icon-arrow-curved lit" /> <i class="fa icon-arrow-curved lit" />
<small>{{ $t('notifications.migrated_to') }}</small> <small>{{ $t('notifications.migrated_to') }}</small>
@@ -87,18 +91,7 @@
</span> </span>
</div> </div>
<div <div
v-if="notification.type === 'follow' || notification.type === 'move'" v-if="isStatusNotification"
class="timeago"
>
<span class="faint">
<Timeago
:time="notification.created_at"
:auto-update="240"
/>
</span>
</div>
<div
v-else
class="timeago" class="timeago"
> >
<router-link <router-link
@@ -112,6 +105,17 @@
/> />
</router-link> </router-link>
</div> </div>
<div
v-else
class="timeago"
>
<span class="faint">
<Timeago
:time="notification.created_at"
:auto-update="240"
/>
</span>
</div>
<a <a
v-if="needMute" v-if="needMute"
href="#" href="#"
@@ -119,12 +123,30 @@
><i class="button-icon icon-eye-off" /></a> ><i class="button-icon icon-eye-off" /></a>
</span> </span>
<div <div
v-if="notification.type === 'follow'" v-if="notification.type === 'follow' || notification.type === 'follow_request'"
class="follow-text" class="follow-text"
> >
<router-link :to="userProfileLink"> <router-link
:to="userProfileLink"
class="follow-name"
>
@{{ notification.from_profile.screen_name }} @{{ notification.from_profile.screen_name }}
</router-link> </router-link>
<div
v-if="notification.type === 'follow_request'"
style="white-space: nowrap;"
>
<i
class="icon-ok button-icon follow-request-accept"
:title="$t('tool_tip.accept_follow_request')"
@click="approveUser()"
/>
<i
class="icon-cancel button-icon follow-request-reject"
:title="$t('tool_tip.reject_follow_request')"
@click="denyUser()"
/>
</div>
</div> </div>
<div <div
v-else-if="notification.type === 'move'" v-else-if="notification.type === 'move'"
@@ -135,11 +157,9 @@
</router-link> </router-link>
</div> </div>
<template v-else> <template v-else>
<status <status-content
class="faint" class="faint"
:compact="true" :status="notification.action"
:statusoid="notification.action"
:no-heading="true"
/> />
</template> </template>
</div> </div>
@@ -148,3 +168,4 @@
</template> </template>
<script src="./notification.js"></script> <script src="./notification.js"></script>
<style src="./notification.scss" lang="scss"></style>
+12 -7
View File
@@ -1,3 +1,4 @@
import { mapGetters } from 'vuex'
import Notification from '../notification/notification.vue' import Notification from '../notification/notification.vue'
import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js' import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
import { import {
@@ -27,6 +28,11 @@ const Notifications = {
seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT
} }
}, },
created () {
const store = this.$store
const credentials = store.state.users.currentUser.credentials
notificationsFetcher.fetchAndUpdate({ store, credentials })
},
computed: { computed: {
mainClass () { mainClass () {
return this.minimalMode ? '' : 'panel panel-default' return this.minimalMode ? '' : 'panel panel-default'
@@ -46,23 +52,22 @@ const Notifications = {
unseenCount () { unseenCount () {
return this.unseenNotifications.length return this.unseenNotifications.length
}, },
unseenCountTitle () {
return this.unseenCount + (this.unreadChatCount)
},
loading () { loading () {
return this.$store.state.statuses.notifications.loading return this.$store.state.statuses.notifications.loading
}, },
notificationsToDisplay () { notificationsToDisplay () {
return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount) return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
} },
...mapGetters(['unreadChatCount'])
}, },
components: { components: {
Notification Notification
}, },
created () {
const { dispatch } = this.$store
dispatch('fetchAndUpdateNotifications')
},
watch: { watch: {
unseenCount (count) { unseenCountTitle (count) {
if (count > 0) { if (count > 0) {
this.$store.dispatch('setPageTitle', `(${count})`) this.$store.dispatch('setPageTitle', `(${count})`)
} else { } else {
+44 -19
View File
@@ -36,8 +36,10 @@
border-bottom: 1px solid; border-bottom: 1px solid;
border-color: $fallback--border; border-color: $fallback--border;
border-color: var(--border, $fallback--border); border-color: var(--border, $fallback--border);
word-wrap: break-word;
word-break: break-word;
&:hover .animated.avatar { &:hover .animated.Avatar {
canvas { canvas {
display: none; display: none;
} }
@@ -46,45 +48,58 @@
} }
} }
.muted {
padding: .25em .6em;
}
.non-mention { .non-mention {
display: flex; display: flex;
flex: 1; flex: 1;
flex-wrap: nowrap; flex-wrap: nowrap;
padding: 0.6em; padding: 0.6em;
min-width: 0; min-width: 0;
.avatar-container { .avatar-container {
width: 32px; width: 32px;
height: 32px; height: 32px;
} }
.status-el {
.status { --link: var(--faintLink);
padding: 0.25em 0; --text: var(--faint);
color: $fallback--faint;
color: var(--faint, $fallback--faint);
a {
color: var(--faintLink);
} }
.status-content a {
color: var(--postFaintLink); .follow-request-accept {
cursor: pointer;
&:hover {
color: $fallback--text;
color: var(--text, $fallback--text);
} }
} }
padding: 0;
.media-body { .follow-request-reject {
margin: 0; cursor: pointer;
}
&:hover {
color: $fallback--cRed;
color: var(--cRed, $fallback--cRed);
} }
} }
.follow-text, .move-text { .follow-text, .move-text {
padding: 0.5em 0; padding: 0.5em 0;
overflow-wrap: break-word; overflow-wrap: break-word;
display: flex;
justify-content: space-between;
.follow-name {
display: block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
.status-el { /* TODO cleanup this */
.Status {
flex: 1; flex: 1;
} }
@@ -96,6 +111,11 @@
flex: 1; flex: 1;
padding-left: 0.8em; padding-left: 0.8em;
min-width: 0; min-width: 0;
.timeago {
min-width: 3em;
text-align: right;
}
} }
.emoji-reaction-emoji { .emoji-reaction-emoji {
@@ -143,6 +163,11 @@
color: var(--cGreen, $fallback--cGreen); color: var(--cGreen, $fallback--cGreen);
} }
.icon-user.lit {
color: $fallback--cBlue;
color: var(--cBlue, $fallback--cBlue);
}
.icon-user-plus.lit { .icon-user-plus.lit {
color: $fallback--cBlue; color: $fallback--cBlue;
color: var(--cBlue, $fallback--cBlue); color: var(--cBlue, $fallback--cBlue);
@@ -0,0 +1,29 @@
<template>
<div class="panel-loading">
<span class="loading-text">
<i class="icon-spin4 animate-spin" />
{{ $t('general.loading') }}
</span>
</div>
</template>
<style lang="scss">
@import 'src/_variables.scss';
.panel-loading {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
font-size: 2em;
color: $fallback--text;
color: var(--text, $fallback--text);
.loading-text i {
font-size: 3em;
line-height: 0;
vertical-align: middle;
color: $fallback--text;
color: var(--text, $fallback--text);
}
}
</style>
@@ -47,11 +47,6 @@ const passwordReset = {
if (status === 204) { if (status === 204) {
this.success = true this.success = true
this.error = null this.error = null
} else if (status === 404 || status === 400) {
this.error = this.$t('password_reset.not_found')
this.$nextTick(() => {
this.$refs.email.focus()
})
} else if (status === 429) { } else if (status === 429) {
this.throttled = true this.throttled = true
this.error = this.$t('password_reset.too_many_requests') this.error = this.$t('password_reset.too_many_requests')
+3 -1
View File
@@ -17,7 +17,8 @@
<span class="result-percentage"> <span class="result-percentage">
{{ percentageForOption(option.votes_count) }}% {{ percentageForOption(option.votes_count) }}%
</span> </span>
<span>{{ option.title }}</span> <!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="option.title_html" />
</div> </div>
<div <div
class="result-fill" class="result-fill"
@@ -96,6 +97,7 @@
align-items: center; align-items: center;
padding: 0.1em 0.25em; padding: 0.1em 0.25em;
z-index: 1; z-index: 1;
word-break: break-word;
} }
.result-percentage { .result-percentage {
width: 3.5em; width: 3.5em;
+1
View File
@@ -75,6 +75,7 @@ export default {
deleteOption (index, event) { deleteOption (index, event) {
if (this.options.length > 2) { if (this.options.length > 2) {
this.options.splice(index, 1) this.options.splice(index, 1)
this.updatePollToParent()
} }
}, },
convertExpiryToUnit (unit, amount) { convertExpiryToUnit (unit, amount) {
+13 -4
View File
@@ -1,4 +1,3 @@
const Popover = { const Popover = {
name: 'Popover', name: 'Popover',
props: { props: {
@@ -10,13 +9,18 @@ const Popover = {
// 'container' for using offsetParent as boundaries for either axis // 'container' for using offsetParent as boundaries for either axis
// or 'viewport' // or 'viewport'
boundTo: Object, boundTo: Object,
// Takes a selector to use as a replacement for the parent container
// for getting boundaries for x an y axis
boundToSelector: String,
// Takes a top/bottom/left/right object, how much space to leave // Takes a top/bottom/left/right object, how much space to leave
// between boundary and popover element // between boundary and popover element
margin: Object, margin: Object,
// Takes a x/y object and tells how many pixels to offset from // Takes a x/y object and tells how many pixels to offset from
// anchor point on either axis // anchor point on either axis
offset: Object, offset: Object,
// Additional styles you may want for the popover container // Replaces the classes you may want for the popover container.
// Use 'popover-default' in addition to get the default popover
// styles with your custom class.
popoverClass: String popoverClass: String
}, },
data () { data () {
@@ -27,6 +31,10 @@ const Popover = {
} }
}, },
methods: { methods: {
containerBoundingClientRect () {
const container = this.boundToSelector ? this.$el.closest(this.boundToSelector) : this.$el.offsetParent
return container.getBoundingClientRect()
},
updateStyles () { updateStyles () {
if (this.hidden) { if (this.hidden) {
this.styles = { this.styles = {
@@ -45,7 +53,8 @@ const Popover = {
// Minor optimization, don't call a slow reflow call if we don't have to // Minor optimization, don't call a slow reflow call if we don't have to
const parentBounds = this.boundTo && const parentBounds = this.boundTo &&
(this.boundTo.x === 'container' || this.boundTo.y === 'container') && (this.boundTo.x === 'container' || this.boundTo.y === 'container') &&
this.$el.offsetParent.getBoundingClientRect() this.containerBoundingClientRect()
const margin = this.margin || {} const margin = this.margin || {}
// What are the screen bounds for the popover? Viewport vs container // What are the screen bounds for the popover? Viewport vs container
@@ -99,7 +108,7 @@ const Popover = {
// single translate or translate3d resulted in blurry text. // single translate or translate3d resulted in blurry text.
this.styles = { this.styles = {
opacity: 1, opacity: 1,
transform: `translateX(${Math.floor(translateX)}px) translateY(${Math.floor(translateY)}px)` transform: `translateX(${Math.round(translateX)}px) translateY(${Math.round(translateY)}px)`
} }
}, },
showPopover () { showPopover () {
+4 -1
View File
@@ -14,7 +14,7 @@
ref="content" ref="content"
:style="styles" :style="styles"
class="popover" class="popover"
:class="popoverClass" :class="popoverClass || 'popover-default'"
> >
<slot <slot
name="content" name="content"
@@ -34,6 +34,9 @@
z-index: 8; z-index: 8;
position: absolute; position: absolute;
min-width: 0; min-width: 0;
}
.popover-default {
transition: opacity 0.3s; transition: opacity 0.3s;
box-shadow: 1px 1px 4px rgba(0,0,0,.6); box-shadow: 1px 1px 4px rgba(0,0,0,.6);
@@ -3,11 +3,13 @@ import MediaUpload from '../media_upload/media_upload.vue'
import ScopeSelector from '../scope_selector/scope_selector.vue' import ScopeSelector from '../scope_selector/scope_selector.vue'
import EmojiInput from '../emoji_input/emoji_input.vue' import EmojiInput from '../emoji_input/emoji_input.vue'
import PollForm from '../poll/poll_form.vue' import PollForm from '../poll/poll_form.vue'
import Attachment from '../attachment/attachment.vue'
import StatusContent from '../status_content/status_content.vue'
import fileTypeService from '../../services/file_type/file_type.service.js' import fileTypeService from '../../services/file_type/file_type.service.js'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js' import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
import { reject, map, uniqBy } from 'lodash' import { reject, map, uniqBy, debounce } from 'lodash'
import suggestor from '../emoji_input/suggestor.js' import suggestor from '../emoji_input/suggestor.js'
import { mapGetters } from 'vuex' import { mapGetters, mapState } from 'vuex'
import Checkbox from '../checkbox/checkbox.vue' import Checkbox from '../checkbox/checkbox.vue'
const buildMentionsString = ({ user, attentions = [] }, currentUser) => { const buildMentionsString = ({ user, attentions = [] }, currentUser) => {
@@ -25,27 +27,54 @@ const buildMentionsString = ({ user, attentions = [] }, currentUser) => {
return mentions.length > 0 ? mentions.join(' ') + ' ' : '' return mentions.length > 0 ? mentions.join(' ') + ' ' : ''
} }
// Converts a string with px to a number like '2px' -> 2
const pxStringToNumber = (str) => {
return Number(str.substring(0, str.length - 2))
}
const PostStatusForm = { const PostStatusForm = {
props: [ props: [
'replyTo', 'replyTo',
'repliedUser', 'repliedUser',
'attentions', 'attentions',
'copyMessageScope', 'copyMessageScope',
'subject' 'subject',
'disableSubject',
'disableScopeSelector',
'disableNotice',
'disableLockWarning',
'disablePolls',
'disableSensitivityCheckbox',
'disableSubmit',
'disablePreview',
'placeholder',
'maxHeight',
'postHandler',
'preserveFocus',
'autoFocus',
'fileLimit',
'submitOnEnter',
'emojiPickerPlacement'
], ],
components: { components: {
MediaUpload, MediaUpload,
EmojiInput, EmojiInput,
PollForm, PollForm,
ScopeSelector, ScopeSelector,
Checkbox Checkbox,
Attachment,
StatusContent
}, },
mounted () { mounted () {
this.updateIdempotencyKey()
this.resize(this.$refs.textarea) this.resize(this.$refs.textarea)
const textLength = this.$refs.textarea.value.length
this.$refs.textarea.setSelectionRange(textLength, textLength)
if (this.replyTo) { if (this.replyTo) {
const textLength = this.$refs.textarea.value.length
this.$refs.textarea.setSelectionRange(textLength, textLength)
}
if (this.replyTo || this.autoFocus) {
this.$refs.textarea.focus() this.$refs.textarea.focus()
} }
}, },
@@ -68,7 +97,7 @@ const PostStatusForm = {
return { return {
dropFiles: [], dropFiles: [],
submitDisabled: false, uploadingFiles: false,
error: null, error: null,
posting: false, posting: false,
highlighted: 0, highlighted: 0,
@@ -78,11 +107,18 @@ const PostStatusForm = {
nsfw: false, nsfw: false,
files: [], files: [],
poll: {}, poll: {},
mediaDescriptions: {},
visibility: scope, visibility: scope,
contentType contentType
}, },
caret: 0, caret: 0,
pollFormVisible: false pollFormVisible: false,
showDropIcon: 'hide',
dropStopTimeout: null,
preview: null,
previewLoading: false,
emojiInputShown: false,
idempotencyKey: ''
} }
}, },
computed: { computed: {
@@ -102,7 +138,7 @@ const PostStatusForm = {
...this.$store.state.instance.customEmoji ...this.$store.state.instance.customEmoji
], ],
users: this.$store.state.users.users, users: this.$store.state.users.users,
updateUsersList: (input) => this.$store.dispatch('searchUsers', input) updateUsersList: (query) => this.$store.dispatch('searchUsers', { query })
}) })
}, },
emojiSuggestor () { emojiSuggestor () {
@@ -151,28 +187,81 @@ const PostStatusForm = {
}, },
pollsAvailable () { pollsAvailable () {
return this.$store.state.instance.pollsAvailable && return this.$store.state.instance.pollsAvailable &&
this.$store.state.instance.pollLimits.max_options >= 2 this.$store.state.instance.pollLimits.max_options >= 2 &&
this.disablePolls !== true
}, },
hideScopeNotice () { hideScopeNotice () {
return this.$store.getters.mergedConfig.hideScopeNotice return this.disableNotice || this.$store.getters.mergedConfig.hideScopeNotice
}, },
pollContentError () { pollContentError () {
return this.pollFormVisible && return this.pollFormVisible &&
this.newStatus.poll && this.newStatus.poll &&
this.newStatus.poll.error this.newStatus.poll.error
}, },
...mapGetters(['mergedConfig']) showPreview () {
return !this.disablePreview && (!!this.preview || this.previewLoading)
},
emptyStatus () {
return this.newStatus.status.trim() === '' && this.newStatus.files.length === 0
},
uploadFileLimitReached () {
return this.newStatus.files.length >= this.fileLimit
},
...mapGetters(['mergedConfig']),
...mapState({
mobileLayout: state => state.interface.mobileLayout
})
},
watch: {
'newStatus': {
deep: true,
handler () {
this.statusChanged()
}
}
}, },
methods: { methods: {
postStatus (newStatus) { statusChanged () {
if (this.posting) { return } this.autoPreview()
if (this.submitDisabled) { return } this.updateIdempotencyKey()
},
if (this.newStatus.status === '') { clearStatus () {
if (this.newStatus.files.length === 0) { const newStatus = this.newStatus
this.error = 'Cannot post an empty status with no files' this.newStatus = {
return status: '',
spoilerText: '',
files: [],
visibility: newStatus.visibility,
contentType: newStatus.contentType,
poll: {},
mediaDescriptions: {}
} }
this.pollFormVisible = false
this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
this.clearPollForm()
if (this.preserveFocus) {
this.$nextTick(() => {
this.$refs.textarea.focus()
})
}
let el = this.$el.querySelector('textarea')
el.style.height = 'auto'
el.style.height = undefined
this.error = null
if (this.preview) this.previewStatus()
},
async postStatus (event, newStatus, opts = {}) {
if (this.posting) { return }
if (this.disableSubmit) { return }
if (this.emojiInputShown) { return }
if (this.submitOnEnter) {
event.stopPropagation()
event.preventDefault()
}
if (this.emptyStatus) {
this.error = this.$t('post_status.empty_status_error')
return
} }
const poll = this.pollFormVisible ? this.newStatus.poll : {} const poll = this.pollFormVisible ? this.newStatus.poll : {}
@@ -182,7 +271,16 @@ const PostStatusForm = {
} }
this.posting = true this.posting = true
statusPoster.postStatus({
try {
await this.setAllMediaDescriptions()
} catch (e) {
this.error = this.$t('post_status.media_description_error')
this.posting = false
return
}
const postingOptions = {
status: newStatus.status, status: newStatus.status,
spoilerText: newStatus.spoilerText || null, spoilerText: newStatus.spoilerText || null,
visibility: newStatus.visibility, visibility: newStatus.visibility,
@@ -191,54 +289,98 @@ const PostStatusForm = {
store: this.$store, store: this.$store,
inReplyToStatusId: this.replyTo, inReplyToStatusId: this.replyTo,
contentType: newStatus.contentType, contentType: newStatus.contentType,
poll poll,
}).then((data) => { idempotencyKey: this.idempotencyKey
if (!data.error) {
this.newStatus = {
status: '',
spoilerText: '',
files: [],
visibility: newStatus.visibility,
contentType: newStatus.contentType,
poll: {}
} }
this.pollFormVisible = false
this.$refs.mediaUpload.clearFile() const postHandler = this.postHandler ? this.postHandler : statusPoster.postStatus
this.clearPollForm()
this.$emit('posted') postHandler(postingOptions).then((data) => {
let el = this.$el.querySelector('textarea') if (!data.error) {
el.style.height = 'auto' this.clearStatus()
el.style.height = undefined this.$emit('posted', data)
this.error = null
} else { } else {
this.error = data.error this.error = data.error
} }
this.posting = false this.posting = false
}) })
}, },
previewStatus () {
if (this.emptyStatus && this.newStatus.spoilerText.trim() === '') {
this.preview = { error: this.$t('post_status.preview_empty') }
this.previewLoading = false
return
}
const newStatus = this.newStatus
this.previewLoading = true
statusPoster.postStatus({
status: newStatus.status,
spoilerText: newStatus.spoilerText || null,
visibility: newStatus.visibility,
sensitive: newStatus.nsfw,
media: [],
store: this.$store,
inReplyToStatusId: this.replyTo,
contentType: newStatus.contentType,
poll: {},
preview: true
}).then((data) => {
// Don't apply preview if not loading, because it means
// user has closed the preview manually.
if (!this.previewLoading) return
if (!data.error) {
this.preview = data
} else {
this.preview = { error: data.error }
}
}).catch((error) => {
this.preview = { error }
}).finally(() => {
this.previewLoading = false
})
},
debouncePreviewStatus: debounce(function () { this.previewStatus() }, 500),
autoPreview () {
if (!this.preview) return
this.previewLoading = true
this.debouncePreviewStatus()
},
closePreview () {
this.preview = null
this.previewLoading = false
},
togglePreview () {
if (this.showPreview) {
this.closePreview()
} else {
this.previewStatus()
}
},
addMediaFile (fileInfo) { addMediaFile (fileInfo) {
this.newStatus.files.push(fileInfo) this.newStatus.files.push(fileInfo)
this.enableSubmit() this.$emit('resize', { delayed: true })
}, },
removeMediaFile (fileInfo) { removeMediaFile (fileInfo) {
let index = this.newStatus.files.indexOf(fileInfo) let index = this.newStatus.files.indexOf(fileInfo)
this.newStatus.files.splice(index, 1) this.newStatus.files.splice(index, 1)
this.$emit('resize')
}, },
uploadFailed (errString, templateArgs) { uploadFailed (errString, templateArgs) {
templateArgs = templateArgs || {} templateArgs = templateArgs || {}
this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs) this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs)
this.enableSubmit()
}, },
disableSubmit () { startedUploadingFiles () {
this.submitDisabled = true this.uploadingFiles = true
}, },
enableSubmit () { finishedUploadingFiles () {
this.submitDisabled = false this.$emit('resize')
this.uploadingFiles = false
}, },
type (fileInfo) { type (fileInfo) {
return fileTypeService.fileType(fileInfo.mimetype) return fileTypeService.fileType(fileInfo.mimetype)
}, },
paste (e) { paste (e) {
this.autoPreview()
this.resize(e) this.resize(e)
if (e.clipboardData.files.length > 0) { if (e.clipboardData.files.length > 0) {
// prevent pasting of file as text // prevent pasting of file as text
@@ -250,13 +392,27 @@ const PostStatusForm = {
} }
}, },
fileDrop (e) { fileDrop (e) {
if (e.dataTransfer.files.length > 0) { if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
e.preventDefault() // allow dropping text like before e.preventDefault() // allow dropping text like before
this.dropFiles = e.dataTransfer.files this.dropFiles = e.dataTransfer.files
clearTimeout(this.dropStopTimeout)
this.showDropIcon = 'hide'
} }
}, },
fileDragStop (e) {
// The false-setting is done with delay because just using leave-events
// directly caused unwanted flickering, this is not perfect either but
// much less noticable.
clearTimeout(this.dropStopTimeout)
this.showDropIcon = 'fade'
this.dropStopTimeout = setTimeout(() => (this.showDropIcon = 'hide'), 500)
},
fileDrag (e) { fileDrag (e) {
e.dataTransfer.dropEffect = 'copy' e.dataTransfer.dropEffect = this.uploadFileLimitReached ? 'none' : 'copy'
if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
clearTimeout(this.dropStopTimeout)
this.showDropIcon = 'show'
}
}, },
onEmojiInputInput (e) { onEmojiInputInput (e) {
this.$nextTick(() => { this.$nextTick(() => {
@@ -270,6 +426,7 @@ const PostStatusForm = {
// Reset to default height for empty form, nothing else to do here. // Reset to default height for empty form, nothing else to do here.
if (target.value === '') { if (target.value === '') {
target.style.height = null target.style.height = null
this.$emit('resize')
this.$refs['emoji-input'].resize() this.$refs['emoji-input'].resize()
return return
} }
@@ -281,7 +438,7 @@ const PostStatusForm = {
* scroll is different for `Window` and `Element`s * scroll is different for `Window` and `Element`s
*/ */
const bottomBottomPaddingStr = window.getComputedStyle(bottomRef)['padding-bottom'] const bottomBottomPaddingStr = window.getComputedStyle(bottomRef)['padding-bottom']
const bottomBottomPadding = Number(bottomBottomPaddingStr.substring(0, bottomBottomPaddingStr.length - 2)) const bottomBottomPadding = pxStringToNumber(bottomBottomPaddingStr)
const scrollerRef = this.$el.closest('.sidebar-scroller') || const scrollerRef = this.$el.closest('.sidebar-scroller') ||
this.$el.closest('.post-form-modal-view') || this.$el.closest('.post-form-modal-view') ||
@@ -290,10 +447,12 @@ const PostStatusForm = {
// Getting info about padding we have to account for, removing 'px' part // Getting info about padding we have to account for, removing 'px' part
const topPaddingStr = window.getComputedStyle(target)['padding-top'] const topPaddingStr = window.getComputedStyle(target)['padding-top']
const bottomPaddingStr = window.getComputedStyle(target)['padding-bottom'] const bottomPaddingStr = window.getComputedStyle(target)['padding-bottom']
const topPadding = Number(topPaddingStr.substring(0, topPaddingStr.length - 2)) const topPadding = pxStringToNumber(topPaddingStr)
const bottomPadding = Number(bottomPaddingStr.substring(0, bottomPaddingStr.length - 2)) const bottomPadding = pxStringToNumber(bottomPaddingStr)
const vertPadding = topPadding + bottomPadding const vertPadding = topPadding + bottomPadding
const oldHeight = pxStringToNumber(target.style.height)
/* Explanation: /* Explanation:
* *
* https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight * https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight
@@ -322,8 +481,15 @@ const PostStatusForm = {
// BEGIN content size update // BEGIN content size update
target.style.height = 'auto' target.style.height = 'auto'
const newHeight = target.scrollHeight - vertPadding const heightWithoutPadding = Math.floor(target.scrollHeight - vertPadding)
let newHeight = this.maxHeight ? Math.min(heightWithoutPadding, this.maxHeight) : heightWithoutPadding
// This is a bit of a hack to combat target.scrollHeight being different on every other input
// on some browsers for whatever reason. Don't change the height if difference is 1px or less.
if (Math.abs(newHeight - oldHeight) <= 1) {
newHeight = oldHeight
}
target.style.height = `${newHeight}px` target.style.height = `${newHeight}px`
this.$emit('resize', newHeight)
// END content size update // END content size update
// We check where the bottom border of form-bottom element is, this uses findOffset // We check where the bottom border of form-bottom element is, this uses findOffset
@@ -374,6 +540,24 @@ const PostStatusForm = {
}, },
dismissScopeNotice () { dismissScopeNotice () {
this.$store.dispatch('setOption', { name: 'hideScopeNotice', value: true }) this.$store.dispatch('setOption', { name: 'hideScopeNotice', value: true })
},
setMediaDescription (id) {
const description = this.newStatus.mediaDescriptions[id]
if (!description || description.trim() === '') return
return statusPoster.setMediaDescription({ store: this.$store, id, description })
},
setAllMediaDescriptions () {
const ids = this.newStatus.files.map(file => file.id)
return Promise.all(ids.map(id => this.setMediaDescription(id)))
},
handleEmojiInputShow (value) {
this.emojiInputShown = value
},
updateIdempotencyKey () {
this.idempotencyKey = Date.now().toString()
},
openProfileTab () {
this.$store.dispatch('openSettingsModalTab', 'profile')
} }
} }
} }
@@ -5,18 +5,30 @@
> >
<form <form
autocomplete="off" autocomplete="off"
@submit.prevent="postStatus(newStatus)" @submit.prevent
@dragover.prevent="fileDrag"
> >
<div
v-show="showDropIcon !== 'hide'"
:style="{ animation: showDropIcon === 'show' ? 'fade-in 0.25s' : 'fade-out 0.5s' }"
class="drop-indicator"
:class="[uploadFileLimitReached ? 'icon-block' : 'icon-upload']"
@dragleave="fileDragStop"
@drop.stop="fileDrop"
/>
<div class="form-group"> <div class="form-group">
<i18n <i18n
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'" v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private' && !disableLockWarning"
path="post_status.account_not_locked_warning" path="post_status.account_not_locked_warning"
tag="p" tag="p"
class="visibility-notice" class="visibility-notice"
> >
<router-link :to="{ name: 'user-settings' }"> <a
href="#"
@click="openProfileTab"
>
{{ $t('post_status.account_not_locked_warning_link') }} {{ $t('post_status.account_not_locked_warning_link') }}
</router-link> </a>
</i18n> </i18n>
<p <p
v-if="!hideScopeNotice && newStatus.visibility === 'public'" v-if="!hideScopeNotice && newStatus.visibility === 'public'"
@@ -61,18 +73,56 @@
<span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span> <span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span>
<span v-else>{{ $t('post_status.direct_warning_to_all') }}</span> <span v-else>{{ $t('post_status.direct_warning_to_all') }}</span>
</p> </p>
<div
v-if="!disablePreview"
class="preview-heading faint"
>
<a
class="preview-toggle faint"
@click.stop.prevent="togglePreview"
>
{{ $t('post_status.preview') }}
<i :class="showPreview ? 'icon-left-open' : 'icon-right-open'" />
</a>
<i
v-show="previewLoading"
class="icon-spin3 animate-spin"
/>
</div>
<div
v-if="showPreview"
class="preview-container"
>
<div
v-if="!preview"
class="preview-status"
>
{{ $t('general.loading') }}
</div>
<div
v-else-if="preview.error"
class="preview-status preview-error"
>
{{ preview.error }}
</div>
<StatusContent
v-else
:status="preview"
class="preview-status"
/>
</div>
<EmojiInput <EmojiInput
v-if="newStatus.spoilerText || alwaysShowSubject" v-if="!disableSubject && (newStatus.spoilerText || alwaysShowSubject)"
v-model="newStatus.spoilerText" v-model="newStatus.spoilerText"
enable-emoji-picker enable-emoji-picker
:suggest="emojiSuggestor" :suggest="emojiSuggestor"
class="form-control" class="form-control"
> >
<input <input
v-model="newStatus.spoilerText" v-model="newStatus.spoilerText"
type="text" type="text"
:placeholder="$t('post_status.content_warning')" :placeholder="$t('post_status.content_warning')"
:disabled="posting"
class="form-post-subject" class="form-post-subject"
> >
</EmojiInput> </EmojiInput>
@@ -80,25 +130,29 @@
ref="emoji-input" ref="emoji-input"
v-model="newStatus.status" v-model="newStatus.status"
:suggest="emojiUserSuggestor" :suggest="emojiUserSuggestor"
:placement="emojiPickerPlacement"
class="form-control main-input" class="form-control main-input"
enable-emoji-picker enable-emoji-picker
hide-emoji-button hide-emoji-button
:newline-on-ctrl-enter="submitOnEnter"
enable-sticker-picker enable-sticker-picker
@input="onEmojiInputInput" @input="onEmojiInputInput"
@sticker-uploaded="addMediaFile" @sticker-uploaded="addMediaFile"
@sticker-upload-failed="uploadFailed" @sticker-upload-failed="uploadFailed"
@shown="handleEmojiInputShow"
> >
<textarea <textarea
ref="textarea" ref="textarea"
v-model="newStatus.status" v-model="newStatus.status"
:placeholder="$t('post_status.default')" :placeholder="placeholder || $t('post_status.default')"
rows="1" rows="1"
cols="1"
:disabled="posting" :disabled="posting"
class="form-post-body" class="form-post-body"
@keydown.meta.enter="postStatus(newStatus)" :class="{ 'scrollable-form': !!maxHeight }"
@keyup.ctrl.enter="postStatus(newStatus)" @keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
@drop="fileDrop" @keydown.meta.enter="postStatus($event, newStatus)"
@dragover.prevent="fileDrag" @keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
@input="resize" @input="resize"
@compositionupdate="resize" @compositionupdate="resize"
@paste="paste" @paste="paste"
@@ -111,7 +165,10 @@
{{ charactersLeft }} {{ charactersLeft }}
</p> </p>
</EmojiInput> </EmojiInput>
<div class="visibility-tray"> <div
v-if="!disableScopeSelector"
class="visibility-tray"
>
<scope-selector <scope-selector
:show-all="showAllScopes" :show-all="showAllScopes"
:user-default="userDefaultScope" :user-default="userDefaultScope"
@@ -169,9 +226,11 @@
ref="mediaUpload" ref="mediaUpload"
class="media-upload-icon" class="media-upload-icon"
:drop-files="dropFiles" :drop-files="dropFiles"
@uploading="disableSubmit" :disabled="uploadFileLimitReached"
@uploading="startedUploadingFiles"
@uploaded="addMediaFile" @uploaded="addMediaFile"
@upload-failed="uploadFailed" @upload-failed="uploadFailed"
@all-uploaded="finishedUploadingFiles"
/> />
<div <div
class="emoji-icon" class="emoji-icon"
@@ -208,11 +267,13 @@
> >
{{ $t('general.submit') }} {{ $t('general.submit') }}
</button> </button>
<!-- touchstart is used to keep the OSK at the same position after a message send -->
<button <button
v-else v-else
:disabled="submitDisabled" :disabled="uploadingFiles || disableSubmit"
type="submit"
class="btn btn-default" class="btn btn-default"
@touchstart.stop.prevent="postStatus($event, newStatus)"
@click.stop.prevent="postStatus($event, newStatus)"
> >
{{ $t('general.submit') }} {{ $t('general.submit') }}
</button> </button>
@@ -237,31 +298,22 @@
class="fa button-icon icon-cancel" class="fa button-icon icon-cancel"
@click="removeMediaFile(file)" @click="removeMediaFile(file)"
/> />
<div class="media-upload-container attachment"> <attachment
<img :attachment="file"
v-if="type(file) === 'image'" :set-media="() => $store.dispatch('setMedia', newStatus.files)"
class="thumbnail media-upload" size="small"
:src="file.url" allow-play="false"
/>
<input
v-model="newStatus.mediaDescriptions[file.id]"
type="text"
:placeholder="$t('post_status.media_description')"
@keydown.enter.prevent=""
> >
<video
v-if="type(file) === 'video'"
:src="file.url"
controls
/>
<audio
v-if="type(file) === 'audio'"
:src="file.url"
controls
/>
<a
v-if="type(file) === 'unknown'"
:href="file.url"
>{{ file.url }}</a>
</div>
</div> </div>
</div> </div>
<div <div
v-if="newStatus.files.length > 0" v-if="newStatus.files.length > 0 && !disableSensitivityCheckbox"
class="upload_settings" class="upload_settings"
> >
<Checkbox v-model="newStatus.nsfw"> <Checkbox v-model="newStatus.nsfw">
@@ -295,14 +347,8 @@
} }
.post-status-form { .post-status-form {
.visibility-tray { position: relative;
display: flex;
justify-content: space-between;
padding-top: 5px;
}
}
.post-status-form {
.form-bottom { .form-bottom {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -328,6 +374,51 @@
max-width: 10em; max-width: 10em;
} }
.preview-heading {
padding-left: 0.5em;
display: flex;
width: 100%;
.icon-spin3 {
margin-left: auto;
}
}
.preview-toggle {
display: flex;
cursor: pointer;
user-select: none;
&:hover {
text-decoration: underline;
}
i {
margin-left: 0.2em;
font-size: 0.8em;
transform: rotate(90deg);
}
}
.preview-container {
margin-bottom: 1em;
}
.preview-error {
font-style: italic;
color: $fallback--faint;
color: var(--faint, $fallback--faint);
}
.preview-status {
border: 1px solid $fallback--border;
border: 1px solid var(--border, $fallback--border);
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
padding: 0.5em;
margin: 0;
line-height: 1.4em;
}
.text-format { .text-format {
.only-format { .only-format {
color: $fallback--faint; color: $fallback--faint;
@@ -335,6 +426,12 @@
} }
} }
.visibility-tray {
display: flex;
justify-content: space-between;
padding-top: 5px;
}
.media-upload-icon, .poll-icon, .emoji-icon { .media-upload-icon, .poll-icon, .emoji-icon {
font-size: 26px; font-size: 26px;
flex: 1; flex: 1;
@@ -346,6 +443,19 @@
color: var(--lightText, $fallback--lightText); color: var(--lightText, $fallback--lightText);
} }
} }
&.disabled {
i {
cursor: not-allowed;
color: $fallback--icon;
color: var(--btnDisabledText, $fallback--icon);
&:hover {
color: $fallback--icon;
color: var(--btnDisabledText, $fallback--icon);
}
}
}
} }
// Order is not necessary but a good indicator // Order is not necessary but a good indicator
@@ -373,11 +483,9 @@
} }
.media-upload-wrapper { .media-upload-wrapper {
flex: 0 0 auto;
max-width: 100%;
min-width: 50px;
margin-right: .2em; margin-right: .2em;
margin-bottom: .5em; margin-bottom: .5em;
width: 18em;
.icon-cancel { .icon-cancel {
display: inline-block; display: inline-block;
@@ -391,6 +499,20 @@
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
img, video {
object-fit: contain;
max-height: 10em;
}
.video {
max-height: 10em;
}
input {
flex: 1;
width: 100%;
}
} }
.status-input-wrapper { .status-input-wrapper {
@@ -400,28 +522,13 @@
flex-direction: column; flex-direction: column;
} }
.attachments { .media-upload-wrapper .attachments {
padding: 0 0.5em; padding: 0 0.5em;
.attachment { .attachment {
margin: 0; margin: 0;
padding: 0;
position: relative; position: relative;
flex: 0 0 auto;
border: 1px solid $fallback--border;
border: 1px solid var(--border, $fallback--border);
text-align: center;
audio {
min-width: 300px;
flex: 1 0 auto;
}
a {
display: block;
text-align: left;
line-height: 1.2;
padding: .5em;
}
} }
i { i {
@@ -446,7 +553,8 @@
form { form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 0.6em; margin: 0.6em;
position: relative;
} }
.form-group { .form-group {
@@ -473,6 +581,10 @@
padding-bottom: 1.75em; padding-bottom: 1.75em;
min-height: 1px; min-height: 1px;
box-sizing: content-box; box-sizing: content-box;
&.scrollable-form {
overflow-y: auto;
}
} }
.main-input { .main-input {
@@ -504,5 +616,42 @@
cursor: pointer; cursor: pointer;
z-index: 4; z-index: 4;
} }
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 0.6; }
}
@keyframes fade-out {
from { opacity: 0.6; }
to { opacity: 0; }
}
.drop-indicator {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
font-size: 5em;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
color: $fallback--text;
color: var(--text, $fallback--text);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
border: 2px dashed $fallback--text;
border: 2px dashed var(--text, $fallback--text);
}
}
// todo: unify with attachment.vue (otherwise the uploaded images are not minified unless a status with an attachment was displayed before)
img.media-upload, .media-upload-container > video {
line-height: 0;
max-height: 200px;
max-width: 100%;
} }
</style> </style>
+6 -3
View File
@@ -2,7 +2,7 @@ import Popover from '../popover/popover.vue'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
const ReactButton = { const ReactButton = {
props: ['status', 'loggedIn'], props: ['status'],
data () { data () {
return { return {
filterWord: '' filterWord: ''
@@ -24,11 +24,14 @@ const ReactButton = {
}, },
computed: { computed: {
commonEmojis () { commonEmojis () {
return ['❤️', '😠', '👀', '😂', '🔥'] return ['👍', '😠', '👀', '😂', '🔥']
}, },
emojis () { emojis () {
if (this.filterWord !== '') { if (this.filterWord !== '') {
return this.$store.state.instance.emoji.filter(emoji => emoji.displayText.includes(this.filterWord)) const filterWordLowercase = this.filterWord.toLowerCase()
return this.$store.state.instance.emoji.filter(emoji =>
emoji.displayText.toLowerCase().includes(filterWordLowercase)
)
} }
return this.$store.state.instance.emoji || [] return this.$store.state.instance.emoji || []
}, },
@@ -37,7 +37,6 @@
</div> </div>
</div> </div>
<i <i
v-if="loggedIn"
slot="trigger" slot="trigger"
class="icon-smile button-icon add-reaction-button" class="icon-smile button-icon add-reaction-button"
:title="$t('tool_tip.add_reaction')" :title="$t('tool_tip.add_reaction')"
+7 -4
View File
@@ -1,5 +1,5 @@
import { validationMixin } from 'vuelidate' import { validationMixin } from 'vuelidate'
import { required, sameAs } from 'vuelidate/lib/validators' import { required, requiredIf, sameAs } from 'vuelidate/lib/validators'
import { mapActions, mapState } from 'vuex' import { mapActions, mapState } from 'vuex'
const registration = { const registration = {
@@ -14,9 +14,10 @@ const registration = {
}, },
captcha: {} captcha: {}
}), }),
validations: { validations () {
return {
user: { user: {
email: { required }, email: { required: requiredIf(() => this.accountActivationRequired) },
username: { required }, username: { required },
fullname: { required }, fullname: { required },
password: { required }, password: { required },
@@ -25,6 +26,7 @@ const registration = {
sameAsPassword: sameAs('password') sameAsPassword: sameAs('password')
} }
} }
}
}, },
created () { created () {
if ((!this.registrationOpen && !this.token) || this.signedIn) { if ((!this.registrationOpen && !this.token) || this.signedIn) {
@@ -43,7 +45,8 @@ const registration = {
signedIn: (state) => !!state.users.currentUser, signedIn: (state) => !!state.users.currentUser,
isPending: (state) => state.users.signUpPending, isPending: (state) => state.users.signUpPending,
serverValidationErrors: (state) => state.users.signUpErrors, serverValidationErrors: (state) => state.users.signUpErrors,
termsOfService: (state) => state.instance.tos termsOfService: (state) => state.instance.tos,
accountActivationRequired: (state) => state.instance.accountActivationRequired
}) })
}, },
methods: { methods: {
-128
View File
@@ -1,128 +0,0 @@
/* eslint-env browser */
import { filter, trim } from 'lodash'
import TabSwitcher from '../tab_switcher/tab_switcher.js'
import StyleSwitcher from '../style_switcher/style_switcher.vue'
import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
import { extractCommit } from '../../services/version/version.service'
import { instanceDefaultProperties, defaultState as configDefaultState } from '../../modules/config.js'
import Checkbox from '../checkbox/checkbox.vue'
const pleromaFeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma-fe/commit/'
const pleromaBeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma/commit/'
const multiChoiceProperties = [
'postContentType',
'subjectLineBehavior'
]
const settings = {
data () {
const instance = this.$store.state.instance
return {
loopSilentAvailable:
// Firefox
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
// Chrome-likes
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') ||
// Future spec, still not supported in Nightly 63 as of 08/2018
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks'),
backendVersion: instance.backendVersion,
frontendVersion: instance.frontendVersion
}
},
components: {
TabSwitcher,
StyleSwitcher,
InterfaceLanguageSwitcher,
Checkbox
},
computed: {
user () {
return this.$store.state.users.currentUser
},
currentSaveStateNotice () {
return this.$store.state.interface.settings.currentSaveStateNotice
},
postFormats () {
return this.$store.state.instance.postFormats || []
},
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
frontendVersionLink () {
return pleromaFeCommitUrl + this.frontendVersion
},
backendVersionLink () {
return pleromaBeCommitUrl + extractCommit(this.backendVersion)
},
// Getting localized values for instance-default properties
...instanceDefaultProperties
.filter(key => multiChoiceProperties.includes(key))
.map(key => [
key + 'DefaultValue',
function () {
return this.$store.getters.instanceDefaultConfig[key]
}
])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
...instanceDefaultProperties
.filter(key => !multiChoiceProperties.includes(key))
.map(key => [
key + 'LocalizedValue',
function () {
return this.$t('settings.values.' + this.$store.getters.instanceDefaultConfig[key])
}
])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
// Generating computed values for vuex properties
...Object.keys(configDefaultState)
.map(key => [key, {
get () { return this.$store.getters.mergedConfig[key] },
set (value) {
this.$store.dispatch('setOption', { name: key, value })
}
}])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
// Special cases (need to transform values or perform actions first)
muteWordsString: {
get () { return this.$store.getters.mergedConfig.muteWords.join('\n') },
set (value) {
this.$store.dispatch('setOption', {
name: 'muteWords',
value: filter(value.split('\n'), (word) => trim(word).length > 0)
})
}
},
useStreamingApi: {
get () { return this.$store.getters.mergedConfig.useStreamingApi },
set (value) {
const promise = value
? this.$store.dispatch('enableMastoSockets')
: this.$store.dispatch('disableMastoSockets')
promise.then(() => {
this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
}).catch((e) => {
console.error('Failed starting MastoAPI Streaming socket', e)
this.$store.dispatch('disableMastoSockets')
this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false })
})
}
}
},
// Updating nested properties
watch: {
notificationVisibility: {
handler (value) {
this.$store.dispatch('setOption', {
name: 'notificationVisibility',
value: this.$store.getters.mergedConfig.notificationVisibility
})
},
deep: true
}
}
}
export default settings
-424
View File
@@ -1,424 +0,0 @@
<template>
<div class="settings panel panel-default">
<div class="panel-heading">
<div class="title">
{{ $t('settings.settings') }}
</div>
<transition name="fade">
<template v-if="currentSaveStateNotice">
<div
v-if="currentSaveStateNotice.error"
class="alert error"
@click.prevent
>
{{ $t('settings.saving_err') }}
</div>
<div
v-if="!currentSaveStateNotice.error"
class="alert transparent"
@click.prevent
>
{{ $t('settings.saving_ok') }}
</div>
</template>
</transition>
</div>
<div class="panel-body">
<keep-alive>
<tab-switcher>
<div :label="$t('settings.general')">
<div class="setting-item">
<h2>{{ $t('settings.interface') }}</h2>
<ul class="setting-list">
<li>
<interface-language-switcher />
</li>
<li v-if="instanceSpecificPanelPresent">
<Checkbox v-model="hideISP">
{{ $t('settings.hide_isp') }}
</Checkbox>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('nav.timeline') }}</h2>
<ul class="setting-list">
<li>
<Checkbox v-model="hideMutedPosts">
{{ $t('settings.hide_muted_posts') }} {{ $t('settings.instance_default', { value: hideMutedPostsLocalizedValue }) }}
</Checkbox>
</li>
<li>
<Checkbox v-model="collapseMessageWithSubject">
{{ $t('settings.collapse_subject') }} {{ $t('settings.instance_default', { value: collapseMessageWithSubjectLocalizedValue }) }}
</Checkbox>
</li>
<li>
<Checkbox v-model="streaming">
{{ $t('settings.streaming') }}
</Checkbox>
<ul
class="setting-list suboptions"
:class="[{disabled: !streaming}]"
>
<li>
<Checkbox
v-model="pauseOnUnfocused"
:disabled="!streaming"
>
{{ $t('settings.pause_on_unfocused') }}
</Checkbox>
</li>
</ul>
</li>
<li>
<Checkbox v-model="useStreamingApi">
{{ $t('settings.useStreamingApi') }}
<br>
<small>
{{ $t('settings.useStreamingApiWarning') }}
</small>
</Checkbox>
</li>
<li>
<Checkbox v-model="autoLoad">
{{ $t('settings.autoload') }}
</Checkbox>
</li>
<li>
<Checkbox v-model="hoverPreview">
{{ $t('settings.reply_link_preview') }}
</Checkbox>
</li>
<li>
<Checkbox v-model="emojiReactionsOnTimeline">
{{ $t('settings.emoji_reactions_on_timeline') }}
</Checkbox>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('settings.composing') }}</h2>
<ul class="setting-list">
<li>
<Checkbox v-model="scopeCopy">
{{ $t('settings.scope_copy') }} {{ $t('settings.instance_default', { value: scopeCopyLocalizedValue }) }}
</Checkbox>
</li>
<li>
<Checkbox v-model="alwaysShowSubjectInput">
{{ $t('settings.subject_input_always_show') }} {{ $t('settings.instance_default', { value: alwaysShowSubjectInputLocalizedValue }) }}
</Checkbox>
</li>
<li>
<div>
{{ $t('settings.subject_line_behavior') }}
<label
for="subjectLineBehavior"
class="select"
>
<select
id="subjectLineBehavior"
v-model="subjectLineBehavior"
>
<option value="email">
{{ $t('settings.subject_line_email') }}
{{ subjectLineBehaviorDefaultValue == 'email' ? $t('settings.instance_default_simple') : '' }}
</option>
<option value="masto">
{{ $t('settings.subject_line_mastodon') }}
{{ subjectLineBehaviorDefaultValue == 'mastodon' ? $t('settings.instance_default_simple') : '' }}
</option>
<option value="noop">
{{ $t('settings.subject_line_noop') }}
{{ subjectLineBehaviorDefaultValue == 'noop' ? $t('settings.instance_default_simple') : '' }}
</option>
</select>
<i class="icon-down-open" />
</label>
</div>
</li>
<li v-if="postFormats.length > 0">
<div>
{{ $t('settings.post_status_content_type') }}
<label
for="postContentType"
class="select"
>
<select
id="postContentType"
v-model="postContentType"
>
<option
v-for="postFormat in postFormats"
:key="postFormat"
:value="postFormat"
>
{{ $t(`post_status.content_type["${postFormat}"]`) }}
{{ postContentTypeDefaultValue === postFormat ? $t('settings.instance_default_simple') : '' }}
</option>
</select>
<i class="icon-down-open" />
</label>
</div>
</li>
<li>
<Checkbox v-model="minimalScopesMode">
{{ $t('settings.minimal_scopes_mode') }} {{ $t('settings.instance_default', { value: minimalScopesModeLocalizedValue }) }}
</Checkbox>
</li>
<li>
<Checkbox v-model="autohideFloatingPostButton">
{{ $t('settings.autohide_floating_post_button') }}
</Checkbox>
</li>
<li>
<Checkbox v-model="padEmoji">
{{ $t('settings.pad_emoji') }}
</Checkbox>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('settings.attachments') }}</h2>
<ul class="setting-list">
<li>
<Checkbox v-model="hideAttachments">
{{ $t('settings.hide_attachments_in_tl') }}
</Checkbox>
</li>
<li>
<Checkbox v-model="hideAttachmentsInConv">
{{ $t('settings.hide_attachments_in_convo') }}
</Checkbox>
</li>
<li>
<label for="maxThumbnails">
{{ $t('settings.max_thumbnails') }}
</label>
<input
id="maxThumbnails"
v-model.number="maxThumbnails"
class="number-input"
type="number"
min="0"
step="1"
>
</li>
<li>
<Checkbox v-model="hideNsfw">
{{ $t('settings.nsfw_clickthrough') }}
</Checkbox>
</li>
<ul class="setting-list suboptions">
<li>
<Checkbox
v-model="preloadImage"
:disabled="!hideNsfw"
>
{{ $t('settings.preload_images') }}
</Checkbox>
</li>
<li>
<Checkbox
v-model="useOneClickNsfw"
:disabled="!hideNsfw"
>
{{ $t('settings.use_one_click_nsfw') }}
</Checkbox>
</li>
</ul>
<li>
<Checkbox v-model="stopGifs">
{{ $t('settings.stop_gifs') }}
</Checkbox>
</li>
<li>
<Checkbox v-model="loopVideo">
{{ $t('settings.loop_video') }}
</Checkbox>
<ul
class="setting-list suboptions"
:class="[{disabled: !streaming}]"
>
<li>
<Checkbox
v-model="loopVideoSilentOnly"
:disabled="!loopVideo || !loopSilentAvailable"
>
{{ $t('settings.loop_video_silent_only') }}
</Checkbox>
<div
v-if="!loopSilentAvailable"
class="unavailable"
>
<i class="icon-globe" />! {{ $t('settings.limited_availability') }}
</div>
</li>
</ul>
</li>
<li>
<Checkbox v-model="playVideosInModal">
{{ $t('settings.play_videos_in_modal') }}
</Checkbox>
</li>
<li>
<Checkbox v-model="useContainFit">
{{ $t('settings.use_contain_fit') }}
</Checkbox>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('settings.notifications') }}</h2>
<ul class="setting-list">
<li>
<Checkbox v-model="webPushNotifications">
{{ $t('settings.enable_web_push_notifications') }}
</Checkbox>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('settings.fun') }}</h2>
<ul class="setting-list">
<li>
<Checkbox v-model="greentext">
{{ $t('settings.greentext') }} {{ $t('settings.instance_default', { value: greentextLocalizedValue }) }}
</Checkbox>
</li>
</ul>
</div>
</div>
<div :label="$t('settings.theme')">
<div class="setting-item">
<style-switcher />
</div>
</div>
<div :label="$t('settings.filtering')">
<div class="setting-item">
<div class="select-multiple">
<span class="label">{{ $t('settings.notification_visibility') }}</span>
<ul class="option-list">
<li>
<Checkbox v-model="notificationVisibility.likes">
{{ $t('settings.notification_visibility_likes') }}
</Checkbox>
</li>
<li>
<Checkbox v-model="notificationVisibility.repeats">
{{ $t('settings.notification_visibility_repeats') }}
</Checkbox>
</li>
<li>
<Checkbox v-model="notificationVisibility.follows">
{{ $t('settings.notification_visibility_follows') }}
</Checkbox>
</li>
<li>
<Checkbox v-model="notificationVisibility.mentions">
{{ $t('settings.notification_visibility_mentions') }}
</Checkbox>
</li>
<li>
<Checkbox v-model="notificationVisibility.moves">
{{ $t('settings.notification_visibility_moves') }}
</Checkbox>
</li>
<li>
<Checkbox v-model="notificationVisibility.emojiReactions">
{{ $t('settings.notification_visibility_emoji_reactions') }}
</Checkbox>
</li>
</ul>
</div>
<div>
{{ $t('settings.replies_in_timeline') }}
<label
for="replyVisibility"
class="select"
>
<select
id="replyVisibility"
v-model="replyVisibility"
>
<option
value="all"
selected
>{{ $t('settings.reply_visibility_all') }}</option>
<option value="following">{{ $t('settings.reply_visibility_following') }}</option>
<option value="self">{{ $t('settings.reply_visibility_self') }}</option>
</select>
<i class="icon-down-open" />
</label>
</div>
<div>
<Checkbox v-model="hidePostStats">
{{ $t('settings.hide_post_stats') }} {{ $t('settings.instance_default', { value: hidePostStatsLocalizedValue }) }}
</Checkbox>
</div>
<div>
<Checkbox v-model="hideUserStats">
{{ $t('settings.hide_user_stats') }} {{ $t('settings.instance_default', { value: hideUserStatsLocalizedValue }) }}
</Checkbox>
</div>
</div>
<div class="setting-item">
<div>
<p>{{ $t('settings.filtering_explanation') }}</p>
<textarea
id="muteWords"
v-model="muteWordsString"
/>
</div>
<div>
<Checkbox v-model="hideFilteredStatuses">
{{ $t('settings.hide_filtered_statuses') }} {{ $t('settings.instance_default', { value: hideFilteredStatusesLocalizedValue }) }}
</Checkbox>
</div>
</div>
</div>
<div :label="$t('settings.version.title')">
<div class="setting-item">
<ul class="setting-list">
<li>
<p>{{ $t('settings.version.backend_version') }}</p>
<ul class="option-list">
<li>
<a
:href="backendVersionLink"
target="_blank"
>{{ backendVersion }}</a>
</li>
</ul>
</li>
<li>
<p>{{ $t('settings.version.frontend_version') }}</p>
<ul class="option-list">
<li>
<a
:href="frontendVersionLink"
target="_blank"
>{{ frontendVersion }}</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</tab-switcher>
</keep-alive>
</div>
</div>
</template>
<script src="./settings.js">
</script>
@@ -0,0 +1,58 @@
import {
instanceDefaultProperties,
multiChoiceProperties,
defaultState as configDefaultState
} from 'src/modules/config.js'
const SharedComputedObject = () => ({
user () {
return this.$store.state.users.currentUser
},
// Getting localized values for instance-default properties
...instanceDefaultProperties
.filter(key => multiChoiceProperties.includes(key))
.map(key => [
key + 'DefaultValue',
function () {
return this.$store.getters.instanceDefaultConfig[key]
}
])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
...instanceDefaultProperties
.filter(key => !multiChoiceProperties.includes(key))
.map(key => [
key + 'LocalizedValue',
function () {
return this.$t('settings.values.' + this.$store.getters.instanceDefaultConfig[key])
}
])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
// Generating computed values for vuex properties
...Object.keys(configDefaultState)
.map(key => [key, {
get () { return this.$store.getters.mergedConfig[key] },
set (value) {
this.$store.dispatch('setOption', { name: key, value })
}
}])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
// Special cases (need to transform values or perform actions first)
useStreamingApi: {
get () { return this.$store.getters.mergedConfig.useStreamingApi },
set (value) {
const promise = value
? this.$store.dispatch('enableMastoSockets')
: this.$store.dispatch('disableMastoSockets')
promise.then(() => {
this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
}).catch((e) => {
console.error('Failed starting MastoAPI Streaming socket', e)
this.$store.dispatch('disableMastoSockets')
this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false })
})
}
}
})
export default SharedComputedObject
@@ -0,0 +1,42 @@
import Modal from 'src/components/modal/modal.vue'
import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue'
import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
const SettingsModal = {
components: {
Modal,
SettingsModalContent: getResettableAsyncComponent(
() => import('./settings_modal_content.vue'),
{
loading: PanelLoading,
error: AsyncComponentError,
delay: 0
}
)
},
methods: {
closeModal () {
this.$store.dispatch('closeSettingsModal')
},
peekModal () {
this.$store.dispatch('togglePeekSettingsModal')
}
},
computed: {
currentSaveStateNotice () {
return this.$store.state.interface.settings.currentSaveStateNotice
},
modalActivated () {
return this.$store.state.interface.settingsModalState !== 'hidden'
},
modalOpenedOnce () {
return this.$store.state.interface.settingsModalLoaded
},
modalPeeked () {
return this.$store.state.interface.settingsModalState === 'minimized'
}
}
}
export default SettingsModal
@@ -0,0 +1,51 @@
@import 'src/_variables.scss';
.settings-modal {
overflow: hidden;
&.peek {
.settings-modal-panel {
/* Explanation:
* Modal is positioned vertically centered.
* 100vh - 100% = Distance between modal's top+bottom boundaries and screen
* (100vh - 100%) / 2 = Distance between bottom (or top) boundary and screen
* + 100% - we move modal completely off-screen, it's top boundary touches
* bottom of the screen
* - 50px - leaving tiny amount of space so that titlebar + tiny amount of modal is visible
*/
transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px));
@media all and (max-width: 800px) {
/* For mobile, the modal takes 100% of the available screen.
This ensures the minimized modal is always 50px above the browser bottom bar regardless of whether or not it is visible.
*/
transform: translateY(calc(100% - 50px));
}
}
}
.settings-modal-panel {
overflow: hidden;
transition: transform;
transition-timing-function: ease-in-out;
transition-duration: 300ms;
width: 1000px;
max-width: 90vw;
height: 90vh;
@media all and (max-width: 800px) {
max-width: 100vw;
height: 100%;
}
>.panel-body {
height: 100%;
overflow-y: hidden;
.btn {
min-height: 28px;
min-width: 10em;
padding: 0 2em;
}
}
}
}
@@ -0,0 +1,54 @@
<template>
<Modal
:is-open="modalActivated"
class="settings-modal"
:class="{ peek: modalPeeked }"
:no-background="modalPeeked"
>
<div class="settings-modal-panel panel">
<div class="panel-heading">
<span class="title">
{{ $t('settings.settings') }}
</span>
<transition name="fade">
<template v-if="currentSaveStateNotice">
<div
v-if="currentSaveStateNotice.error"
class="alert error"
@click.prevent
>
{{ $t('settings.saving_err') }}
</div>
<div
v-if="!currentSaveStateNotice.error"
class="alert transparent"
@click.prevent
>
{{ $t('settings.saving_ok') }}
</div>
</template>
</transition>
<button
class="btn"
@click="peekModal"
>
{{ $t('general.peek') }}
</button>
<button
class="btn"
@click="closeModal"
>
{{ $t('general.close') }}
</button>
</div>
<div class="panel-body">
<SettingsModalContent v-if="modalOpenedOnce" />
</div>
</div>
</Modal>
</template>
<script src="./settings_modal.js"></script>
<style src="./settings_modal.scss" lang="scss"></style>
@@ -0,0 +1,62 @@
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
import DataImportExportTab from './tabs/data_import_export_tab.vue'
import MutesAndBlocksTab from './tabs/mutes_and_blocks_tab.vue'
import NotificationsTab from './tabs/notifications_tab.vue'
import FilteringTab from './tabs/filtering_tab.vue'
import SecurityTab from './tabs/security_tab/security_tab.vue'
import ProfileTab from './tabs/profile_tab.vue'
import GeneralTab from './tabs/general_tab.vue'
import VersionTab from './tabs/version_tab.vue'
import ThemeTab from './tabs/theme_tab/theme_tab.vue'
const SettingsModalContent = {
components: {
TabSwitcher,
DataImportExportTab,
MutesAndBlocksTab,
NotificationsTab,
FilteringTab,
SecurityTab,
ProfileTab,
GeneralTab,
VersionTab,
ThemeTab
},
computed: {
isLoggedIn () {
return !!this.$store.state.users.currentUser
},
open () {
return this.$store.state.interface.settingsModalState !== 'hidden'
}
},
methods: {
onOpen () {
const targetTab = this.$store.state.interface.settingsModalTargetTab
// We're being told to open in specific tab
if (targetTab) {
const tabIndex = this.$refs.tabSwitcher.$slots.default.findIndex(elm => {
return elm.data && elm.data.attrs['data-tab-name'] === targetTab
})
if (tabIndex >= 0) {
this.$refs.tabSwitcher.setTab(tabIndex)
}
}
// Clear the state of target tab, so that next time settings is opened
// it doesn't force it.
this.$store.dispatch('clearSettingsModalTargetTab')
}
},
mounted () {
this.onOpen()
},
watch: {
open: function (value) {
if (value) this.onOpen()
}
}
}
export default SettingsModalContent
@@ -0,0 +1,43 @@
@import 'src/_variables.scss';
.settings_tab-switcher {
height: 100%;
.setting-item {
border-bottom: 2px solid var(--fg, $fallback--fg);
margin: 1em 1em 1.4em;
padding-bottom: 1.4em;
> div {
margin-bottom: .5em;
&:last-child {
margin-bottom: 0;
}
}
&:last-child {
border-bottom: none;
padding-bottom: 0;
margin-bottom: 1em;
}
select {
min-width: 10em;
}
textarea {
width: 100%;
max-width: 100%;
height: 100px;
}
.unavailable,
.unavailable i {
color: var(--cRed, $fallback--cRed);
color: $fallback--cRed;
}
.number-input {
max-width: 6em;
}
}
}
@@ -0,0 +1,82 @@
<template>
<tab-switcher
ref="tabSwitcher"
class="settings_tab-switcher"
:side-tab-bar="true"
:scrollable-tabs="true"
>
<div
:label="$t('settings.general')"
icon="wrench"
data-tab-name="general"
>
<GeneralTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.profile_tab')"
icon="user"
data-tab-name="profile"
>
<ProfileTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.security_tab')"
icon="lock"
data-tab-name="security"
>
<SecurityTab />
</div>
<div
:label="$t('settings.filtering')"
icon="filter"
data-tab-name="filtering"
>
<FilteringTab />
</div>
<div
:label="$t('settings.theme')"
icon="brush"
data-tab-name="theme"
>
<ThemeTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.notifications')"
icon="bell-ringing-o"
data-tab-name="notifications"
>
<NotificationsTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.data_import_export_tab')"
icon="download"
data-tab-name="dataImportExport"
>
<DataImportExportTab />
</div>
<div
v-if="isLoggedIn"
:label="$t('settings.mutes_and_blocks')"
:fullHeight="true"
icon="eye-off"
data-tab-name="mutesAndBlocks"
>
<MutesAndBlocksTab />
</div>
<div
:label="$t('settings.version.title')"
icon="info-circled"
data-tab-name="version"
>
<VersionTab />
</div>
</tab-switcher>
</template>
<script src="./settings_modal_content.js"></script>
<style src="./settings_modal_content.scss" lang="scss"></style>

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