Compare commits

..

762 commits

Author SHA1 Message Date
Henry Jameson
8a47252d33 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-27 01:24:25 +02:00
Henry Jameson
0264090953 fix highlights, i'll deal with tests later - they are wrong 2026-03-27 01:24:01 +02:00
Henry Jameson
8dbe6ad1ad fix visibility notice styles 2026-03-25 17:40:38 +02:00
Henry Jameson
b21685cb0c Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-25 15:39:14 +02:00
Henry Jameson
ce99dca909 fix extra notifications set 2026-03-25 15:38:58 +02:00
Henry Jameson
ff621d9d80 fix tests 2026-03-25 15:38:31 +02:00
Henry Jameson
d03ca88eba Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-25 11:56:24 +02:00
Henry Jameson
54298927da fix font overrides 2026-03-25 11:56:09 +02:00
Henry Jameson
5e574dca77 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-25 11:43:32 +02:00
Henry Jameson
e202f18720 fix rouge local configs overriding sync config 2026-03-25 11:41:42 +02:00
Henry Jameson
2ab1d86d92 fix settings modal expert level 2026-03-25 11:04:57 +02:00
Henry Jameson
a597d947bd Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-24 23:07:41 +02:00
Henry Jameson
77cc2e5201 fix themes not working 2026-03-24 23:07:26 +02:00
Henry Jameson
739c4420f3 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-24 21:48:38 +02:00
Henry Jameson
6cdb921e56 fixes 2026-03-24 21:48:28 +02:00
Henry Jameson
94570af748 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-24 21:42:40 +02:00
Henry Jameson
4e235562aa massive rename and separation merged config into its own "store" 2026-03-24 21:42:22 +02:00
Henry Jameson
1be0debc63 fix 2026-03-24 20:31:05 +02:00
Henry Jameson
784ae2edb6 remove old config module 2026-03-24 20:22:07 +02:00
Henry Jameson
a461068e40 restructure settings definitions 2026-03-24 20:07:54 +02:00
Henry Jameson
bb0656c505 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-24 15:46:02 +02:00
Henry Jameson
694a1f0103 instance defaults definitions 2026-03-24 15:45:31 +02:00
Henry Jameson
f57c24cf6d instance identity config definitions 2026-03-24 15:13:24 +02:00
Henry Jameson
0e2a94bf34 should have done this long time ago 2026-03-24 14:44:05 +02:00
Henry Jameson
a1cde6ce0f renamed identity stuff to be more clear 2026-03-24 14:37:51 +02:00
Henry Jameson
2520935676 lint 2026-03-24 13:59:47 +02:00
Henry Jameson
00cd46d05a Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-23 15:48:35 +02:00
Henry Jameson
4e21031fde fix being unable to delete filters 2026-03-23 15:44:11 +02:00
Henry Jameson
ea69bf5fd9 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-23 15:37:58 +02:00
Henry Jameson
b8557bb678 fix modified indicator for local settings 2026-03-23 15:37:48 +02:00
Henry Jameson
b08d5dd8df fix modified indicator for local settings 2026-03-23 15:37:31 +02:00
Henry Jameson
a2eb4fd202 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-23 15:25:58 +02:00
Henry Jameson
8ee71cdfff update tests, add journal trimming 2026-03-23 15:23:35 +02:00
Henry Jameson
1aedd8f16d Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-20 13:46:55 +02:00
Henry Jameson
45771001e6 fix quick filters + filters tab 2026-03-20 13:46:45 +02:00
Henry Jameson
bf8a4bdbe4 fix sw.js 2026-03-20 13:46:33 +02:00
Henry Jameson
e775098da8 fix sw.js 2026-03-20 13:46:17 +02:00
Henry Jameson
5785bb850d fix quick filters + filters tab 2026-03-20 13:45:59 +02:00
Henry Jameson
6592955a28 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-18 16:52:37 +02:00
Henry Jameson
c905219e8c "fix" posts font 2026-03-18 16:52:13 +02:00
Henry Jameson
e6649c7c25 fixed fonts (post fonts seem to be broken in develop) 2026-03-18 16:46:39 +02:00
Henry Jameson
b2ec9cb890 fix font settings 2026-03-18 15:08:52 +02:00
Henry Jameson
63b4586c0f fix remote interaction link 2026-03-18 12:48:12 +02:00
Henry Jameson
9a6f3bfbe4 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-17 00:46:08 +02:00
Henry Jameson
b57f681f85 apply theme only after sync config is done 2026-03-17 00:45:53 +02:00
Henry Jameson
c50181f7d0 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-16 23:41:06 +02:00
Henry Jameson
dbededdea4 fuck it, no more push on init 2026-03-16 23:40:35 +02:00
Henry Jameson
b1f3e097f4 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-16 20:22:50 +02:00
Henry Jameson
c2be818504 fix a lot of stuff by simply moving const to a proper place 2026-03-16 20:22:34 +02:00
Henry Jameson
12b4b00e49 debounce instead of throttle 2026-03-16 18:51:43 +02:00
Henry Jameson
f29710d0af fix mention links 2026-03-16 18:45:57 +02:00
Henry Jameson
c4c4f3bae7 throttle push config + cleanup excessive calls 2026-03-16 18:34:34 +02:00
Henry Jameson
f6fd5ed8c4 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-16 17:58:52 +02:00
Henry Jameson
55fdb8687a lint 2026-03-16 17:11:16 +02:00
Henry Jameson
d881b92f86 fix import/export 2026-03-16 16:58:36 +02:00
Henry Jameson
195e353b3a remainder of old config references 2026-03-16 16:58:36 +02:00
Henry Jameson
650785bb5b make SW use pinia cache instead of vuex 2026-03-16 16:58:36 +02:00
Henry Jameson
392b595dc9 more locals 2026-03-16 15:21:37 +02:00
Henry Jameson
1fb7b6b48b fix mute filters being broken 2026-03-16 15:16:50 +02:00
Henry Jameson
18b10ea042 fix not defaulting to hardcoded defaults 2026-03-16 15:13:48 +02:00
Henry Jameson
6a8abf1b32 more local-only stuff 2026-03-16 14:42:31 +02:00
Henry Jameson
10b4259de6 don't persist old config 2026-03-16 09:51:32 +02:00
Henry Jameson
7528a72b2e fix config/highlight not working upon login 2026-03-16 09:51:29 +02:00
Henry Jameson
7b517a85bf Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-15 19:55:02 +02:00
Henry Jameson
47cf88426d fix number setting icons 2026-03-15 19:54:45 +02:00
Henry Jameson
cac39ed49d fix default avatar 2026-03-15 19:53:44 +02:00
Henry Jameson
e6a6a06465 cleanup + fixes for instance tab 2026-03-15 19:47:11 +02:00
Henry Jameson
f31a9a42da fix socket 2026-03-15 19:26:36 +02:00
Henry Jameson
d0fb8bcf85 more local settings 2026-03-15 19:08:02 +02:00
Henry Jameson
5c42661ca7 fixed post formats 2026-03-15 18:57:57 +02:00
Henry Jameson
6659308ace use json-clone to compare recent and stale data 2026-03-15 18:52:52 +02:00
Henry Jameson
3121eaad01 better migration 2026-03-15 18:10:57 +02:00
Henry Jameson
de7844cbaa attempt to do migration in a different way 2026-03-15 17:59:13 +02:00
Henry Jameson
702e6b231c Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-13 15:32:50 +02:00
Henry Jameson
c7501f21a9 lint 2026-03-13 15:32:21 +02:00
Henry Jameson
fb10ee54ed Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-13 13:21:23 +02:00
Henry Jameson
e77e8431c6 fix not synching 2026-03-13 13:21:02 +02:00
Henry Jameson
6e55622963 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-13 13:15:10 +02:00
Henry Jameson
e610361217 debug 2026-03-13 13:15:05 +02:00
Henry Jameson
71c7bb86f9 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-13 13:11:55 +02:00
Henry Jameson
1785546120 better logging + needUpload 2026-03-13 13:11:42 +02:00
Henry Jameson
b564fc2d66 remove console logs 2026-03-13 13:09:39 +02:00
Henry Jameson
b29b573b9d Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-13 12:52:43 +02:00
Henry Jameson
2290816e8b fix error 2026-03-13 12:49:34 +02:00
Henry Jameson
756a931ea5 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-13 12:44:28 +02:00
Henry Jameson
314dc3888c rename prefs to highlight, fix highlight journal replay 2026-03-13 12:18:09 +02:00
Henry Jameson
9ddbd625f6 fix console error 2026-03-13 12:13:32 +02:00
Henry Jameson
3b850ae223 fix 2026-03-13 12:11:43 +02:00
Henry Jameson
3a136f6c11 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-13 12:10:00 +02:00
Henry Jameson
fda58058a3 debug 2026-03-13 12:09:52 +02:00
Henry Jameson
826e726391 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-13 12:07:58 +02:00
Henry Jameson
13d9a281c9 debug 2026-03-13 12:07:50 +02:00
Henry Jameson
ca4604602d Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-13 12:01:29 +02:00
Henry Jameson
da49b0d009 highlight sync 2026-03-13 11:52:15 +02:00
Henry Jameson
4e44af04b7 fix pleroma-tan being stabbed by panel-footer 2026-03-13 11:16:45 +02:00
Henry Jameson
a7b2a9a823 please bear with us 2026-03-13 10:54:16 +02:00
Henry Jameson
914df47a35 migration 2026-03-13 10:47:31 +02:00
Henry Jameson
7f42d33f67 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-12 00:33:05 +02:00
Henry Jameson
d358b8370f update theme loading upon login 2026-03-12 00:32:46 +02:00
Henry Jameson
3a9ff0797b fix palette selectors not working 2026-03-11 23:53:03 +02:00
Henry Jameson
884b47845d Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-11 23:48:44 +02:00
Henry Jameson
d4f8f60475 theme sync attempt 2026-03-11 23:48:23 +02:00
Henry Jameson
53a3c8bcd6 Merge remote-tracking branch 'origin/develop' into setttingssync 2026-03-11 23:33:50 +02:00
HJ
aa8c9fec3b Merge pull request 'Fix emojis breaking user bio editing' (#3483) from shibao/pleroma-fe:fix-emojis into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3483
2026-03-09 00:17:47 +00:00
shibao
a1f4dd2142 add changelog note for emojis breaking user bio editing 2026-03-08 16:42:47 +00:00
shibao
6eceac4723 fix emojis breaking user bio editing 2026-03-08 16:41:13 +00:00
Henry Jameson
11045fd81c Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-06 17:55:38 +02:00
Henry Jameson
773c56f69d fix build 2026-03-06 17:53:40 +02:00
Henry Jameson
f0aa25059b lint 2026-03-06 17:48:56 +02:00
Henry Jameson
35a1dec8a5 lint 2026-03-06 17:31:34 +02:00
Henry Jameson
ab7fb1ba51 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-06 17:15:35 +02:00
Henry Jameson
e582b23ad7 fix test from another MR 2026-03-06 17:14:29 +02:00
Henry Jameson
b09efcb5f3 more test fixes 2026-03-06 17:10:56 +02:00
Henry Jameson
7505c94986 test fix 2026-03-06 16:53:25 +02:00
Henry Jameson
bb24349c68 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-06 16:35:13 +02:00
Henry Jameson
bcb13f3a49 tests 2026-03-06 16:35:02 +02:00
Henry Jameson
98b2e1e829 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-06 15:44:20 +02:00
Henry Jameson
97a50cba7e fix notifications 2026-03-06 15:44:13 +02:00
Henry Jameson
183bd73af0 Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-06 15:35:00 +02:00
Henry Jameson
65cb78eef8 user_highlight 2026-03-06 15:34:52 +02:00
Henry Jameson
9bb9192f0f Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-06 15:31:23 +02:00
Henry Jameson
adfe233250 oops forgot a file 2026-03-06 15:31:07 +02:00
Henry Jameson
8edee4bd9d Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-06 15:26:04 +02:00
Henry Jameson
18a3bbbd49 lint 2026-03-06 15:25:30 +02:00
Henry Jameson
e84fb2acad Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-06 15:22:05 +02:00
Henry Jameson
bc97016ea3 user highlight 2026-03-06 15:21:38 +02:00
Henry Jameson
fbfd62d98a Merge branch 'setttingssync' into shigusegubu-themes3 2026-03-06 13:33:42 +02:00
Henry Jameson
eb7cff467e more settings made local-only 2026-03-06 13:33:28 +02:00
Henry Jameson
44ae68b025 Merge remote-tracking branch 'origin/develop' into setttingssync 2026-03-06 13:14:24 +02:00
HJ
e3234226a3 Merge pull request 'fix bio being interpreted as html for editing' (#3482) from fix-bio-18 into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3482
2026-03-06 11:08:39 +00:00
Henry Jameson
8a43624f2e lint 2026-03-06 13:07:28 +02:00
Henry Jameson
98e2a60948 fix bio being interpreted as html for editing 2026-03-06 13:04:33 +02:00
Henry Jameson
a605e4c337 fix error with themes index 2026-03-06 12:56:59 +02:00
Henry Jameson
bc70905d96 fix crash when editing profile 2026-03-06 12:56:59 +02:00
Henry Jameson
42cd240347 cleanup warnings 2026-03-06 12:53:48 +02:00
HJ
9967f453e7 Merge pull request 'attachment: fix over reliance on fileType()' (#3479) from Yonle/pleroma-fe:filetypeservice-1 into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3479
2026-03-04 18:41:30 +00:00
Yonle
f40fdf1240
attachment&gallery: code: use === instead of == 2026-03-03 12:52:17 +07:00
HJ
fa53bd2ebe Merge pull request 'Readd issue and PR templates' (#3471) from phnt/pleroma-fe:forgejo-templates into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3471
2026-03-02 21:20:05 +00:00
HJ
a9172acd35 Merge pull request 'Fix HTML attribute parsing for escaped quotes' (#3476) from mkljczk/pleroma-fe:attrs-parsing-fix into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3476
2026-03-02 21:18:27 +00:00
HJ
edaff65ce6 Merge pull request 'Update git links in docs, remove /api/statusnet/config.json mention' (#3480) from mkljczk/pleroma-fe:docs-updates into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3480
2026-03-02 21:12:35 +00:00
HJ
a9a86bdbdc Merge pull request 'list_edit: setListAccounts() is never an Promise<>, So don't need to use .then()' (#3481) from Yonle/pleroma-fe:list_edit_fix-1 into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3481
2026-03-02 21:11:56 +00:00
Yonle
4faf6d8b0d
list_edit: setListAccounts() is never an Promise<>, So don't need to use .then() 2026-02-28 16:13:17 +07:00
nicole mikołajczyk
1727a7e266 Update git links in docs, remove /api/statusnet/config.json mention
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
2026-02-27 19:19:09 +01:00
Henry Jameson
e804a6aa06 Merge branch 'setttingssync' into shigusegubu-themes3 2026-02-27 17:55:43 +02:00
Henry Jameson
f1b6e8417d fix uninitialized badly-stored local config 2026-02-27 17:55:27 +02:00
Henry Jameson
0ed54185b4 Merge branch 'setttingssync' into shigusegubu-themes3 2026-02-27 17:41:41 +02:00
Henry Jameson
85ac9a21fe use merged config for dontShowUpdateNotifs 2026-02-27 17:41:22 +02:00
Yonle
68eab91db8
gallery: use Set on displaytypes instead 2026-02-27 22:14:01 +07:00
Yonle
0c3d81d0a0
user_card: fix avatar not clickable 2026-02-27 22:13:44 +07:00
Henry Jameson
5655875e16 Merge branch 'setttingssync' into shigusegubu-themes3 2026-02-27 17:13:00 +02:00
Henry Jameson
d5222ff212 typo 2026-02-27 17:12:51 +02:00
Henry Jameson
e127895568 Merge branch 'setttingssync' into shigusegubu-themes3 2026-02-27 16:21:18 +02:00
Henry Jameson
463cc2ef5c local indicator 2026-02-27 16:19:54 +02:00
Henry Jameson
449c244d11 work on local-only settings 2026-02-27 15:49:44 +02:00
Henry Jameson
24be3da17e take local config into account for mergedConfig 2026-02-27 15:08:41 +02:00
Henry Jameson
dbdec60110 fix emoji size setting not working 2026-02-27 14:46:01 +02:00
Yonle
38444f3165
attachment: fix over reliance on fileType()
Signed-off-by: Yonle <yonle@proton.me>
2026-02-27 14:09:45 +07:00
Henry Jameson
033b227edb Merge branch 'setttingssync' into shigusegubu-themes3 2026-02-25 01:32:32 +02:00
Henry Jameson
a0d4e107a9 oops 2026-02-25 01:32:25 +02:00
Henry Jameson
9d83b8ecc2 Merge branch 'setttingssync' into shigusegubu-themes3 2026-02-24 21:21:00 +02:00
Henry Jameson
ee73a0746b oops 2026-02-24 21:20:52 +02:00
Henry Jameson
f3308f463f Merge branch 'setttingssync' into shigusegubu-themes3 2026-02-24 21:17:11 +02:00
Henry Jameson
bac19670f7 some initial work on local only config 2026-02-24 21:16:38 +02:00
Henry Jameson
406df8c27a functionality to force other sessions to wipe journals 2026-02-24 21:12:08 +02:00
Henry Jameson
5b1536eb3e command to clear journals in case of a leak or development leak 2026-02-24 20:45:52 +02:00
Henry Jameson
42e5deb133 Merge branch 'setttingssync' into shigusegubu-themes3 2026-02-23 20:15:43 +02:00
Henry Jameson
ca0da60bcd even more getter fixes 2026-02-23 20:14:39 +02:00
Henry Jameson
7738ce87e6 more getters migrated 2026-02-23 19:55:43 +02:00
Henry Jameson
f77e1225b5 added config stores to global scope for easier debug 2026-02-23 19:55:36 +02:00
Henry Jameson
f70972d011 Merge branch 'setttingssync' into shigusegubu-themes3 2026-02-23 19:30:44 +02:00
Henry Jameson
30aae4a346 cleanup 2026-02-23 19:28:12 +02:00
Henry Jameson
d2c4bc3b2b fix settings modal crashing 2026-02-23 19:21:35 +02:00
Henry Jameson
70177deb49 do not fail on debug 2026-02-20 11:16:39 +02:00
Henry Jameson
dae4e5b2ac hooo boy this one was a doozy 2026-02-16 20:02:30 +02:00
Henry Jameson
209637ee8a debug 2026-02-16 19:10:24 +02:00
Henry Jameson
9d657395ff improve locale setting 2026-02-16 19:09:57 +02:00
Henry Jameson
6d6c627c3e improved visibility notice styles while i'm at it 2026-02-16 17:05:19 +02:00
Henry Jameson
20162e5358 fix notifications 2026-02-16 16:58:17 +02:00
Henry Jameson
2e53707324 pass 2 2026-02-16 16:58:11 +02:00
nicole mikołajczyk
208600bd16 Fix HTML attribute parsing for escaped quotes
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
2026-02-16 13:56:27 +01:00
Henry Jameson
6124d9c04c setOption pass 1 2026-02-16 02:24:44 +02:00
Henry Jameson
f62e0c5718 fix background image 2026-02-15 21:48:12 +02:00
Henry Jameson
3fbf21a757 notification-related settings 2026-02-15 21:33:31 +02:00
Henry Jameson
a4845bf275 fix quick view settings 2026-02-15 21:33:21 +02:00
Henry Jameson
f7c67130f5 cleanup in conversation 2026-02-15 21:32:45 +02:00
Henry Jameson
63bffe73db Merge remote-tracking branch 'origin/develop' into setttingssync 2026-02-13 15:37:19 +02:00
HJ
2ce11e56d4 Merge pull request 'misc-fixes' (#3475) from misc-fixes into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3475
2026-02-13 13:35:48 +00:00
Henry Jameson
dbdf81d8b3 undo sync-config-related changes 2026-02-13 15:34:37 +02:00
Henry Jameson
496099bb00 made it work 2026-02-13 15:21:59 +02:00
Henry Jameson
6967151275 move language switch logic entirely into plugin 2026-02-13 15:21:59 +02:00
Henry Jameson
ea020cefdb typo 2026-02-13 15:21:59 +02:00
Henry Jameson
6f3f75d9b4 i18n
This reverts commit 829018b656c52e8f8dc8bd5a9f74e83f46b71567.
2026-02-13 15:21:59 +02:00
Henry Jameson
2c4cb8a67a rollback language.js
This reverts commit 37d0a6d3f1b277f4470618a249dca77f14c8c11f.
2026-02-13 15:21:59 +02:00
Henry Jameson
0e84cffa41 wip migration change 2026-02-13 15:21:59 +02:00
Henry Jameson
d2a870ac96 better handling of SW subscription 2026-02-13 15:21:59 +02:00
Henry Jameson
76d3ec1b39 Revert "notification_utils.js"
This reverts commit 67059addfe64d3cf531a4797a3b9d38174f622e4.
2026-02-13 15:21:59 +02:00
Henry Jameson
85976a61b8 notification_utils.js 2026-02-13 15:21:59 +02:00
Henry Jameson
682ad334c1 users.js 2026-02-13 15:21:59 +02:00
Henry Jameson
2c673f439f interface.js 2026-02-13 15:21:59 +02:00
Henry Jameson
71172ec93a after_store.js 2026-02-13 15:21:54 +02:00
Henry Jameson
d3d4b899d2 less spooky errors 2026-02-13 15:21:20 +02:00
Henry Jameson
b08df84282 App.js 2026-02-13 15:21:20 +02:00
Henry Jameson
dbc9bd9c46 components 2026-02-13 15:21:20 +02:00
Henry Jameson
c9dede920e main.js 2026-02-13 15:21:20 +02:00
Henry Jameson
8fabbe9525 semi-related changes 2026-02-13 15:20:48 +02:00
Henry Jameson
0688cdab86 unrelated change 2026-02-13 15:20:48 +02:00
Henry Jameson
5dc6ca6d58 unrelated change 2026-02-13 15:20:48 +02:00
Henry Jameson
dc96e5ac53 another unrelated change 2026-02-13 15:20:48 +02:00
Henry Jameson
fc66830138 another unrelated change 2026-02-13 15:20:48 +02:00
Henry Jameson
cd66dabf94 unrelated change 2026-02-13 15:20:48 +02:00
Henry Jameson
69656e0181 minor change for paths in SW 2026-02-13 14:07:41 +02:00
Henry Jameson
29e71c8a26 sss -> sc 2026-02-13 14:06:36 +02:00
HJ
2881b31ff2 Merge pull request 'instance-migration' (#3470) from instance-migration into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3470
2026-02-12 12:42:38 +00:00
HJ
0f5c9ace4f Merge pull request 'Fix iOS scroll' (#3472) from fix-ios-scroll into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3472
2026-02-11 20:22:25 +00:00
Henry Jameson
9ae56174c9 Fix iOS scroll not working in settings modal 2026-02-11 22:21:05 +02:00
Henry Jameson
db2f48a1f9 lint 2026-02-11 20:42:31 +02:00
Phantasm
23fae89413
Change .gitea to .forgejo
This is not really needed as Forgejo can handle it right now.
2026-02-11 18:50:54 +01:00
Phantasm
3b9187694c
Forgejo: Readd feature request/suggestion issue template from Gitlab 2026-02-11 18:48:02 +01:00
Phantasm
8d06ccb04e
Forgejo: Remake Gitlab Bug issue template 2026-02-11 18:31:20 +01:00
Phantasm
89ae2e6f46
Forgejo: Add PR template from Gitlab 2026-02-11 14:19:51 +01:00
Henry Jameson
4cd6c6e9f0 Merge branch 'instance-migration' of ssh://git.pleroma.social/pleroma/pleroma-fe into instance-migration 2026-02-11 00:39:39 +02:00
Henry Jameson
fd2986ea85 fix tests 2026-02-10 21:29:48 +02:00
Henry Jameson
b2011429c6 Merge branch 'develop' of ssh://git.pleroma.social/pleroma/pleroma-fe into instance-migration 2026-02-10 19:01:51 +02:00
Henry Jameson
8dce2d75aa fix featurespanel 2026-02-10 18:54:42 +02:00
Henry Jameson
fbf01fce09 fix featurespanel 2026-02-10 18:54:07 +02:00
Henry Jameson
c87abc9eb7 avoid collision with a keyword 2026-02-05 01:28:56 +02:00
Henry Jameson
9060977790 lint 2026-02-05 01:24:53 +02:00
Henry Jameson
6750397ef7 fix custom emoji reacts now working 2026-02-05 01:19:03 +02:00
Henry Jameson
58f06dea94 fixed hideISP 2026-02-05 01:17:20 +02:00
Henry Jameson
8985094644 changelog 2026-02-05 01:02:44 +02:00
Henry Jameson
13cd50886b fix vertical tab switcher 2026-02-05 01:01:47 +02:00
Henry Jameson
c5aee1c012 Revert "undo tabswitcher-related changes"
This reverts commit 92d0916f40.
2026-02-05 01:01:10 +02:00
Henry Jameson
92d0916f40 undo tabswitcher-related changes 2026-02-05 00:59:26 +02:00
Henry Jameson
1fcc7c6ae8 fix error in console 2026-02-05 00:34:53 +02:00
Henry Jameson
c2f5840a02 move scrobblesAvailable to instance capabilities store 2026-02-05 00:34:14 +02:00
Henry Jameson
1e93e0a9c3 separate featureset into instancecapabilites store 2026-02-05 00:28:45 +02:00
Henry Jameson
848d48e404 fix bookmarks 2026-01-29 21:42:02 +02:00
Henry Jameson
20e781c71d fix ISP (again) 2026-01-29 21:32:53 +02:00
Henry Jameson
e3a441310f fix/revert privateMode to avoid conflict with keyword 2026-01-29 21:32:37 +02:00
Henry Jameson
3cdcfe19f4 minor change for consistency sake 2026-01-29 21:15:17 +02:00
Henry Jameson
74d83a996c ISP fixes 2026-01-29 21:06:42 +02:00
Henry Jameson
3e71833cab typos 2026-01-29 20:50:04 +02:00
Henry Jameson
d028a86013 always include .js for stores 2026-01-29 20:44:55 +02:00
Henry Jameson
18d8ea6b63 better lint 2026-01-29 20:40:00 +02:00
Henry Jameson
e554eeeef6 separation support 2026-01-29 20:37:48 +02:00
Henry Jameson
5e21134d9b move stickers to emoji store 2026-01-29 15:14:17 +02:00
Henry Jameson
573a980512 separation 2026-01-29 15:11:47 +02:00
Henry Jameson
912aa228d1 fix invalid option copy 2026-01-29 15:07:00 +02:00
Henry Jameson
095abb2914 pass 4 - non-obvious changes 2026-01-29 13:44:33 +02:00
Henry Jameson
617613dfb4 pass 3 - mapState updates 2026-01-29 13:06:19 +02:00
Henry Jameson
4156b1597a pass 2 + emoji store separation 2026-01-29 01:45:31 +02:00
Henry Jameson
24ce2dc0a5 first pass of migration - states and obvious replacements 2026-01-29 00:51:37 +02:00
HJ
7e67b5274d Merge branch 'fix-admin-panel-init-on-logger-console' into 'develop'
fix: Allow admin panel to init when c.tuple doesn't exists

See merge request pleroma/pleroma-fe!2200
2026-01-21 15:46:51 +00:00
Eragon
cf77127335
fix: Allow admin panel to init when c.tuple doesn't exists 2026-01-21 16:07:44 +01:00
HJ
02f952047d Merge branch 'master' into 'develop'
Mergeback master into develop

See merge request pleroma/pleroma-fe!2199
2026-01-12 20:48:59 +00:00
HJ
b9f1c33b50 Merge branch 'release-2-10-1' into 'master'
Release 2.10.1

See merge request pleroma/pleroma-fe!2197
2026-01-12 20:44:06 +00:00
Henry Jameson
98110c0596 changelog 2026-01-12 22:30:22 +02:00
HJ
127396d2af Merge branch '2-10-1-fixes' into 'develop'
Fixes for 2.10.1

See merge request pleroma/pleroma-fe!2195
2026-01-12 20:24:56 +00:00
Henry Jameson
320899c9a2 more changelogs 2026-01-12 22:09:32 +02:00
Henry Jameson
69edded5b0 fix error when clicking mute.. item 2026-01-08 22:09:18 +02:00
Henry Jameson
1e08616b1f unescape 2026-01-08 19:14:58 +02:00
Henry Jameson
dcb7ed1b8c Merge remote-tracking branch 'origin/develop' into 2-10-1-fixes 2026-01-08 19:12:32 +02:00
HJ
aa25cd04b1 Merge branch 'biome' into 'develop'
Migration to Biome

See merge request pleroma/pleroma-fe!2193
2026-01-08 16:10:23 +00:00
Henry Jameson
42930252b1 better imports organization 2026-01-08 17:42:20 +02:00
Henry Jameson
6f5eb6c442 Merge remote-tracking branch 'origin/develop' into biome 2026-01-08 17:16:14 +02:00
HJ
e13e84e26d Merge branch 'fix-e2e' into 'develop'
proper playwright version

See merge request pleroma/pleroma-fe!2196
2026-01-08 15:15:35 +00:00
Henry Jameson
3d78a34daa chlg 2026-01-08 17:11:09 +02:00
Henry Jameson
ca10ffca8d proper playwright version 2026-01-08 17:04:15 +02:00
Henry Jameson
4b87fa083e Merge remote-tracking branch 'origin/develop' into 2-10-1-fixes 2026-01-08 16:55:02 +02:00
Henry Jameson
851c100a24 Merge remote-tracking branch 'origin/develop' into biome 2026-01-08 16:48:27 +02:00
HJ
9e980dcd87 Merge branch 'e2e-testing' into 'develop'
E2E: add user smoke tests

See merge request pleroma/pleroma-fe!2194
2026-01-08 14:36:23 +00:00
Lain Soykaf
6fc8cc32ca Test: exclude Playwright e2e specs from vitest
Vitest picks up *.spec.js under test/ by default; exclude test/e2e-playwright while preserving default excludes.
2026-01-08 15:09:41 +04:00
Lain Soykaf
b8bfd6b1a9 CI: enable per-build network for e2e
Fixes Pleroma service being unable to reach the Postgres service on runners that use legacy container links.
2026-01-08 14:35:51 +04:00
Henry Jameson
0dc8305e95 don't display muted label on profile since backend doesn't work this way
improve display logic for mute/block cards
2026-01-07 23:18:09 +02:00
Henry Jameson
949aa90faa fixed being unable to mute/unmute domains from status context menu 2026-01-07 21:09:01 +02:00
Henry Jameson
03b6178d17 fix missing string 2026-01-07 21:01:34 +02:00
Lain Soykaf
d2f528bb15 CI: run e2e job without docker-in-docker
Use GitLab services for Postgres + Pleroma and run Playwright tests directly in the Playwright image (works with gitlab-ci-local too).
2026-01-07 20:11:16 +04:00
Henry Jameson
0492a8d6a0 fix actor type not setting 2026-01-07 17:06:02 +02:00
Lain Soykaf
33c7876a8a Add changelog 2026-01-07 18:07:27 +04:00
Lain Soykaf
7f54706821 E2E: add user smoke tests
- Disable captcha + open registrations in E2E Pleroma config
- Await login after signup to avoid redirect race
- Add basic register/login/post smoke specs
- Fix failure artifact copying order
2026-01-07 11:05:10 +04:00
Lain Soykaf
99d2efac6c Reduce E2E output to Playwright logs 2026-01-07 10:00:18 +04:00
Lain Soykaf
7ffec2c324 Add docker-compose Playwright E2E stack 2026-01-07 09:44:50 +04:00
Henry Jameson
ae600da287 fix tests 2026-01-06 18:38:00 +02:00
Henry Jameson
2197c030de lint 2026-01-06 18:22:32 +02:00
Henry Jameson
85c2947714 oops 2026-01-06 18:21:11 +02:00
Henry Jameson
f30537f25e changelog 2026-01-06 18:18:29 +02:00
Henry Jameson
d15642d93d Merge remote-tracking branch 'origin/develop' into biome 2026-01-06 18:17:40 +02:00
Henry Jameson
b2964612ae separate lint jobs in ci for better parallelism and display 2026-01-06 18:16:25 +02:00
Henry Jameson
e956d55219 update package json 2026-01-06 18:15:24 +02:00
Henry Jameson
04797f8bd2 manual lint 2026-01-06 18:06:23 +02:00
Henry Jameson
7f54d11834 nerf eslint to only lint vue files since vue support in biome is experimental 2026-01-06 18:04:11 +02:00
Henry Jameson
1c53ac84cc manual lint 2026-01-06 17:32:22 +02:00
Henry Jameson
1654234e32 simplified biome configuration + test globals 2026-01-06 16:35:44 +02:00
Henry Jameson
b05a501236 biome check --write 2026-01-06 16:23:17 +02:00
Henry Jameson
9262e803ec biome format --write 2026-01-06 16:22:52 +02:00
Henry Jameson
8372348148 biome lint --write 2026-01-06 16:21:06 +02:00
Henry Jameson
b9a77cc61d add biome 2026-01-06 16:21:06 +02:00
HJ
1fe112c7f3 Merge branch 'renovate/vite-6.x-lockfile' into 'develop'
Update dependency vite to v6.4.1

See merge request pleroma/pleroma-fe!2176
2026-01-06 14:04:05 +00:00
HJ
59b7da77c9 Merge branch 'renovate/eslint-plugin-vue-10.x' into 'develop'
Update dependency eslint-plugin-vue to v10.6.2

See merge request pleroma/pleroma-fe!2179
2026-01-06 14:03:01 +00:00
HJ
b33ded3a45 Merge branch 'renovate/playwright-monorepo' into 'develop'
Update dependency playwright to v1.57.0

See merge request pleroma/pleroma-fe!2180
2026-01-06 14:02:31 +00:00
HJ
46cbef8253 Merge branch 'renovate/eslint-monorepo' into 'develop'
Update dependency eslint to v9.39.2

See merge request pleroma/pleroma-fe!2181
2026-01-06 14:02:21 +00:00
HJ
308c5cd222 Merge branch 'renovate/vue-router-4.x' into 'develop'
Update dependency vue-router to v4.6.4

See merge request pleroma/pleroma-fe!2182
2026-01-06 14:02:07 +00:00
HJ
bbee089d64 Merge branch 'renovate/babel-monorepo' into 'develop'
Update babel monorepo to v7.28.5

See merge request pleroma/pleroma-fe!2185
2026-01-06 14:01:26 +00:00
HJ
fd03a5ade3 Merge branch 'mergeback-210' into 'develop'
Release 2.10.0

See merge request pleroma/pleroma-fe!2192
2025-12-23 14:18:02 +00:00
Henry Jameson
d7b1aaa616 Release 2.10.0 2025-12-23 16:16:08 +02:00
HJ
971161a441 Merge branch 'fix-poll-expand' into 'develop'
fix poll notifications not being expandable

See merge request pleroma/pleroma-fe!2191
2025-12-23 13:46:28 +00:00
Henry Jameson
29fe616c1c changelog 2025-12-23 15:36:07 +02:00
Henry Jameson
50105a8f92 fix poll notifications not being expandable 2025-12-23 15:31:54 +02:00
HJ
fb77d084d4 Merge branch 'fix-interactionlist-popover-emoji' into 'develop'
fix incorrect emoji in post interaction lists

See merge request pleroma/pleroma-fe!2190
2025-12-22 15:24:05 +00:00
Henry Jameson
6ae05ab956 changelog 2025-12-22 17:19:42 +02:00
Henry Jameson
963c55cf71 fix incorrect emoji in post interaction lists 2025-12-22 17:18:27 +02:00
HJ
fd086febfa Merge branch 'fix-lists-edit' into 'develop'
fix list title edit not working

See merge request pleroma/pleroma-fe!2189
2025-12-22 12:21:50 +00:00
Henry Jameson
202d1618c2 fix list title edit not working 2025-12-22 14:15:28 +02:00
HJ
657de70153 Merge branch 'fix-1385' into 'develop'
Fix #1385

Closes #1385

See merge request pleroma/pleroma-fe!2188
2025-12-19 17:34:25 +00:00
Henry Jameson
c2732e3f40 changelogs + forgotten changelogs from previous MR 2025-12-19 19:29:27 +02:00
Henry Jameson
c9b47a0ca9 fix #1385 2025-12-19 19:27:33 +02:00
Henry Jameson
3aae2f33d2 hide compositing tab for anon viewers 2025-12-19 19:21:45 +02:00
HJ
aa426b3d14 Merge branch 'admin-tabs-2' into 'develop'
Most of the remaining admin tabs

See merge request pleroma/pleroma-fe!2187
2025-12-19 17:21:35 +00:00
Henry Jameson
c71a36de30 cleanup console logs (changed actual proper logs to .info()) 2025-12-19 19:13:24 +02:00
Henry Jameson
7d88140bb4 more changelogs 2025-12-19 19:10:53 +02:00
Henry Jameson
db7e4a3434 actual countdown 2025-12-15 22:56:04 +02:00
Henry Jameson
4edf6b03ce better styles 2025-12-15 22:39:59 +02:00
Henry Jameson
25a9033b6b better separation + style of PWA manifest 2025-12-15 22:35:35 +02:00
Henry Jameson
d26dca92e2 manifest is grouped together as a setting 2025-12-15 22:31:54 +02:00
Pleroma Renovate Bot
f97658b6d6 Update dependency eslint to v9.39.2 2025-12-13 09:04:36 +00:00
Pleroma Renovate Bot
b9e6b9b1f0 Update dependency vue-router to v4.6.4 2025-12-12 09:04:41 +00:00
Henry Jameson
d2d8b0167c fix submit button again 2025-12-11 21:58:30 +02:00
Henry Jameson
3355f1d797 pleasing the linter gods 2025-12-11 18:29:55 +02:00
Henry Jameson
06608adec4 fix submit button 2025-12-11 18:27:12 +02:00
Henry Jameson
68aadcdd08 MANIFEST EDITING FUCK YEAH 2025-12-11 18:18:05 +02:00
Henry Jameson
0222d493f8 fix rates tab 2025-12-11 15:45:27 +02:00
Henry Jameson
bb802ed756 mailer styling controls added 2025-12-11 15:33:51 +02:00
Henry Jameson
db3bfb6fc3 color setting prettification 2025-12-11 15:14:32 +02:00
Henry Jameson
3ec21cb442 prettified pwa setting 2025-12-11 15:06:36 +02:00
Henry Jameson
5b143b2aea small improvement to label/desc display 2025-12-11 14:37:58 +02:00
Henry Jameson
96fc30a1b7 proxy setting fixes 2025-12-11 14:37:51 +02:00
Henry Jameson
edbf5f3276 developer tab fixes 2025-12-11 14:23:43 +02:00
Henry Jameson
189b092d2c fix style tab 2025-12-11 14:13:14 +02:00
Henry Jameson
bf75c7af85 fix follow button 2025-12-11 14:07:17 +02:00
Henry Jameson
952800410e emoji tab fixes 2025-12-11 14:05:46 +02:00
Henry Jameson
c085acd2dd fix emoji form & eliminate copypasta 2025-12-11 13:35:41 +02:00
Henry Jameson
e4a33bf6d7 Merge remote-tracking branch 'origin/admin-tabs-2' into admin-tabs-2 2025-12-10 20:37:16 +02:00
Henry Jameson
625954721e fixed up unitsetting 2025-12-10 20:36:09 +02:00
Henry Jameson
76dd3540e4 more visual fixes 2025-12-10 20:24:39 +02:00
Henry Jameson
494f6b471e visual stuff again 2025-12-10 19:57:34 +02:00
Henry Jameson
1642d62b82 massive visual overhaul 2025-12-10 18:34:19 +02:00
Henry Jameson
5a6f4fb466 remove most of the stuff mentioned in internal.exs in !pleroma/4403 2025-12-10 15:23:41 +02:00
Henry Jameson
13ace1d24e remove most of the stuff mentioned in internal.exs in !pleroma/4403 2025-12-10 14:42:40 +02:00
Henry Jameson
57dfbd8a53 rate limits page 2025-12-09 13:23:54 +02:00
Henry Jameson
576774540f lint 2025-12-08 22:44:31 +02:00
Henry Jameson
caa0213ac6 changelog 2025-12-08 22:31:05 +02:00
Henry Jameson
a7fa7558b3 pretty much "the rest" 2025-12-08 22:30:27 +02:00
Henry Jameson
bdb992a8e5 lint 2025-12-08 18:17:11 +02:00
Henry Jameson
bc47bef80d branding/manifest part done 2025-12-08 18:12:21 +02:00
Henry Jameson
c4f83808b0 http tab more or less done 2025-12-08 17:09:07 +02:00
Henry Jameson
5aed9a20b8 fix listsetting & http ssl options 2025-12-08 13:50:08 +02:00
Henry Jameson
e961b6e14c small visual fixes 2025-12-08 13:49:30 +02:00
Henry Jameson
df8df5a0bf fix hard-reset not showing 2025-12-07 23:14:30 +02:00
Henry Jameson
7d0f03fdba proxy setting, init http tab, broken 2025-12-07 23:11:54 +02:00
Henry Jameson
0bc5442eb9 third pass of emoji reorganizing 2025-12-07 17:42:10 +02:00
Henry Jameson
4329502422 second pass of reorganizing emoji tab 2025-12-07 17:06:41 +02:00
Henry Jameson
2fe5efc69d first pass of reorganizing emoji tab 2025-12-07 15:43:57 +02:00
Henry Jameson
ace8295c03 style updates 2025-12-07 15:43:52 +02:00
Henry Jameson
d5c75915e6 federation tab 2025-12-07 14:01:33 +02:00
Henry Jameson
d7453c09b2 improvements to listsetting, added some localization strings 2025-12-07 14:01:09 +02:00
Henry Jameson
9c043533f2 MapSetting component 2025-12-06 14:38:53 +02:00
Henry Jameson
b0bce1bf18 proper tuple support 2025-12-06 14:38:39 +02:00
Henry Jameson
48ba3892c3 additions to auth tab 2025-12-04 17:39:57 +02:00
Henry Jameson
ac751320f4 Auth tab done 2025-12-04 17:20:11 +02:00
Henry Jameson
3fca18e248 hide list add string if not in expert mode 2025-12-04 15:47:48 +02:00
Henry Jameson
56a6a25112 Queues tab done 2025-12-04 14:37:25 +02:00
Henry Jameson
cdbf3f42b8 part of job queues tab done 2025-12-03 23:05:46 +02:00
Henry Jameson
0a9a3648d6 registrations tab update 2025-12-03 21:55:53 +02:00
Henry Jameson
5042db43ab add more :instance stuff to attachments tab 2025-12-03 20:40:49 +02:00
Henry Jameson
cae19d8a9a fix links tab, boost description font size 2025-12-03 20:28:39 +02:00
Henry Jameson
311e9d255d media proxy done + misc changes 2025-12-03 20:16:33 +02:00
Henry Jameson
3a321ca756 media proxy completeness 2025-12-03 12:31:57 +02:00
Henry Jameson
42a5da93ea links tab done (finally) 2025-12-03 12:31:57 +02:00
Henry Jameson
b7a97b8603 merge multicheckbox and list inputs 2025-12-02 20:48:13 +02:00
Henry Jameson
c93f55e8f7 list and multicheckbox initial implementation 2025-12-01 23:56:49 +02:00
Henry Jameson
4a98ec9611 minor fixes and moves 2025-12-01 20:14:01 +02:00
Henry Jameson
388ecd9a5e Merge remote-tracking branch 'origin/develop' into admin-tabs-2 2025-12-01 18:52:04 +02:00
HJ
0252d39c75 Merge branch 'settings-shuffle' into 'develop'
Settings shuffle

See merge request pleroma/pleroma-fe!2186
2025-12-01 09:01:46 +00:00
Henry Jameson
235e6bd233 Media proxy tab 2025-11-28 15:44:22 +02:00
Henry Jameson
672bedaf6d fix disabled classes 2025-11-28 15:44:03 +02:00
Henry Jameson
55b5d2c5d7 fixup! uploads tab 2025-11-28 14:00:00 +02:00
Henry Jameson
b73c9ae4e8 frontends tab proper hierarchy 2025-11-28 13:59:13 +02:00
Pleroma Renovate Bot
5718483558 Update dependency eslint-plugin-vue to v10.6.2 2025-11-28 09:04:37 +00:00
Henry Jameson
114d49b6d6 i18n 2025-11-28 02:11:28 +02:00
Henry Jameson
bc92f535de uploads tab 2025-11-28 02:02:39 +02:00
Henry Jameson
db535ae057 cleanup 2025-11-28 02:02:29 +02:00
Henry Jameson
eae09226b5 registrations tab 2025-11-27 20:28:58 +02:00
Henry Jameson
16f456eaea fun UI for profile background 2025-11-27 19:33:47 +02:00
Henry Jameson
489fb17070 Merge branch 'settings-shuffle' into admin-tabs-2 2025-11-27 18:20:58 +02:00
Henry Jameson
0d04b1c8ce fix scroll again 2025-11-27 18:20:47 +02:00
Henry Jameson
b38343705c mailer tab + beginning of monitoring tab 2025-11-27 18:18:03 +02:00
Henry Jameson
3bc8800c35 i18n 2025-11-27 12:27:38 +02:00
Henry Jameson
e4c5a88913 fix fun content not hiding properly 2025-11-27 12:27:36 +02:00
Henry Jameson
ad13f2417f fix tab cutoff 2025-11-27 12:27:33 +02:00
Henry Jameson
951dc87c09 fix vertical cutoff 2025-11-26 18:43:58 +02:00
Henry Jameson
7b02072133 clean up and update icon list 2025-11-26 13:02:39 +02:00
Pleroma Renovate Bot
2af0c83389 Update dependency playwright to v1.57.0 2025-11-26 08:51:51 +00:00
Henry Jameson
04a21e4698 lint, ugh 2025-11-26 00:25:30 +02:00
Henry Jameson
41b6e80171 minor visual fix 2025-11-25 23:18:48 +02:00
Henry Jameson
b519c0f3aa lint 2025-11-25 23:18:10 +02:00
Henry Jameson
355a5955b3 made shadow control usable on mobile 2025-11-25 23:07:02 +02:00
Henry Jameson
dba63e6825 improved theme-related stuff on mobile somewhat (except shadow editor) 2025-11-25 22:56:14 +02:00
Henry Jameson
f24f164995 improve import/export table 2025-11-25 20:44:20 +02:00
Henry Jameson
e6bda9638b revise expert status for settings 2025-11-25 20:39:44 +02:00
Henry Jameson
e01753830d better two-column layout 2025-11-25 20:29:37 +02:00
Henry Jameson
1e73c7e8cb Merge remote-tracking branch 'origin/develop' into settings-shuffle 2025-11-25 20:13:40 +02:00
Henry Jameson
94864276c1 improve settings nav on mobile 2025-11-25 20:11:08 +02:00
Henry Jameson
2a3999bf42 fix suboptions on mobile 2025-11-25 20:09:47 +02:00
Henry Jameson
171bb3b7ca changelog 2025-11-25 20:04:18 +02:00
Henry Jameson
23c5a6fab7 slightly adjusted security tab. that tab needs general overhaul 2025-11-25 20:02:00 +02:00
Henry Jameson
f7fc678c23 proper suboptions hierarchy 2025-11-25 19:51:34 +02:00
Henry Jameson
38a99b2f39 fixed incorrect headers on notifications tab 2025-11-25 19:45:10 +02:00
Henry Jameson
9fae1d086b rearrange layout tab sections 2025-11-25 19:45:01 +02:00
Henry Jameson
6f9cd347df capitalization of some sections 2025-11-25 19:37:34 +02:00
Henry Jameson
402cbf6593 move tree suboptions closer to its parent 2025-11-25 19:37:19 +02:00
Henry Jameson
452a522fa4 lint 2025-11-25 19:35:52 +02:00
Henry Jameson
1fd6584374 fix mutes-and-blocks tab 2025-11-25 19:27:10 +02:00
Henry Jameson
ce04595e36 move vertical tab switcher into helpers since it's not meant to be used elsewhere 2025-11-25 19:12:18 +02:00
Henry Jameson
2d0bd043cb remove extraneous separator line 2025-11-25 18:40:05 +02:00
Henry Jameson
db73631459 somewhat better import-export tab + minor fixes 2025-11-24 20:53:01 +02:00
Henry Jameson
b3bf4fca75 better scroll + back-header on mobile 2025-11-24 20:26:13 +02:00
Henry Jameson
3716797e04 removed extraneous header from profile tab 2025-11-24 20:25:59 +02:00
Henry Jameson
7c57be22e4 working prototype 2025-11-24 20:05:38 +02:00
Henry Jameson
50ede338e7 further separation of tabs 2025-11-24 17:06:55 +02:00
Henry Jameson
b0f725671a improve scope selector 2025-11-24 16:19:23 +02:00
Henry Jameson
fba7d15a2c improve font control 2025-11-24 16:06:28 +02:00
Henry Jameson
9572b9704c fix logic not working on other tabs 2025-11-20 22:09:33 +02:00
Henry Jameson
63535b1494 less spam of events, fix nesting headers (again) 2025-11-20 21:54:52 +02:00
Henry Jameson
8b8af2889b better impl of header hiding? 2025-11-20 21:17:44 +02:00
Henry Jameson
3f4ad34377 crappy implementation of hiding extra header 2025-11-20 20:52:18 +02:00
Henry Jameson
7d1799e929 fix order of expansion, WIP hiding headers 2025-11-20 20:10:20 +02:00
Henry Jameson
8e6800fd1e level 2 collapse 2025-11-20 18:29:19 +02:00
Henry Jameson
e6f025bf6e after 9000 hours it finally works 2025-11-20 12:12:14 +02:00
Henry Jameson
5958c32acf fix turd that kept breaking UI on hot reload 2025-11-20 08:07:51 +02:00
Henry Jameson
a96f533777 vertical tab switcher initial implementation 2025-11-20 02:07:00 +02:00
Henry Jameson
a3a35e76a8 remove side-tabs from tab-switcher, splitting functionality into separate component 2025-11-20 01:24:38 +02:00
Pleroma Renovate Bot
1683d98699 Update babel monorepo to v7.28.5 2025-10-29 09:05:56 +00:00
Pleroma Renovate Bot
3601012cc2 Update dependency vite to v6.4.1 2025-10-21 09:06:08 +00:00
HJ
848f2c7ddb Merge branch 'fix-fix-broken-convos' into 'develop'
Fix fix broken convos

See merge request pleroma/pleroma-fe!2178
2025-10-15 14:01:29 +00:00
Henry Jameson
043c02ff40 simpler fix 2025-10-15 17:00:47 +03:00
Henry Jameson
661ab34889 revert vue file 2025-10-15 16:58:08 +03:00
Henry Jameson
debd3a3e7b initial nested settings impl 2025-10-15 16:53:16 +03:00
HJ
2f8ea4f3b3 Merge branch 'renovate/major-font-awesome' into 'develop'
Update Font Awesome to v7 (major)

See merge request pleroma/pleroma-fe!2162
2025-10-15 12:04:15 +00:00
HJ
30f9b84f08 Merge branch 'tusooa/everything-instance-default' into 'develop'
Make every configuration option default-overridable by instance admins

See merge request pleroma/pleroma-fe!2175
2025-10-14 22:25:29 +00:00
tusooa
dd910ff8a8
Make every configuration option default-overridable by instance admins 2025-10-14 18:17:29 -04:00
HJ
90f7dee343 Merge branch 'renovate/semver-7.x' into 'develop'
Update dependency semver to v7.7.3

See merge request pleroma/pleroma-fe!2169
2025-10-14 13:14:57 +00:00
HJ
f616c583f2 Merge branch 'renovate/eslint-plugin-vue-10.x' into 'develop'
Update dependency eslint-plugin-vue to v10.5.0

See merge request pleroma/pleroma-fe!2173
2025-10-14 13:14:37 +00:00
HJ
f0cf1da920 Merge branch 'renovate/globals-16.x-lockfile' into 'develop'
Update dependency globals to v16.4.0

See merge request pleroma/pleroma-fe!2174
2025-10-14 13:14:25 +00:00
Pleroma Renovate Bot
004bdd6b79 Update dependency globals to v16.4.0 2025-10-14 08:52:39 +00:00
Pleroma Renovate Bot
3286725510 Update dependency eslint-plugin-vue to v10.5.0 2025-10-14 08:52:04 +00:00
HJ
ce048667f5 Merge branch 'renovate/phoenix-1.x' into 'develop'
Update dependency phoenix to v1.8.1

See merge request pleroma/pleroma-fe!2168
2025-10-14 07:52:52 +00:00
HJ
bc23d46615 Merge branch 'renovate/chalk-5.x' into 'develop'
Update dependency chalk to v5.6.2

See merge request pleroma/pleroma-fe!2167
2025-10-14 07:52:30 +00:00
HJ
aa7911665b Merge branch 'renovate/vite-6.x-lockfile' into 'develop'
Update dependency vite to v6.3.6

See merge request pleroma/pleroma-fe!2170
2025-10-14 07:51:30 +00:00
HJ
7ba0b1d622 Merge branch 'renovate/vue-i18n-11.x-lockfile' into 'develop'
Update dependency vue-i18n to v11.1.12

See merge request pleroma/pleroma-fe!2171
2025-10-14 07:50:46 +00:00
HJ
be0bf5e119 Merge branch 'renovate/eslint-plugin-n-17.x' into 'develop'
Update dependency eslint-plugin-n to v17.23.1

See merge request pleroma/pleroma-fe!2172
2025-10-14 07:50:16 +00:00
Pleroma Renovate Bot
e9f1b29e1c Update dependency eslint-plugin-n to v17.23.1 2025-10-13 09:05:18 +00:00
Pleroma Renovate Bot
b4cd8d8fab Update dependency vue-i18n to v11.1.12 2025-10-12 09:07:07 +00:00
Pleroma Renovate Bot
d5723bbf34 Update dependency vite to v6.3.6 2025-10-12 09:06:37 +00:00
Pleroma Renovate Bot
d67967dc78 Update Font Awesome to v7 2025-10-11 09:06:29 +00:00
Pleroma Renovate Bot
59b65891af Update dependency semver to v7.7.3 2025-10-11 09:05:50 +00:00
Pleroma Renovate Bot
ff9127973e Update dependency phoenix to v1.8.1 2025-10-11 09:05:41 +00:00
Pleroma Renovate Bot
8cd50f6df3 Update dependency chalk to v5.6.2 2025-10-11 09:05:24 +00:00
HJ
b3b71fcf18 Merge branch 'renovate/chai-5.x' into 'develop'
Update dependency chai to v5.3.3

See merge request pleroma/pleroma-fe!2152
2025-10-08 12:09:05 +00:00
HJ
ce0921e208 Merge branch 'renovate/sinon-chai-4.x' into 'develop'
Update dependency sinon-chai to v4.0.1

See merge request pleroma/pleroma-fe!2153
2025-10-08 12:08:36 +00:00
HJ
e40e56a988 Merge branch 'renovate/eslint-monorepo' into 'develop'
Update dependency eslint to v9.37.0

See merge request pleroma/pleroma-fe!2155
2025-10-08 12:08:31 +00:00
HJ
41bcc4c93e Merge branch 'renovate/babel-monorepo' into 'develop'
Update babel monorepo to v7.28.4

See merge request pleroma/pleroma-fe!2165
2025-10-08 12:07:33 +00:00
HJ
d2266303d1 Merge branch 'renovate/font-awesome' into 'develop'
Update dependency @fortawesome/vue-fontawesome to v3.1.2

See merge request pleroma/pleroma-fe!2166
2025-10-08 12:07:17 +00:00
Pleroma Renovate Bot
6c965789d8 Update dependency sinon-chai to v4.0.1 2025-10-07 08:53:01 +00:00
Pleroma Renovate Bot
3196eb79de Update dependency @fortawesome/vue-fontawesome to v3.1.2 2025-10-07 08:52:35 +00:00
Pleroma Renovate Bot
f3e897588b Update babel monorepo to v7.28.4 2025-10-07 08:52:15 +00:00
HJ
abe1b9c565 Merge branch 'renovate/stylelint-16.x' into 'develop'
Update dependency stylelint to v16.25.0

See merge request pleroma/pleroma-fe!2160
2025-10-07 07:36:42 +00:00
HJ
e0b76eeda6 Merge branch 'renovate/vue-monorepo' into 'develop'
Update vue monorepo to v3.5.22

See merge request pleroma/pleroma-fe!2154
2025-10-07 07:36:30 +00:00
HJ
811eb3d361 Merge branch 'renovate/sass-1.x' into 'develop'
Update dependency sass to v1.93.2

See merge request pleroma/pleroma-fe!2159
2025-10-07 07:36:01 +00:00
HJ
320a53835a Merge branch 'fix-broken-conversation' into 'develop'
Fix broken conversation

See merge request pleroma/pleroma-fe!2163
2025-10-07 06:48:19 +00:00
Pleroma User
00ba6b7c5d Fix broken conversation 2025-10-07 06:48:17 +00:00
Pleroma Renovate Bot
7a919e7c76 Update dependency stylelint to v16.25.0 2025-10-04 08:52:01 +00:00
Pleroma Renovate Bot
326d4976a3 Update dependency eslint to v9.37.0 2025-10-04 08:51:40 +00:00
Pleroma Renovate Bot
df05a7a8a7 Update dependency sass to v1.93.2 2025-09-25 09:08:13 +00:00
Pleroma Renovate Bot
fb828b07f9 Update vue monorepo to v3.5.22 2025-09-25 09:07:21 +00:00
HJ
ed10af15e2 Merge branch 'master' into 'develop'
Mergeback to develop

See merge request pleroma/pleroma-fe!2158
2025-09-18 15:33:22 +00:00
HJ
6341a48d05 Merge branch 'release/2.9.x' into 'master'
2.9.3 into master

See merge request pleroma/pleroma-fe!2157
2025-09-18 15:32:55 +00:00
Henry Jameson
53afb86da1 2.9.3 2025-09-18 18:30:46 +03:00
HJ
aa7d5c0efd Merge branch 'fixes-291' into 'develop'
fix error when updating profile

See merge request pleroma/pleroma-fe!2156
2025-09-18 15:28:28 +00:00
Henry Jameson
8353db33ad fix error when updating profile 2025-09-15 19:44:23 +03:00
HJ
40eb565f2a Merge branch 'master' into 'develop'
Mergeback

See merge request pleroma/pleroma-fe!2249
2025-08-31 13:06:12 +00:00
HJ
0ecbae9675 Merge branch 'release/2.9.x' into 'master'
Release/2.9.x

See merge request pleroma/pleroma-fe!2248
2025-08-31 13:02:47 +00:00
Henry Jameson
6e1c65a574 more changelog updates 2025-08-31 15:56:15 +03:00
Henry Jameson
b7c75dcba2 update 2025-08-31 15:26:30 +03:00
Henry Jameson
205e03ea15 template 2025-08-31 15:08:01 +03:00
Henry Jameson
f9848daf48 changelog + bump 2025-08-31 15:02:12 +03:00
HJ
30917ff017 Merge branch 'block-fix' into 'develop'
oops + release prep

See merge request pleroma/pleroma-fe!2247
2025-08-29 11:26:22 +00:00
Henry Jameson
f9570b132f frontend-specifics 2025-08-29 14:25:02 +03:00
Henry Jameson
473133aa02 prepare for release 2025-08-29 14:24:10 +03:00
Henry Jameson
a62ffbfbab oops
This partially reverts commit e3bfbcf0d2.
2025-08-29 14:23:16 +03:00
HJ
2551947ee6 Merge branch 'block-fix' into 'develop'
fix being unable to (un)block users in some cases

See merge request pleroma/pleroma-fe!2246
2025-08-29 10:57:43 +00:00
Henry Jameson
e3bfbcf0d2 fix being unable to (un)block users in some cases 2025-08-29 13:54:03 +03:00
HJ
683b2d7fea Merge branch 'final-fixes-frfr' into 'develop'
Final fixes frfr

See merge request pleroma/pleroma-fe!2245
2025-08-28 15:58:13 +00:00
Henry Jameson
0fe823aeec changelog + cleanup 2025-08-28 18:51:18 +03:00
Henry Jameson
b46c8358ae fix focus+hover for toggled buttons 2025-08-28 18:50:33 +03:00
Henry Jameson
65b40f8f72 don't adopt styelsheets prematurely 2025-08-28 15:37:53 +03:00
Henry Jameson
6b4057c6ab fix buttons being cut-off on mobile popovers 2025-08-28 15:16:31 +03:00
HJ
2a69abf374 Merge branch 'final-fixes' into 'develop'
Final fixes

Closes #1386

See merge request pleroma/pleroma-fe!2241
2025-08-27 20:25:15 +00:00
Henry Jameson
0560110868 changelog 2025-08-27 23:16:34 +03:00
Henry Jameson
3df779f02f reduce drop-shadow on usercard to avoid blinking 2025-08-27 22:47:38 +03:00
Henry Jameson
0b78c64928 fix notification dot 2025-08-27 22:46:00 +03:00
Pleroma Renovate Bot
9b8bccd27d Update dependency chai to v5.3.3 2025-08-23 08:52:09 +00:00
HJ
2854b012c9 Merge branch 'renovate/playwright-monorepo' into 'develop'
Update dependency playwright to v1.55.0

See merge request pleroma/pleroma-fe!2232
2025-08-22 12:07:24 +00:00
HJ
82b560ccf2 Merge branch 'renovate/vue-eslint-parser-10.x' into 'develop'
Update dependency vue-eslint-parser to v10.2.0

See merge request pleroma/pleroma-fe!2185
2025-08-22 12:07:11 +00:00
HJ
288ad9dd06 Merge branch 'renovate/pinia-3.x-lockfile' into 'develop'
Update dependency pinia to v3.0.3

See merge request pleroma/pleroma-fe!2182
2025-08-22 11:51:04 +00:00
HJ
fdaaf333c5 Merge branch 'renovate/chai-5.x' into 'develop'
Update dependency chai to v5.3.2

See merge request pleroma/pleroma-fe!2231
2025-08-22 11:50:52 +00:00
HJ
2601c09660 Merge branch 'renovate/vitejs-plugin-vue-jsx-4.x-lockfile' into 'develop'
Update dependency @vitejs/plugin-vue-jsx to v4.2.0

See merge request pleroma/pleroma-fe!2221
2025-08-22 11:50:30 +00:00
Pleroma Renovate Bot
38fc7f784e Update dependency playwright to v1.55.0 2025-08-22 09:06:07 +00:00
Pleroma Renovate Bot
4c4144f1eb Update dependency @vitejs/plugin-vue-jsx to v4.2.0 2025-08-22 09:05:56 +00:00
Pleroma Renovate Bot
088be118c3 Update dependency chai to v5.3.2 2025-08-22 09:05:33 +00:00
HJ
58ce121010 Merge branch 'renovate/ruffle-rs-ruffle-0.x' into 'develop'
Update dependency @ruffle-rs/ruffle to v0.1.0-nightly.2025.6.22

See merge request pleroma/pleroma-fe!1981
2025-08-22 08:00:19 +00:00
HJ
7e04269b7e Merge branch 'renovate/eslint-plugin-import-2.x' into 'develop'
Update dependency eslint-plugin-import to v2.32.0

See merge request pleroma/pleroma-fe!2224
2025-08-22 07:38:18 +00:00
HJ
3bacacd214 Merge branch 'renovate/chai-5.x' into 'develop'
Update dependency chai to v5.3.1

See merge request pleroma/pleroma-fe!2227
2025-08-22 07:37:15 +00:00
HJ
b3f5edb913 Merge branch 'renovate/globals-16.x-lockfile' into 'develop'
Update dependency globals to v16.3.0

See merge request pleroma/pleroma-fe!2228
2025-08-22 07:37:07 +00:00
HJ
785c8d518a Merge branch 'renovate/vue-monorepo' into 'develop'
Update vue monorepo to v3.5.19

See merge request pleroma/pleroma-fe!2229
2025-08-22 07:36:57 +00:00
HJ
a5af76603f Merge branch 'renovate/phoenix-1.x' into 'develop'
Update dependency phoenix to v1.8.0

See merge request pleroma/pleroma-fe!2230
2025-08-22 07:36:50 +00:00
HJ
589737dced Merge branch 'misc-style-fixes-or-changes' into 'develop'
Misc style fixes or changes

Closes #1378

See merge request pleroma/pleroma-fe!2219
2025-08-21 14:58:26 +00:00
Henry Jameson
542db846a6 px -> em for reprööter bar 2025-08-21 17:40:42 +03:00
Henry Jameson
798178a86f lint 2025-08-21 17:36:43 +03:00
Henry Jameson
8f320faa2d lint 2025-08-21 17:33:57 +03:00
Henry Jameson
528cc9f388 fix emoji vertical alignment when using stealer 2025-08-21 17:28:18 +03:00
Henry Jameson
17c04d4d83 Merge remote-tracking branch 'origin/develop' into misc-style-fixes-or-changes
+ better comment
2025-08-21 17:15:50 +03:00
Pleroma Renovate Bot
0b9547b289 Update dependency phoenix to v1.8.0 2025-08-21 09:06:13 +00:00
Pleroma Renovate Bot
0ad18d3e5e Update vue monorepo to v3.5.19 2025-08-21 09:05:57 +00:00
HJ
0424992a35 Merge branch 'copy-emoji-one' into 'develop'
Allow copying a single emoji from a pack, copying emojis from posts, and show a popover on emoji click

See merge request pleroma/pleroma-fe!2207
2025-08-21 06:56:13 +00:00
Pleroma Renovate Bot
4ce9a011da Update dependency globals to v16.3.0 2025-08-20 09:06:26 +00:00
Pleroma Renovate Bot
91ab21b37b Update dependency eslint-plugin-import to v2.32.0 2025-08-20 09:06:11 +00:00
Pleroma Renovate Bot
b3ba505e51 Update dependency chai to v5.3.1 2025-08-20 09:05:57 +00:00
HJ
7678b7f597 Merge branch 'renovate/eslint-plugin-vue-10.x' into 'develop'
Update dependency eslint-plugin-vue to v10.4.0

See merge request pleroma/pleroma-fe!2226
2025-08-20 03:50:18 +00:00
HJ
d148f36474 Merge branch 'renovate/vue-babel-plugin-jsx-1.x' into 'develop'
Update dependency @vue/babel-plugin-jsx to v1.5.0

See merge request pleroma/pleroma-fe!2222
2025-08-20 03:49:50 +00:00
HJ
f252585b7f Merge branch 'renovate/chalk-5.x' into 'develop'
Update dependency chalk to v5.6.0

See merge request pleroma/pleroma-fe!2223
2025-08-20 03:49:39 +00:00
HJ
4aa27c5be0 Merge branch 'renovate/eslint-plugin-n-17.x' into 'develop'
Update dependency eslint-plugin-n to v17.21.3

See merge request pleroma/pleroma-fe!2225
2025-08-20 03:49:10 +00:00
Henry Jameson
bebb3fcfa6 better click handler 2025-08-19 17:28:19 +03:00
Henry Jameson
8780e0191e proper collapse 2025-08-19 16:37:50 +03:00
Henry Jameson
26a2232e18 fix incorrect collapse state 2025-08-19 16:21:44 +03:00
Henry Jameson
0d6453baec fix emoji reactions notifs being non-expandable 2025-08-19 16:21:30 +03:00
Pleroma Renovate Bot
80b0117ba2 Update dependency eslint-plugin-vue to v10.4.0 2025-08-18 08:53:12 +00:00
Pleroma Renovate Bot
7b3ef62b96 Update dependency eslint-plugin-n to v17.21.3 2025-08-17 08:53:36 +00:00
Pleroma Renovate Bot
15239cf92d Update dependency chalk to v5.6.0 2025-08-17 08:52:59 +00:00
Pleroma Renovate Bot
3ba9c01e15 Update dependency @vue/babel-plugin-jsx to v1.5.0 2025-08-16 09:06:10 +00:00
Ekaterina Vaartis
7ff72e5ae2 Correct shortcode attr, sort names instead of emoji, success info 2025-08-15 22:14:06 +03:00
HJ
48651518df Merge branch 'renovate/babel-monorepo' into 'develop'
Update babel monorepo to v7.28.3

See merge request pleroma/pleroma-fe!2220
2025-08-15 09:38:24 +00:00
Pleroma Renovate Bot
202f5d87b3 Update babel monorepo to v7.28.3 2025-08-15 09:04:59 +00:00
Henry Jameson
7b2f3b648a improve visibilty of attachment buttons 2025-08-15 10:03:34 +03:00
Henry Jameson
16ada7ec6c add padding to compact status' poll icon 2025-08-15 09:49:28 +03:00
Henry Jameson
028556f8ab remove icons from "show more" since we show attachments anyway 2025-08-15 09:48:28 +03:00
Henry Jameson
9ed2fd3c4b default to 1rem instead 14px 2025-08-15 09:41:08 +03:00
Henry Jameson
c47bfe53ff aria 2025-08-15 09:38:36 +03:00
Henry Jameson
35fea49bd0 reduce bookmark action button width 2025-08-15 09:29:08 +03:00
Henry Jameson
866b416dbe changelog update 2025-08-15 09:14:26 +03:00
Henry Jameson
1c0aa025a3 fix shadows on profile and add spacing between user handle and nickname 2025-08-15 09:13:53 +03:00
Henry Jameson
bfd1809a34 move expand click handler to StatusContent 2025-08-15 09:10:26 +03:00
HJ
96d886d213 Merge branch 'renovate/msw-2.x' into 'develop'
Update dependency msw to v2.10.5

See merge request pleroma/pleroma-fe!2217
2025-08-14 20:32:45 +00:00
HJ
1a0f4c8c34 Merge branch 'renovate/font-awesome' into 'develop'
Update dependency @fortawesome/vue-fontawesome to v3.1.1

See merge request pleroma/pleroma-fe!2218
2025-08-14 20:29:12 +00:00
Henry Jameson
9b58b8c290 better paddings 2025-08-14 23:15:06 +03:00
Henry Jameson
9f481052d0 restore border for visibility notice 2025-08-14 21:03:25 +03:00
Henry Jameson
69726895b1 changelog 2025-08-14 18:19:57 +03:00
Henry Jameson
e560b1d6c7 fix plasma browser? 2025-08-14 18:15:03 +03:00
Henry Jameson
c65c0afce3 fix emoji input 2025-08-14 18:06:08 +03:00
Henry Jameson
c7ad009496 apply shadow to everything, not just icons 2025-08-14 17:58:04 +03:00
Henry Jameson
f0250ffc52 fix edit banner button 2025-08-14 17:57:38 +03:00
Henry Jameson
267692f56a improve shadows on user-card 2025-08-14 17:49:59 +03:00
Henry Jameson
49a98a6fe2 bunch of px -> em, small fixes 2025-08-14 17:27:13 +03:00
Henry Jameson
4c17458e58 fix favicons 2025-08-14 17:08:52 +03:00
Henry Jameson
6399de114c fix right icons alignment 2025-08-14 16:56:59 +03:00
Henry Jameson
bfb9f6c5e2 scale avatars 2025-08-14 16:50:00 +03:00
Henry Jameson
e076403fcb em 2025-08-14 16:48:47 +03:00
Henry Jameson
97adff267f delay splash screen, only show if loading takes too long 2025-08-14 16:45:24 +03:00
Henry Jameson
5bb0424397 improve visibilty of user profile text 2025-08-14 16:17:34 +03:00
Henry Jameson
00c3719618 Merge remote-tracking branch 'origin/develop' into misc-style-fixes-or-changes 2025-08-14 16:14:41 +03:00
Henry Jameson
aa5688a43b unstyle the content type selector 2025-08-14 16:07:27 +03:00
Henry Jameson
f35633c855 remove redundand emoji button 2025-08-14 13:07:03 +03:00
Henry Jameson
6e479d246b visually combine subject and content into one input 2025-08-14 12:59:51 +03:00
Henry Jameson
0b9b7a51a6 collapse notifications and also allow expand on click anywhere 2025-08-14 12:53:19 +03:00
Henry Jameson
a6c844e522 make favs etc strip newlines so status is more visible 2025-08-14 12:31:12 +03:00
Henry Jameson
2869ab787d fix #1378 2025-08-14 12:24:09 +03:00
HJ
22a14d8985 Merge branch 'themes-3-1' into 'develop'
Themes 3.1: You can (not) overthink

See merge request pleroma/pleroma-fe!2209
2025-08-14 09:01:15 +00:00
HJ
ce5638d223 Merge branch 'renovate/vue-i18n-11.x-lockfile' into 'develop'
Update dependency vue-i18n to v11.1.11

See merge request pleroma/pleroma-fe!2215
2025-08-14 08:58:03 +00:00
Pleroma Renovate Bot
2a2ffff992 Update dependency @fortawesome/vue-fontawesome to v3.1.1 2025-08-14 08:53:12 +00:00
Pleroma Renovate Bot
0a4c867519 Update dependency msw to v2.10.5 2025-08-14 08:52:53 +00:00
HJ
4886b34c2f Merge branch 'renovate/vue-monorepo' into 'develop'
Update vue monorepo to v3.5.18

See merge request pleroma/pleroma-fe!2216
2025-08-14 08:52:24 +00:00
HJ
f6cdf06ba1 Merge branch 'renovate/vite-plugin-eslint2-5.x-lockfile' into 'develop'
Update dependency vite-plugin-eslint2 to v5.0.4

See merge request pleroma/pleroma-fe!2213
2025-08-14 08:52:03 +00:00
HJ
adbeb9d0dd Merge branch 'renovate/vite-plugin-stylelint-6.x-lockfile' into 'develop'
Update dependency vite-plugin-stylelint to v6.0.2

See merge request pleroma/pleroma-fe!2214
2025-08-14 08:51:54 +00:00
Henry Jameson
8958f70a48 fix hacks not applying immideately 2025-08-14 11:42:56 +03:00
Henry Jameson
f7fca3a1ba transparent attachment buttons 2025-08-14 10:43:37 +03:00
Henry Jameson
918fbecf3e fix button pressed state 2025-08-14 10:39:27 +03:00
Pleroma Renovate Bot
25d54cbeea Update vue monorepo to v3.5.18 2025-08-13 09:05:13 +00:00
Pleroma Renovate Bot
be3170b50b Update dependency vue-i18n to v11.1.11 2025-08-13 09:05:05 +00:00
Pleroma Renovate Bot
6f29f9e735 Update dependency vite-plugin-stylelint to v6.0.2 2025-08-12 09:06:05 +00:00
Pleroma Renovate Bot
1e4544b2ca Update dependency vite-plugin-eslint2 to v5.0.4 2025-08-12 09:05:48 +00:00
HJ
286bb6aa97 Merge branch 'renovate/msw-2.x' into 'develop'
Update dependency msw to v2.10.4

See merge request pleroma/pleroma-fe!2212
2025-08-11 19:42:42 +00:00
HJ
9d16aa5ace Merge branch 'renovate/cropperjs-2.x' into 'develop'
Update dependency cropperjs to v2.0.1

See merge request pleroma/pleroma-fe!2211
2025-08-11 19:42:28 +00:00
Pleroma Renovate Bot
72cd6b40ea Update dependency vue-eslint-parser to v10.2.0 2025-08-11 09:16:38 +00:00
Pleroma Renovate Bot
e3a7d3dca8 Update dependency msw to v2.10.4 2025-08-11 09:16:20 +00:00
Pleroma Renovate Bot
4ba1e55f05 Update dependency cropperjs to v2.0.1 2025-08-11 09:16:05 +00:00
Ekaterina Vaartis
24c0e0e497 Formatting fixes 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
bf4dbaf077 Fix rich content test 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
a6a2dbd2f7 Fix CSS lint complaint 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
c3872147c0 Correct component name 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
e04d7d2c97 Check if emoji is already a local one 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
0ccff2019f Changelogs 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
04c180e0d9 Allow copying emoji from posts 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
ec635426c3 Allow uploading single emojis from URL 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
a0159f1e18 Allow copying just one emoji from another pack into a local one 2025-08-11 07:55:48 +00:00
HJ
d62393bf6b Merge branch 'emoji-pack-upload' into 'develop'
Add a way to upload new packs from URL/ZIP file

See merge request pleroma/pleroma-fe!1999
2025-08-10 20:37:46 +00:00
HJ
1d3b271e7c Apply 1 suggestion(s) to 1 file(s) 2025-08-10 20:30:24 +00:00
Henry Jameson
6e5da62233 changelog 2025-08-10 23:25:42 +03:00
HJ
b80035cbb0 Apply 1 suggestion(s) to 1 file(s) 2025-08-10 20:23:09 +00:00
HJ
047dda5525 Apply 2 suggestion(s) to 1 file(s) 2025-08-10 20:22:39 +00:00
HJ
e82de98892 Merge branch 'renovate/eslint-monorepo' into 'develop'
Update dependency eslint to v9.33.0

See merge request pleroma/pleroma-fe!2172
2025-08-10 20:20:37 +00:00
HJ
cf4aa692e3 Merge branch 'renovate/babel-monorepo' into 'develop'
Update babel monorepo

See merge request pleroma/pleroma-fe!2174
2025-08-10 20:20:29 +00:00
HJ
8f16da2f6f Merge branch 'renovate/nightwatch-3.x' into 'develop'
Update dependency nightwatch to v3.12.2

See merge request pleroma/pleroma-fe!2181
2025-08-10 20:19:51 +00:00
HJ
1f53c8bb07 Merge branch 'renovate/postcss-8.x' into 'develop'
Update dependency postcss to v8.5.6

See merge request pleroma/pleroma-fe!2183
2025-08-10 20:19:33 +00:00
HJ
2830b55d41 Merge branch 'renovate/stylelint-config-recommended-vue-1.x-lockfile' into 'develop'
Update dependency stylelint-config-recommended-vue to v1.6.1

See merge request pleroma/pleroma-fe!2184
2025-08-10 20:19:20 +00:00
HJ
f86cc5d8b5 Merge branch 'renovate/chai-5.x' into 'develop'
Update dependency chai to v5.2.1

See merge request pleroma/pleroma-fe!2208
2025-08-10 20:19:00 +00:00
Henry Jameson
8b8975adb2 even even more weight loss 2025-08-10 23:16:29 +03:00
Henry Jameson
2aabaeb5c6 notification fixes 2025-08-10 23:06:33 +03:00
Henry Jameson
67f606a3b0 user-card fixes 2025-08-10 23:03:50 +03:00
Henry Jameson
9440d35266 even more weight loss 2025-08-10 22:49:09 +03:00
Henry Jameson
6341747ec9 more weight reduction 2025-08-10 22:38:31 +03:00
Henry Jameson
9ec2ff409d panel-header avatar is now hard-coded in user-card 2025-08-10 21:35:59 +03:00
Henry Jameson
4ff257be57 only put heavy components if truly needed 2025-08-10 21:35:19 +03:00
HJ
5e77a0a23d Merge branch 'profile-edit' into 'develop'
Profile edit overhaul

See merge request pleroma/pleroma-fe!2205
2025-08-10 16:41:28 +00:00
Henry Jameson
370a7f8291 lint 2025-08-10 19:37:17 +03:00
Henry Jameson
0f51550802 lint 2025-08-10 17:55:54 +03:00
Henry Jameson
700e096dd4 fix sss 2025-08-10 17:42:37 +03:00
Pleroma Renovate Bot
d879b6f6eb Update dependency eslint to v9.33.0 2025-08-09 09:04:58 +00:00
Pleroma Renovate Bot
8d141cbeab Update dependency chai to v5.2.1 2025-08-08 08:53:03 +00:00
Henry Jameson
81bb4f133b fix drawer styling 2025-08-07 17:28:57 +03:00
Henry Jameson
a4de299c58 improve styles for user_panel 2025-08-07 17:25:04 +03:00
Henry Jameson
11e6349e8d don't use newAvatar/Banner in non-editables 2025-08-07 17:10:56 +03:00
Henry Jameson
409816748e use img instead of background-image since img works with large files but bg-image does not 2025-08-07 17:10:52 +03:00
Henry Jameson
cfb4868c55 change wording because we only upload on "save" 2025-08-07 16:00:11 +03:00
Henry Jameson
f2783260f1 considerably improve styling of upload modal on mobile 2025-08-07 15:44:09 +03:00
Henry Jameson
7fd46f2d6c Revert "fix visibility-notice"
This reverts commit 202bfbad02.
2025-08-07 15:22:03 +03:00
Henry Jameson
f27099a4b3 fix small screens avatar upload 2025-08-07 15:20:33 +03:00
Ekaterina Vaartis
18110d6821 Add a way to upload new packs from URL/ZIP file 2025-08-06 21:52:33 +03:00
Henry Jameson
202bfbad02 fix visibility-notice 2025-08-05 18:45:42 +03:00
Henry Jameson
7b643b5486 lint 2025-08-05 18:33:38 +03:00
Henry Jameson
1c4f19e56f fix 2025-08-05 18:33:09 +03:00
Henry Jameson
d66dd17f7b vuex cleanup 2025-08-05 18:21:49 +03:00
Henry Jameson
76e67a08c6 appearance card fix 2025-08-05 18:20:25 +03:00
Henry Jameson
ebe727b378 user-card cleanup 2025-08-05 18:16:09 +03:00
Henry Jameson
99886ac28c user avatar cleanup 2025-08-05 17:17:01 +03:00
Henry Jameson
d4c0ccf659 profile tab cleanup 2025-08-05 17:09:07 +03:00
Henry Jameson
6e44a3afa9 language switcher cleanup 2025-08-05 17:04:06 +03:00
Henry Jameson
ee01395071 image cropper cleanup 2025-08-05 17:00:21 +03:00
Henry Jameson
fa67b2330f fix 2025-08-05 15:20:19 +03:00
Henry Jameson
7e9fd4d1dd lint 2025-08-05 15:17:07 +03:00
Henry Jameson
bc2964c327 dirty state 2025-08-05 14:58:53 +03:00
Henry Jameson
3311c676ad better layout & wording 2025-08-05 14:43:37 +03:00
Henry Jameson
184f1cdc24 remove useless stuff 2025-08-05 14:39:15 +03:00
Henry Jameson
8268d0d349 better headers 2025-08-05 14:18:09 +03:00
Henry Jameson
a4e6a72ca2 fix banner overflowing 2025-08-05 14:10:07 +03:00
Henry Jameson
49422705a8 fixed interaction button size 2025-08-05 13:59:56 +03:00
Henry Jameson
d34ce95d17 scale bottom line too 2025-08-05 00:27:20 +03:00
Henry Jameson
fc90a92ecf better interaction buttons. can't get them quite right - limitation of html? 2025-08-05 00:24:00 +03:00
Henry Jameson
ea3e054c21 changelog 2025-08-04 23:16:27 +03:00
Henry Jameson
d89f564b5e proper resets, better upload dialog 2025-08-04 23:15:26 +03:00
Henry Jameson
a4802030be visible role preview 2025-08-04 14:25:14 +03:00
Henry Jameson
86f8f46b95 i have no idea how this worked before, but now it does fr 2025-08-04 14:22:20 +03:00
Henry Jameson
41267a5d43 small fixes 2025-08-04 14:09:25 +03:00
Henry Jameson
c8fa72c791 move background to appearance tab 2025-08-04 14:04:28 +03:00
Henry Jameson
800ab90cf9 reorganization of some settings 2025-08-04 13:48:09 +03:00
Henry Jameson
59de80639f small updates 2025-08-04 11:34:41 +03:00
Henry Jameson
f79c61c4e7 name/bio/avatar/banner edit support 2025-08-04 11:10:43 +03:00
Henry Jameson
b305748a92 avatar upload works 2025-08-04 03:35:09 +03:00
Henry Jameson
7d985bd475 misc 2025-08-04 00:45:13 +03:00
Henry Jameson
60363e66fb editable name 2025-08-04 00:14:34 +03:00
Henry Jameson
50314fe253 better fields 2025-08-03 23:15:35 +03:00
Henry Jameson
20beb30fc3 bio editable 2025-08-03 23:05:16 +03:00
Henry Jameson
2df895ab02 editable meta and bdey 2025-08-03 21:56:45 +03:00
Henry Jameson
51eb61180d fix missing computed in general tab 2025-08-03 18:32:18 +03:00
HJ
38b9b04385 Merge branch 'profile-overhaul' into 'develop'
Profile overhaul

Closes #1375

See merge request pleroma/pleroma-fe!2202
2025-08-03 15:06:58 +00:00
Henry Jameson
28422adc8c lint 2025-08-03 18:01:09 +03:00
Henry Jameson
3c2c572661 lint 2025-08-03 17:51:27 +03:00
HJ
39602f36bb Merge branch 'block-expiration-param' into 'develop'
Correct the block expiration parameter name

See merge request pleroma/pleroma-fe!2203
2025-08-02 22:53:42 +00:00
Ekaterina Vaartis
68c88677a0 Correct the block expiration parameter name 2025-08-02 00:35:19 +03:00
Henry Jameson
d82ab81e4e make popovers even more nicer 2025-08-01 02:19:48 +03:00
Henry Jameson
66b83558bb fix self popover... again 2025-08-01 02:13:44 +03:00
Henry Jameson
9a85c3f8fb save some space in popovers 2025-08-01 00:58:59 +03:00
Henry Jameson
34c29e4fdc enforce min height to prevent buttonless (self) popovers from being tiny 2025-07-31 22:46:06 +03:00
Henry Jameson
6e7ad5d554 fuck it, i dunno why it doesn't work right without flex 2025-07-31 22:34:46 +03:00
Henry Jameson
07a6d58660 fix lock icon 2025-07-31 22:02:04 +03:00
Henry Jameson
ddcd9007dc improve styles again 2025-07-31 21:44:08 +03:00
Henry Jameson
f8a5918fc8 improve other-actions again 2025-07-31 21:35:43 +03:00
Henry Jameson
bb044315cd improve gradient styles again 2025-07-31 21:29:58 +03:00
Henry Jameson
426fb90522 fix short names 2025-07-31 21:28:34 +03:00
Henry Jameson
2008fcce22 simpligy gradient style and improve other-actions layout 2025-07-31 21:28:00 +03:00
Henry Jameson
ba5efe4a67 fix tags showing up when they shouldn't 2025-07-30 16:53:33 +03:00
Henry Jameson
83ad4078bb fix stats on small screens 2025-07-30 16:53:22 +03:00
Henry Jameson
41d794d3ab fix border on popovers 2025-07-30 10:29:28 +03:00
Henry Jameson
eb68857293 user tags overflow 2025-07-30 10:22:37 +03:00
Henry Jameson
add9535b1a fix overflows and small screens 2025-07-30 10:00:03 +03:00
Henry Jameson
1fdb676eca better buttons sizing 2025-07-30 01:20:27 +03:00
Henry Jameson
9e77071e77 requested options 2025-07-30 01:13:24 +03:00
Henry Jameson
f5d167950d proper bottom spacing in user popover 2025-07-30 01:03:47 +03:00
Henry Jameson
4ffd8499a7 scale avatar and user title with container width 2025-07-30 00:30:07 +03:00
Henry Jameson
b05ebeaed5 made profile action buttons easier to press 2025-07-30 00:29:46 +03:00
Henry Jameson
518144f308 changelog 2025-07-29 23:46:39 +03:00
Henry Jameson
731e9eed6e style updates 2025-07-29 19:00:45 +03:00
Henry Jameson
0b1e7dbcea simpler format for bdey/stats 2025-07-29 18:38:41 +03:00
Henry Jameson
ef0a4f4023 "fix" select styles 2025-07-29 17:52:52 +03:00
HJ
2c1bea0e0b Merge branch 'whats-this-doing-here' into 'develop'
what's this doing here, begone

See merge request pleroma/pleroma-fe!2201
2025-07-29 14:42:16 +00:00
Henry Jameson
d299e864bf what's this doing here, begone 2025-07-29 17:41:18 +03:00
Henry Jameson
79868aeeb9 initial profile overhaul 2025-07-29 17:38:08 +03:00
HJ
38130fce90 Merge branch 'misc/lint-and-markup' into 'develop'
Lint some things

See merge request pleroma/pleroma-fe!2199
2025-07-27 17:28:21 +00:00
Pleroma User
3294df0325 Lint some things 2025-07-27 17:28:21 +00:00
Pleroma Renovate Bot
0e56f8f103 Update babel monorepo 2025-07-25 08:52:58 +00:00
HJ
e747ee896e Merge branch 'timed-user-mutes' into 'develop'
Timed user mutes

See merge request pleroma/pleroma-fe!2197
2025-07-18 14:17:43 +00:00
HJ
e1cbb1ccd2 Merge branch 'firefix' into 'develop'
Fix firefox and theme change not working

See merge request pleroma/pleroma-fe!2198
2025-07-17 14:56:17 +00:00
Henry Jameson
04ee1a892c proper detection 2025-07-17 17:55:20 +03:00
Henry Jameson
23975b506e fix theme change not working 2025-07-17 17:50:38 +03:00
Henry Jameson
c12e1a69cf fix firefox 2025-07-17 17:32:09 +03:00
Henry Jameson
6aae8a8705 move "ask" options to filtering tab 2025-07-17 16:20:16 +03:00
Henry Jameson
6ae52e192b cleanup 2025-07-17 16:13:35 +03:00
Henry Jameson
eb77c5dbb9 changelog 2025-07-17 15:23:05 +03:00
Henry Jameson
b014489295 better modal layout 2025-07-17 15:22:03 +03:00
Henry Jameson
35c2e87131 show expiry only if available 2025-07-17 15:22:03 +03:00
Henry Jameson
9f0b65654e show expirety in user profile (currently broken?) 2025-07-09 19:03:58 +03:00
Henry Jameson
2441e6508d settings for confirmations 2025-07-09 18:37:05 +03:00
Henry Jameson
71d1baffcc better support for lack of block expiration 2025-07-09 17:56:52 +03:00
Henry Jameson
8436f39eff block/mute cards update to show expiry and ask for it 2025-07-09 17:45:13 +03:00
Henry Jameson
385f921c41 Merge remote-tracking branch 'origin/develop' into timed-user-mutes 2025-07-09 15:57:23 +03:00
HJ
74b410da2b Merge branch 'fix-zoom-lag' into 'develop'
fix zoom being applied with a delay

See merge request pleroma/pleroma-fe!2196
2025-07-09 12:53:51 +00:00
Henry Jameson
d9639c543c chore 2025-07-09 15:49:54 +03:00
Henry Jameson
c433aa38fb fix 2025-07-09 15:48:36 +03:00
Henry Jameson
a626b37354 fix zoom being applied with a delay 2025-07-09 15:46:05 +03:00
HJ
2e9f23542c Merge branch 'redmond-fix' into 'develop'
Redmond fix

See merge request pleroma/pleroma-fe!2195
2025-07-03 18:22:16 +00:00
Henry Jameson
1d729cb0c5 fix and default 2025-07-03 21:21:21 +03:00
Henry Jameson
0f573637a7 fix redmond 2025-07-03 21:19:12 +03:00
Henry Jameson
b2f7309e1e sorting, typo 2025-07-03 17:11:23 +03:00
HJ
c179daaf80 Merge branch 'fix-style-editors' into 'develop'
Fix style editors, properly default from unavailable adopted sheets

See merge request pleroma/pleroma-fe!2194
2025-07-03 08:25:18 +00:00
Henry Jameson
558251ce74 back to kazv upstream of pinchzoom 2025-07-03 11:20:45 +03:00
Henry Jameson
7bc5dd440b remove backdrop-filter only if unsupported 2025-07-03 01:55:43 +03:00
Henry Jameson
a635f025be chore 2025-07-03 00:20:48 +03:00
Henry Jameson
1bc53262d6 theme tab support 2025-07-02 23:24:19 +03:00
Henry Jameson
d6ebc5049e cleanup 2025-07-02 23:24:12 +03:00
Henry Jameson
3081504c64 update tabs to use new API 2025-07-02 22:54:45 +03:00
Henry Jameson
dc531d4ef3 streamline custom styles additions 2025-07-02 19:39:25 +03:00
Henry Jameson
5a6a77bd75 update component preview on render 2025-07-02 18:20:05 +03:00
Henry Jameson
bc96d16e11 small fix 2025-07-02 18:20:05 +03:00
Henry Jameson
82d67a634e undo link->style 2025-07-02 18:20:05 +03:00
Henry Jameson
3822aaf137 i spent too much time on this 2025-07-02 18:20:05 +03:00
Henry Jameson
0d32a7ddac fix component preview styles 2025-07-02 18:20:05 +03:00
Pleroma Renovate Bot
8483268cb3 Update dependency stylelint-config-recommended-vue to v1.6.1 2025-06-27 09:04:51 +00:00
Pleroma Renovate Bot
230e61235d Update dependency postcss to v8.5.6 2025-06-27 09:04:41 +00:00
Henry Jameson
e4b44f8c7b better dialog, actually working dontAskAgain 2025-06-26 16:28:02 +03:00
Pleroma Renovate Bot
046b959bd7 Update dependency pinia to v3.0.3 2025-06-26 09:04:52 +00:00
Pleroma Renovate Bot
4146c071ce Update dependency nightwatch to v3.12.2 2025-06-26 09:04:43 +00:00
Pleroma Renovate Bot
537031f0cb Update dependency @ruffle-rs/ruffle to v0.1.0-nightly.2025.6.22 2025-06-22 09:05:47 +00:00
Henry Jameson
c9a4aee954 forgotten files 2025-06-12 22:03:15 +03:00
Henry Jameson
b9161ef697 some basic expiration modal. "don't as again" doesn't work yet 2025-06-12 20:04:39 +03:00
609 changed files with 71590 additions and 25219 deletions

12
.dockerignore Normal file
View file

@ -0,0 +1,12 @@
node_modules/
dist/
logs/
.DS_Store
.git/
config/local.json
pleroma-backend/
test/e2e/reports/
test/e2e-playwright/test-results/
test/e2e-playwright/playwright-report/
__screenshots__/

View file

@ -0,0 +1,87 @@
name: 'Bug report'
about: 'Bug report for Pleroma FE'
labels:
- Bug
body:
- type: input
id: env-browser
attributes:
label: Browser and OS
description: What browser are you using, including version, and what OS are you running?
placeholder: Firefox 140, Arch Linux
validations:
required: true
- type: input
id: env-instance
attributes:
label: Instance URL
validations:
required: false
- type: input
id: env-backend
attributes:
label: Backend version information
description: Backend version being used. (See Settings->Show advanced->Developer)
placeholder: Pleroma BE 2.10
validations:
required: true
- type: input
id: env-frontend
attributes:
label: Frontend version information
description: Frontend version being used. (See Settings->Show advanced->Developer)
placeholder: Pleroma FE 2.10
validations:
required: true
- type: input
id: env-extensions
attributes:
label: Browser extensions
description: List of browser extensions you are using, like uBlock, rikaichamp etc. If none leave empty.
validations:
required: false
- type: input
id: env-modifications
attributes:
label: Known instance/user customizations
description: Whether you are using a Pleroma FE fork, any mods mods or instance level styles among others.
validations:
required: false
- type: textarea
id: bug-text
attributes:
label: Bug description
description: A short description of the bug. Images can be helpful.
validations:
required: true
- type: textarea
id: bug-reproducer
attributes:
label: Reproduction steps
description: Ordered list of reproduction steps needed to make the bug happen. If you don't have reproduction steps, leave this empty.
placeholder: |
1. Log in with a fresh browser session
2. Open timeline X
3. Click on button Y
4. Z broke
validations:
required: false
- type: textarea
id: bug-seriousness
attributes:
label: Bug seriousness
value: |
* How annoying it is:
* How often does it happen:
* How many people does it affect:
* Is there a workaround for it:
- type: checkboxes
id: duplicate-issues
attributes:
label: Duplicate issues
hide_label: true
description: Before submitting this issue, search for same or similar issues on the [Pleroma FE bug tracker](https://git.pleroma.social/pleroma/pleroma-fe/issues).
options:
- label: I've searched for same or similar issues before submitting this issue.
required: true
visible: [form]

View file

@ -0,0 +1,22 @@
name: 'Feature request / Suggestion / Improvement'
about: 'Feature requests, suggestions and improvements for Pleroma FE'
labels:
- Feature Request / Enhancement
body:
- type: textarea
id: issue-text
attributes:
label: Proposal
placeholder: Make groups happen!
validations:
required: true
- type: checkboxes
id: duplicate-issues
attributes:
label: Duplicate issues
hide_label: true
description: Before submitting this issue, search for same or similar requests on the [Pleroma FE bug tracker](https://git.pleroma.social/pleroma/pleroma-fe/issues).
options:
- label: I've searched for same or similar requests before submitting this issue.
required: true
visible: [form]

View file

@ -0,0 +1,12 @@
### Checklist
- [ ] Adding a changelog: In the `changelog.d` directory, create a file named `<code>.<type>`.
<!--
`<code>` can be anything, but we recommend using a more or less unique identifier to avoid collisions, such as the branch name.
`<type>` can be `add`, `change`, `remove`, `fix`, `security` or `skip`. `skip` is only used if there is no user-visible change in the MR (for example, only editing comments in the code). Otherwise, choose a type that corresponds to your change.
In the file, write the changelog entry. For example, if an MR adds group functionality, we can create a file named `group.add` and write `Add group functionality` in it.
If one changelog entry is not enough, you may add more. But that might mean you can split it into two MRs. Only use more than one changelog entry if you really need to (for example, when one change in the code fix two different bugs, or when refactoring).
-->

3
.gitignore vendored
View file

@ -4,8 +4,11 @@ dist/
npm-debug.log
test/unit/coverage
test/e2e/reports
test/e2e-playwright/test-results
test/e2e-playwright/playwright-report
selenium-debug.log
.idea/
.gitlab-ci-local/
config/local.json
src/assets/emoji.json
logs/

View file

@ -34,12 +34,23 @@ check-changelog:
- apk add git
- sh ./tools/check-changelog
lint:
lint-eslint:
stage: lint
script:
- yarn
- yarn lint
- yarn stylelint
- yarn ci-eslint
lint-biome:
stage: lint
script:
- yarn
- yarn ci-biome
lint-stylelint:
stage: lint
script:
- yarn
- yarn ci-stylelint
test:
stage: test
@ -60,6 +71,135 @@ test:
- test/**/__screenshots__
when: on_failure
e2e-pleroma:
stage: test
image: mcr.microsoft.com/playwright:v1.57.0-jammy
services:
- name: postgres:15-alpine
alias: db
- name: $PLEROMA_IMAGE
alias: pleroma
entrypoint: ["/bin/ash", "-c"]
command:
- |
set -eu
SEED_SENTINEL_PATH=/var/lib/pleroma/.e2e_seeded
CONFIG_OVERRIDE_PATH=/var/lib/pleroma/config.exs
echo '-- Waiting for database...'
while ! pg_isready -U ${DB_USER:-pleroma} -d postgres://${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-pleroma} -t 1; do
sleep 1s
done
echo '-- Writing E2E config overrides...'
cat > $CONFIG_OVERRIDE_PATH <<EOF
import Config
config :pleroma, Pleroma.Captcha,
enabled: false
config :pleroma, :instance,
registrations_open: true,
account_activation_required: false,
approval_required: false
EOF
echo '-- Running migrations...'
/opt/pleroma/bin/pleroma_ctl migrate
echo '-- Starting!'
/opt/pleroma/bin/pleroma start &
PLEROMA_PID=$!
cleanup() {
if kill -0 $PLEROMA_PID 2>/dev/null; then
kill -TERM $PLEROMA_PID
wait $PLEROMA_PID || true
fi
}
trap cleanup INT TERM
echo '-- Waiting for API...'
api_ok=false
for _i in $(seq 1 120); do
if wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null 2>&1; then
api_ok=true
break
fi
sleep 1s
done
if [ $api_ok != true ]; then
echo 'Timed out waiting for Pleroma API to become available'
exit 1
fi
if [ ! -f $SEED_SENTINEL_PATH ]; then
if [ -n ${E2E_ADMIN_USERNAME:-} ] && [ -n ${E2E_ADMIN_PASSWORD:-} ] && [ -n ${E2E_ADMIN_EMAIL:-} ]; then
echo '-- Seeding admin user' $E2E_ADMIN_USERNAME '...'
if ! /opt/pleroma/bin/pleroma_ctl user new $E2E_ADMIN_USERNAME $E2E_ADMIN_EMAIL --admin --password $E2E_ADMIN_PASSWORD -y; then
echo '-- User already exists or creation failed, ensuring admin + confirmed...'
/opt/pleroma/bin/pleroma_ctl user set $E2E_ADMIN_USERNAME --admin --confirmed
fi
else
echo '-- Skipping admin seeding (missing E2E_ADMIN_* env)'
fi
touch $SEED_SENTINEL_PATH
fi
wait $PLEROMA_PID
tags:
- amd64
- himem
variables:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
FF_NETWORK_PER_BUILD: "true"
PLEROMA_IMAGE: git.pleroma.social:5050/pleroma/pleroma:stable
POSTGRES_USER: pleroma
POSTGRES_PASSWORD: pleroma
POSTGRES_DB: pleroma
DB_USER: pleroma
DB_PASS: pleroma
DB_NAME: pleroma
DB_HOST: db
DB_PORT: 5432
DOMAIN: localhost
INSTANCE_NAME: Pleroma E2E
E2E_ADMIN_USERNAME: admin
E2E_ADMIN_PASSWORD: adminadmin
E2E_ADMIN_EMAIL: admin@example.com
ADMIN_EMAIL: $E2E_ADMIN_EMAIL
NOTIFY_EMAIL: $E2E_ADMIN_EMAIL
VITE_PROXY_TARGET: http://pleroma:4000
VITE_PROXY_ORIGIN: http://localhost:4000
E2E_BASE_URL: http://localhost:8080
script:
- npm install -g yarn@1.22.22
- yarn --frozen-lockfile
- |
echo "-- Waiting for Pleroma API..."
api_ok="false"
for _i in $(seq 1 120); do
if wget -qO- http://pleroma:4000/api/v1/instance >/dev/null 2>&1; then
api_ok="true"
break
fi
sleep 1s
done
if [ "$api_ok" != "true" ]; then
echo "Timed out waiting for Pleroma API to become available"
exit 1
fi
- yarn e2e:pw
artifacts:
when: on_failure
paths:
- test/e2e-playwright/test-results
- test/e2e-playwright/playwright-report
build:
stage: build
tags:

View file

@ -0,0 +1,8 @@
### Release checklist
* [ ] Bump version in `package.json`
* [ ] Compile a changelog with the `tools/collect-changelog` script
* [ ] Create an MR with an announcement to pleroma.social
#### post-merge
* [ ] Tag the release on the merge commit
* [ ] Make the tag into a Gitlab Release™
* [ ] Merge `master` into `develop` (in case the fixes are already in develop, use `git merge -s ours --no-commit` and manually merge the changelogs)

View file

@ -12,6 +12,8 @@
"custom-property-pattern": null,
"keyframes-name-pattern": null,
"scss/operator-no-newline-after": null,
"declaration-property-value-no-unknown": true,
"scss/declaration-property-value-no-unknown": true,
"declaration-block-no-redundant-longhand-properties": [
true,
{

View file

@ -2,6 +2,76 @@
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/).
## 2.10.1
### Fixed
- fixed being unable to set actor type from profile page
- fixed error when clicking mute menu itself (instead of submenu items)
- fixed mute -> domain status submenu not working
### Internal
- Add playwright E2E-tests with an optional docker-based backend
## 2.10.0
### Changed
- Temporary changes modal now shows actual countdown instead of fixed timeout
- Disabled elements are more disabled now
- Rearranged and split settings to make more sense and be less of a wall of text
- On mobile settings now take up full width and presented in navigation style
improved styles for settings
### Added
- Most of the remaining AdminFE tabs were added into Admin Dashboard
- It's now possible to customize PWA Manfiest from PleromaFE
- Make every configuration option default-overridable by instance admins
### Fixed
- Fixed settings not appearing if user never touched "show advanced" toggle
- Fix display of the broken/deleted/banned users
- Fixed incorrect emoji display in post interaction lists
- Fixed list title not being saved when editing
- Fixed poll notifications not being expandable
## 2.9.3
### Fixed
- Being unable to update profile
## 2.9.2
### Changed
- BREAKING: due to some internal technical changes logging into AdminFE through PleromaFE is no longer possible
- User card/profile got an overhaul
- Profile editing overhaul
- Visually combined subject and content fields in post form
- Moved post form's emoji button into input field
- Minor visual changes and fixes
- Clicking on fav/rt/emoji notifications' contents expands/collapses it
- Reduced time taken processing theme by half
- Splash screen only appears if loading takes more than 2 seconds
### Added
- Mutes received an update, adding support for regex, muting based on username and expiration time.
- Mutes are now synchronized across sessions
- Support for expiring mutes and blocks (if available)
- Clicking on emoji shows bigger version of it alongside with its shortcode
- Admins also are able to copy it into a local pack
- Added support for Akkoma and IceShrimp.NET backends
- Compatibility with stricter CSP (Akkoma backend)
- Added a way to upload new packs from a URL or ZIP file via the Admin Dashboard
- Unify show/hide content buttons
- Add support for detachable scrollTop button
- Option to left-align user bio
- Cache assets and emojis with service worker
- Indicate currently active V3 theme as a body element class
- Add arithmetic blend ISS function
### Fixed
- Display counter for status action buttons when they are in the menu
- Fix bookmark button alignment in the extra actions menu
- Instance favicons are no longer stretched
- A lot more scalable UI fixes
- Emoji picker now should work fine when emoji size is increased
## 2.8.0
### Changed
- BREAKING: static/img/nsfw.2958239.png is now static/img/nsfw.DepQPhG0.png, which may affect people who specify exactly this path as the cover image
@ -34,8 +104,8 @@ This does not guarantee that browsers will or will not work.
- Support displaying time in absolute format
- Add draft management system
- Compress most kinds of images on upload.
- Added option to always convert images to JPEG format instead of using WebP when compressing images.
- Added configurable image compression option in general settings, allowing users to control whether images are compressed before upload.
- Added option to always convert images to JPEG format instead of using WebP when compressing images.
- Added configurable image compression option in general settings, allowing users to control whether images are compressed before upload.
- Inform users that Smithereen public polls are public
- Splash screen + loading indicator to make process of identifying initialization issues and load performance
- UI for making v3 themes and palettes, support for bundling v3 themes

View file

@ -6,7 +6,7 @@
# For Translators
To translate Pleroma-FE, use our weblate server: https://translate.pleroma.social/. If you need to add your language it should be added as a json file in [src/i18n/](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/src/i18n/languages.js).
To translate Pleroma-FE, use our weblate server: https://translate.pleroma.social/. If you need to add your language it should be added as a json file in [src/i18n/](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/languages.js).
Pleroma-FE will set your language by your browser locale, but you can change language in settings.
@ -32,10 +32,10 @@ yarn unit
# For Contributors:
You can create file `/config/local.json` (see [example](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/config/local.example.json)) to enable some convenience dev options:
You can create file `/config/local.json` (see [example](https://git.pleroma.social/pleroma/pleroma-fe/src/config/local.example.json)) to enable some convenience dev options:
* `target`: makes local dev server redirect to some existing instance's BE instead of local BE, useful for testing things in near-production environment and searching for real-life use-cases.
* `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/statusnet/config.json`. Only works in dev mode.
* `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/pleroma/frontend_configurations`. Only works in dev mode.
FE Build process also leaves current commit hash in global variable `___pleromafe_commit_hash` so that you can easily see which pleroma-fe commit instance is running, also helps pinpointing which commit was used when FE was bundled into BE.

149
biome.json Normal file
View file

@ -0,0 +1,149 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"includes": ["**", "!!**/dist", "!!tools/emojis.json"]
},
"formatter": {
"enabled": true,
"indentStyle": "space"
},
"linter": {
"enabled": true,
"domains": {
"vue": "recommended"
},
"rules": {
"recommended": false,
"complexity": {
"noAdjacentSpacesInRegex": "error",
"noExtraBooleanCast": "error",
"noUselessCatch": "error",
"noUselessEscapeInRegex": "error"
},
"correctness": {
"noConstAssign": "error",
"noConstantCondition": "error",
"noEmptyCharacterClassInRegex": "error",
"noEmptyPattern": "error",
"noGlobalObjectCalls": "error",
"noInvalidBuiltinInstantiation": "error",
"noInvalidConstructorSuper": "error",
"noNonoctalDecimalEscape": "error",
"noPrecisionLoss": "error",
"noSelfAssign": "error",
"noSetterReturn": "error",
"noSwitchDeclarations": "error",
"noUndeclaredVariables": "error",
"noUnreachable": "error",
"noUnreachableSuper": "error",
"noUnsafeFinally": "error",
"noUnsafeOptionalChaining": "error",
"noUnusedLabels": "error",
"noUnusedPrivateClassMembers": "error",
"noUnusedVariables": "error",
"useIsNan": "error",
"useValidForDirection": "error",
"useValidTypeof": "error",
"useYield": "error"
},
"suspicious": {
"noAsyncPromiseExecutor": "error",
"noCatchAssign": "error",
"noClassAssign": "error",
"noCompareNegZero": "error",
"noConstantBinaryExpressions": "error",
"noControlCharactersInRegex": "error",
"noDebugger": "error",
"noDuplicateCase": "error",
"noDuplicateClassMembers": "error",
"noDuplicateElseIf": "error",
"noDuplicateObjectKeys": "error",
"noDuplicateParameters": "error",
"noEmptyBlockStatements": "error",
"noFallthroughSwitchClause": "error",
"noFunctionAssign": "error",
"noGlobalAssign": "error",
"noImportAssign": "error",
"noIrregularWhitespace": "error",
"noMisleadingCharacterClass": "error",
"noPrototypeBuiltins": "error",
"noRedeclare": "error",
"noShadowRestrictedNames": "error",
"noSparseArray": "error",
"noUnsafeNegation": "error",
"noUselessRegexBackrefs": "error",
"noWith": "error",
"useGetterReturn": "error"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded"
},
"globals": []
},
"overrides": [
{
"includes": ["**/*.spec.js", "test/fixtures/*.js"],
"javascript": {
"globals": [
"vi",
"describe",
"it",
"test",
"expect",
"before",
"beforeEach",
"after",
"afterEach"
]
}
},
{
"includes": ["**/*.vue"],
"linter": {
"rules": {
"style": {
"useConst": "off",
"useImportType": "off"
},
"correctness": {
"noUnusedVariables": "off",
"noUnusedImports": "off"
}
}
}
}
],
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": {
"level": "on",
"options": {
"groups": [
[":NODE:", ":PACKAGE:", "!src/**", "!@fortawesome/**"],
":BLANK_LINE:",
[":PATH:", "src/components/**"],
":BLANK_LINE:",
[":PATH:", "src/stores/**"],
":BLANK_LINE:",
[":PATH:", "src/**", "src/stores/**", "src/components/**"],
":BLANK_LINE:",
"@fortawesome/fontawesome-svg-core",
"@fortawesome/*"
]
}
}
}
}
}
}

View file

@ -1,5 +1,5 @@
import semver from 'semver'
import chalk from 'chalk'
import semver from 'semver'
import packageConfig from '../package.json' with { type: 'json' }
@ -7,8 +7,8 @@ var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
versionRequirement: packageConfig.engines.node,
},
]
export default function () {
@ -16,15 +16,22 @@ export default function () {
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
warnings.push(
mod.name +
': ' +
chalk.red(mod.currentVersion) +
' should be ' +
chalk.green(mod.versionRequirement),
)
}
}
if (warnings.length) {
console.warn(chalk.yellow('\nTo use this template, you must update following to modules:\n'))
console.warn(
chalk.yellow(
'\nTo use this template, you must update following to modules:\n',
),
)
for (let i = 0; i < warnings.length; i++) {
const warning = warnings[i]
console.warn(' ' + warning)

View file

@ -1,8 +1,8 @@
import childProcess from 'child_process'
export const getCommitHash = (() => {
const subst = "$Format:%h$"
if(!subst.match(/Format:/)) {
export const getCommitHash = () => {
const subst = '$Format:%h$'
if (!subst.match(/Format:/)) {
return subst
} else {
try {
@ -15,4 +15,4 @@ export const getCommitHash = (() => {
return 'UNKNOWN'
}
}
})
}

View file

@ -1,8 +1,8 @@
import serveStatic from 'serve-static'
import { resolve } from 'node:path'
import { cp } from 'node:fs/promises'
import { resolve } from 'node:path'
import serveStatic from 'serve-static'
const getPrefix = s => {
const getPrefix = (s) => {
const padEnd = s.endsWith('/') ? s : s + '/'
return padEnd.startsWith('/') ? padEnd : '/' + padEnd
}
@ -13,28 +13,31 @@ const copyPlugin = ({ inUrl, inFs }) => {
let copyTarget
const handler = serveStatic(inFs)
return [{
name: 'copy-plugin-serve',
apply: 'serve',
configureServer (server) {
server.middlewares.use(prefix, handler)
}
}, {
name: 'copy-plugin-build',
apply: 'build',
configResolved (config) {
copyTarget = resolve(config.root, config.build.outDir, subdir)
return [
{
name: 'copy-plugin-serve',
apply: 'serve',
configureServer(server) {
server.middlewares.use(prefix, handler)
},
},
closeBundle: {
order: 'post',
sequential: true,
async handler () {
console.log(`Copying '${inFs}' to ${copyTarget}...`)
await cp(inFs, copyTarget, { recursive: true })
console.log('Done.')
}
}
}]
{
name: 'copy-plugin-build',
apply: 'build',
configResolved(config) {
copyTarget = resolve(config.root, config.build.outDir, subdir)
},
closeBundle: {
order: 'post',
sequential: true,
async handler() {
console.info(`Copying '${inFs}' to ${copyTarget}...`)
await cp(inFs, copyTarget, { recursive: true })
console.info('Done.')
},
},
},
]
}
export default copyPlugin

View file

@ -1,21 +1,23 @@
import { resolve } from 'node:path'
import { access } from 'node:fs/promises'
import { languages, langCodeToCldrName } from '../src/i18n/languages.js'
import { resolve } from 'node:path'
import { languages } from '../src/i18n/languages.js'
const annotationsImportPrefix = '@kazvmoe-infra/unicode-emoji-json/annotations/'
const specialAnnotationsLocale = {
ja_easy: 'ja'
ja_easy: 'ja',
}
const internalToAnnotationsLocale = (internal) => specialAnnotationsLocale[internal] || internal
const internalToAnnotationsLocale = (internal) =>
specialAnnotationsLocale[internal] || internal
// This gets all the annotations that are accessible (whose language
// can be chosen in the settings). Data for other languages are
// discarded because there is no way for it to be fetched.
const getAllAccessibleAnnotations = async (projectRoot) => {
const imports = (await Promise.all(
languages
.map(async lang => {
const imports = (
await Promise.all(
languages.map(async (lang) => {
const destLang = internalToAnnotationsLocale(lang)
const importModule = `${annotationsImportPrefix}${destLang}.json`
const importFile = resolve(projectRoot, 'node_modules', importModule)
@ -23,11 +25,18 @@ const getAllAccessibleAnnotations = async (projectRoot) => {
await access(importFile)
return `'${lang}': () => import('${importModule}')`
} catch (e) {
if (e.message.match(/ENOENT/)) {
console.warn(`Missing emoji annotations locale: ${destLang}`)
} else {
console.error('test', e.message)
}
return
}
})))
.filter(k => k)
.join(',\n')
}),
)
)
.filter((k) => k)
.join(',\n')
return `
export const annotationsLoader = {
@ -43,21 +52,21 @@ const emojisPlugin = () => {
let projectRoot
return {
name: 'emojis-plugin',
configResolved (conf) {
configResolved(conf) {
projectRoot = conf.root
},
resolveId (id) {
resolveId(id) {
if (id === emojiAnnotationsId) {
return emojiAnnotationsIdResolved
}
return null
},
async load (id) {
async load(id) {
if (id === emojiAnnotationsIdResolved) {
return await getAllAccessibleAnnotations(projectRoot)
}
return null
}
},
}
}

View file

@ -1,5 +1,5 @@
import { resolve } from 'node:path'
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
const target = 'node_modules/msw/lib/mockServiceWorker.js'
@ -8,10 +8,10 @@ const mswPlugin = () => {
return {
name: 'msw-plugin',
apply: 'serve',
configResolved (conf) {
configResolved(conf) {
projectRoot = conf.root
},
configureServer (server) {
configureServer(server) {
server.middlewares.use(async (req, res, next) => {
if (req.path === '/mockServiceWorker.js') {
const file = await readFile(resolve(projectRoot, target))
@ -21,7 +21,7 @@ const mswPlugin = () => {
next()
}
})
}
},
}
}

View file

@ -1,11 +1,12 @@
import { languages, langCodeToJsonName } from '../src/i18n/languages.js'
import { readFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { langCodeToJsonName, languages } from '../src/i18n/languages.js'
const i18nDir = resolve(
dirname(dirname(fileURLToPath(import.meta.url))),
'src/i18n'
'src/i18n',
)
export const i18nFiles = languages.reduce((acc, lang) => {
@ -16,13 +17,15 @@ export const i18nFiles = languages.reduce((acc, lang) => {
}, {})
export const generateServiceWorkerMessages = async () => {
const msgArray = await Promise.all(Object.entries(i18nFiles).map(async ([lang, file]) => {
const fileContent = await readFile(file, 'utf-8')
const msg = {
notifications: JSON.parse(fileContent).notifications || {}
}
return [lang, msg]
}))
const msgArray = await Promise.all(
Object.entries(i18nFiles).map(async ([lang, file]) => {
const fileContent = await readFile(file, 'utf-8')
const msg = {
notifications: JSON.parse(fileContent).notifications || {},
}
return [lang, msg]
}),
)
return msgArray.reduce((acc, [lang, msg]) => {
acc[lang] = msg
return acc

View file

@ -1,9 +1,13 @@
import { fileURLToPath } from 'node:url'
import { dirname, resolve } from 'node:path'
import { readFile } from 'node:fs/promises'
import { build } from 'vite'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import * as esbuild from 'esbuild'
import { generateServiceWorkerMessages, i18nFiles } from './service_worker_messages.js'
import { build } from 'vite'
import {
generateServiceWorkerMessages,
i18nFiles,
} from './service_worker_messages.js'
const getSWMessagesAsText = async () => {
const messages = await generateServiceWorkerMessages()
@ -14,14 +18,10 @@ const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)))
const swEnvName = 'virtual:pleroma-fe/service_worker_env'
const swEnvNameResolved = '\0' + swEnvName
const getDevSwEnv = () => `self.serviceWorkerOption = { assets: [] };`
const getProdSwEnv = ({ assets }) => `self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };`
const getProdSwEnv = ({ assets }) =>
`self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };`
export const devSwPlugin = ({
swSrc,
swDest,
transformSW,
alias
}) => {
export const devSwPlugin = ({ swSrc, swDest, transformSW, alias }) => {
const swFullSrc = resolve(projectRoot, swSrc)
const esbuildAlias = {}
Object.entries(alias).forEach(([source, dest]) => {
@ -31,9 +31,10 @@ export const devSwPlugin = ({
return {
name: 'dev-sw-plugin',
apply: 'serve',
configResolved (conf) {
configResolved() {
/* no-op */
},
resolveId (id) {
resolveId(id) {
const name = id.startsWith('/') ? id.slice(1) : id
if (name === swDest) {
return swFullSrc
@ -42,7 +43,7 @@ export const devSwPlugin = ({
}
return null
},
async load (id) {
async load(id) {
if (id === swFullSrc) {
return readFile(swFullSrc, 'utf-8')
} else if (id === swEnvNameResolved) {
@ -55,7 +56,7 @@ export const devSwPlugin = ({
* during dev, and firefox does not support ESM as service worker
* https://bugzilla.mozilla.org/show_bug.cgi?id=1360870
*/
async transform (code, id) {
async transform(code, id) {
if (id === swFullSrc && transformSW) {
const res = await esbuild.build({
entryPoints: [swSrc],
@ -63,52 +64,54 @@ export const devSwPlugin = ({
write: false,
outfile: 'sw-pleroma.js',
alias: esbuildAlias,
plugins: [{
name: 'vite-like-root-resolve',
setup (b) {
b.onResolve(
{ filter: new RegExp(/^\//) },
args => ({
path: resolve(projectRoot, args.path.slice(1))
})
)
}
}, {
name: 'sw-messages',
setup (b) {
b.onResolve(
{ filter: new RegExp('^' + swMessagesName + '$') },
args => ({
path: args.path,
namespace: 'sw-messages'
plugins: [
{
name: 'vite-like-root-resolve',
setup(b) {
b.onResolve({ filter: new RegExp(/^\//) }, (args) => ({
path: resolve(projectRoot, args.path.slice(1)),
}))
b.onLoad(
{ filter: /.*/, namespace: 'sw-messages' },
async () => ({
contents: await getSWMessagesAsText()
},
},
{
name: 'sw-messages',
setup(b) {
b.onResolve(
{ filter: new RegExp('^' + swMessagesName + '$') },
(args) => ({
path: args.path,
namespace: 'sw-messages',
}),
)
b.onLoad(
{ filter: /.*/, namespace: 'sw-messages' },
async () => ({
contents: await getSWMessagesAsText(),
}),
)
},
},
{
name: 'sw-env',
setup(b) {
b.onResolve(
{ filter: new RegExp('^' + swEnvName + '$') },
(args) => ({
path: args.path,
namespace: 'sw-env',
}),
)
b.onLoad({ filter: /.*/, namespace: 'sw-env' }, () => ({
contents: getDevSwEnv(),
}))
}
}, {
name: 'sw-env',
setup (b) {
b.onResolve(
{ filter: new RegExp('^' + swEnvName + '$') },
args => ({
path: args.path,
namespace: 'sw-env'
}))
b.onLoad(
{ filter: /.*/, namespace: 'sw-env' },
() => ({
contents: getDevSwEnv()
}))
}
}]
},
},
],
})
const text = res.outputFiles[0].text
return text
}
}
},
}
}
@ -118,16 +121,13 @@ export const devSwPlugin = ({
// however, we must compile the service worker to iife because of browser support.
// Run another vite build just for the service worker targeting iife at
// the end of the build.
export const buildSwPlugin = ({
swSrc,
swDest,
}) => {
export const buildSwPlugin = ({ swSrc, swDest }) => {
let config
return {
name: 'build-sw-plugin',
enforce: 'post',
apply: 'build',
configResolved (resolvedConfig) {
configResolved(resolvedConfig) {
config = {
define: resolvedConfig.define,
resolve: resolvedConfig.resolve,
@ -138,50 +138,50 @@ export const buildSwPlugin = ({
lib: {
entry: swSrc,
formats: ['iife'],
name: 'sw_pleroma'
name: 'sw_pleroma',
},
emptyOutDir: false,
rollupOptions: {
output: {
entryFileNames: swDest
}
}
entryFileNames: swDest,
},
},
},
configFile: false
configFile: false,
}
},
generateBundle: {
order: 'post',
sequential: true,
async handler (_, bundle) {
async handler(_, bundle) {
const assets = Object.keys(bundle)
.filter(name => !/\.map$/.test(name))
.map(name => '/' + name)
.filter((name) => !/\.map$/.test(name))
.map((name) => '/' + name)
config.plugins.push({
name: 'build-sw-env-plugin',
resolveId (id) {
resolveId(id) {
if (id === swEnvName) {
return swEnvNameResolved
}
return null
},
load (id) {
load(id) {
if (id === swEnvNameResolved) {
return getProdSwEnv({ assets })
}
return null
}
},
})
}
},
},
closeBundle: {
order: 'post',
sequential: true,
async handler () {
console.log('Building service worker for production')
async handler() {
console.info('Building service worker for production')
await build(config)
}
}
},
},
}
}
@ -191,9 +191,9 @@ const swMessagesNameResolved = '\0' + swMessagesName
export const swMessagesPlugin = () => {
return {
name: 'sw-messages-plugin',
resolveId (id) {
resolveId(id) {
if (id === swMessagesName) {
Object.values(i18nFiles).forEach(f => {
Object.values(i18nFiles).forEach((f) => {
this.addWatchFile(f)
})
return swMessagesNameResolved
@ -201,11 +201,11 @@ export const swMessagesPlugin = () => {
return null
}
},
async load (id) {
async load(id) {
if (id === swMessagesNameResolved) {
return await getSWMessagesAsText()
}
return null
}
},
}
}

View file

@ -1,22 +1,21 @@
import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with { type: 'json' }
import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with {
type: 'json',
}
import fs from 'fs'
Object.keys(emojis)
.map(k => {
emojis[k].map(e => {
delete e.unicode_version
delete e.emoji_version
delete e.skin_tone_support_unicode_version
})
Object.keys(emojis).map((k) => {
emojis[k].map((e) => {
delete e.unicode_version
delete e.emoji_version
delete e.skin_tone_support_unicode_version
})
})
const res = {}
Object.keys(emojis)
.map(k => {
const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
res[groupId] = emojis[k]
})
Object.keys(emojis).map((k) => {
const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
res[groupId] = emojis[k]
})
console.info('Updating emojis...')
fs.writeFileSync('src/assets/emoji.json', JSON.stringify(res))

View file

@ -1 +0,0 @@
Display counter for status action buttons when they are on the menu

View file

@ -1 +0,0 @@
Added support for Akkoma and IceShrimp.NET backend

View file

@ -1,2 +0,0 @@
Add arithmetic blend ISS function

View file

@ -0,0 +1 @@
Fix HTML attribute parsing for escaped quotes

View file

@ -1 +0,0 @@
Add support for detachable scrollTop button

View file

@ -1 +0,0 @@
Fix bookmark button alignment in the extra actions menu

View file

@ -1 +0,0 @@
Compatibility with stricter CSP (Akkoma backend)

View file

@ -0,0 +1 @@
Fix emojis breaking user bio/description editing

View file

@ -1 +0,0 @@
Synchronized mutes, advanced mute control (regexp, expiry, naming)

View file

@ -1 +0,0 @@
Fix error styling for user profiles

View file

@ -1 +0,0 @@
Cache assets and emojis with service worker

View file

@ -1 +0,0 @@
Indicate currently active V3 theme as a body element class

View file

@ -1 +0,0 @@
Unify show/hide content buttons

57
docker-compose.e2e.yml Normal file
View file

@ -0,0 +1,57 @@
services:
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: pleroma
POSTGRES_PASSWORD: pleroma
POSTGRES_DB: pleroma
healthcheck:
test: ["CMD-SHELL", "pg_isready -U pleroma -d pleroma"]
interval: 2s
timeout: 2s
retries: 30
pleroma:
image: ${PLEROMA_IMAGE:-git.pleroma.social:5050/pleroma/pleroma:stable}
environment:
DB_USER: pleroma
DB_PASS: pleroma
DB_NAME: pleroma
DB_HOST: db
DB_PORT: 5432
DOMAIN: localhost
INSTANCE_NAME: Pleroma E2E
ADMIN_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
NOTIFY_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin}
E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin}
E2E_ADMIN_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
depends_on:
db:
condition: service_healthy
volumes:
- ./docker/pleroma/entrypoint.e2e.sh:/opt/pleroma/entrypoint.e2e.sh:ro
entrypoint: ["/bin/ash", "/opt/pleroma/entrypoint.e2e.sh"]
healthcheck:
# NOTE: "localhost" may resolve to ::1 in some images (IPv6) while Pleroma only
# listens on IPv4 in this container. Use 127.0.0.1 to avoid false negatives.
test: ["CMD-SHELL", "test -f /var/lib/pleroma/.e2e_seeded && wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null || exit 1"]
interval: 5s
timeout: 3s
retries: 60
e2e:
build:
context: .
dockerfile: docker/e2e/Dockerfile.e2e
depends_on:
pleroma:
condition: service_healthy
environment:
CI: "1"
VITE_PROXY_TARGET: http://pleroma:4000
VITE_PROXY_ORIGIN: http://localhost:4000
E2E_BASE_URL: http://localhost:8080
E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin}
E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin}
command: ["yarn", "e2e:pw"]

16
docker/e2e/Dockerfile.e2e Normal file
View file

@ -0,0 +1,16 @@
FROM mcr.microsoft.com/playwright:v1.57.0-jammy
WORKDIR /app
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
RUN npm install -g yarn@1.22.22
COPY package.json yarn.lock ./
RUN yarn --frozen-lockfile
COPY . .
ENV CI=1
CMD ["yarn", "e2e:pw"]

View file

@ -0,0 +1,71 @@
#!/bin/ash
set -eu
SEED_SENTINEL_PATH="/var/lib/pleroma/.e2e_seeded"
CONFIG_OVERRIDE_PATH="/var/lib/pleroma/config.exs"
echo "-- Waiting for database..."
while ! pg_isready -U "${DB_USER:-pleroma}" -d "postgres://${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-pleroma}" -t 1; do
sleep 1s
done
echo "-- Writing E2E config overrides..."
cat > "$CONFIG_OVERRIDE_PATH" <<'EOF'
import Config
config :pleroma, Pleroma.Captcha,
enabled: false
config :pleroma, :instance,
registrations_open: true,
account_activation_required: false,
approval_required: false
EOF
echo "-- Running migrations..."
/opt/pleroma/bin/pleroma_ctl migrate
echo "-- Starting!"
/opt/pleroma/bin/pleroma start &
PLEROMA_PID="$!"
cleanup() {
if [ -n "${PLEROMA_PID:-}" ] && kill -0 "$PLEROMA_PID" 2>/dev/null; then
kill -TERM "$PLEROMA_PID"
wait "$PLEROMA_PID" || true
fi
}
trap cleanup INT TERM
echo "-- Waiting for API..."
api_ok="false"
for _i in $(seq 1 120); do
if wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null 2>&1; then
api_ok="true"
break
fi
sleep 1s
done
if [ "$api_ok" != "true" ]; then
echo "Timed out waiting for Pleroma API to become available"
exit 1
fi
if [ ! -f "$SEED_SENTINEL_PATH" ]; then
if [ -n "${E2E_ADMIN_USERNAME:-}" ] && [ -n "${E2E_ADMIN_PASSWORD:-}" ] && [ -n "${E2E_ADMIN_EMAIL:-}" ]; then
echo "-- Seeding admin user (${E2E_ADMIN_USERNAME})..."
if ! /opt/pleroma/bin/pleroma_ctl user new "$E2E_ADMIN_USERNAME" "$E2E_ADMIN_EMAIL" --admin --password "$E2E_ADMIN_PASSWORD" -y; then
echo "-- User already exists (or creation failed), ensuring admin + confirmed..."
/opt/pleroma/bin/pleroma_ctl user set "$E2E_ADMIN_USERNAME" --admin --confirmed
fi
else
echo "-- Skipping admin seeding (missing E2E_ADMIN_* env)"
fi
touch "$SEED_SENTINEL_PATH"
fi
wait "$PLEROMA_PID"

View file

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

View file

@ -79,7 +79,7 @@ server {
In 99% cases PleromaFE uses [MastoAPI](https://docs.joinmastodon.org/api/) with [Pleroma Extensions](../backend/API/differences_in_mastoapi_responses.md) to fetch the data. The rest is either QvitterAPI leftovers or pleroma-exclusive APIs. QvitterAPI doesn't exactly have documentation and uses different JSON structure and sometimes different parameters and workflows, [this](https://twitter-api.readthedocs.io/en/latest/index.html) could be a good reference though. Some pleroma-exclusive API may still be using QvitterAPI JSON structure.
PleromaFE supports both formats by transforming them into internal format which is basically QvitterAPI one with some additions and renaming. All data is passed trough [Entity Normalizer](https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/src/services/entity_normalizer/entity_normalizer.service.js) which can serve as a reference of API and what's actually used, it's also a host for all the hacks and data transformation.
PleromaFE supports both formats by transforming them into internal format which is basically QvitterAPI one with some additions and renaming. All data is passed trough [Entity Normalizer](https://git.pleroma.social/pleroma/pleroma-fe/src/src/services/entity_normalizer/entity_normalizer.service.js) which can serve as a reference of API and what's actually used, it's also a host for all the hacks and data transformation.
For most part, PleromaFE tries to store all the info it can get in global vuex store - every user and post are passed trough updating mechanism where data is either added or merged with existing data, reactively updating the information throughout UI, so if in newest request user's post counter increased, it will be instantly updated in open user profile cards. This is also used to find users, posts and sometimes to build timelines and/or request parameters.

View file

@ -1,37 +1,34 @@
import vue from "eslint-plugin-vue";
import js from "@eslint/js";
import globals from "globals";
import js from '@eslint/js'
import { defineConfig, globalIgnores } from 'eslint/config'
import vue from 'eslint-plugin-vue'
import globals from 'globals'
export default [
export default defineConfig([
...vue.configs['flat/recommended'],
js.configs.recommended,
globalIgnores(['**/*.js', 'build/', 'dist/', 'config/']),
{
files: ["**/*.js", "**/*.mjs", "**/*.vue"],
ignores: ["build/*.js", "config/*.js"],
files: ['src/**/*.vue'],
plugins: { js },
extends: ['js/recommended'],
languageOptions: {
ecmaVersion: 2024,
sourceType: "module",
sourceType: 'module',
parserOptions: {
parser: "@babel/eslint-parser",
parser: '@babel/eslint-parser',
},
globals: {
...globals.browser,
...globals.vitest,
...globals.chai,
...globals.commonjs,
...globals.serviceworker
}
...globals.serviceworker,
},
},
rules: {
'arrow-parens': 0,
'generator-star-spacing': 0,
'no-debugger': 0,
'vue/require-prop-types': 0,
'vue/multi-word-component-names': 0,
}
}
]
},
},
])

View file

@ -11,14 +11,12 @@
<link rel="preload" href="/static/pleromatan_apology_fox_small.webp" as="image" />
<!-- putting styles here to avoid having to wait for styles to load up -->
<link rel="stylesheet" id="splashscreen" href="/static/splash.css" />
<link rel="stylesheet" id="pleroma-eager-styles" type="text/css" href="/static/empty.css" />
<link rel="stylesheet" id="pleroma-lazy-styles" type="text/css" href="/static/empty.css" />
<link rel="stylesheet" id="theme-holder" type="text/css" href="/static/empty.css" />
<link rel="stylesheet" id="custom-styles-holder" type="text/css" href="/static/empty.css" />
<!--server-generated-meta-->
</head>
<body>
<noscript>To use Pleroma, please enable JavaScript.</noscript>
<div id="splash">
<div id="splash" class="initial-hidden">
<!-- we are hiding entire graphic so no point showing credit -->
<div aria-hidden="true" id="splash-credit">
Art by pipivovott

View file

@ -1,8 +1,8 @@
{
"name": "pleroma_fe",
"version": "2.7.1",
"version": "2.10.1",
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>",
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/src/CONTRIBUTORS.md>",
"private": false,
"scripts": {
"dev": "node build/update-emoji.js && vite dev",
@ -10,36 +10,39 @@
"unit": "node build/update-emoji.js && vitest --run",
"unit-ci": "node build/update-emoji.js && vitest --run --browser.headless",
"unit:watch": "node build/update-emoji.js && vitest",
"e2e": "node test/e2e/runner.js",
"e2e:pw": "playwright test --config test/e2e-playwright/playwright.config.mjs",
"e2e": "sh ./tools/e2e/run.sh",
"test": "yarn run unit && yarn run e2e",
"stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'",
"lint": "eslint src test/unit/specs test/e2e/specs",
"lint-fix": "eslint --fix src test/unit/specs test/e2e/specs"
"ci-biome": "yarn exec biome check",
"ci-eslint": "yarn exec eslint",
"ci-stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'",
"lint": "yarn ci-biome; yarn ci-eslint; yarn ci-stylelint",
"lint-fix": "yarn exec eslint -- --fix; yarn exec stylelint '**/*.scss' '**/*.vue' --fix; biome check --write"
},
"dependencies": {
"@babel/runtime": "7.27.1",
"@babel/runtime": "7.28.4",
"@chenfengyuan/vue-qrcode": "2.0.0",
"@fortawesome/fontawesome-svg-core": "6.7.2",
"@fortawesome/free-regular-svg-icons": "6.7.2",
"@fortawesome/free-solid-svg-icons": "6.7.2",
"@fortawesome/vue-fontawesome": "3.0.8",
"@floatingghost/pinch-zoom-element": "1.3.1",
"@fortawesome/fontawesome-svg-core": "7.1.0",
"@fortawesome/free-regular-svg-icons": "7.1.0",
"@fortawesome/free-solid-svg-icons": "7.1.0",
"@fortawesome/vue-fontawesome": "3.1.2",
"@kazvmoe-infra/pinch-zoom-element": "1.3.0",
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
"@ruffle-rs/ruffle": "0.1.0-nightly.2025.1.13",
"@ruffle-rs/ruffle": "0.1.0-nightly.2025.6.22",
"@vuelidate/core": "2.0.3",
"@vuelidate/validators": "2.0.4",
"@web3-storage/parse-link-header": "^3.1.0",
"body-scroll-lock": "3.1.5",
"chromatism": "3.0.0",
"click-outside-vue3": "4.0.1",
"cropperjs": "2.0.0",
"cropperjs": "2.0.1",
"escape-html": "1.0.3",
"globals": "^16.0.0",
"hash-sum": "^2.0.0",
"js-cookie": "3.0.5",
"localforage": "1.10.0",
"parse-link-header": "2.0.0",
"phoenix": "1.7.21",
"phoenix": "1.8.1",
"pinia": "^3.0.0",
"punycode.js": "2.3.1",
"qrcode": "1.5.4",
@ -47,63 +50,64 @@
"url": "0.11.4",
"utf8": "3.0.0",
"uuid": "11.1.0",
"vue": "3.5.17",
"vue": "3.5.22",
"vue-i18n": "11",
"vue-router": "4.5.1",
"vue-router": "4.6.4",
"vue-virtual-scroller": "^2.0.0-beta.7",
"vuex": "4.1.0"
},
"devDependencies": {
"@babel/core": "7.27.1",
"@babel/eslint-parser": "7.27.1",
"@babel/plugin-transform-runtime": "7.27.1",
"@babel/preset-env": "7.27.2",
"@babel/register": "7.27.1",
"@babel/core": "7.28.5",
"@babel/eslint-parser": "7.28.5",
"@babel/plugin-transform-runtime": "7.28.5",
"@babel/preset-env": "7.28.5",
"@babel/register": "7.28.3",
"@biomejs/biome": "2.3.11",
"@pinia/testing": "1.0.3",
"@ungap/event-target": "0.2.4",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"@vitest/browser": "^3.0.7",
"@vitest/ui": "^3.0.7",
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
"@vue/babel-plugin-jsx": "1.4.0",
"@vue/compiler-sfc": "3.5.17",
"@vue/babel-plugin-jsx": "1.5.0",
"@vue/compiler-sfc": "3.5.22",
"@vue/test-utils": "2.4.6",
"autoprefixer": "10.4.21",
"babel-plugin-lodash": "3.3.4",
"chai": "5.2.0",
"chalk": "5.4.1",
"chai": "5.3.3",
"chalk": "5.6.2",
"chromedriver": "135.0.4",
"connect-history-api-fallback": "2.0.0",
"cross-spawn": "7.0.6",
"custom-event-polyfill": "1.0.7",
"eslint": "9.26.0",
"vue-eslint-parser": "10.1.3",
"eslint": "9.39.2",
"eslint-config-standard": "17.1.0",
"eslint-formatter-friendly": "7.0.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-n": "17.18.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-n": "17.23.1",
"eslint-plugin-promise": "7.2.1",
"eslint-plugin-vue": "10.1.0",
"eslint-plugin-vue": "10.6.2",
"eventsource-polyfill": "0.9.6",
"express": "5.1.0",
"function-bind": "1.1.2",
"http-proxy-middleware": "3.0.5",
"iso-639-1": "3.1.5",
"lodash": "4.17.21",
"msw": "2.10.2",
"nightwatch": "3.12.1",
"playwright": "1.52.0",
"postcss": "8.5.3",
"msw": "2.10.5",
"nightwatch": "3.12.2",
"playwright": "1.57.0",
"postcss": "8.5.6",
"postcss-html": "^1.5.0",
"postcss-scss": "^4.0.6",
"sass": "1.89.2",
"sass": "1.93.2",
"selenium-server": "3.141.59",
"semver": "7.7.2",
"semver": "7.7.3",
"serve-static": "2.2.0",
"shelljs": "0.10.0",
"sinon": "20.0.0",
"sinon-chai": "4.0.0",
"stylelint": "16.19.1",
"sinon-chai": "4.0.1",
"stylelint": "16.25.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^16.0.0",
"stylelint-config-recommended-scss": "^14.0.0",
@ -112,7 +116,8 @@
"vite": "^6.1.0",
"vite-plugin-eslint2": "^5.0.3",
"vite-plugin-stylelint": "^6.0.0",
"vitest": "^3.0.7"
"vitest": "^3.0.7",
"vue-eslint-parser": "10.2.0"
},
"type": "module",
"engines": {

View file

@ -1,7 +1,5 @@
import autoprefixer from 'autoprefixer'
export default {
plugins: [
autoprefixer
]
plugins: [autoprefixer],
}

View file

@ -1 +0,0 @@
// nothing here //

View file

@ -1,6 +1,26 @@
{
"pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
"pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
"pleroma-dark": [
"Pleroma Dark",
"#121a24",
"#182230",
"#b9b9ba",
"#d8a070",
"#d31014",
"#0fa00f",
"#0095ff",
"#ffa500"
],
"pleroma-light": [
"Pleroma Light",
"#f2f4f6",
"#dbe0e8",
"#304055",
"#f86f0f",
"#d31014",
"#0fa00f",
"#0095ff",
"#ffa500"
],
"classic-dark": {
"name": "Classic Dark",
"bg": "#161c20",
@ -12,8 +32,28 @@
"cBlue": "#0095ff",
"cOrange": "#ffa500"
},
"bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
"pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
"bird": [
"Bird",
"#f8fafd",
"#e6ecf0",
"#14171a",
"#0084b8",
"#e0245e",
"#17bf63",
"#1b95e0",
"#fab81e"
],
"pleroma-amoled": [
"Pleroma Dark AMOLED",
"#000000",
"#111111",
"#b0b0b1",
"#d8a070",
"#aa0000",
"#0fa00f",
"#0095ff",
"#d59500"
],
"tomorrow-night": {
"name": "Tomorrow Night",
"bg": "#1d1f21",
@ -36,8 +76,28 @@
"cGreen": "#50FA7B",
"cOrange": "#FFB86C"
},
"ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
"monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ],
"ir-black": [
"Ir Black",
"#000000",
"#242422",
"#b5b3aa",
"#ff6c60",
"#FF6C60",
"#A8FF60",
"#96CBFE",
"#FFFFB6"
],
"monokai": [
"Monokai",
"#272822",
"#383830",
"#f8f8f2",
"#f92672",
"#F92672",
"#a6e22e",
"#66d9ef",
"#f4bf75"
],
"purple-stream": {
"name": "Purple stream",
"bg": "#17171A",

View file

@ -5,15 +5,14 @@ body {
#splash {
--scale: 1;
width: 100vw;
height: 100vh;
display: grid;
grid-template-rows: auto;
grid-template-columns: auto;
align-content: center;
align-items: center;
justify-content: center;
justify-items: center;
place-items: center;
flex-direction: column;
background: #0f161e;
font-family: sans-serif;
@ -21,13 +20,20 @@ body {
position: absolute;
z-index: 9999;
font-size: calc(1vw + 1vh + 1vmin);
opacity: 1;
transition: opacity 500ms ease-out 2s;
}
#splash.hidden,
#splash.initial-hidden {
opacity: 0;
}
#splash-credit {
position: absolute;
font-size: 14px;
bottom: 16px;
right: 16px;
font-size: 1em;
bottom: 1em;
right: 1em;
}
#splash-container {
@ -59,16 +65,17 @@ body {
z-index: 2;
grid-template-rows: repeat(8, 1fr);
grid-template-columns: repeat(5, 1fr);
grid-template-areas: "P P . L L"
"P P . L L"
"P P . L L"
"P P . L L"
"P P . . ."
"P P . . ."
"P P . E E"
"P P . E E";
grid-template-areas:
"P P . L L"
"P P . L L"
"P P . L L"
"P P . L L"
"P P . . ."
"P P . . ."
"P P . E E"
"P P . E E";
--logoChunkSize: calc(2em * 0.5 * var(--scale))
--logoChunkSize: calc(2em * 0.5 * var(--scale));
}
.chunk {
@ -78,7 +85,7 @@ body {
#chunk-P {
grid-area: P;
border-top-left-radius: calc(var(--logoChunkSize) / 2);
border-top-left-radius: calc(var(--logoChunkSize) / 2);
}
#chunk-L {

View file

@ -1,6 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
"extends": ["config:base"]
}

View file

@ -1,34 +1,48 @@
import UserPanel from './components/user_panel/user_panel.vue'
import NavPanel from './components/nav_panel/nav_panel.vue'
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
import ShoutPanel from './components/shout_panel/shout_panel.vue'
import MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
import MobileNav from './components/mobile_nav/mobile_nav.vue'
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import { getOrCreateServiceWorker } from './services/sw/sw'
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
import { mapGetters } from 'vuex'
import { defineAsyncComponent } from 'vue'
import { useShoutStore } from './stores/shout'
import { useInterfaceStore } from './stores/interface'
import { throttle } from 'lodash'
import { mapState } from 'pinia'
import { defineAsyncComponent, toValue } from 'vue'
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
import FeaturesPanel from './components/features_panel/features_panel.vue'
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
import MediaModal from './components/media_modal/media_modal.vue'
import MobileNav from './components/mobile_nav/mobile_nav.vue'
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
import NavPanel from './components/nav_panel/nav_panel.vue'
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
import ShoutPanel from './components/shout_panel/shout_panel.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
import UserPanel from './components/user_panel/user_panel.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
import { getOrCreateServiceWorker } from './services/sw/sw'
import { windowHeight, windowWidth } from './services/window_utils/window_utils'
import { useEmojiStore } from 'src/stores/emoji.js'
import { useI18nStore } from 'src/stores/i18n.js'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useShoutStore } from 'src/stores/shout.js'
import messages from 'src/i18n/messages'
import localeService from 'src/services/locale/locale.service.js'
// Helper to unwrap reactive proxies
window.toValue = toValue
export default {
name: 'app',
components: {
UserPanel,
NavPanel,
Notifications: defineAsyncComponent(() => import('./components/notifications/notifications.vue')),
Notifications: defineAsyncComponent(
() => import('./components/notifications/notifications.vue'),
),
InstanceSpecificPanel,
FeaturesPanel,
WhoToFollowPanel,
@ -38,150 +52,193 @@ export default {
MobilePostStatusButton,
MobileNav,
DesktopNav,
SettingsModal: defineAsyncComponent(() => import('./components/settings_modal/settings_modal.vue')),
UpdateNotification: defineAsyncComponent(() => import('./components/update_notification/update_notification.vue')),
SettingsModal: defineAsyncComponent(
() => import('./components/settings_modal/settings_modal.vue'),
),
UpdateNotification: defineAsyncComponent(
() => import('./components/update_notification/update_notification.vue'),
),
UserReportingModal,
PostStatusModal,
EditStatusModal,
StatusHistoryModal,
GlobalNoticeList
GlobalNoticeList,
},
data: () => ({
mobileActivePanel: 'timeline'
mobileActivePanel: 'timeline',
}),
watch: {
themeApplied () {
themeApplied() {
this.removeSplash()
},
currentTheme () {
currentTheme() {
this.setThemeBodyClass()
},
layoutType () {
layoutType() {
document.getElementById('modal').classList = ['-' + this.layoutType]
}
},
},
created () {
created() {
// Load the locale from the storage
const val = this.$store.getters.mergedConfig.interfaceLanguage
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
const value = useMergedConfigStore().mergedConfig.interfaceLanguage
useI18nStore().setLanguage(value)
useEmojiStore().loadUnicodeEmojiData(value)
document.getElementById('modal').classList = ['-' + this.layoutType]
// Create bound handlers
this.updateScrollState = throttle(this.scrollHandler, 200)
this.updateMobileState = throttle(this.resizeHandler, 200)
},
mounted () {
mounted() {
window.addEventListener('resize', this.updateMobileState)
this.scrollParent.addEventListener('scroll', this.updateScrollState)
if (useInterfaceStore().themeApplied) {
if (this.themeApplied) {
this.setThemeBodyClass()
this.removeSplash()
}
getOrCreateServiceWorker()
},
unmounted () {
unmounted() {
window.removeEventListener('resize', this.updateMobileState)
this.scrollParent.removeEventListener('scroll', this.updateScrollState)
},
computed: {
themeApplied () {
return useInterfaceStore().themeApplied
},
currentTheme () {
if (useInterfaceStore().styleDataUsed) {
const styleMeta = useInterfaceStore().styleDataUsed.find(x => x.component === '@meta')
currentTheme() {
if (this.styleDataUsed) {
const styleMeta = this.styleDataUsed.find(
(x) => x.component === '@meta',
)
if (styleMeta !== undefined) {
return styleMeta.directives.name.replaceAll(" ", "-").toLowerCase()
return styleMeta.directives.name.replaceAll(' ', '-').toLowerCase()
}
}
return 'stock'
},
layoutModalClass () {
layoutModalClass() {
return '-' + this.layoutType
},
classes () {
classes() {
return [
{
'-reverse': this.reverseLayout,
'-no-sticky-headers': this.noSticky,
'-has-new-post-button': this.newPostButtonShown
'-has-new-post-button': this.newPostButtonShown,
},
'-' + this.layoutType
'-' + this.layoutType,
]
},
navClasses () {
const { navbarColumnStretch } = this.$store.getters.mergedConfig
navClasses() {
const { navbarColumnStretch } = useMergedConfigStore().mergedConfig
return [
'-' + this.layoutType,
...(navbarColumnStretch ? ['-column-stretch'] : [])
...(navbarColumnStretch ? ['-column-stretch'] : []),
]
},
currentUser () { return this.$store.state.users.currentUser },
userBackground () { return this.currentUser.background_image },
instanceBackground () {
return this.mergedConfig.hideInstanceWallpaper
? null
: this.$store.state.instance.background
currentUser() {
return this.$store.state.users.currentUser
},
background () { return this.userBackground || this.instanceBackground },
bgStyle () {
userBackground() {
return this.currentUser.background_image
},
instanceBackground() {
return useMergedConfigStore().mergedConfig.hideInstanceWallpaper
? null
: this.instanceBackgroundUrl
},
background() {
return this.userBackground || this.instanceBackground
},
bgStyle() {
if (this.background) {
return {
'--body-background-image': `url(${this.background})`
'--body-background-image': `url(${this.background})`,
}
}
},
shout () { return useShoutStore().joined },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
!this.$store.getters.mergedConfig.hideISP &&
this.$store.state.instance.instanceSpecificPanelContent
shoutJoined() {
return useShoutStore().joined
},
isChats () {
isChats() {
return this.$route.name === 'chat' || this.$route.name === 'chats'
},
isListEdit () {
isListEdit() {
return this.$route.name === 'lists-edit'
},
newPostButtonShown () {
newPostButtonShown() {
if (this.isChats) return false
if (this.isListEdit) return false
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
return (
useMergedConfigStore().mergedConfig.alwaysShowNewPostButton ||
this.layoutType === 'mobile'
)
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
editingAvailable () { return this.$store.state.instance.editingAvailable },
shoutboxPosition () {
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false
shoutboxPosition() {
return (
useMergedConfigStore().mergedConfig.alwaysShowNewPostButton || false
)
},
hideShoutbox () {
return this.$store.getters.mergedConfig.hideShoutbox
hideShoutbox() {
return useMergedConfigStore().mergedConfig.hideShoutbox
},
layoutType () { return useInterfaceStore().layoutType },
privateMode () { return this.$store.state.instance.private },
reverseLayout () {
const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig
reverseLayout() {
const { thirdColumnMode, sidebarRight: reverseSetting } =
useMergedConfigStore().mergedConfig
if (this.layoutType !== 'wide') {
return reverseSetting
} else {
return thirdColumnMode === 'notifications' ? reverseSetting : !reverseSetting
return thirdColumnMode === 'notifications'
? reverseSetting
: !reverseSetting
}
},
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars },
scrollParent () { return window; /* this.$refs.appContentRef */ },
...mapGetters(['mergedConfig'])
noSticky() {
return useMergedConfigStore().mergedConfig.disableStickyHeaders
},
showScrollbars() {
return useMergedConfigStore().mergedConfig.showScrollbars
},
scrollParent() {
return window /* this.$refs.appContentRef */
},
showInstanceSpecificPanel() {
return (
this.instanceSpecificPanelPresent &&
!useMergedConfigStore().mergedConfig.hideISP
)
},
...mapState(useMergedConfigStore, ['mergedConfig']),
...mapState(useInterfaceStore, [
'themeApplied',
'styleDataUsed',
'layoutType',
]),
...mapState(useInstanceStore, ['styleDataUsed']),
...mapState(useInstanceCapabilitiesStore, [
'suggestionsEnabled',
'editingAvailable',
]),
...mapState(useInstanceStore, {
instanceBackgroundUrl: (store) => store.instanceIdentity.background,
showFeaturesPanel: (store) => store.instanceIdentity.showFeaturesPanel,
instanceSpecificPanelPresent: (store) =>
store.instanceIdentity.showInstanceSpecificPanel &&
store.instanceIdentity.instanceSpecificPanelContent,
}),
},
methods: {
resizeHandler () {
resizeHandler() {
useInterfaceStore().setLayoutWidth(windowWidth())
useInterfaceStore().setLayoutHeight(windowHeight())
},
scrollHandler () {
const scrollPosition = this.scrollParent === window ? window.scrollY : this.scrollParent.scrollTop
scrollHandler() {
const scrollPosition =
this.scrollParent === window
? window.scrollY
: this.scrollParent.scrollTop
if (scrollPosition != 0) {
this.$refs.appContentRef.classList.add(['-scrolled'])
@ -189,10 +246,10 @@ export default {
this.$refs.appContentRef.classList.remove(['-scrolled'])
}
},
setThemeBodyClass () {
setThemeBodyClass() {
const themeName = this.currentTheme
const classList = Array.from(document.body.classList)
const oldTheme = classList.filter(c => c.startsWith('theme-'))
const oldTheme = classList.filter((c) => c.startsWith('theme-'))
if (themeName !== null && themeName !== '') {
const newTheme = `theme-${themeName.toLowerCase()}`
@ -208,14 +265,19 @@ export default {
document.body.classList.remove(...oldTheme)
}
},
removeSplash () {
document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4))
removeSplash() {
document.querySelector('#status').textContent = this.$t(
'splash.fun_' + Math.ceil(Math.random() * 4),
)
const splashscreenRoot = document.querySelector('#splash')
splashscreenRoot.addEventListener('transitionend', () => {
splashscreenRoot.remove()
})
setTimeout(() => {
splashscreenRoot.remove() // forcibly remove it, should fix my plasma browser widget t. HJ
}, 600)
splashscreenRoot.classList.add('hidden')
document.querySelector('#app').classList.remove('hidden')
}
}
},
},
}

View file

@ -3,7 +3,7 @@
@use "panel";
@import '@fortawesome/fontawesome-svg-core/styles.css';
@import '@floatingghost/pinch-zoom-element/dist/pinch-zoom.css';
@import '@kazvmoe-infra/pinch-zoom-element/dist/pinch-zoom.css';
:root {
--status-margin: 0.75em;
@ -21,7 +21,7 @@
}
html {
font-size: var(--textSize, 14px);
font-size: var(--textSize, 1rem);
--navbar-height: var(--navbarSize, 3.5rem);
--emoji-size: var(--emojiSize, 32px);
@ -382,6 +382,10 @@ nav {
font-family: sans-serif;
font-family: var(--font);
&.-transparent {
backdrop-filter: blur(0.125em) contrast(60%);
}
&::-moz-focus-inner {
border: none;
}
@ -509,6 +513,12 @@ nav {
}
}
label {
&.-disabled {
color: var(--textFaint);
}
}
input,
textarea {
border: none;
@ -525,6 +535,10 @@ textarea {
height: unset;
}
&::placeholder {
color: var(--textFaint)
}
--_padding: 0.5em;
border: none;
@ -545,6 +559,10 @@ textarea {
&[disabled="disabled"],
&.disabled {
cursor: not-allowed;
color: var(--textFaint);
/* stylelint-disable-next-line declaration-no-important */
background-color: transparent !important;
}
&[type="range"] {
@ -570,6 +588,8 @@ textarea {
& + label::before {
opacity: 0.5;
}
background-color: var(--background);
}
+ label::before {
@ -669,7 +689,8 @@ option {
list-style: none;
display: grid;
grid-auto-flow: row dense;
grid-template-columns: 1fr 1fr;
grid-template-columns: repeat(auto-fit, minmax(20em, 1fr));
grid-gap: 0.5em;
li {
border: 1px solid var(--border);
@ -679,11 +700,6 @@ option {
}
}
.btn-block {
display: block;
width: 100%;
}
.btn-group {
position: relative;
display: inline-flex;
@ -695,7 +711,6 @@ option {
--_roundness-right: 0;
position: relative;
flex: 1 1 auto;
}
> *:first-child,
@ -742,17 +757,15 @@ option {
}
&.-dot {
min-height: 8px;
max-height: 8px;
min-width: 8px;
max-width: 8px;
padding: 0;
min-height: 0.6em;
max-height: 0.6em;
min-width: 0.6em;
max-width: 0.6em;
left: calc(50% + 0.5em);
top: calc(50% - 1em);
line-height: 0;
font-size: 0;
left: calc(50% - 4px);
top: calc(50% - 4px);
margin-left: 6px;
margin-top: -6px;
padding: 0;
margin: 0;
}
&.-counter {
@ -783,21 +796,18 @@ option {
color: var(--text);
}
.visibility-notice {
padding: 0.5em;
border: 1px solid var(--textFaint);
border-radius: var(--roundness);
}
.notice-dismissible {
padding-right: 4rem;
position: relative;
display: flex;
padding: 0.75em 1em;
align-items: baseline;
line-height: 1.5;
span {
display: block;
flex: 1 1 auto;
}
.dismiss {
position: absolute;
top: 0;
right: 0;
padding: 0.5em;
color: inherit;
}
}
@ -936,12 +946,7 @@ option {
#splash {
pointer-events: none;
transition: opacity 0.5s;
opacity: 1;
&.hidden {
opacity: 0;
}
// transition: opacity 0.5s;
#status {
&.css-ok {
@ -1080,7 +1085,7 @@ option {
scale: 1.0063 0.9938;
translate: 0 -10%;
transform: rotateZ(var(--defaultZ));
animation-timing-function: ease-in-ou;
animation-timing-function: ease-in-out;
}
90% {

View file

@ -60,8 +60,8 @@
/>
</div>
<MediaModal />
<shout-panel
v-if="currentUser && shout && !hideShoutbox"
<ShoutPanel
v-if="currentUser && !hideShoutbox && shoutJoined"
:floating="true"
class="floating-shout mobile-hidden"
:class="{ '-left': shoutboxPosition }"

View file

@ -1,29 +1,48 @@
/* global process */
import vClickOutside from 'click-outside-vue3'
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import vClickOutside from 'click-outside-vue3'
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
import { config } from '@fortawesome/fontawesome-svg-core';
import { config } from '@fortawesome/fontawesome-svg-core'
import {
FontAwesomeIcon,
FontAwesomeLayers,
} from '@fortawesome/vue-fontawesome'
config.autoAddCss = false
import App from '../App.vue'
import routes from './routes'
import VBodyScrollLock from 'src/directives/body_scroll_lock'
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { applyConfig } from '../services/style_setter/style_setter.js'
import FaviconService from '../services/favicon_service/favicon_service.js'
import { applyStyleConfig } from '../services/style_setter/style_setter.js'
import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
import {
windowHeight,
windowWidth,
} from '../services/window_utils/window_utils'
import routes from './routes'
import { useOAuthStore } from 'src/stores/oauth'
import { useI18nStore } from 'src/stores/i18n'
import { useInterfaceStore } from 'src/stores/interface'
import { useAnnouncementsStore } from 'src/stores/announcements'
import { useAuthFlowStore } from 'src/stores/auth_flow'
import { useEmojiStore } from 'src/stores/emoji.js'
import { useI18nStore } from 'src/stores/i18n'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useLocalConfigStore } from 'src/stores/local_config.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useOAuthStore } from 'src/stores/oauth'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
import VBodyScrollLock from 'src/directives/body_scroll_lock'
import {
INSTANCE_DEFAULT_CONFIG_DEFINITIONS,
INSTANCE_IDENTITY_DEFAULT_DEFINITIONS,
} from 'src/modules/default_config_state.js'
let staticInitialResults = null
@ -32,7 +51,9 @@ const parsedInitialResults = () => {
return null
}
if (!staticInitialResults) {
staticInitialResults = JSON.parse(document.getElementById('initial-results').textContent)
staticInitialResults = JSON.parse(
document.getElementById('initial-results').textContent,
)
}
return staticInitialResults
}
@ -54,7 +75,7 @@ const preloadFetch = async (request) => {
return {
ok: true,
json: () => requestData,
text: () => requestData
text: () => requestData,
}
}
@ -63,20 +84,38 @@ const getInstanceConfig = async ({ store }) => {
const res = await preloadFetch('/api/v1/instance')
if (res.ok) {
const data = await res.json()
const textlimit = data.max_toot_chars
const textLimit = data.max_toot_chars
const vapidPublicKey = data.pleroma.vapid_public_key
store.dispatch('setInstanceOption', { name: 'pleromaExtensionsAvailable', value: data.pleroma })
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma?.metadata.birthday_required })
store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma?.metadata.birthday_min_age || 0 })
useInstanceCapabilitiesStore().set(
'pleromaExtensionsAvailable',
data.pleroma,
)
useInstanceStore().set({
path: 'limits.textLimit',
value: textLimit,
})
useInstanceStore().set({
path: 'accountApprovalRequired',
value: data.approval_required,
})
useInstanceStore().set({
path: 'birthdayRequired',
value: !!data.pleroma?.metadata.birthday_required,
})
useInstanceStore().set({
path: 'birthdayMinAge',
value: data.pleroma?.metadata.birthday_min_age || 0,
})
if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
useInstanceStore().set({
path: 'vapidPublicKey',
value: vapidPublicKey,
})
}
} else {
throw (res)
throw res
}
} catch (error) {
console.error('Could not load instance config, potentially fatal')
@ -93,10 +132,12 @@ const getBackendProvidedConfig = async () => {
const data = await res.json()
return data.pleroma_fe
} else {
throw (res)
throw res
}
} catch (error) {
console.error('Could not load backend-provided frontend config, potentially fatal')
console.error(
'Could not load backend-provided frontend config, potentially fatal',
)
console.error(error)
}
}
@ -107,7 +148,7 @@ const getStaticConfig = async () => {
if (res.ok) {
return res.json()
} else {
throw (res)
throw res
}
} catch (error) {
console.warn('Failed to load static/config.json, continuing without it.')
@ -129,51 +170,21 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
config = Object.assign({}, staticConfig, apiConfig)
}
const copyInstanceOption = (name) => {
store.dispatch('setInstanceOption', { name, value: config[name] })
}
Object.keys(INSTANCE_IDENTITY_DEFAULT_DEFINITIONS).forEach((source) =>
useInstanceStore().set({
value: config[source],
path: `instanceIdentity.${source}`,
}),
)
copyInstanceOption('theme')
copyInstanceOption('style')
copyInstanceOption('palette')
copyInstanceOption('embeddedToS')
copyInstanceOption('nsfwCensorImage')
copyInstanceOption('background')
copyInstanceOption('hidePostStats')
copyInstanceOption('hideBotIndication')
copyInstanceOption('hideUserStats')
copyInstanceOption('hideFilteredStatuses')
copyInstanceOption('logo')
Object.keys(INSTANCE_DEFAULT_CONFIG_DEFINITIONS).forEach((source) =>
useInstanceStore().set({
value: config[source],
path: `prefsStorage.${source}`,
}),
)
store.dispatch('setInstanceOption', {
name: 'logoMask',
value: typeof config.logoMask === 'undefined'
? true
: config.logoMask
})
store.dispatch('setInstanceOption', {
name: 'logoMargin',
value: typeof config.logoMargin === 'undefined'
? 0
: config.logoMargin
})
copyInstanceOption('logoLeft')
useAuthFlowStore().setInitialStrategy(config.loginMethod)
copyInstanceOption('redirectRootNoLogin')
copyInstanceOption('redirectRootLogin')
copyInstanceOption('showInstanceSpecificPanel')
copyInstanceOption('minimalScopesMode')
copyInstanceOption('hideMutedPosts')
copyInstanceOption('collapseMessageWithSubject')
copyInstanceOption('scopeCopy')
copyInstanceOption('subjectLineBehavior')
copyInstanceOption('postContentType')
copyInstanceOption('alwaysShowSubjectInput')
copyInstanceOption('showFeaturesPanel')
copyInstanceOption('hideSitename')
copyInstanceOption('sidebarRight')
}
const getTOS = async ({ store }) => {
@ -181,9 +192,9 @@ const getTOS = async ({ store }) => {
const res = await window.fetch('/static/terms-of-service.html')
if (res.ok) {
const html = await res.text()
store.dispatch('setInstanceOption', { name: 'tos', value: html })
useInstanceStore().set({ path: 'instanceIdentity.tos', value: html })
} else {
throw (res)
throw res
}
} catch (e) {
console.warn("Can't load TOS\n", e)
@ -195,9 +206,12 @@ const getInstancePanel = async ({ store }) => {
const res = await preloadFetch('/instance/panel.html')
if (res.ok) {
const html = await res.text()
store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html })
useInstanceStore().set({
path: 'instanceIdentity.instanceSpecificPanelContent',
value: html,
})
} else {
throw (res)
throw res
}
} catch (e) {
console.warn("Can't load instance panel\n", e)
@ -209,25 +223,27 @@ const getStickers = async ({ store }) => {
const res = await window.fetch('/static/stickers.json')
if (res.ok) {
const values = await res.json()
const stickers = (await Promise.all(
Object.entries(values).map(async ([name, path]) => {
const resPack = await window.fetch(path + 'pack.json')
let meta = {}
if (resPack.ok) {
meta = await resPack.json()
}
return {
pack: name,
path,
meta
}
})
)).sort((a, b) => {
const stickers = (
await Promise.all(
Object.entries(values).map(async ([name, path]) => {
const resPack = await window.fetch(path + 'pack.json')
let meta = {}
if (resPack.ok) {
meta = await resPack.json()
}
return {
pack: name,
path,
meta,
}
}),
)
).sort((a, b) => {
return a.meta.title.localeCompare(b.meta.title)
})
store.dispatch('setInstanceOption', { name: 'stickers', value: stickers })
useEmojiStore().setStickers(stickers)
} else {
throw (res)
throw res
}
} catch (e) {
console.warn("Can't load stickers\n", e)
@ -237,13 +253,19 @@ const getStickers = async ({ store }) => {
const getAppSecret = async ({ store }) => {
const oauth = useOAuthStore()
if (oauth.userToken) {
store.commit('setBackendInteractor', backendInteractorService(oauth.getToken))
store.commit(
'setBackendInteractor',
backendInteractorService(oauth.getToken),
)
}
}
const resolveStaffAccounts = ({ store, accounts }) => {
const nicknames = accounts.map(uri => uri.split('/').pop())
store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
const nicknames = accounts.map((uri) => uri.split('/').pop())
useInstanceStore().set({
path: 'staffAccounts',
value: nicknames,
})
}
const getNodeInfo = async ({ store }) => {
@ -254,76 +276,165 @@ const getNodeInfo = async ({ store }) => {
const data = await res.json()
const metadata = data.metadata
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: 'safeDM', value: features.includes('safe_dm_mentions') })
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
store.dispatch('setInstanceOption', {
name: 'pleromaCustomEmojiReactionsAvailable',
value:
features.includes('pleroma_custom_emoji_reactions') ||
features.includes('custom_emoji_reactions')
useInstanceStore().set({
path: 'name',
value: metadata.nodeName,
})
store.dispatch('setInstanceOption', { name: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') })
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
store.dispatch('setInstanceOption', { name: 'localBubbleInstances', value: metadata.localBubbleInstances ?? [] })
useInstanceStore().set({
path: 'registrationOpen',
value: data.openRegistrations,
})
useInstanceCapabilitiesStore().set(
'mediaProxyAvailable',
features.includes('media_proxy'),
)
useInstanceCapabilitiesStore().set(
'safeDM',
features.includes('safe_dm_mentions'),
)
useInstanceCapabilitiesStore().set(
'shoutAvailable',
features.includes('chat'),
)
useInstanceCapabilitiesStore().set(
'pleromaChatMessagesAvailable',
features.includes('pleroma_chat_messages'),
)
useInstanceCapabilitiesStore().set(
'pleromaCustomEmojiReactionsAvailable',
features.includes('pleroma_custom_emoji_reactions') ||
features.includes('custom_emoji_reactions'),
)
useInstanceCapabilitiesStore().set(
'pleromaBookmarkFoldersAvailable',
features.includes('pleroma:bookmark_folders'),
)
useInstanceCapabilitiesStore().set(
'gopherAvailable',
features.includes('gopher'),
)
useInstanceCapabilitiesStore().set(
'pollsAvailable',
features.includes('polls'),
)
useInstanceCapabilitiesStore().set(
'editingAvailable',
features.includes('editing'),
)
useInstanceCapabilitiesStore().set(
'mailerEnabled',
metadata.mailerEnabled,
)
useInstanceCapabilitiesStore().set(
'quotingAvailable',
features.includes('quote_posting'),
)
useInstanceCapabilitiesStore().set(
'groupActorAvailable',
features.includes('pleroma:group_actors'),
)
useInstanceCapabilitiesStore().set(
'blockExpiration',
features.includes('pleroma:block_expiration'),
)
useInstanceStore().set({
path: 'localBubbleInstances',
value: metadata.localBubbleInstances ?? [],
})
useInstanceCapabilitiesStore().set(
'localBubble',
(metadata.localBubbleInstances ?? []).length > 0,
)
useInstanceStore().set({
path: 'limits.pollLimits',
value: metadata.pollLimits,
})
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 })
useInstanceStore().set({
path: 'limits.uploadlimit',
value: parseInt(uploadLimits.general),
})
useInstanceStore().set({
path: 'limits.avatarlimit',
value: parseInt(uploadLimits.avatar),
})
useInstanceStore().set({
path: 'limits.backgroundlimit',
value: parseInt(uploadLimits.background),
})
useInstanceStore().set({
path: 'limits.bannerlimit',
value: parseInt(uploadLimits.banner),
})
useInstanceStore().set({
path: 'limits.fieldsLimits',
value: metadata.fieldsLimits,
})
store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
useInstanceStore().set({
path: 'restrictedNicknames',
value: metadata.restrictedNicknames,
})
useInstanceCapabilitiesStore().set('postFormats', metadata.postFormats)
const suggestions = metadata.suggestions
store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled })
store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web })
useInstanceCapabilitiesStore().set(
'suggestionsEnabled',
suggestions.enabled,
)
// this is unused, why?
useInstanceCapabilitiesStore().set('suggestionsWeb', suggestions.web)
const software = data.software
store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository })
useInstanceStore().set({
path: 'backendVersion',
value: software.version,
})
useInstanceStore().set({
path: 'backendRepository',
value: software.repository,
})
const priv = metadata.private
store.dispatch('setInstanceOption', { name: 'private', value: priv })
useInstanceStore().set({ path: 'privateMode', value: priv })
const frontendVersion = window.___pleromafe_commit_hash
store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
useInstanceStore().set({
path: 'frontendVersion',
value: frontendVersion,
})
const federation = metadata.federation
store.dispatch('setInstanceOption', {
name: 'tagPolicyAvailable',
value: typeof federation.mrf_policies === 'undefined'
useInstanceCapabilitiesStore().set(
'tagPolicyAvailable',
typeof federation.mrf_policies === 'undefined'
? false
: metadata.federation.mrf_policies.includes('TagPolicy')
})
: metadata.federation.mrf_policies.includes('TagPolicy'),
)
store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
store.dispatch('setInstanceOption', {
name: 'federating',
value: typeof federation.enabled === 'undefined'
? true
: federation.enabled
useInstanceStore().set({
path: 'federationPolicy',
value: federation,
})
useInstanceStore().set({
path: 'federating',
value:
typeof federation.enabled === 'undefined' ? true : federation.enabled,
})
const accountActivationRequired = metadata.accountActivationRequired
store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired })
useInstanceStore().set({
path: 'accountActivationRequired',
value: accountActivationRequired,
})
const accounts = metadata.staffAccounts
resolveStaffAccounts({ store, accounts })
} else {
throw (res)
throw res
}
} catch (e) {
console.warn('Could not load nodeinfo')
@ -333,7 +444,10 @@ const getNodeInfo = async ({ store }) => {
const setConfig = async ({ store }) => {
// apiConfig, staticConfig
const configInfos = await Promise.all([getBackendProvidedConfig({ store }), getStaticConfig()])
const configInfos = await Promise.all([
getBackendProvidedConfig({ store }),
getStaticConfig(),
])
const apiConfig = configInfos[0]
const staticConfig = configInfos[1]
@ -364,29 +478,37 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
if (process.env.NODE_ENV === 'development') {
// do some checks to avoid common errors
if (!Object.keys(allStores).length) {
throw new Error('No stores are available. Check the code in src/boot/after_store.js')
throw new Error(
'No stores are available. Check the code in src/boot/after_store.js',
)
}
}
await Promise.all(
Object.entries(allStores)
.map(async ([name, mod]) => {
const isStoreName = name => name.startsWith('use')
if (process.env.NODE_ENV === 'development') {
if (Object.keys(mod).filter(isStoreName).length !== 1) {
throw new Error('Each store file must export exactly one store as a named export. Check your code in src/stores/')
}
Object.entries(allStores).map(async ([name, mod]) => {
const isStoreName = (name) => name.startsWith('use')
if (process.env.NODE_ENV === 'development') {
if (Object.keys(mod).filter(isStoreName).length !== 1) {
throw new Error(
'Each store file must export exactly one store as a named export. Check your code in src/stores/',
)
}
const storeFuncName = Object.keys(mod).find(isStoreName)
if (storeFuncName && typeof mod[storeFuncName] === 'function') {
const p = mod[storeFuncName]().$persistLoaded
if (!(p instanceof Promise)) {
throw new Error(`${name} store's $persistLoaded is not a Promise. The persist plugin is not applied.`)
}
await p
} else {
throw new Error(`Store module ${name} does not export a 'use...' function`)
}
const storeFuncName = Object.keys(mod).find(isStoreName)
if (storeFuncName && typeof mod[storeFuncName] === 'function') {
const p = mod[storeFuncName]().$persistLoaded
if (!(p instanceof Promise)) {
throw new Error(
`${name} store's $persistLoaded is not a Promise. The persist plugin is not applied.`,
)
}
}))
await p
} else {
throw new Error(
`Store module ${name} does not export a 'use...' function`,
)
}
}),
)
}
try {
@ -397,11 +519,18 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
}
if (storageError) {
useInterfaceStore().pushGlobalNotice({ messageKey: 'errors.storage_unavailable', level: 'error' })
useInterfaceStore().pushGlobalNotice({
messageKey: 'errors.storage_unavailable',
level: 'error',
})
}
useInterfaceStore().setLayoutWidth(windowWidth())
useInterfaceStore().setLayoutHeight(windowHeight())
window.syncConfig = useSyncConfigStore()
window.mergedConfig = useMergedConfigStore()
window.localConfig = useLocalConfigStore()
window.highlightConfig = useUserHighlightStore()
FaviconService.initFaviconService()
initServiceWorker(store)
@ -409,18 +538,25 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
window.addEventListener('focus', () => updateFocus())
const overrides = window.___pleromafe_dev_overrides || {}
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
store.dispatch('setInstanceOption', { name: 'server', value: server })
const server =
typeof overrides.target !== 'undefined'
? overrides.target
: window.location.origin
useInstanceStore().set({ path: 'server', value: server })
await setConfig({ store })
try {
await useInterfaceStore().applyTheme().catch((e) => { console.error('Error setting theme', e) })
await useInterfaceStore()
.applyTheme()
.catch((e) => {
console.error('Error setting theme', e)
})
} catch (e) {
window.splashError(e)
return Promise.reject(e)
}
applyConfig(store.state.config, i18n.global)
applyStyleConfig(useMergedConfigStore().mergedConfig, i18n.global)
// Now we can try getting the server settings and logging in
// Most of these are preloaded into the index.html so blocking is minimized
@ -428,8 +564,8 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
checkOAuthToken({ store }),
getInstancePanel({ store }),
getNodeInfo({ store }),
getInstanceConfig({ store })
]).catch(e => Promise.reject(e))
getInstanceConfig({ store }),
]).catch((e) => Promise.reject(e))
// Start fetching things that don't need to block the UI
store.dispatch('fetchMutes')
@ -442,11 +578,11 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
history: createWebHistory(),
routes: routes(store),
scrollBehavior: (to, _from, savedPosition) => {
if (to.matched.some(m => m.meta.dontScroll)) {
if (to.matched.some((m) => m.meta.dontScroll)) {
return false
}
return savedPosition || { left: 0, top: 0 }
}
},
})
useI18nStore().setI18n(i18n)

View file

@ -1,42 +1,48 @@
import PublicTimeline from 'components/public_timeline/public_timeline.vue'
import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue'
import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue'
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue'
import ConversationPage from 'components/conversation-page/conversation-page.vue'
import Interactions from 'components/interactions/interactions.vue'
import DMs from 'components/dm_timeline/dm_timeline.vue'
import ChatList from 'components/chat_list/chat_list.vue'
import Chat from 'components/chat/chat.vue'
import UserProfile from 'components/user_profile/user_profile.vue'
import Search from 'components/search/search.vue'
import Registration from 'components/registration/registration.vue'
import PasswordReset from 'components/password_reset/password_reset.vue'
import FollowRequests from 'components/follow_requests/follow_requests.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
import Notifications from 'components/notifications/notifications.vue'
import AuthForm from 'components/auth_form/auth_form.js'
import ShoutPanel from 'components/shout_panel/shout_panel.vue'
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
import About from 'components/about/about.vue'
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
import Lists from 'components/lists/lists.vue'
import ListsTimeline from 'components/lists_timeline/lists_timeline.vue'
import ListsEdit from 'components/lists_edit/lists_edit.vue'
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue'
import AuthForm from 'components/auth_form/auth_form.js'
import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue'
import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue'
import Chat from 'components/chat/chat.vue'
import ChatList from 'components/chat_list/chat_list.vue'
import ConversationPage from 'components/conversation-page/conversation-page.vue'
import DMs from 'components/dm_timeline/dm_timeline.vue'
import Drafts from 'components/drafts/drafts.vue'
import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue'
import FollowRequests from 'components/follow_requests/follow_requests.vue'
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
import Interactions from 'components/interactions/interactions.vue'
import Lists from 'components/lists/lists.vue'
import ListsEdit from 'components/lists_edit/lists_edit.vue'
import ListsTimeline from 'components/lists_timeline/lists_timeline.vue'
import Notifications from 'components/notifications/notifications.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
import PasswordReset from 'components/password_reset/password_reset.vue'
import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue'
import PublicTimeline from 'components/public_timeline/public_timeline.vue'
import Registration from 'components/registration/registration.vue'
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
import Search from 'components/search/search.vue'
import ShoutPanel from 'components/shout_panel/shout_panel.vue'
import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
import UserProfile from 'components/user_profile/user_profile.vue'
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
import BookmarkFolderEdit from '../components/bookmark_folder_edit/bookmark_folder_edit.vue'
import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue'
import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
export default (store) => {
const validateAuthenticatedRoute = (to, from, next) => {
if (store.state.users.currentUser) {
next()
} else {
next(store.state.instance.redirectRootNoLogin || '/main/all')
next(
useInstanceStore().instanceIdentity.redirectRootNoLogin || '/main/all',
)
}
}
@ -45,46 +51,125 @@ export default (store) => {
name: 'root',
path: '/',
redirect: () => {
return (store.state.users.currentUser
? store.state.instance.redirectRootLogin
: store.state.instance.redirectRootNoLogin) || '/main/all'
}
return (
(store.state.users.currentUser
? useInstanceStore().instanceIdentity.redirectRootLogin
: useInstanceStore().instanceIdentity.redirectRootNoLogin) ||
'/main/all'
)
},
},
{
name: 'public-external-timeline',
path: '/main/all',
component: PublicAndExternalTimeline,
},
{
name: 'public-timeline',
path: '/main/public',
component: PublicTimeline,
},
{
name: 'friends',
path: '/main/friends',
component: FriendsTimeline,
beforeEnter: validateAuthenticatedRoute,
},
{ name: 'public-external-timeline', path: '/main/all', component: PublicAndExternalTimeline },
{ name: 'public-timeline', path: '/main/public', component: PublicTimeline },
{ name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'bubble', path: '/bubble', component: BubbleTimeline },
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
{
name: 'conversation',
path: '/notice/:id',
component: ConversationPage,
meta: { dontScroll: true },
},
{ name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
{
name: 'remote-user-profile-acct',
path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'remote-user-profile',
path: '/remote-users/:hostname/:username',
component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'external-user-profile',
path: '/users/$:id',
component: UserProfile,
},
{
name: 'interactions',
path: '/users/:username/interactions',
component: Interactions,
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'dms',
path: '/users/:username/dms',
component: DMs,
beforeEnter: validateAuthenticatedRoute,
},
{ name: 'external-user-profile', path: '/users/$:id', component: UserProfile },
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'registration', path: '/registration', component: Registration },
{ name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true },
{ name: 'registration-token', path: '/registration/:token', component: Registration },
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
{ name: 'notifications', path: '/:username/notifications', component: Notifications, props: () => ({ disableTeleport: true }), beforeEnter: validateAuthenticatedRoute },
{
name: 'password-reset',
path: '/password-reset',
component: PasswordReset,
props: true,
},
{
name: 'registration-token',
path: '/registration/:token',
component: Registration,
},
{
name: 'friend-requests',
path: '/friend-requests',
component: FollowRequests,
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'notifications',
path: '/:username/notifications',
component: Notifications,
props: () => ({ disableTeleport: true }),
beforeEnter: validateAuthenticatedRoute,
},
{ name: 'login', path: '/login', component: AuthForm },
{ name: 'shout-panel', path: '/shout-panel', component: ShoutPanel, props: () => ({ floating: false }) },
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
{
name: 'shout-panel',
path: '/shout-panel',
component: ShoutPanel,
props: () => ({ floating: false }),
},
{
name: 'oauth-callback',
path: '/oauth-callback',
component: OAuthCallback,
props: (route) => ({ code: route.query.code }),
},
{
name: 'search',
path: '/search',
component: Search,
props: (route) => ({ query: route.query.query }),
},
{
name: 'who-to-follow',
path: '/who-to-follow',
component: WhoToFollow,
beforeEnter: validateAuthenticatedRoute,
},
{ name: 'about', path: '/about', component: About },
{ name: 'announcements', path: '/announcements', component: AnnouncementsPage },
{
name: 'announcements',
path: '/announcements',
component: AnnouncementsPage,
},
{ name: 'drafts', path: '/drafts', component: Drafts },
{ name: 'user-profile', path: '/users/:name', component: UserProfile },
{ name: 'legacy-user-profile', path: '/:name', component: UserProfile },
@ -92,17 +177,51 @@ export default (store) => {
{ name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
{ name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
{ name: 'lists-new', path: '/lists/new', component: ListsEdit },
{ name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute },
{ name: 'bookmark-folders', path: '/bookmark_folders', component: BookmarkFolders },
{ name: 'bookmark-folder-new', path: '/bookmarks/new-folder', component: BookmarkFolderEdit },
{ name: 'bookmark-folder', path: '/bookmarks/:id', component: BookmarkTimeline },
{ name: 'bookmark-folder-edit', path: '/bookmarks/:id/edit', component: BookmarkFolderEdit }
{
name: 'edit-navigation',
path: '/nav-edit',
component: NavPanel,
props: () => ({ forceExpand: true, forceEditMode: true }),
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'bookmark-folders',
path: '/bookmark_folders',
component: BookmarkFolders,
},
{
name: 'bookmark-folder-new',
path: '/bookmarks/new-folder',
component: BookmarkFolderEdit,
},
{
name: 'bookmark-folder',
path: '/bookmarks/:id',
component: BookmarkTimeline,
},
{
name: 'bookmark-folder-edit',
path: '/bookmarks/:id/edit',
component: BookmarkFolderEdit,
},
]
if (store.state.instance.pleromaChatMessagesAvailable) {
if (useInstanceCapabilitiesStore().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 }
{
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,
},
])
}

View file

@ -1,8 +1,11 @@
import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue'
import FeaturesPanel from '../features_panel/features_panel.vue'
import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue'
import StaffPanel from '../staff_panel/staff_panel.vue'
import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue'
import MRFTransparencyPanel from '../mrf_transparency_panel/mrf_transparency_panel.vue'
import StaffPanel from '../staff_panel/staff_panel.vue'
import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
const About = {
components: {
@ -10,16 +13,20 @@ const About = {
FeaturesPanel,
TermsOfServicePanel,
StaffPanel,
MRFTransparencyPanel
MRFTransparencyPanel,
},
computed: {
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
!this.$store.getters.mergedConfig.hideISP &&
this.$store.state.instance.instanceSpecificPanelContent
}
}
showFeaturesPanel() {
return useInstanceStore().instanceIdentity.showFeaturesPanel
},
showInstanceSpecificPanel() {
return (
useInstanceStore().instanceIdentity.showInstanceSpecificPanel &&
!useMergedConfigStore().mergedConfig.hideISP &&
useInstanceStore().instanceIdentity.instanceSpecificPanelContent
)
},
},
}
export default About

View file

@ -1,99 +1,105 @@
import { mapState } from 'vuex'
import ProgressButton from '../progress_button/progress_button.vue'
import Popover from '../popover/popover.vue'
import { mapState } from 'pinia'
import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisV
} from '@fortawesome/free-solid-svg-icons'
import Popover from '../popover/popover.vue'
import ProgressButton from '../progress_button/progress_button.vue'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useReportsStore } from 'src/stores/reports'
library.add(
faEllipsisV
)
import { library } from '@fortawesome/fontawesome-svg-core'
import { faEllipsisV } from '@fortawesome/free-solid-svg-icons'
library.add(faEllipsisV)
const AccountActions = {
props: [
'user', 'relationship'
],
data () {
props: ['user', 'relationship'],
data() {
return {
showingConfirmBlock: false,
showingConfirmRemoveFollower: false
showingConfirmRemoveFollower: false,
}
},
components: {
ProgressButton,
Popover,
UserListMenu,
ConfirmModal
ConfirmModal,
UserTimedFilterModal,
},
methods: {
showConfirmBlock () {
this.showingConfirmBlock = true
},
hideConfirmBlock () {
this.showingConfirmBlock = false
},
showConfirmRemoveUserFromFollowers () {
showConfirmRemoveUserFromFollowers() {
this.showingConfirmRemoveFollower = true
},
hideConfirmRemoveUserFromFollowers () {
hideConfirmRemoveUserFromFollowers() {
this.showingConfirmRemoveFollower = false
},
showRepeats () {
hideConfirmBlock() {
this.showingConfirmBlock = false
},
showRepeats() {
this.$store.dispatch('showReblogs', this.user.id)
},
hideRepeats () {
hideRepeats() {
this.$store.dispatch('hideReblogs', this.user.id)
},
blockUser () {
if (!this.shouldConfirmBlock) {
this.doBlockUser()
blockUser() {
if (this.$refs.timedBlockDialog) {
this.$refs.timedBlockDialog.optionallyPrompt()
} else {
this.showConfirmBlock()
if (!this.shouldConfirmBlock) {
this.doBlockUser()
} else {
this.showingConfirmBlock = true
}
}
},
doBlockUser () {
this.$store.dispatch('blockUser', this.user.id)
doBlockUser() {
this.$store.dispatch('blockUser', { id: this.user.id })
this.hideConfirmBlock()
},
unblockUser () {
unblockUser() {
this.$store.dispatch('unblockUser', this.user.id)
},
removeUserFromFollowers () {
removeUserFromFollowers() {
if (!this.shouldConfirmRemoveUserFromFollowers) {
this.doRemoveUserFromFollowers()
} else {
this.showConfirmRemoveUserFromFollowers()
}
},
doRemoveUserFromFollowers () {
doRemoveUserFromFollowers() {
this.$store.dispatch('removeUserFromFollowers', this.user.id)
this.hideConfirmRemoveUserFromFollowers()
},
reportUser () {
reportUser() {
useReportsStore().openUserReportingModal({ userId: this.user.id })
},
openChat () {
openChat() {
this.$router.push({
name: 'chat',
params: { username: this.$store.state.users.currentUser.screen_name, recipient_id: this.user.id }
params: {
username: this.$store.state.users.currentUser.screen_name,
recipient_id: this.user.id,
},
})
}
},
},
computed: {
shouldConfirmBlock () {
return this.$store.getters.mergedConfig.modalOnBlock
shouldConfirmBlock() {
return useMergedConfigStore().mergedConfig.modalOnBlock
},
shouldConfirmRemoveUserFromFollowers () {
return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
shouldConfirmRemoveUserFromFollowers() {
return useMergedConfigStore().mergedConfig.modalOnRemoveUserFromFollowers
},
...mapState({
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
})
}
...mapState(useInstanceCapabilitiesStore, [
'blockExpiration',
'pleromaChatMessagesAvailable',
]),
},
}
export default AccountActions

View file

@ -3,7 +3,6 @@
<Popover
trigger="click"
placement="bottom"
:bound-to="{ x: 'container' }"
remove-padding
>
<template #content>
@ -96,7 +95,8 @@
</Popover>
<teleport to="#modal">
<confirm-modal
v-if="showingConfirmBlock"
v-if="showingConfirmBlock && !blockExpiration"
ref="blockDialog"
:title="$t('user_card.block_confirm_title')"
:confirm-text="$t('user_card.block_confirm_accept_button')"
:cancel-text="$t('user_card.block_confirm_cancel_button')"
@ -137,6 +137,12 @@
</template>
</i18n-t>
</confirm-modal>
<UserTimedFilterModal
v-if="blockExpiration"
ref="timedBlockDialog"
:is-mute="false"
:user="user"
/>
</teleport>
</div>
</template>

View file

@ -1,57 +1,51 @@
export default {
name: 'Alert',
selector: '.alert',
validInnerComponents: [
'Text',
'Icon',
'Link',
'Border',
'ButtonUnstyled'
],
validInnerComponents: ['Text', 'Icon', 'Link', 'Border', 'ButtonUnstyled'],
variants: {
normal: '.neutral',
error: '.error',
warning: '.warning',
success: '.success'
success: '.success',
},
editor: {
border: 1,
aspect: '3 / 1'
aspect: '3 / 1',
},
defaultRules: [
{
directives: {
background: '--text',
opacity: 0.5,
blur: '9px'
}
blur: '9px',
},
},
{
parent: {
component: 'Alert'
component: 'Alert',
},
component: 'Border',
directives: {
textColor: '--parent'
}
textColor: '--parent',
},
},
{
variant: 'error',
directives: {
background: '--cRed'
}
background: '--cRed',
},
},
{
variant: 'warning',
directives: {
background: '--cOrange'
}
background: '--cOrange',
},
},
{
variant: 'success',
directives: {
background: '--cGreen'
}
}
]
background: '--cGreen',
},
},
],
}

View file

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

View file

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

View file

@ -1,59 +1,67 @@
import { mapState } from 'vuex'
import Announcement from '../announcement/announcement.vue'
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
import { useAnnouncementsStore } from 'src/stores/announcements'
import { useAnnouncementsStore } from 'src/stores/announcements.js'
const AnnouncementsPage = {
components: {
Announcement,
AnnouncementEditor
AnnouncementEditor,
},
data () {
data() {
return {
newAnnouncement: {
content: '',
startsAt: undefined,
endsAt: undefined,
allDay: false
allDay: false,
},
posting: false,
error: undefined
error: undefined,
}
},
mounted () {
mounted() {
useAnnouncementsStore().fetchAnnouncements()
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
currentUser: (state) => state.users.currentUser,
}),
announcements () {
announcements() {
return useAnnouncementsStore().announcements
},
canPostAnnouncement () {
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
}
canPostAnnouncement() {
return (
this.currentUser &&
this.currentUser.privileges.includes(
'announcements_manage_announcements',
)
)
},
},
methods: {
postAnnouncement () {
postAnnouncement() {
this.posting = true
useAnnouncementsStore().postAnnouncement(this.newAnnouncement)
useAnnouncementsStore()
.postAnnouncement(this.newAnnouncement)
.then(() => {
this.newAnnouncement.content = ''
this.startsAt = undefined
this.endsAt = undefined
})
.catch(error => {
.catch((error) => {
this.error = error.error
})
.finally(() => {
this.posting = false
})
},
clearError () {
clearError() {
this.error = undefined
}
}
},
},
}
export default AnnouncementsPage

View file

@ -21,10 +21,10 @@
export default {
emits: ['resetAsyncComponent'],
methods: {
retry () {
retry() {
this.$emit('resetAsyncComponent')
}
}
},
},
}
</script>

View file

@ -1,24 +1,29 @@
import StillImage from '../still-image/still-image.vue'
import Flash from '../flash/flash.vue'
import VideoAttachment from '../video_attachment/video_attachment.vue'
import { mapState } from 'pinia'
import nsfwImage from '../../assets/nsfw.png'
import fileTypeService from '../../services/file_type/file_type.service.js'
import { mapGetters } from 'vuex'
import Flash from '../flash/flash.vue'
import StillImage from '../still-image/still-image.vue'
import VideoAttachment from '../video_attachment/video_attachment.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useMediaViewerStore } from 'src/stores/media_viewer'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faAlignRight,
faFile,
faMusic,
faImage,
faVideo,
faPlayCircle,
faTimes,
faStop,
faSearchPlus,
faTrashAlt,
faMusic,
faPencilAlt,
faAlignRight
faPlayCircle,
faSearchPlus,
faStop,
faTimes,
faTrashAlt,
faVideo,
} from '@fortawesome/free-solid-svg-icons'
import { useMediaViewerStore } from 'src/stores/media_viewer'
library.add(
faFile,
@ -31,7 +36,7 @@ library.add(
faSearchPlus,
faTrashAlt,
faPencilAlt,
faAlignRight
faAlignRight,
)
const Attachment = {
@ -46,72 +51,72 @@ const Attachment = {
'remove',
'shiftUp',
'shiftDn',
'edit'
'edit',
],
data () {
data() {
return {
localDescription: this.description || this.attachment.description,
nsfwImage: this.$store.state.instance.nsfwCensorImage || nsfwImage,
hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw,
preloadImage: this.$store.getters.mergedConfig.preloadImage,
nsfwImage:
useInstanceStore().instanceIdentity.nsfwCensorImage || nsfwImage,
hideNsfwLocal: useMergedConfigStore().mergedConfig.hideNsfw,
preloadImage: useMergedConfigStore().mergedConfig.preloadImage,
loading: false,
img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'),
img: this.attachment.type === 'image' && document.createElement('img'),
modalOpen: false,
showHidden: false,
flashLoaded: false,
showDescription: false
showDescription: false,
}
},
components: {
Flash,
StillImage,
VideoAttachment
VideoAttachment,
},
computed: {
classNames () {
classNames() {
return [
{
'-loading': this.loading,
'-nsfw-placeholder': this.hidden,
'-editable': this.edit !== undefined,
'-compact': this.compact
'-compact': this.compact,
},
'-type-' + this.type,
'-type-' + this.attachment.type,
this.size && '-size-' + this.size,
`-${this.useContainFit ? 'contain' : 'cover'}-fit`
`-${this.useContainFit ? 'contain' : 'cover'}-fit`,
]
},
usePlaceholder () {
usePlaceholder() {
return this.size === 'hide'
},
useContainFit () {
return this.$store.getters.mergedConfig.useContainFit
useContainFit() {
return this.mergedConfig.useContainFit
},
placeholderName () {
placeholderName() {
if (this.attachment.description === '' || !this.attachment.description) {
return this.type.toUpperCase()
return this.attachment.type.toUpperCase()
}
return this.attachment.description
},
placeholderIconClass () {
if (this.type === 'image') return 'image'
if (this.type === 'video') return 'video'
if (this.type === 'audio') return 'music'
placeholderIconClass() {
if (this.attachment.type === 'image') return 'image'
if (this.attachment.type === 'video') return 'video'
if (this.attachment.type === 'audio') return 'music'
return 'file'
},
referrerpolicy () {
return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer'
referrerpolicy() {
return useInstanceCapabilitiesStore().mediaProxyAvailable
? ''
: 'no-referrer'
},
type () {
return fileTypeService.fileType(this.attachment.mimetype)
},
hidden () {
hidden() {
return this.nsfw && this.hideNsfwLocal && !this.showHidden
},
isEmpty () {
return (this.type === 'html' && !this.attachment.oembed)
isEmpty() {
return this.attachment.type === 'html' && !this.attachment.oembed
},
useModal () {
useModal() {
let modalTypes = []
switch (this.size) {
case 'hide':
@ -124,64 +129,66 @@ const Attachment = {
: ['image']
break
}
return modalTypes.includes(this.type)
return modalTypes.includes(this.attachment.type)
},
videoTag () {
videoTag() {
return this.useModal ? 'button' : 'span'
},
...mapGetters(['mergedConfig'])
...mapState(useMergedConfigStore, ['mergedConfig']),
},
watch: {
'attachment.description' (newVal) {
'attachment.description'(newVal) {
this.localDescription = newVal
},
localDescription (newVal) {
localDescription(newVal) {
this.onEdit(newVal)
}
},
},
methods: {
linkClicked ({ target }) {
linkClicked({ target }) {
if (target.tagName === 'A') {
window.open(target.href, '_blank')
}
},
openModal () {
openModal() {
if (this.useModal) {
this.$emit('setMedia')
useMediaViewerStore().setCurrentMedia(this.attachment)
} else if (this.type === 'unknown') {
} else if (this.attachment.type === 'unknown') {
window.open(this.attachment.url)
}
},
openModalForce () {
openModalForce() {
this.$emit('setMedia')
useMediaViewerStore().setCurrentMedia(this.attachment)
},
onEdit (event) {
onEdit(event) {
this.edit && this.edit(this.attachment, event)
},
onRemove () {
onRemove() {
this.remove && this.remove(this.attachment)
},
onShiftUp () {
onShiftUp() {
this.shiftUp && this.shiftUp(this.attachment)
},
onShiftDn () {
onShiftDn() {
this.shiftDn && this.shiftDn(this.attachment)
},
stopFlash () {
stopFlash() {
this.$refs.flash.closePlayer()
},
setFlashLoaded (event) {
setFlashLoaded(event) {
this.flashLoaded = event
},
toggleDescription () {
toggleDescription() {
this.showDescription = !this.showDescription
},
toggleHidden (event) {
toggleHidden(event) {
if (
(this.mergedConfig.useOneClickNsfw && !this.showHidden) &&
(this.type !== 'video' || this.mergedConfig.playVideosInModal)
this.mergedConfig.useOneClickNsfw &&
!this.showHidden &&
(this.attachment.type !== 'video' ||
this.mergedConfig.playVideosInModal)
) {
this.openModal(event)
return
@ -201,12 +208,12 @@ const Attachment = {
this.showHidden = !this.showHidden
}
},
onImageLoad (image) {
onImageLoad(image) {
const width = image.naturalWidth
const height = image.naturalHeight
this.$emit('naturalSizeLoad', { id: this.attachment.id, width, height })
}
}
},
},
}
export default Attachment

View file

@ -107,9 +107,9 @@
.play-icon {
position: absolute;
font-size: 64px;
top: calc(50% - 32px);
left: calc(50% - 32px);
font-size: 4.5em;
top: calc(50% - 2.25rem);
left: calc(50% - 2.25rem);
color: rgb(255 255 255 / 75%);
text-shadow: 0 0 2px rgb(0 0 0 / 40%);

View file

@ -1,27 +0,0 @@
export default {
name: 'Attachment',
selector: '.Attachment',
notEditable: true,
validInnerComponents: [
'Border',
'Button',
'Input'
],
defaultRules: [
{
directives: {
roundness: 3
}
},
{
component: 'Button',
parent: {
component: 'Attachment'
},
directives: {
background: '#FFFFFF',
opacity: 0.5
}
}
]
}

View file

@ -6,7 +6,7 @@
@click="openModal"
>
<a
v-if="type !== 'html'"
v-if="attachment.type !== 'html'"
class="placeholder"
target="_blank"
:href="attachment.url"
@ -23,7 +23,7 @@
>
<button
v-if="remove"
class="button-default attachment-button"
class="button-default attachment-button -transparent"
@click.prevent="onRemove"
>
<FAIcon icon="trash-alt" />
@ -70,7 +70,7 @@
:src="nsfwImage"
>
<FAIcon
v-if="type === 'video'"
v-if="attachment.type === 'video'"
class="play-icon"
icon="play-circle"
/>
@ -80,24 +80,24 @@
class="attachment-buttons"
>
<button
v-if="type === 'flash' && flashLoaded"
class="button-default attachment-button"
v-if="attachment.type === 'flash' && flashLoaded"
class="button-default attachment-button -transparent"
:title="$t('status.attachment_stop_flash')"
@click.prevent="stopFlash"
>
<FAIcon icon="stop" />
</button>
<button
v-if="attachment.description && size !== 'small' && !edit && type !== 'unknown'"
class="button-default attachment-button"
v-if="attachment.description && size !== 'small' && !edit && attachment.type !== 'unknown'"
class="button-default attachment-button -transparent"
:title="$t('status.show_attachment_description')"
@click.prevent="toggleDescription"
>
<FAIcon icon="align-right" />
</button>
<button
v-if="!useModal && type !== 'unknown'"
class="button-default attachment-button"
v-if="!useModal && attachment.type !== 'unknown'"
class="button-default attachment-button -transparent"
:title="$t('status.show_attachment_in_modal')"
@click.prevent="openModalForce"
>
@ -105,7 +105,7 @@
</button>
<button
v-if="nsfw && hideNsfwLocal"
class="button-default attachment-button"
class="button-default attachment-button -transparent"
:title="$t('status.hide_attachment')"
@click.prevent="toggleHidden"
>
@ -113,7 +113,7 @@
</button>
<button
v-if="shiftUp"
class="button-default attachment-button"
class="button-default attachment-button -transparent"
:title="$t('status.move_up')"
@click.prevent="onShiftUp"
>
@ -121,7 +121,7 @@
</button>
<button
v-if="shiftDn"
class="button-default attachment-button"
class="button-default attachment-button -transparent"
:title="$t('status.move_down')"
@click.prevent="onShiftDn"
>
@ -129,7 +129,7 @@
</button>
<button
v-if="remove"
class="button-default attachment-button"
class="button-default attachment-button -transparent"
:title="$t('status.remove_attachment')"
@click.prevent="onRemove"
>
@ -138,7 +138,7 @@
</div>
<a
v-if="type === 'image' && (!hidden || preloadImage)"
v-if="attachment.type === 'image' && (!hidden || preloadImage)"
class="image-container"
:class="{'-hidden': hidden && preloadImage }"
:href="attachment.url"
@ -156,7 +156,7 @@
</a>
<a
v-if="type === 'unknown' && !hidden"
v-if="attachment.type === 'unknown' && !hidden"
class="placeholder-container"
:href="attachment.url"
target="_blank"
@ -173,7 +173,7 @@
<component
:is="videoTag"
v-if="type === 'video' && !hidden"
v-if="attachment.type === 'video' && !hidden"
class="video-container"
:href="attachment.url"
@click.stop.prevent="openModal"
@ -193,13 +193,13 @@
</component>
<span
v-if="type === 'audio' && !hidden"
v-if="attachment.type === 'audio' && !hidden"
class="audio-container"
:href="attachment.url"
@click.stop.prevent="openModal"
>
<audio
v-if="type === 'audio'"
v-if="attachment.type === 'audio'"
:src="attachment.url"
:alt="attachment.description"
:title="attachment.description"
@ -210,7 +210,7 @@
</span>
<div
v-if="type === 'html' && attachment.oembed"
v-if="attachment.type === 'html' && attachment.oembed"
class="oembed-container"
@click.prevent="linkClicked"
>
@ -229,7 +229,7 @@
</div>
<span
v-if="type === 'flash' && !hidden"
v-if="attachment.type === 'flash' && !hidden"
class="flash-container"
:href="attachment.url"
@click.stop.prevent="openModal"

View file

@ -1,28 +1,34 @@
import { mapState } from 'pinia'
import { h, resolveComponent } from 'vue'
import LoginForm from '../login_form/login_form.vue'
import MFARecoveryForm from '../mfa_form/recovery_form.vue'
import MFATOTPForm from '../mfa_form/totp_form.vue'
import { mapState } from 'pinia'
import { useAuthFlowStore } from 'src/stores/auth_flow'
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
const AuthForm = {
name: 'AuthForm',
render () {
render() {
return h(resolveComponent(this.authForm))
},
computed: {
authForm () {
if (this.requiredTOTP) { return 'MFATOTPForm' }
if (this.requiredRecovery) { return 'MFARecoveryForm' }
authForm() {
if (this.requiredTOTP) {
return 'MFATOTPForm'
}
if (this.requiredRecovery) {
return 'MFARecoveryForm'
}
return 'LoginForm'
},
...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery'])
...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery']),
},
components: {
MFARecoveryForm,
MFATOTPForm,
LoginForm
}
LoginForm,
},
}
export default AuthForm

View file

@ -2,51 +2,55 @@ const debounceMilliseconds = 500
export default {
props: {
query: { // function to query results and return a promise
query: {
// function to query results and return a promise
type: Function,
required: true
required: true,
},
filter: { // function to filter results in real time
type: Function
filter: {
// function to filter results in real time
type: Function,
},
placeholder: {
type: String,
default: 'Search...'
}
default: 'Search...',
},
},
data () {
data() {
return {
term: '',
timeout: null,
results: [],
resultsVisible: false
resultsVisible: false,
}
},
computed: {
filtered () {
filtered() {
return this.filter ? this.filter(this.results) : this.results
}
},
},
watch: {
term (val) {
term(val) {
this.fetchResults(val)
}
},
},
methods: {
fetchResults (term) {
fetchResults(term) {
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.results = []
if (term) {
this.query(term).then((results) => { this.results = results })
this.query(term).then((results) => {
this.results = results
})
}
}, debounceMilliseconds)
},
onInputClick () {
onInputClick() {
this.resultsVisible = true
},
onClickOutside () {
onClickOutside() {
this.resultsVisible = false
}
}
},
},
}

View file

@ -1,21 +1,28 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const AvatarList = {
props: ['users'],
computed: {
slicedUsers () {
slicedUsers() {
return this.users ? this.users.slice(0, 15) : []
}
},
},
components: {
UserAvatar
UserAvatar,
},
methods: {
userProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
}
}
userProfileLink(user) {
return generateProfileLink(
user.id,
user.screen_name,
useInstanceStore().restrictedNicknames,
)
},
},
}
export default AvatarList

View file

@ -1,30 +1,27 @@
export default {
name: 'Badge',
selector: '.badge',
validInnerComponents: [
'Text',
'Icon'
],
validInnerComponents: ['Text', 'Icon'],
variants: {
notification: '.-notification'
notification: '.-notification',
},
defaultRules: [
{
component: 'Root',
directives: {
'--badgeNotification': 'color | --cRed'
}
'--badgeNotification': 'color | --cRed',
},
},
{
directives: {
background: '--cGreen'
}
background: '--cGreen',
},
},
{
variant: 'notification',
directives: {
background: '--cRed'
}
}
]
background: '--cRed',
},
},
],
}

View file

@ -1,24 +1,29 @@
import UserPopover from '../user_popover/user_popover.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import UserAvatar from '../user_avatar/user_avatar.vue'
import UserLink from '../user_link/user_link.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import UserPopover from '../user_popover/user_popover.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const BasicUserCard = {
props: [
'user'
],
props: ['user'],
components: {
UserPopover,
UserAvatar,
RichContent,
UserLink
UserLink,
},
methods: {
userProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
}
}
userProfileLink(user) {
return generateProfileLink(
user.id,
user.screen_name,
useInstanceStore().restrictedNicknames,
)
},
},
}
export default BasicUserCard

View file

@ -1,40 +1,50 @@
import { mapState } from 'pinia'
import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
const BlockCard = {
props: ['userId'],
data () {
return {
progress: false
}
},
computed: {
user () {
user() {
return this.$store.getters.findUser(this.userId)
},
relationship () {
relationship() {
return this.$store.getters.relationship(this.userId)
},
blocked () {
blocked() {
return this.relationship.blocking
}
},
blockExpiryAvailable() {
return Object.hasOwn(this.user, 'block_expires_at')
},
blockExpiry() {
return this.user.block_expires_at === false
? this.$t('user_card.block_expires_forever')
: this.$t('user_card.block_expires_at', [
new Date(this.user.mute_expires_at).toLocaleString(),
])
},
...mapState(useInstanceCapabilitiesStore, ['blockExpiration']),
},
components: {
BasicUserCard
BasicUserCard,
UserTimedFilterModal,
},
methods: {
unblockUser () {
this.progress = true
this.$store.dispatch('unblockUser', this.user.id).then(() => {
this.progress = false
})
unblockUser() {
this.$store.dispatch('unblockUser', this.user.id)
},
blockUser () {
this.progress = true
this.$store.dispatch('blockUser', this.user.id).then(() => {
this.progress = false
})
}
}
blockUser() {
if (this.blockExpiration) {
this.$refs.timedBlockDialog.optionallyPrompt()
} else {
this.$store.dispatch('blockUser', { id: this.user.id })
}
},
},
}
export default BlockCard

View file

@ -1,33 +1,35 @@
<template>
<basic-user-card :user="user">
<div class="block-card-content-container">
<span
v-if="blocked && blockExpiryAvailable"
class="alert neutral"
>
{{ blockExpiry }}
</span>
{{ ' ' }}
<button
v-if="blocked"
class="btn button-default"
:disabled="progress"
@click="unblockUser"
>
<template v-if="progress">
{{ $t('user_card.unblock_progress') }}
</template>
<template v-else>
{{ $t('user_card.unblock') }}
</template>
{{ $t('user_card.unblock') }}
</button>
<button
v-else
class="btn button-default"
:disabled="progress"
@click="blockUser"
>
<template v-if="progress">
{{ $t('user_card.block_progress') }}
</template>
<template v-else>
{{ $t('user_card.block') }}
</template>
{{ $t('user_card.block') }}
</button>
</div>
<teleport to="#modal">
<UserTimedFilterModal
ref="timedBlockDialog"
:user="user"
:is-mute="false"
/>
</teleport>
</basic-user-card>
</template>

View file

@ -1,22 +1,15 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisH
} from '@fortawesome/free-solid-svg-icons'
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons'
library.add(
faEllipsisH
)
library.add(faEllipsisH)
const BookmarkFolderCard = {
props: [
'folder',
'allBookmarks'
],
props: ['folder', 'allBookmarks'],
computed: {
firstLetter () {
firstLetter() {
return this.folder ? this.folder.name[0] : null
}
}
},
},
}
export default BookmarkFolderCard

View file

@ -1,10 +1,11 @@
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
import apiService from '../../services/api/api.service'
import { useInterfaceStore } from 'src/stores/interface'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
import { useInterfaceStore } from 'src/stores/interface.js'
const BookmarkFolderEdit = {
data () {
data() {
return {
name: '',
nameDraft: '',
@ -13,54 +14,59 @@ const BookmarkFolderEdit = {
emojiDraft: '',
emojiUrlDraft: null,
emojiPickerExpanded: false,
reallyDelete: false
reallyDelete: false,
}
},
components: {
EmojiPicker
EmojiPicker,
},
created () {
created() {
if (!this.id) return
const credentials = this.$store.state.users.currentUser.credentials
apiService.fetchBookmarkFolders({ credentials })
.then((folders) => {
const folder = folders.find(folder => folder.id === this.id)
if (!folder) return
apiService.fetchBookmarkFolders({ credentials }).then((folders) => {
const folder = folders.find((folder) => folder.id === this.id)
if (!folder) return
this.nameDraft = this.name = folder.name
this.emojiDraft = this.emoji = folder.emoji
this.emojiUrlDraft = this.emojiUrl = folder.emoji_url
})
this.nameDraft = this.name = folder.name
this.emojiDraft = this.emoji = folder.emoji
this.emojiUrlDraft = this.emojiUrl = folder.emoji_url
})
},
computed: {
id () {
id() {
return this.$route.params.id
}
},
},
methods: {
selectEmoji (event) {
selectEmoji(event) {
this.emojiDraft = event.insertion
this.emojiUrlDraft = event.insertionUrl
},
showEmojiPicker () {
showEmojiPicker() {
if (!this.emojiPickerExpanded) {
this.$refs.picker.showPicker()
}
},
onShowPicker () {
onShowPicker() {
this.emojiPickerExpanded = true
},
onClosePicker () {
onClosePicker() {
this.emojiPickerExpanded = false
},
updateFolder () {
useBookmarkFoldersStore().updateBookmarkFolder({ folderId: this.id, name: this.nameDraft, emoji: this.emojiDraft })
updateFolder() {
useBookmarkFoldersStore()
.updateBookmarkFolder({
folderId: this.id,
name: this.nameDraft,
emoji: this.emojiDraft,
})
.then(() => {
this.$router.push({ name: 'bookmark-folders' })
})
},
createFolder () {
useBookmarkFoldersStore().createBookmarkFolder({ name: this.nameDraft, emoji: this.emojiDraft })
createFolder() {
useBookmarkFoldersStore()
.createBookmarkFolder({ name: this.nameDraft, emoji: this.emojiDraft })
.then(() => {
this.$router.push({ name: 'bookmark-folders' })
})
@ -68,15 +74,15 @@ const BookmarkFolderEdit = {
useInterfaceStore().pushGlobalNotice({
messageKey: 'bookmark_folders.error',
messageArgs: [e.message],
level: 'error'
level: 'error',
})
})
},
deleteFolder () {
deleteFolder() {
useBookmarkFoldersStore().deleteBookmarkFolder({ folderId: this.id })
this.$router.push({ name: 'bookmark-folders' })
}
}
},
},
}
export default BookmarkFolderEdit

View file

@ -1,28 +1,29 @@
import BookmarkFolderCard from '../bookmark_folder_card/bookmark_folder_card.vue'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
const BookmarkFolders = {
data () {
data() {
return {
isNew: false
isNew: false,
}
},
components: {
BookmarkFolderCard
BookmarkFolderCard,
},
computed: {
bookmarkFolders () {
bookmarkFolders() {
return useBookmarkFoldersStore().allFolders
}
},
},
methods: {
cancelNewFolder () {
cancelNewFolder() {
this.isNew = false
},
newFolder () {
newFolder() {
this.isNew = true
}
}
},
},
}
export default BookmarkFolders

View file

@ -1,20 +1,20 @@
import { mapState } from 'pinia'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import { getBookmarkFolderEntries } from 'src/components/navigation/filter.js'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
export const BookmarkFoldersMenuContent = {
props: [
'showPin'
],
props: ['showPin'],
components: {
NavigationEntry
NavigationEntry,
},
computed: {
...mapState(useBookmarkFoldersStore, {
folders: getBookmarkFolderEntries
})
}
folders: getBookmarkFolderEntries,
}),
},
}
export default BookmarkFoldersMenuContent

View file

@ -1,32 +1,38 @@
import Timeline from '../timeline/timeline.vue'
const Bookmarks = {
created () {
created() {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null })
this.$store.dispatch('startFetchingTimeline', {
timeline: 'bookmarks',
bookmarkFolderId: this.folderId || null,
})
},
components: {
Timeline
Timeline,
},
computed: {
folderId () {
folderId() {
return this.$route.params.id
},
timeline () {
timeline() {
return this.$store.state.statuses.timelines.bookmarks
}
},
},
watch: {
folderId () {
folderId() {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null })
}
this.$store.dispatch('startFetchingTimeline', {
timeline: 'bookmarks',
bookmarkFolderId: this.folderId || null,
})
},
},
unmounted () {
unmounted() {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
}
},
}
export default Bookmarks

View file

@ -6,8 +6,8 @@ export default {
{
directives: {
textColor: '$mod(--parent 10)',
textAuto: 'no-auto'
}
}
]
textAuto: 'no-auto',
},
},
],
}

View file

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

View file

@ -10,26 +10,24 @@ export default {
// normal: '' // normal state is implicitly added, it is always included
toggled: '.toggled',
focused: ':focus-within',
pressed: ':focus:active',
pressed: ':active',
hover: ':is(:hover, :focus-visible):not(:disabled)',
disabled: ':disabled'
disabled: ':disabled',
},
// Variants are mutually exclusive, each component implicitly has "normal" variant, and all other variants inherit from it.
variants: {
// Variants save on computation time since adding new variant just adds one more "set".
// normal: '', // you can override normal variant, it will be appenended to the main class
danger: '.danger'
danger: '.-danger',
transparent: '.-transparent',
// Overall the compuation difficulty is N*((1/6)M^3+M) where M is number of distinct states and N is number of variants.
// This (currently) is further multipled by number of places where component can exist.
},
editor: {
aspect: '2 / 1'
aspect: '2 / 1',
},
// This lists all other components that can possibly exist within one. Recursion is currently not supported (and probably won't be supported ever).
validInnerComponents: [
'Text',
'Icon'
],
validInnerComponents: ['Text', 'Icon'],
// Default rules, used as "default theme", essentially.
defaultRules: [
{
@ -38,9 +36,11 @@ export default {
'--buttonDefaultHoverGlow': 'shadow | 0 0 1 2 --text / 0.4',
'--buttonDefaultFocusGlow': 'shadow | 0 0 1 2 --link / 0.5',
'--buttonDefaultShadow': 'shadow | 0 0 2 #000000',
'--buttonDefaultBevel': 'shadow | $borderSide(#FFFFFF top 0.2 1), $borderSide(#000000 bottom 0.2 1)',
'--buttonPressedBevel': 'shadow | inset 0 0 4 #000000, $borderSide(#FFFFFF bottom 0.2 1), $borderSide(#000000 top 0.2 1)'
}
'--buttonDefaultBevel':
'shadow | $borderSide(#FFFFFF top 0.2 1), $borderSide(#000000 bottom 0.2 1)',
'--buttonPressedBevel':
'shadow | inset 0 0 4 #000000, $borderSide(#FFFFFF bottom 0.2 1), $borderSide(#000000 top 0.2 1)',
},
},
{
// component: 'Button', // no need to specify components every time unless you're specifying how other component should look
@ -48,89 +48,128 @@ export default {
directives: {
background: '--fg',
shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel'],
roundness: 3
}
roundness: 3,
},
},
{
state: ['hover'],
variant: 'danger',
directives: {
shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel']
}
background: '--cRed',
},
},
{
state: ['focused'],
variant: 'transparent',
directives: {
shadow: ['--buttonDefaultFocusGlow', '--buttonDefaultBevel']
}
},
{
state: ['pressed'],
directives: {
shadow: ['--buttonDefaultShadow', '--buttonPressedBevel']
}
},
{
state: ['pressed', 'hover'],
directives: {
shadow: ['--buttonPressedBevel', '--buttonDefaultHoverGlow']
}
},
{
state: ['toggled'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultShadow', '--buttonPressedBevel']
}
},
{
state: ['toggled', 'hover'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel']
}
},
{
state: ['toggled', 'focused'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel']
}
},
{
state: ['toggled', 'disabled'],
directives: {
background: '$blend(--accent 0.25 --parent)',
shadow: ['--buttonPressedBevel']
}
},
{
state: ['disabled'],
directives: {
background: '$blend(--inheritedBackground 0.25 --parent)',
shadow: ['--buttonDefaultBevel']
}
opacity: 0.5,
},
},
{
component: 'Text',
parent: {
component: 'Button',
state: ['disabled']
variant: 'transparent',
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend'
}
textColor: '--text',
},
},
{
component: 'Icon',
parent: {
component: 'Button',
state: ['disabled']
variant: 'transparent',
},
directives: {
textColor: '--text',
},
},
{
state: ['hover'],
directives: {
shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel'],
},
},
{
state: ['focused'],
directives: {
shadow: ['--buttonDefaultFocusGlow', '--buttonDefaultBevel'],
},
},
{
state: ['pressed'],
directives: {
shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'],
},
},
{
state: ['pressed', 'hover'],
directives: {
shadow: ['--buttonPressedBevel', '--buttonDefaultHoverGlow'],
},
},
{
state: ['toggled'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'],
},
},
{
state: ['toggled', 'hover'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'],
},
},
{
state: ['toggled', 'focused'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'],
},
},
{
state: ['toggled', 'hover', 'focused'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'],
},
},
{
state: ['toggled', 'disabled'],
directives: {
background: '$blend(--accent 0.25 --parent)',
shadow: ['--buttonPressedBevel'],
},
},
{
state: ['disabled'],
directives: {
background: '$blend(--inheritedBackground 0.25 --parent)',
shadow: ['--buttonDefaultBevel'],
},
},
{
component: 'Text',
parent: {
component: 'Button',
state: ['disabled'],
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend'
}
}
]
textOpacityMode: 'blend',
},
},
{
component: 'Icon',
parent: {
component: 'Button',
state: ['disabled'],
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend',
},
},
],
}

View file

@ -7,91 +7,86 @@ export default {
toggled: '.toggled',
disabled: ':disabled',
hover: ':is(:hover, :focus-visible):not(:disabled)',
focused: ':focus-within:not(:is(:focus-visible))'
focused: ':focus-within:not(:is(:focus-visible))',
},
validInnerComponents: [
'Text',
'Link',
'Icon',
'Badge'
],
validInnerComponents: ['Text', 'Link', 'Icon', 'Badge'],
defaultRules: [
{
directives: {
shadow: []
}
shadow: [],
},
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
state: ['hover']
state: ['hover'],
},
directives: {
textColor: '--parent--text'
}
textColor: '--parent--text',
},
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
state: ['toggled']
state: ['toggled'],
},
directives: {
textColor: '--parent--text'
}
textColor: '--parent--text',
},
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
state: ['toggled', 'hover']
state: ['toggled', 'hover'],
},
directives: {
textColor: '--parent--text'
}
textColor: '--parent--text',
},
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
state: ['toggled', 'focused']
state: ['toggled', 'focused'],
},
directives: {
textColor: '--parent--text'
}
textColor: '--parent--text',
},
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
state: ['toggled', 'focused', 'hover']
state: ['toggled', 'focused', 'hover'],
},
directives: {
textColor: '--parent--text'
}
textColor: '--parent--text',
},
},
{
component: 'Text',
parent: {
component: 'ButtonUnstyled',
state: ['disabled']
state: ['disabled'],
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend'
}
textOpacityMode: 'blend',
},
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
state: ['disabled']
state: ['disabled'],
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend'
}
}
]
textOpacityMode: 'blend',
},
},
],
}

View file

@ -1,25 +1,27 @@
import _ from 'lodash'
import { WSConnectionStatus } from '../../services/api/api.service.js'
import { mapGetters, mapState } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
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 { mapGetters, mapState } from 'vuex'
import { WSConnectionStatus } from '../../services/api/api.service.js'
import chatService from '../../services/chat_service/chat_service.js'
import { promiseInterval } from '../../services/promise_interval/promise_interval.js'
import { getScrollPosition, getNewTopPosition, isBottomedOut, isScrollable } from './chat_layout_utils.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faChevronDown,
faChevronLeft
} from '@fortawesome/free-solid-svg-icons'
import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js'
import { promiseInterval } from '../../services/promise_interval/promise_interval.js'
import ChatMessage from '../chat_message/chat_message.vue'
import ChatTitle from '../chat_title/chat_title.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue'
import {
getNewTopPosition,
getScrollPosition,
isBottomedOut,
isScrollable,
} from './chat_layout_utils.js'
import { useInterfaceStore } from 'src/stores/interface.js'
library.add(
faChevronDown,
faChevronLeft
)
import { library } from '@fortawesome/fontawesome-svg-core'
import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons'
library.add(faChevronDown, faChevronLeft)
const BOTTOMED_OUT_OFFSET = 10
const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 10
@ -31,78 +33,95 @@ const Chat = {
components: {
ChatMessage,
ChatTitle,
PostStatusForm
PostStatusForm,
},
data () {
data() {
return {
jumpToBottomButtonVisible: false,
hoveredMessageChainId: undefined,
lastScrollPosition: {},
scrollableContainerHeight: '100%',
errorLoadingChat: false,
messageRetriers: {}
messageRetriers: {},
}
},
created () {
created() {
this.startFetching()
window.addEventListener('resize', this.handleResize)
},
mounted () {
mounted() {
window.addEventListener('scroll', this.handleScroll)
if (typeof document.hidden !== 'undefined') {
document.addEventListener('visibilitychange', this.handleVisibilityChange, false)
document.addEventListener(
'visibilitychange',
this.handleVisibilityChange,
false,
)
}
this.$nextTick(() => {
this.handleResize()
})
},
unmounted () {
unmounted() {
window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleResize)
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
if (typeof document.hidden !== 'undefined')
document.removeEventListener(
'visibilitychange',
this.handleVisibilityChange,
false,
)
this.$store.dispatch('clearCurrentChat')
},
computed: {
recipient () {
recipient() {
return this.currentChat && this.currentChat.account
},
recipientId () {
recipientId() {
return this.$route.params.recipient_id
},
formPlaceholder () {
formPlaceholder() {
if (this.recipient) {
return this.$t('chats.message_user', { nickname: this.recipient.screen_name_ui })
return this.$t('chats.message_user', {
nickname: this.recipient.screen_name_ui,
})
} else {
return ''
}
},
chatViewItems () {
chatViewItems() {
return chatService.getView(this.currentChatMessageService)
},
newMessageCount () {
return this.currentChatMessageService && this.currentChatMessageService.newMessageCount
newMessageCount() {
return (
this.currentChatMessageService &&
this.currentChatMessageService.newMessageCount
)
},
streamingEnabled () {
return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED
streamingEnabled() {
return (
this.mergedConfig.useStreamingApi &&
this.mastoUserSocketStatus === WSConnectionStatus.JOINED
)
},
...mapGetters([
'currentChat',
'currentChatMessageService',
'findOpenedChatByRecipientId',
'mergedConfig'
'mergedConfig',
]),
...mapPiniaState(useInterfaceStore, {
mobileLayout: store => store.layoutType === 'mobile'
mobileLayout: (store) => store.layoutType === 'mobile',
}),
...mapState({
backendInteractor: state => state.api.backendInteractor,
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus,
currentUser: state => state.users.currentUser
})
backendInteractor: (state) => state.api.backendInteractor,
mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus,
currentUser: (state) => state.users.currentUser,
}),
},
watch: {
chatViewItems () {
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)
@ -115,23 +134,23 @@ const Chat = {
$route: function () {
this.startFetching()
},
mastoUserSocketStatus (newValue) {
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 }) {
onMessageHover({ isHovered, messageChainId }) {
this.hoveredMessageChainId = isHovered ? messageChainId : undefined
},
onFilesDropped () {
onFilesDropped() {
this.$nextTick(() => {
this.handleResize()
})
},
handleVisibilityChange () {
handleVisibilityChange() {
this.$nextTick(() => {
if (!document.hidden && this.bottomedOut(BOTTOMED_OUT_OFFSET)) {
this.scrollDown({ forceRead: true })
@ -139,7 +158,7 @@ const Chat = {
})
},
// "Sticks" scroll to bottom instead of top, helps with OSK resizing the viewport
handleResize (opts = {}) {
handleResize(opts = {}) {
const { delayed = false } = opts
if (delayed) {
@ -160,40 +179,56 @@ const Chat = {
this.lastScrollPosition = getScrollPosition()
})
},
scrollDown (options = {}) {
scrollDown(options = {}) {
const { behavior = 'auto', forceRead = false } = options
this.$nextTick(() => {
window.scrollTo({ top: document.documentElement.scrollHeight, behavior })
window.scrollTo({
top: document.documentElement.scrollHeight,
behavior,
})
})
if (forceRead) {
this.readChat()
}
},
readChat () {
if (!(this.currentChatMessageService && this.currentChatMessageService.maxId)) { return }
if (document.hidden) { return }
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
lastReadId,
})
},
bottomedOut (offset) {
bottomedOut(offset) {
return isBottomedOut(offset)
},
reachedTop () {
reachedTop() {
return window.scrollY <= 0
},
cullOlderCheck () {
cullOlderCheck() {
window.setTimeout(() => {
if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
this.$store.dispatch('cullOlderMessages', this.currentChatMessageService.chatId)
this.$store.dispatch(
'cullOlderMessages',
this.currentChatMessageService.chatId,
)
}
}, 5000)
},
handleScroll: _.throttle(function () {
this.lastScrollPosition = getScrollPosition()
if (!this.currentChat) { return }
if (!this.currentChat) {
return
}
if (this.reachedTop()) {
this.fetchChat({ maxId: this.currentChatMessageService.minId })
@ -213,22 +248,27 @@ const Chat = {
this.jumpToBottomButtonVisible = true
}
}, 200),
handleScrollUp (positionBeforeLoading) {
handleScrollUp(positionBeforeLoading) {
const positionAfterLoading = getScrollPosition()
window.scrollTo({
top: getNewTopPosition(positionBeforeLoading, positionAfterLoading)
top: getNewTopPosition(positionBeforeLoading, positionAfterLoading),
})
},
fetchChat ({ isFirstFetch = false, fetchLatest = false, maxId }) {
fetchChat({ isFirstFetch = false, fetchLatest = false, maxId }) {
const chatMessageService = this.currentChatMessageService
if (!chatMessageService) { return }
if (fetchLatest && this.streamingEnabled) { return }
if (!chatMessageService) {
return
}
if (fetchLatest && this.streamingEnabled) {
return
}
const chatId = chatMessageService.chatId
const fetchOlderMessages = !!maxId
const sinceId = fetchLatest && chatMessageService.maxId
return this.backendInteractor.chatMessages({ id: chatId, maxId, sinceId })
return this.backendInteractor
.chatMessages({ id: chatId, maxId, sinceId })
.then((messages) => {
// Clear the current chat in case we're recovering from a ws connection loss.
if (isFirstFetch) {
@ -236,28 +276,34 @@ const Chat = {
}
const positionBeforeUpdate = getScrollPosition()
this.$store.dispatch('addChatMessages', { chatId, messages }).then(() => {
this.$nextTick(() => {
if (fetchOlderMessages) {
this.handleScrollUp(positionBeforeUpdate)
}
this.$store
.dispatch('addChatMessages', { chatId, messages })
.then(() => {
this.$nextTick(() => {
if (fetchOlderMessages) {
this.handleScrollUp(positionBeforeUpdate)
}
// In vertical screens, the first batch of fetched messages may not always take the
// full height of the scrollable container.
// If this is the case, we want to fetch the messages until the scrollable container
// is fully populated so that the user has the ability to scroll up and load the history.
if (!isScrollable() && messages.length > 0) {
this.fetchChat({ maxId: this.currentChatMessageService.minId })
}
// In vertical screens, the first batch of fetched messages may not always take the
// full height of the scrollable container.
// If this is the case, we want to fetch the messages until the scrollable container
// is fully populated so that the user has the ability to scroll up and load the history.
if (!isScrollable() && messages.length > 0) {
this.fetchChat({
maxId: this.currentChatMessageService.minId,
})
}
})
})
})
})
},
async startFetching () {
async startFetching() {
let chat = this.findOpenedChatByRecipientId(this.recipientId)
if (!chat) {
try {
chat = await this.backendInteractor.getOrCreateChat({ accountId: this.recipientId })
chat = await this.backendInteractor.getOrCreateChat({
accountId: this.recipientId,
})
} catch (e) {
console.error('Error creating or getting a chat', e)
this.errorLoadingChat = true
@ -271,13 +317,14 @@ const Chat = {
this.doStartFetching()
}
},
doStartFetching () {
doStartFetching() {
this.$store.dispatch('startFetchingCurrentChat', {
fetcher: () => promiseInterval(() => this.fetchChat({ fetchLatest: true }), 5000)
fetcher: () =>
promiseInterval(() => this.fetchChat({ fetchLatest: true }), 5000),
})
this.fetchChat({ isFirstFetch: true })
},
handleAttachmentPosting () {
handleAttachmentPosting() {
this.$nextTick(() => {
this.handleResize()
// When the posting form size changes because of a media attachment, we need an extra resize
@ -285,11 +332,11 @@ const Chat = {
this.scrollDown({ forceRead: true })
})
},
sendMessage ({ status, media, idempotencyKey }) {
sendMessage({ status, media, idempotencyKey }) {
const params = {
id: this.currentChat.id,
content: status,
idempotencyKey
idempotencyKey,
}
if (media[0]) {
@ -301,52 +348,72 @@ const Chat = {
chatId: this.currentChat.id,
content: status,
userId: this.currentUser.id,
idempotencyKey
idempotencyKey,
})
this.$store.dispatch('addChatMessages', {
chatId: this.currentChat.id,
messages: [fakeMessage]
}).then(() => {
this.handleAttachmentPosting()
})
this.$store
.dispatch('addChatMessages', {
chatId: this.currentChat.id,
messages: [fakeMessage],
})
.then(() => {
this.handleAttachmentPosting()
})
return this.doSendMessage({ params, fakeMessage, retriesLeft: MAX_RETRIES })
return this.doSendMessage({
params,
fakeMessage,
retriesLeft: MAX_RETRIES,
})
},
doSendMessage ({ params, fakeMessage, retriesLeft = MAX_RETRIES }) {
doSendMessage({ params, fakeMessage, retriesLeft = MAX_RETRIES }) {
if (retriesLeft <= 0) return
this.backendInteractor.sendChatMessage(params)
.then(data => {
this.backendInteractor
.sendChatMessage(params)
.then((data) => {
this.$store.dispatch('addChatMessages', {
chatId: this.currentChat.id,
updateMaxId: false,
messages: [{ ...data, fakeId: fakeMessage.id }]
messages: [{ ...data, fakeId: fakeMessage.id }],
})
return data
})
.catch(error => {
.catch((error) => {
console.error('Error sending message', error)
this.$store.dispatch('handleMessageError', {
chatId: this.currentChat.id,
fakeId: fakeMessage.id,
isRetry: retriesLeft !== MAX_RETRIES
isRetry: retriesLeft !== MAX_RETRIES,
})
if ((error.statusCode >= 500 && error.statusCode < 600) || error.message === 'Failed to fetch') {
this.messageRetriers[fakeMessage.id] = setTimeout(() => {
this.doSendMessage({ params, fakeMessage, retriesLeft: retriesLeft - 1 })
}, 1000 * (2 ** (MAX_RETRIES - retriesLeft)))
if (
(error.statusCode >= 500 && error.statusCode < 600) ||
error.message === 'Failed to fetch'
) {
this.messageRetriers[fakeMessage.id] = setTimeout(
() => {
this.doSendMessage({
params,
fakeMessage,
retriesLeft: retriesLeft - 1,
})
},
1000 * 2 ** (MAX_RETRIES - retriesLeft),
)
}
return {}
})
return Promise.resolve(fakeMessage)
},
goBack () {
this.$router.push({ name: 'chats', params: { username: this.currentUser.screen_name } })
}
}
goBack() {
this.$router.push({
name: 'chats',
params: { username: this.currentUser.screen_name },
})
},
},
}
export default Chat

View file

@ -1,19 +1,13 @@
export default {
name: 'Chat',
selector: '.chat-message-list',
validInnerComponents: [
'Text',
'Link',
'Icon',
'Avatar',
'ChatMessage'
],
validInnerComponents: ['Text', 'Link', 'Icon', 'Avatar', 'ChatMessage'],
defaultRules: [
{
directives: {
background: '--bg',
blur: '5px'
}
}
]
blur: '5px',
},
},
],
}

View file

@ -3,14 +3,17 @@ export const getScrollPosition = () => {
return {
scrollTop: window.scrollY,
scrollHeight: document.documentElement.scrollHeight,
offsetHeight: window.innerHeight
offsetHeight: window.innerHeight,
}
}
// A helper function that is used to keep the scroll position fixed as the new elements are added to the top
// Takes two scroll positions, before and after the update.
export const getNewTopPosition = (previousPosition, newPosition) => {
return previousPosition.scrollTop + (newPosition.scrollHeight - previousPosition.scrollHeight)
return (
previousPosition.scrollTop +
(newPosition.scrollHeight - previousPosition.scrollHeight)
)
}
export const isBottomedOut = (offset = 0) => {

View file

@ -1,4 +1,5 @@
import { mapState, mapGetters } from 'vuex'
import { mapGetters, mapState } 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'
@ -7,31 +8,31 @@ const ChatList = {
components: {
ChatListItem,
List,
ChatNew
ChatNew,
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
currentUser: (state) => state.users.currentUser,
}),
...mapGetters(['sortedChatList'])
...mapGetters(['sortedChatList']),
},
data () {
data() {
return {
isNew: false
isNew: false,
}
},
created () {
created() {
this.$store.dispatch('fetchChats', { latest: true })
},
methods: {
cancelNewChat () {
cancelNewChat() {
this.isNew = false
this.$store.dispatch('fetchChats', { latest: true })
},
newChat () {
newChat() {
this.isNew = true
}
}
},
},
}
export default ChatList

View file

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

View file

@ -1,24 +1,23 @@
import { mapState, mapGetters } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import Popover from '../popover/popover.vue'
import { defineAsyncComponent } from 'vue'
import { mapGetters, mapState } from 'vuex'
import Attachment from '../attachment/attachment.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import ChatMessageDate from '../chat_message_date/chat_message_date.vue'
import Gallery from '../gallery/gallery.vue'
import LinkPreview from '../link-preview/link-preview.vue'
import Popover from '../popover/popover.vue'
import StatusContent from '../status_content/status_content.vue'
import ChatMessageDate from '../chat_message_date/chat_message_date.vue'
import { defineAsyncComponent } from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
faEllipsisH
} from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from 'src/stores/interface'
import UserAvatar from '../user_avatar/user_avatar.vue'
library.add(
faTimes,
faEllipsisH
)
import { useInstanceStore } from 'src/stores/instance.js'
import { useInterfaceStore } from 'src/stores/interface'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faEllipsisH, faTimes } from '@fortawesome/free-solid-svg-icons'
library.add(faTimes, faEllipsisH)
const ChatMessage = {
name: 'ChatMessage',
@ -27,7 +26,7 @@ const ChatMessage = {
'edited',
'noHeading',
'chatViewItem',
'hoveredMessageChain'
'hoveredMessageChain',
],
emits: ['hover'],
components: {
@ -38,73 +37,82 @@ const ChatMessage = {
Gallery,
LinkPreview,
ChatMessageDate,
UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
UserPopover: defineAsyncComponent(
() => import('../user_popover/user_popover.vue'),
),
},
computed: {
// Returns HH:MM (hours and minutes) in local time.
createdAt () {
createdAt() {
const time = this.chatViewItem.data.created_at
return time.toLocaleTimeString('en', { hour: '2-digit', minute: '2-digit', hour12: false })
return time.toLocaleTimeString('en', {
hour: '2-digit',
minute: '2-digit',
hour12: false,
})
},
isCurrentUser () {
isCurrentUser() {
return this.message.account_id === this.currentUser.id
},
message () {
message() {
return this.chatViewItem.data
},
isMessage () {
isMessage() {
return this.chatViewItem.type === 'message'
},
messageForStatusContent () {
messageForStatusContent() {
return {
summary: '',
emojis: this.message.emojis,
raw_html: this.message.content || '',
text: this.message.content || '',
attachments: this.message.attachments
attachments: this.message.attachments,
}
},
hasAttachment () {
hasAttachment() {
return this.message.attachments.length > 0
},
...mapPiniaState(useInterfaceStore, {
betterShadow: store => store.browserSupport.cssFilter
betterShadow: (store) => store.browserSupport.cssFilter,
}),
...mapState({
currentUser: state => state.users.currentUser,
restrictedNicknames: state => state.instance.restrictedNicknames
currentUser: (state) => state.users.currentUser,
restrictedNicknames: (state) => useInstanceStore().restrictedNicknames,
}),
popoverMarginStyle () {
popoverMarginStyle() {
if (this.isCurrentUser) {
return {}
} else {
return { left: 50 }
}
},
...mapGetters(['mergedConfig', 'findUser'])
...mapPiniaState(useMergedConfigStore, ['mergedConfig', 'findUser']),
},
data () {
data() {
return {
hovered: false,
menuOpened: false
menuOpened: false,
}
},
methods: {
onHover (bool) {
this.$emit('hover', { isHovered: bool, messageChainId: this.chatViewItem.messageChainId })
onHover(bool) {
this.$emit('hover', {
isHovered: bool,
messageChainId: this.chatViewItem.messageChainId,
})
},
async deleteMessage () {
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
chatId: this.chatViewItem.data.chat_id,
})
}
this.hovered = false
this.menuOpened = false
}
}
},
},
}
export default ChatMessage

View file

@ -2,29 +2,21 @@ export default {
name: 'ChatMessage',
selector: '.chat-message',
variants: {
outgoing: '.outgoing'
outgoing: '.outgoing',
},
validInnerComponents: [
'Text',
'Icon',
'Border',
'Button',
'RichContent',
'Attachment',
'PollGraph'
],
validInnerComponents: ['Text', 'Icon', 'Border', 'PollGraph'],
defaultRules: [
{
directives: {
background: '--bg, 2',
backgroundNoCssColor: 'yes'
}
backgroundNoCssColor: 'yes',
},
},
{
variant: 'outgoing',
directives: {
background: '--bg, 5'
}
}
]
background: '--bg, 5',
},
},
],
}

View file

@ -11,16 +11,19 @@ export default {
name: 'Timeago',
props: ['date'],
computed: {
displayDate () {
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(localeService.internalToBrowserLocale(this.$i18n.locale), { day: 'numeric', month: 'long' })
return this.date.toLocaleDateString(
localeService.internalToBrowserLocale(this.$i18n.locale),
{ day: 'numeric', month: 'long' },
)
}
}
}
},
},
}
</script>

View file

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

View file

@ -1,23 +1,24 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import { defineAsyncComponent } from 'vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import UserAvatar from '../user_avatar/user_avatar.vue'
export default {
name: 'ChatTitle',
components: {
UserAvatar,
RichContent,
UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
UserPopover: defineAsyncComponent(
() => import('../user_popover/user_popover.vue'),
),
},
props: [
'user', 'withAvatar'
],
props: ['user', 'withAvatar'],
computed: {
title () {
title() {
return this.user ? this.user.screen_name_ui : ''
},
htmlTitle () {
htmlTitle() {
return this.user ? this.user.name_html : ''
}
}
},
},
}

View file

@ -19,6 +19,7 @@
:title="'@'+(user && user.screen_name_ui)"
:html="htmlTitle"
:emoji="user.emoji || []"
:is-local="user.is_local"
/>
</div>
</template>

View file

@ -1,7 +1,7 @@
<template>
<label
class="checkbox"
:class="[{ disabled, indeterminate, 'indeterminate-fix': indeterminateTransitionFix }, radio ? '-radio' : '-checkbox']"
:class="[{ ['-disabled']: disabled, indeterminate, 'indeterminate-fix': indeterminateTransitionFix }, radio ? '-radio' : '-checkbox']"
>
<span
v-if="!!$slots.before"
@ -36,30 +36,25 @@
<script>
export default {
props: [
'radio',
'modelValue',
'indeterminate',
'disabled'
],
props: ['radio', 'modelValue', 'indeterminate', 'disabled'],
emits: ['update:modelValue'],
data: (vm) => ({
indeterminateTransitionFix: vm.indeterminate
indeterminateTransitionFix: vm.indeterminate,
}),
watch: {
indeterminate (e) {
indeterminate(e) {
if (e) {
this.indeterminateTransitionFix = true
}
}
},
},
methods: {
onTransitionEnd () {
onTransitionEnd() {
if (!this.indeterminate) {
this.indeterminateTransitionFix = false
}
}
}
},
},
}
</script>
@ -123,7 +118,7 @@ export default {
.disabled {
.checkbox-indicator::before {
background-color: var(--background);
background-color: transparent;
}
}

View file

@ -1,18 +1,27 @@
.color-input {
display: inline-flex;
flex-wrap: wrap;
max-width: 10em;
&.-compact {
max-width: none;
}
.label {
flex: 1 1 auto;
grid-area: label;
}
.opt {
grid-area: checkbox;
margin-right: 0.5em;
}
&-field.input {
display: inline-flex;
flex: 0 0 0;
max-width: 9em;
flex: 1 1 10em;
max-width: 10em;
grid-area: input;
display: flex;
align-items: stretch;
input {

View file

@ -1,7 +1,7 @@
<template>
<div
class="color-input style-control"
:class="{ disabled: !present || disabled }"
:class="{ disabled: !present || disabled, '-compact': compact }"
>
<label
:for="name"
@ -19,7 +19,7 @@
/>
<div
class="input color-input-field"
:class="{ disabled: !present || disabled }"
:class="{ disabled: !present || disabled, unstyled }"
>
<input
:id="name + '-t'"
@ -64,86 +64,95 @@
</div>
</template>
<script>
import Checkbox from '../checkbox/checkbox.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
import { throttle } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEyeDropper
} from '@fortawesome/free-solid-svg-icons'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
import Checkbox from '../checkbox/checkbox.vue'
library.add(
faEyeDropper
)
import { library } from '@fortawesome/fontawesome-svg-core'
import { faEyeDropper } from '@fortawesome/free-solid-svg-icons'
library.add(faEyeDropper)
export default {
components: {
Checkbox
Checkbox,
},
props: {
// Name of color, used for identifying
name: {
required: true,
type: String
type: String,
},
// Readable label
label: {
required: true,
type: String
required: false,
type: String,
},
// use unstyled, uh, style
unstyled: {
required: false,
type: Boolean,
},
// Color value, should be required but vue cannot tell the difference
// between "property missing" and "property set to undefined"
modelValue: {
required: false,
type: String,
default: undefined
default: undefined,
},
// Color fallback to use when value is not defeind
fallback: {
required: false,
type: String,
default: undefined
default: undefined,
},
// Disable the control
disabled: {
required: false,
type: Boolean,
default: false
default: false,
},
// Show "optional" tickbox, for when value might become mandatory
showOptionalCheckbox: {
required: false,
type: Boolean,
default: true
default: true,
},
// Force "optional" tickbox to hide
hideOptionalCheckbox: {
required: false,
type: Boolean,
default: false
}
default: false,
},
compact: {
required: false,
type: Boolean,
},
},
emits: ['update:modelValue'],
computed: {
present () {
present() {
return typeof this.modelValue !== 'undefined'
},
validColor () {
validColor() {
return hex2rgb(this.modelValue || this.fallback)
},
transparentColor () {
transparentColor() {
return this.modelValue === 'transparent'
},
computedColor () {
return this.modelValue && (this.modelValue.startsWith('--') || this.modelValue.startsWith('$'))
}
computedColor() {
return (
this.modelValue &&
(this.modelValue.startsWith('--') || this.modelValue.startsWith('$'))
)
},
},
methods: {
updateValue: throttle(function (value) {
this.$emit('update:modelValue', value)
}, 100)
}
}, 100),
},
}
</script>
<style lang="scss" src="./color_input.scss"></style>

View file

@ -0,0 +1,87 @@
import Checkbox from 'src/components/checkbox/checkbox.vue'
import ColorInput from 'src/components/color_input/color_input.vue'
import genRandomSeed from 'src/services/random_seed/random_seed.service.js'
import {
adoptStyleSheets,
createStyleSheet,
} from 'src/services/style_setter/style_setter.js'
export default {
components: {
Checkbox,
ColorInput,
},
props: [
'shadow',
'shadowControl',
'previewClass',
'previewStyle',
'previewCss',
'disabled',
'invalid',
'noColorControl',
],
emits: ['update:shadow'],
data() {
return {
colorOverride: undefined,
lightGrid: false,
zoom: 100,
randomSeed: genRandomSeed(),
}
},
mounted() {
this.update()
},
computed: {
hideControls() {
return typeof this.shadow === 'string'
},
},
watch: {
previewCss() {
this.update()
},
previewStyle() {
this.update()
},
zoom() {
this.update()
},
},
methods: {
updateProperty(axis, value) {
this.$emit('update:shadow', { axis, value: Number(value) })
},
update() {
const sheet = createStyleSheet('style-component-preview', 90)
sheet.clear()
const result = [this.previewCss]
if (this.colorOverride) result.push(`--background: ${this.colorOverride}`)
const styleRule = [
'#component-preview-',
this.randomSeed,
' {\n',
'.preview-block {\n',
`zoom: ${this.zoom / 100};`,
this.previewStyle,
'\n}',
'\n}',
].join('')
sheet.addRule(styleRule)
sheet.addRule(
['#component-preview-', this.randomSeed, ' {\n', ...result, '\n}'].join(
'',
),
)
sheet.ready = true
adoptStyleSheets()
},
},
}

View file

@ -0,0 +1,151 @@
.ComponentPreview {
display: grid;
grid-template-columns: 1em 1fr 1fr 1em;
grid-template-rows: 2em 1fr 1fr 1fr 1em 2em max-content;
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"x-slide x-slide x-slide . "
"x-num x-num y-num y-num "
"assists assists assists assists";
grid-gap: 0.5em;
&:not(.-shadow-controls) {
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"assists assists assists assists";
grid-template-rows: 2em 1fr 1fr 1fr max-content;
}
.header {
grid-area: header;
place-self: baseline center;
line-height: 2;
}
.invalid-container {
position: absolute;
inset: 0;
display: grid;
place-items: center center;
background-color: rgb(100 0 0 / 50%);
.alert {
padding: 0.5em 1em;
}
}
.assists {
grid-area: assists;
display: grid;
grid-auto-flow: row;
grid-auto-rows: 2em;
grid-gap: 0.5em;
}
.input-light-grid {
justify-self: center;
}
.input-number {
min-width: 2em;
}
.x-shift-number {
grid-area: x-num;
justify-self: right;
}
.y-shift-number {
grid-area: y-num;
justify-self: left;
}
.x-shift-number,
.y-shift-number {
input {
max-width: 4em;
}
}
.x-shift-slider {
grid-area: x-slide;
height: auto;
align-self: start;
min-width: 10em;
}
.y-shift-slider {
grid-area: y-slide;
writing-mode: vertical-lr;
justify-self: left;
min-height: 10em;
}
.x-shift-slider,
.y-shift-slider {
padding: 0;
}
.preview-window {
--__grid-color1: rgb(102 102 102);
--__grid-color2: rgb(153 153 153);
--__grid-color1-disabled: rgb(102 102 102 / 20%);
--__grid-color2-disabled: rgb(153 153 153 / 20%);
&.-light-grid {
--__grid-color1: rgb(205 205 205);
--__grid-color2: rgb(255 255 255);
--__grid-color1-disabled: rgb(205 205 205 / 20%);
--__grid-color2-disabled: rgb(255 255 255 / 20%);
}
position: relative;
grid-area: preview;
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
min-width: 10em;
min-height: 10em;
background-color: var(--__grid-color2);
background-image:
linear-gradient(45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1) 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
border-radius: var(--roundness);
&.disabled {
background-color: var(--__grid-color2-disabled);
background-image:
linear-gradient(45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1-disabled) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1-disabled) 75%);
}
.preview-block {
background: var(--background, var(--bg));
display: flex;
justify-content: center;
align-items: center;
min-width: 33%;
min-height: 33%;
max-width: 80%;
max-height: 80%;
border-width: 0;
border-style: solid;
border-color: var(--border);
border-radius: var(--roundness);
box-shadow: var(--shadow);
}
}
}

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