Compare commits

..

156 commits

Author SHA1 Message Date
Natsu Kagami 7fa08d4b6d
Don't statically pin the size of the carousel 2024-05-19 02:01:52 +02:00
Natsu Kagami 97790d929e
Link to GtS settings when we know we are on GtS 2024-05-19 02:01:52 +02:00
Natsu Kagami 394bcb9cf8
Make main column bigger 2024-05-19 02:01:52 +02:00
Natsu Kagami 2f3adec3ea
Add a bit more touch 2024-05-19 02:01:52 +02:00
Natsu Kagami d25ab5b38f
Force display instance
Because I don't like this decision from Phanpy
2024-05-19 02:01:51 +02:00
Natsu Kagami 5324b69bc5
Automatically put people into DTTHDon login 2024-05-19 02:01:51 +02:00
Natsu Kagami f51b94f56b
Add some DTTH notice 2024-05-19 02:01:51 +02:00
Natsu Kagami b3d419b38a
Incorporate commit hash 2024-05-19 02:01:51 +02:00
Natsu Kagami 56fdd902f7
Get flakes to work 2024-05-19 02:01:50 +02:00
Chee Aun 6976191113
Merge pull request #532 from cheeaun/main
Update from main
2024-05-15 21:34:39 +08:00
Lim Chee Aun d4a0a080b5 Bump up max entries for icons 2024-05-15 19:38:28 +08:00
Lim Chee Aun bc4e3b0f72 Fix red too faint in dark mode 2024-05-14 23:39:48 +08:00
Lim Chee Aun ac760265da Fix post preview internals becoming clickable 2024-05-11 13:09:08 +08:00
Lim Chee Aun 98b0ccf032 Default to floor rounding mode 2024-05-10 12:11:57 +08:00
Lim Chee Aun 90f06c511a Test allow linking to post from generic accounts modal 2024-05-08 10:29:00 +08:00
Lim Chee Aun e7aad03279 Preliminary implementation of moderation_warning notifications 2024-05-08 10:28:34 +08:00
Chee Aun e224bdc8c3
Merge pull request #525 from cheeaun/main
Update from main
2024-05-06 21:42:48 +08:00
Lim Chee Aun 1c6b0aa0d7 Upgrade dependencies 2024-05-06 12:48:55 +08:00
Lim Chee Aun 3e1b9ff53d Apply filter context in compact status too 2024-05-02 23:29:01 +08:00
Lim Chee Aun 5c9a47c31e Might as well re-use it for instances search 2024-05-02 00:14:48 +08:00
Lim Chee Aun 65a4c3441c Add search for custom emojis 2024-05-02 00:14:25 +08:00
Lim Chee Aun 77bc06545c Handle inline images 2024-05-01 15:05:29 +08:00
Lim Chee Aun 11e64a2cc4 Fix filter expiry wrongly set if there's no expiry 2024-04-28 08:30:52 +08:00
Lim Chee Aun 5433e4e119 initStates needed for standalone compose page 2024-04-28 08:30:52 +08:00
Lim Chee Aun c8dc32b884 Test caching shazam states 2024-04-28 08:30:52 +08:00
Lim Chee Aun 1f29aee26e Upgrade dependencies 2024-04-28 08:30:52 +08:00
Lim Chee Aun daae055f4d List out forks 2024-04-28 08:30:52 +08:00
Chee Aun 044f754d7e
Merge pull request #522 from mickaobrien/timeline-enter-keyboard-shortcut-fix
Fix `enter` keyboard shortcut on timeline
2024-04-28 08:29:47 +08:00
Mick O'Brien 5ae2058c07 Fix enter keyboard shortcut on timeline
Currently pressing `enter` opens the active status if the status or any
focusable child of the status is focused e.g. the avatar or a link.

I think it should only open the post details when the post itself is
focused.
2024-04-26 12:23:53 +01:00
Chee Aun 51457302f8
Merge pull request #506 from cheeaun/main
Update from main
2024-04-19 09:18:55 +08:00
Chee Aun b6cf53c221
Merge pull request #489 from cheeaun/main
Update from main
2024-04-17 20:21:00 +08:00
Chee Aun 65d51b077f
Merge pull request #475 from cheeaun/main
Update from main
2024-04-04 19:41:09 +08:00
Chee Aun ecd308ce39
Merge pull request #472 from cheeaun/main
Update from main
2024-03-28 20:55:24 +08:00
Chee Aun 8fc5e10be9
Merge pull request #465 from cheeaun/main
Update from main
2024-03-26 22:19:53 +08:00
Chee Aun c7c764a4f0
Merge pull request #459 from cheeaun/main
Update from main
2024-03-17 21:12:58 +08:00
Chee Aun e73ae563e6
Merge pull request #456 from cheeaun/main
Update from main
2024-03-12 14:57:02 +08:00
Chee Aun d34cce6d18
Merge pull request #450 from cheeaun/main
Update from main
2024-03-11 17:14:49 +08:00
Chee Aun aed84226a1
Merge pull request #446 from cheeaun/main
Update from main
2024-03-07 22:28:08 +08:00
Chee Aun 6827a3811e
Merge pull request #445 from cheeaun/main
Update from main
2024-03-06 19:24:58 +08:00
Chee Aun 83f9498b79
Merge pull request #429 from cheeaun/main
Update from main
2024-03-06 17:52:16 +08:00
Chee Aun 77818d3ac8
Merge pull request #412 from cheeaun/main
Update from main
2024-02-25 20:32:39 +08:00
Chee Aun 35a31e1613
Merge pull request #406 from cheeaun/main
Update from main
2024-02-04 00:40:45 +08:00
Chee Aun 247ed4a7e1
Merge pull request #404 from cheeaun/main
Update from main
2024-01-21 23:05:33 +08:00
Chee Aun 98e6a6e42e
Merge pull request #401 from cheeaun/main
Update from main
2024-01-18 10:49:55 +08:00
Chee Aun fe91c13703
Merge pull request #397 from cheeaun/main
Update from main
2024-01-16 18:36:41 +08:00
Chee Aun e791ea8015
Merge pull request #393 from cheeaun/main
Update from main
2024-01-11 21:49:26 +08:00
Chee Aun dbef125ee3
Merge pull request #387 from cheeaun/main
Update from main
2024-01-09 00:13:19 +08:00
Chee Aun 32c53b8c6e
Merge pull request #388 from cheeaun/hotfix/infinite-loop-intersection-observer
[Hotfix] Infinite loop bug in intersection observer
2024-01-06 03:24:21 +08:00
Lim Chee Aun bd112f19b1 Potential fix to infinite loop of intersection observer 2024-01-06 03:18:08 +08:00
Chee Aun adf0b351c1
Merge pull request #383 from cheeaun/main
Update from main
2024-01-05 10:16:09 +08:00
Chee Aun 8aa05422b1
Merge pull request #375 from cheeaun/main
Update from main
2023-12-31 18:21:25 +08:00
Chee Aun 1e21f519f3
Merge pull request #360 from cheeaun/main
Update from main
2023-12-22 23:34:15 +08:00
Chee Aun 19da64a787
Merge pull request #352 from cheeaun/main
Update from main
2023-12-13 23:18:23 +08:00
Chee Aun efca016d81
Merge pull request #337 from cheeaun/main
Update from main
2023-12-06 15:23:20 +08:00
Chee Aun 1d9d22a214
Merge pull request #334 from cheeaun/main
Update from main
2023-11-25 18:04:32 +08:00
Chee Aun e4dd76cb11
Merge pull request #333 from cheeaun/main
Update from main
2023-11-22 11:16:11 +08:00
Chee Aun 0f537667d8
Merge pull request #327 from cheeaun/main
Update from main
2023-11-20 13:29:33 +08:00
Chee Aun 3f575142f1
Merge pull request #321 from cheeaun/main
Update from main
2023-11-16 20:01:02 +08:00
Chee Aun 11407d0f3c
Merge pull request #311 from cheeaun/main
Update from main
2023-11-13 22:13:48 +08:00
Chee Aun 84b6368253
Merge pull request #310 from cheeaun/main
Update from main
2023-11-07 14:59:48 +08:00
Chee Aun a75b214999
Merge pull request #308 from cheeaun/main
Update from main
2023-11-07 00:52:17 +08:00
Chee Aun 33c3b63b6e
Merge pull request #307 from cheeaun/main
Update from main
2023-11-06 23:05:24 +08:00
Chee Aun 74991c326d
Merge pull request #286 from cheeaun/main
Update from main
2023-11-06 21:18:15 +08:00
Chee Aun f558a8cd32
Merge pull request #283 from cheeaun/main
Update from main
2023-10-27 01:04:55 +08:00
Chee Aun 87f1d17ce3
Merge pull request #272 from cheeaun/main
Update from main
2023-10-26 20:26:20 +08:00
Chee Aun 0cf7d683ee
Merge pull request #268 from cheeaun/main
Update from main
2023-10-20 00:44:53 +08:00
Chee Aun cb80057f21
Merge pull request #267 from cheeaun/main
Update from main
2023-10-19 07:17:54 +08:00
Chee Aun bf6ee572fb
Merge pull request #266 from cheeaun/main
Update from main
2023-10-18 16:36:11 +08:00
Chee Aun 7b049385f7
Merge pull request #264 from cheeaun/main
Update from main
2023-10-16 22:10:32 +08:00
Chee Aun 6feb2e7b41
Merge pull request #263 from cheeaun/main
Update from main
2023-10-16 20:06:11 +08:00
Chee Aun 4ea8e2c145
Merge pull request #261 from cheeaun/main
Update from main
2023-10-16 19:53:00 +08:00
Chee Aun cd68aee186
Merge pull request #260 from cheeaun/main
Update from main
2023-10-12 01:34:30 +08:00
Chee Aun 5ba2af0970
Merge pull request #259 from cheeaun/main
Update from main
2023-10-09 23:10:34 +08:00
Chee Aun 9b1800dc56
Merge pull request #254 from cheeaun/main
Update from main
2023-10-07 22:23:18 +08:00
Chee Aun 2e97f19133
Merge pull request #252 from cheeaun/main
Update from main
2023-10-06 21:19:29 +08:00
Chee Aun 3f23d42966
Merge pull request #251 from cheeaun/main
Update from main
2023-10-04 12:35:04 +08:00
Chee Aun e9c5025d31
Merge pull request #250 from cheeaun/main
Update from main
2023-10-04 00:00:27 +08:00
Chee Aun eb013645e7
Merge pull request #248 from cheeaun/main
Update from main
2023-10-03 19:21:44 +08:00
Chee Aun d5a3b48f0f
Merge pull request #228 from cheeaun/main
Update from main
2023-10-02 23:05:44 +08:00
Chee Aun b8d92bceb2
Merge pull request #223 from cheeaun/main
Update from main
2023-09-01 13:00:48 +08:00
Chee Aun def1e8d099
Merge pull request #206 from cheeaun/main
Update from main
2023-08-24 17:50:49 +08:00
Chee Aun 8e2099daa7
Merge pull request #200 from cheeaun/main
Update from main
2023-08-06 12:03:53 +08:00
Chee Aun ba81352844
Merge pull request #195 from cheeaun/main
Update from main
2023-07-30 15:23:09 +08:00
Chee Aun 650b71e9cc
Merge pull request #194 from cheeaun/main
Update from main
2023-07-23 23:29:16 +08:00
Chee Aun a6e369b1a8
Merge pull request #193 from cheeaun/main 2023-07-23 01:07:14 +08:00
Chee Aun f479feba65
Merge pull request #192 from cheeaun/main
Update from main
2023-07-22 21:06:22 +08:00
Chee Aun 38680aa6e7
Merge pull request #190 from cheeaun/main
Update from main
2023-07-22 10:04:50 +08:00
Chee Aun d1d606fa10
Merge pull request #182 from cheeaun/main
Update from main
2023-07-21 10:32:30 +08:00
Chee Aun 375da8d173
Merge pull request #173 from cheeaun/main
Update from main
2023-06-30 23:25:52 +08:00
Chee Aun 2c31e8e04c
Merge pull request #172 from cheeaun/main
Update from main
2023-06-24 09:15:26 +08:00
Chee Aun 534c4c97cd
Merge pull request #170 from cheeaun/main
Update from main
2023-06-20 21:38:11 +08:00
Chee Aun 482a64cfac
Merge pull request #166 from cheeaun/main
Update from main
2023-06-16 18:21:45 +08:00
Chee Aun 2dc1343f54
Merge pull request #156 from cheeaun/main
Update from main
2023-06-15 09:05:28 +08:00
Chee Aun 5e52fa87e0
Merge pull request #153 from cheeaun/main
Update from main
2023-05-30 09:46:02 +08:00
Chee Aun 6b03ae1fee
Merge pull request #152 from cheeaun/main
Update from main
2023-05-27 21:57:38 +08:00
Chee Aun c763d8b954
Merge pull request #151 from cheeaun/main
Update from main
2023-05-23 13:11:19 +08:00
Chee Aun 0a5d7267d5
Merge pull request #143 from cheeaun/main
Update from main
2023-05-22 23:40:44 +08:00
Chee Aun 5ee926481a
Merge pull request #142 from cheeaun/main
Update from main
2023-05-18 00:13:24 +08:00
Chee Aun 0cd9a2db6e
Merge pull request #141 from cheeaun/main
Update from main
2023-05-16 19:38:41 +08:00
Chee Aun 69f9b750c2
Merge pull request #139 from cheeaun/main
Update from main
2023-05-14 22:02:10 +08:00
Chee Aun f5955ef258
Merge pull request #133 from cheeaun/main
Update from main
2023-05-14 11:55:03 +08:00
Chee Aun 27a999f733
Merge pull request #122 from cheeaun/main
Update from main
2023-05-11 21:59:39 +08:00
Chee Aun 54271101c1
Merge pull request #119 from cheeaun/main
Update from main
2023-04-28 23:23:10 +08:00
Chee Aun d0cbb0812d
Merge pull request #117 from cheeaun/main
Update from main
2023-04-26 15:36:11 +08:00
Chee Aun ad45bf9d19
Merge pull request #97 from cheeaun/main
Update from main
2023-04-25 22:54:01 +08:00
Chee Aun 982f7b3ec4
Merge pull request #94 from cheeaun/main
Update from main
2023-04-07 21:57:29 +08:00
Chee Aun 4e50f227d8
Merge pull request #93 from cheeaun/main
Update from main
2023-04-03 09:28:40 +08:00
Chee Aun 546e77d3e1
Merge pull request #92 from cheeaun/main
Update from main
2023-04-03 01:25:32 +08:00
Chee Aun e29f14bbcf
Merge pull request #86 from cheeaun/main
Update from main
2023-03-31 23:21:27 +08:00
Chee Aun 05e87e084a
Merge pull request #84 from cheeaun/main
Update from main
2023-03-16 23:31:30 +08:00
Chee Aun 01f10d3daa
Merge pull request #83 from cheeaun/main
Update from main
2023-03-15 23:25:58 +08:00
Chee Aun fc615e0c0d
Merge pull request #82 from cheeaun/main
Update from main
2023-03-15 22:12:17 +08:00
Chee Aun 25e9771754
Merge pull request #80 from cheeaun/main
Update from main
2023-03-15 21:21:04 +08:00
Chee Aun 5e916559b3
Merge pull request #79 from cheeaun/main
Update from main
2023-03-15 20:49:59 +08:00
Chee Aun 883fe39b6c
Merge pull request #78 from cheeaun/main
Update from main
2023-03-03 13:08:28 +08:00
Chee Aun 9933d83846
Merge pull request #77 from cheeaun/main
Update from main
2023-03-02 22:56:43 +08:00
Chee Aun 7d806301f2
Merge pull request #74 from cheeaun/main
Update from main
2023-03-02 22:29:28 +08:00
Chee Aun faf9cbf23d
Merge pull request #73 from cheeaun/main
Update from main
2023-02-24 12:35:44 +08:00
Chee Aun a0f79e7eea
Merge pull request #71 from cheeaun/main
Update from main
2023-02-24 12:04:00 +08:00
Chee Aun 0b1974e94b
Merge pull request #70 from cheeaun/main
Update from main
2023-02-23 23:31:09 +08:00
Chee Aun b4a4615b9a
Merge pull request #68 from cheeaun/main
Update from main
2023-02-22 09:51:37 +08:00
Chee Aun dda14587c0
Merge pull request #67 from cheeaun/main
Update from main
2023-02-22 00:47:07 +08:00
Chee Aun ed9289d8c6
Merge pull request #66 from cheeaun/main
Update from main
2023-02-21 09:22:54 +08:00
Chee Aun 6274f2f24f
Merge pull request #63 from cheeaun/main
Update from main
2023-02-20 00:49:21 +08:00
Chee Aun b4e8ba820c
Merge pull request #62 from cheeaun/main
Update from main
2023-02-20 00:23:56 +08:00
Chee Aun 29896dfe0e
Merge pull request #54 from cheeaun/main
Update from main
2023-02-19 22:33:56 +08:00
Chee Aun 69c3f1a082
Merge pull request #53 from cheeaun/main
Update from main
2023-02-01 02:10:43 +08:00
Chee Aun 451dc57a69
Merge pull request #49 from cheeaun/main
Update from main
2023-02-01 01:27:15 +08:00
Chee Aun 4fbee9168d
Merge pull request #48 from cheeaun/main
Update from main
2023-01-17 21:34:52 +08:00
Chee Aun 6ecc015199
Merge pull request #47 from cheeaun/main
Update from main
2023-01-17 18:05:25 +08:00
Chee Aun a7a3d5605b
Merge pull request #42 from cheeaun/main
Update from main
2023-01-06 23:24:13 +08:00
Chee Aun ad4ed66cd6
Merge pull request #41 from cheeaun/main
Update from main
2023-01-01 19:26:30 +08:00
Chee Aun 4277992773
Merge pull request #39 from cheeaun/main
Update from main
2023-01-01 19:06:33 +08:00
Chee Aun 6bcf6b143c
Merge pull request #38 from cheeaun/main
Update from main
2022-12-28 20:57:56 +08:00
Chee Aun 9e9f7a6ea1
Merge pull request #37 from cheeaun/main
Update from main
2022-12-27 22:07:51 +08:00
Chee Aun f0014cb26a
Merge pull request #36 from cheeaun/main
Update from main
2022-12-27 19:59:16 +08:00
Chee Aun b0e118fcab
Merge pull request #34 from cheeaun/main
Update from main
2022-12-27 09:57:15 +08:00
Chee Aun f51201a787
Merge pull request #33 from cheeaun/main
Update from main
2022-12-27 01:18:41 +08:00
Chee Aun 5a035089ab
Merge pull request #31 from cheeaun/main
Update from main
2022-12-27 00:10:32 +08:00
Chee Aun 206f00af40
Merge pull request #27 from cheeaun/main
Update from main
2022-12-24 23:20:13 +08:00
Chee Aun 13de3d9263
Merge pull request #20 from cheeaun/main
Update from main
2022-12-24 23:06:13 +08:00
Chee Aun eb41ddf2de
Merge pull request #18 from cheeaun/main
Update from main
2022-12-22 09:04:05 +08:00
Chee Aun 940e8f5376
Merge pull request #16 from cheeaun/main
Update from main
2022-12-22 08:44:56 +08:00
Chee Aun 77ba42dba9
Merge pull request #13 from cheeaun/main
Update from main
2022-12-21 21:32:38 +08:00
Chee Aun 95e204c439
Merge pull request #12 from cheeaun/main
Update from main
2022-12-19 20:12:56 +08:00
Chee Aun 82770e8035
Merge pull request #11 from cheeaun/main 2022-12-19 18:44:01 +08:00
Chee Aun 818c8e61cd
Merge pull request #10 from cheeaun/main
Update from main
2022-12-18 13:07:45 +08:00
Chee Aun 3b8592e946
Merge pull request #9 from cheeaun/main
Update from main
2022-12-17 18:37:19 +08:00
Chee Aun c0c7d65034
Merge pull request #8 from cheeaun/main
Update from main
2022-12-16 13:59:24 +08:00
Chee Aun 5631126e8d
Merge pull request #7 from cheeaun/main
Update from main
2022-12-16 13:33:54 +08:00
Chee Aun bd2ed53f32
Merge pull request #6 from cheeaun/main
Update from main
2022-12-16 12:28:52 +08:00
Chee Aun 694fa22942
Merge pull request #5 from cheeaun/main
Update from main
2022-12-15 21:48:39 +08:00
Chee Aun 15c3979815
Merge pull request #4 from cheeaun/main
Update from main
2022-12-15 17:45:04 +08:00
Chee Aun ab5f53273f
Merge pull request #3 from cheeaun/main
Update from main
2022-12-15 14:48:02 +08:00
Chee Aun 19c2f9b048
Merge pull request #2 from cheeaun/main
Update from main
2022-12-15 13:17:46 +08:00
Chee Aun a45250ac96
Merge pull request #1 from cheeaun/main
Update from main
2022-12-15 12:02:26 +08:00
21 changed files with 2189 additions and 1688 deletions

View file

@ -244,6 +244,8 @@ And here I am. Building a Mastodon web client.
## Alternative web clients ## Alternative web clients
- Phanpy forks ↓
- [Agora](https://agorasocial.app/)
- [Pinafore](https://pinafore.social/) ([retired](https://nolanlawson.com/2023/01/09/retiring-pinafore/)) - forks ↓ - [Pinafore](https://pinafore.social/) ([retired](https://nolanlawson.com/2023/01/09/retiring-pinafore/)) - forks ↓
- [Semaphore](https://semaphore.social/) - [Semaphore](https://semaphore.social/)
- [Enafore](https://enafore.social/) - [Enafore](https://enafore.social/)

View file

@ -18,7 +18,7 @@
src = lib.cleanSource ./.; src = lib.cleanSource ./.;
npmFlags = [ "--legacy-peer-deps" ]; npmFlags = [ "--legacy-peer-deps" ];
npmDepsHash = "sha256-9tcZ3jQg+mHeI2K/dNFs0C1k7CfHwxbx+/+I8pdO/wQ="; npmDepsHash = "sha256-pL/bqTNMtekD5GzTCNPOy60OD2MC36zlg1A6MP0Fn2M=";
# npmDepsHash = lib.fakeHash; # npmDepsHash = lib.fakeHash;
# DTTH-specific env variables # DTTH-specific env variables

3074
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -12,33 +12,34 @@
"dependencies": { "dependencies": {
"@formatjs/intl-localematcher": "~0.5.4", "@formatjs/intl-localematcher": "~0.5.4",
"@formatjs/intl-segmenter": "~11.5.5", "@formatjs/intl-segmenter": "~11.5.5",
"@formkit/auto-animate": "~0.8.1", "@formkit/auto-animate": "~0.8.2",
"@github/text-expander-element": "~2.6.1", "@github/text-expander-element": "~2.6.1",
"@iconify-icons/mingcute": "~1.2.9", "@iconify-icons/mingcute": "~1.2.9",
"@justinribeiro/lite-youtube": "~1.5.0", "@justinribeiro/lite-youtube": "~1.5.0",
"@szhsin/react-menu": "~4.1.0", "@szhsin/react-menu": "~4.1.0",
"@uidotdev/usehooks": "~2.4.1", "@uidotdev/usehooks": "~2.4.1",
"compare-versions": "~6.1.0", "compare-versions": "~6.1.0",
"dayjs": "~1.11.10", "dayjs": "~1.11.11",
"dayjs-twitter": "~0.5.0", "dayjs-twitter": "~0.5.0",
"fast-blurhash": "~1.1.2", "fast-blurhash": "~1.1.2",
"fast-equals": "~5.0.1", "fast-equals": "~5.0.1",
"fuse.js": "~7.0.0",
"html-prettify": "^1.0.7", "html-prettify": "^1.0.7",
"idb-keyval": "~6.2.1", "idb-keyval": "~6.2.1",
"just-debounce-it": "~3.2.0", "just-debounce-it": "~3.2.0",
"lz-string": "~1.5.0", "lz-string": "~1.5.0",
"masto": "~6.7.0", "masto": "~6.7.7",
"moize": "~6.1.6", "moize": "~6.1.6",
"p-retry": "~6.2.0", "p-retry": "~6.2.0",
"p-throttle": "~6.1.0", "p-throttle": "~6.1.0",
"preact": "~10.20.1", "preact": "~10.21.0",
"punycode": "~2.3.1", "punycode": "~2.3.1",
"react-hotkeys-hook": "~4.5.0", "react-hotkeys-hook": "~4.5.0",
"react-intersection-observer": "~9.8.1", "react-intersection-observer": "~9.10.2",
"react-quick-pinch-zoom": "~5.1.0", "react-quick-pinch-zoom": "~5.1.0",
"react-router-dom": "6.6.2", "react-router-dom": "6.6.2",
"string-length": "6.0.0", "string-length": "6.0.0",
"swiped-events": "~1.1.9", "swiped-events": "~1.2.0",
"toastify-js": "~1.12.0", "toastify-js": "~1.12.0",
"uid": "~2.0.2", "uid": "~2.0.2",
"use-debounce": "~10.0.0", "use-debounce": "~10.0.0",
@ -50,18 +51,18 @@
"@preact/preset-vite": "~2.8.2", "@preact/preset-vite": "~2.8.2",
"@trivago/prettier-plugin-sort-imports": "~4.3.0", "@trivago/prettier-plugin-sort-imports": "~4.3.0",
"postcss": "~8.4.38", "postcss": "~8.4.38",
"postcss-dark-theme-class": "~1.2.1", "postcss-dark-theme-class": "~1.3.0",
"postcss-preset-env": "~9.5.4", "postcss-preset-env": "~9.5.11",
"twitter-text": "~3.1.0", "twitter-text": "~3.1.0",
"vite": "~5.2.8", "vite": "~5.2.11",
"vite-plugin-generate-file": "~0.1.1", "vite-plugin-generate-file": "~0.1.1",
"vite-plugin-html-config": "~1.0.11", "vite-plugin-html-config": "~1.0.11",
"vite-plugin-pwa": "~0.19.7", "vite-plugin-pwa": "~0.20.0",
"vite-plugin-remove-console": "~2.2.0", "vite-plugin-remove-console": "~2.2.0",
"workbox-cacheable-response": "~7.0.0", "workbox-cacheable-response": "~7.1.0",
"workbox-expiration": "~7.0.0", "workbox-expiration": "~7.1.0",
"workbox-routing": "~7.0.0", "workbox-routing": "~7.1.0",
"workbox-strategies": "~7.0.0" "workbox-strategies": "~7.1.0"
}, },
"postcss": { "postcss": {
"plugins": { "plugins": {

View file

@ -62,7 +62,7 @@ const iconsRoute = new Route(
cacheName: 'icons', cacheName: 'icons',
plugins: [ plugins: [
new ExpirationPlugin({ new ExpirationPlugin({
maxEntries: 50, maxEntries: 300,
maxAgeSeconds: 3 * 24 * 60 * 60, // 3 days maxAgeSeconds: 3 * 24 * 60 * 60, // 3 days
purgeOnQuotaError: true, purgeOnQuotaError: true,
}), }),

View file

@ -1966,6 +1966,10 @@ body > .szh-menu-container {
.szh-menu .szh-menu
.szh-menu__item.danger:not(.szh-menu__item--disabled).szh-menu__item--hover { .szh-menu__item.danger:not(.szh-menu__item--disabled).szh-menu__item--hover {
background-color: var(--red-text-color); background-color: var(--red-text-color);
@media (prefers-color-scheme: dark) {
background-color: var(--red-color);
}
} }
.szh-menu .szh-menu
.szh-menu__item:not(.szh-menu__item--disabled):not( .szh-menu__item:not(.szh-menu__item--disabled):not(

View file

@ -597,41 +597,123 @@
#custom-emojis-sheet { #custom-emojis-sheet {
max-height: 50vh; max-height: 50vh;
max-height: 50dvh; max-height: 50dvh;
}
#custom-emojis-sheet main { header {
mask-image: none; .loader-container {
} margin: 0;
#custom-emojis-sheet .custom-emojis-list .section-header { }
font-size: 80%;
text-transform: uppercase; form {
color: var(--text-insignificant-color); margin: 8px 0 0;
padding: 8px 0 4px;
position: sticky; input {
top: 0; width: 100%;
background-color: var(--bg-blur-color); min-width: 0;
backdrop-filter: blur(1px); font-size: 0.8em;
} }
#custom-emojis-sheet .custom-emojis-list section { }
display: flex; }
flex-wrap: wrap;
} main {
#custom-emojis-sheet .custom-emojis-list button { mask-image: none;
border-radius: 8px; min-height: 40vh;
background-image: radial-gradient( padding-bottom: 88px;
closest-side, }
var(--img-bg-color),
transparent .custom-emojis-matches {
); margin: 0;
} padding: 0;
#custom-emojis-sheet .custom-emojis-list button:is(:hover, :focus) { list-style: none;
filter: none; display: flex;
background-color: var(--bg-faded-color); flex-wrap: wrap;
} }
#custom-emojis-sheet .custom-emojis-list button img {
transition: transform 0.1s ease-out; .custom-emojis-list {
} .section-header {
#custom-emojis-sheet .custom-emojis-list button:is(:hover, :focus) img { font-size: 80%;
transform: scale(1.5); text-transform: uppercase;
color: var(--text-insignificant-color);
padding: 8px 0 4px;
position: sticky;
top: 0;
background-color: var(--bg-color);
z-index: 1;
}
section {
display: flex;
flex-wrap: wrap;
}
button {
color: var(--text-color);
border-radius: 8px;
background-image: radial-gradient(
closest-side,
var(--img-bg-color),
transparent
);
text-shadow: 0 1px 0 var(--bg-color);
position: relative;
min-width: 44px;
min-height: 44px;
font-variant-numeric: slashed-zero;
font-feature-settings: 'ss01';
&[data-title]:after {
max-width: 50vw;
pointer-events: none;
position: absolute;
content: attr(data-title);
left: 50%;
top: 0;
background-color: var(--bg-color);
padding: 2px 4px;
border-radius: 4px;
font-size: 12px;
border: 1px solid var(--text-color);
transform: translate(-50%, -110%);
opacity: 0;
transition: opacity 0.1s ease-out 0.1s;
font-family: var(--monospace-font);
line-height: 1;
}
&.edge-left[data-title]:after {
left: 0;
transform: translate(0, -110%);
}
&.edge-right[data-title]:after {
left: 100%;
transform: translate(-100%, -110%);
}
&:is(:hover, :focus) {
z-index: 1;
filter: none;
background-color: var(--bg-faded-color);
&[data-title]:after {
opacity: 1;
}
}
img {
transition: transform 0.1s ease-out;
}
&:is(:hover, :focus) img {
transform: scale(2);
}
&.edge-left img {
transform-origin: left center;
}
&.edge-right img {
transform-origin: right center;
}
code {
font-size: 0.8em;
}
}
}
} }
.compose-field-container { .compose-field-container {

View file

@ -3,8 +3,16 @@ import './compose.css';
import '@github/text-expander-element'; import '@github/text-expander-element';
import { MenuItem } from '@szhsin/react-menu'; import { MenuItem } from '@szhsin/react-menu';
import { deepEqual } from 'fast-equals'; import { deepEqual } from 'fast-equals';
import Fuse from 'fuse.js';
import { memo } from 'preact/compat';
import { forwardRef } from 'preact/compat'; import { forwardRef } from 'preact/compat';
import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'preact/hooks';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import stringLength from 'string-length'; import stringLength from 'string-length';
import { uid } from 'uid/single'; import { uid } from 'uid/single';
@ -21,6 +29,7 @@ import db from '../utils/db';
import emojifyText from '../utils/emojify-text'; import emojifyText from '../utils/emojify-text';
import localeMatch from '../utils/locale-match'; import localeMatch from '../utils/locale-match';
import openCompose from '../utils/open-compose'; import openCompose from '../utils/open-compose';
import pmem from '../utils/pmem';
import shortenNumber from '../utils/shorten-number'; import shortenNumber from '../utils/shorten-number';
import showToast from '../utils/show-toast'; import showToast from '../utils/show-toast';
import states, { saveStatus } from '../utils/states'; import states, { saveStatus } from '../utils/states';
@ -181,6 +190,8 @@ function highlightText(text, { maxCharacters = Infinity }) {
const rtf = new Intl.RelativeTimeFormat(); const rtf = new Intl.RelativeTimeFormat();
const CUSTOM_EMOJIS_COUNT = 100;
function Compose({ function Compose({
onClose, onClose,
replyToStatus, replyToStatus,
@ -1423,25 +1434,40 @@ function autoResizeTextarea(textarea) {
} }
} }
async function _getCustomEmojis(instance, masto) {
const emojis = await masto.v1.customEmojis.list();
const visibleEmojis = emojis.filter((e) => e.visibleInPicker);
const searcher = new Fuse(visibleEmojis, {
keys: ['shortcode'],
findAllMatches: true,
});
return [visibleEmojis, searcher];
}
const getCustomEmojis = pmem(_getCustomEmojis, {
// Limit by time to reduce memory usage
// Cached by instance
matchesArg: (cacheKeyArg, keyArg) => cacheKeyArg.instance === keyArg.instance,
maxAge: 30 * 60 * 1000, // 30 minutes
});
const Textarea = forwardRef((props, ref) => { const Textarea = forwardRef((props, ref) => {
const { masto } = api(); const { masto, instance } = api();
const [text, setText] = useState(ref.current?.value || ''); const [text, setText] = useState(ref.current?.value || '');
const { maxCharacters, performSearch = () => {}, ...textareaProps } = props; const { maxCharacters, performSearch = () => {}, ...textareaProps } = props;
// const snapStates = useSnapshot(states); // const snapStates = useSnapshot(states);
// const charCount = snapStates.composerCharacterCount; // const charCount = snapStates.composerCharacterCount;
const customEmojis = useRef(); // const customEmojis = useRef();
const searcherRef = useRef();
useEffect(() => { useEffect(() => {
(async () => { getCustomEmojis(instance, masto)
try { .then((r) => {
const emojis = await masto.v1.customEmojis.list(); const [emojis, searcher] = r;
console.log({ emojis }); searcherRef.current = searcher;
customEmojis.current = emojis; })
} catch (e) { .catch((e) => {
// silent fail
console.error(e); console.error(e);
} });
})();
}, []); }, []);
const textExpanderRef = useRef(); const textExpanderRef = useRef();
@ -1467,23 +1493,26 @@ const Textarea = forwardRef((props, ref) => {
// const emojis = customEmojis.current.filter((emoji) => // const emojis = customEmojis.current.filter((emoji) =>
// emoji.shortcode.startsWith(text), // emoji.shortcode.startsWith(text),
// ); // );
const emojis = filterShortcodes(customEmojis.current, text); // const emojis = filterShortcodes(customEmojis.current, text);
const results = searcherRef.current?.search(text, {
limit: 5,
});
let html = ''; let html = '';
emojis.forEach((emoji) => { results.forEach(({ item: emoji }) => {
const { shortcode, url } = emoji; const { shortcode, url } = emoji;
html += ` html += `
<li role="option" data-value="${encodeHTML(shortcode)}"> <li role="option" data-value="${encodeHTML(shortcode)}">
<img src="${encodeHTML( <img src="${encodeHTML(
url, url,
)}" width="16" height="16" alt="" loading="lazy" /> )}" width="16" height="16" alt="" loading="lazy" />
:${encodeHTML(shortcode)}: ${encodeHTML(shortcode)}
</li>`; </li>`;
}); });
// console.log({ emojis, html }); // console.log({ emojis, html });
menu.innerHTML = html; menu.innerHTML = html;
provide( provide(
Promise.resolve({ Promise.resolve({
matched: emojis.length > 0, matched: results.length > 0,
fragment: menu, fragment: menu,
}), }),
); );
@ -2185,38 +2214,19 @@ function CustomEmojisModal({
}) { }) {
const [uiState, setUIState] = useState('default'); const [uiState, setUIState] = useState('default');
const customEmojisList = useRef([]); const customEmojisList = useRef([]);
const [customEmojis, setCustomEmojis] = useState({}); const [customEmojis, setCustomEmojis] = useState([]);
const recentlyUsedCustomEmojis = useMemo( const recentlyUsedCustomEmojis = useMemo(
() => store.account.get('recentlyUsedCustomEmojis') || [], () => store.account.get('recentlyUsedCustomEmojis') || [],
); );
const searcherRef = useRef();
useEffect(() => { useEffect(() => {
setUIState('loading'); setUIState('loading');
(async () => { (async () => {
try { try {
const emojis = await masto.v1.customEmojis.list(); const [emojis, searcher] = await getCustomEmojis(instance, masto);
// Group emojis by category console.log('emojis', emojis);
const emojisCat = { searcherRef.current = searcher;
'--recent--': recentlyUsedCustomEmojis.filter((emoji) => setCustomEmojis(emojis);
emojis.find((e) => e.shortcode === emoji.shortcode),
),
};
const othersCat = [];
emojis.forEach((emoji) => {
if (!emoji.visibleInPicker) return;
customEmojisList.current?.push?.(emoji);
if (!emoji.category) {
othersCat.push(emoji);
return;
}
if (!emojisCat[emoji.category]) {
emojisCat[emoji.category] = [];
}
emojisCat[emoji.category].push(emoji);
});
if (othersCat.length) {
emojisCat['--others--'] = othersCat;
}
setCustomEmojis(emojisCat);
setUIState('default'); setUIState('default');
} catch (e) { } catch (e) {
setUIState('error'); setUIState('error');
@ -2225,6 +2235,83 @@ function CustomEmojisModal({
})(); })();
}, []); }, []);
const customEmojisCatList = useMemo(() => {
// Group emojis by category
const emojisCat = {
'--recent--': recentlyUsedCustomEmojis.filter((emoji) =>
customEmojis.find((e) => e.shortcode === emoji.shortcode),
),
};
const othersCat = [];
customEmojis.forEach((emoji) => {
customEmojisList.current?.push?.(emoji);
if (!emoji.category) {
othersCat.push(emoji);
return;
}
if (!emojisCat[emoji.category]) {
emojisCat[emoji.category] = [];
}
emojisCat[emoji.category].push(emoji);
});
if (othersCat.length) {
emojisCat['--others--'] = othersCat;
}
return emojisCat;
}, [customEmojis]);
const scrollableRef = useRef();
const [matches, setMatches] = useState(null);
const onFind = useCallback(
(e) => {
const { value } = e.target;
if (value) {
const results = searcherRef.current?.search(value, {
limit: CUSTOM_EMOJIS_COUNT,
});
setMatches(results.map((r) => r.item));
scrollableRef.current?.scrollTo?.(0, 0);
} else {
setMatches(null);
}
},
[customEmojis],
);
const onSelectEmoji = useCallback(
(emoji) => {
onSelect?.(emoji);
onClose?.();
queueMicrotask(() => {
let recentlyUsedCustomEmojis =
store.account.get('recentlyUsedCustomEmojis') || [];
const recentlyUsedEmojiIndex = recentlyUsedCustomEmojis.findIndex(
(e) => e.shortcode === emoji.shortcode,
);
if (recentlyUsedEmojiIndex !== -1) {
// Move emoji to index 0
recentlyUsedCustomEmojis.splice(recentlyUsedEmojiIndex, 1);
recentlyUsedCustomEmojis.unshift(emoji);
} else {
recentlyUsedCustomEmojis.unshift(emoji);
// Remove unavailable ones
recentlyUsedCustomEmojis = recentlyUsedCustomEmojis.filter((e) =>
customEmojisList.current?.find?.(
(emoji) => emoji.shortcode === e.shortcode,
),
);
// Limit to 10
recentlyUsedCustomEmojis = recentlyUsedCustomEmojis.slice(0, 10);
}
// Store back
store.account.set('recentlyUsedCustomEmojis', recentlyUsedCustomEmojis);
});
},
[onSelect],
);
return ( return (
<div id="custom-emojis-sheet" class="sheet"> <div id="custom-emojis-sheet" class="sheet">
{!!onClose && ( {!!onClose && (
@ -2233,107 +2320,167 @@ function CustomEmojisModal({
</button> </button>
)} )}
<header> <header>
<b>Custom emojis</b>{' '} <div>
{uiState === 'loading' ? ( <b>Custom emojis</b>{' '}
<Loader /> {uiState === 'loading' ? (
) : ( <Loader />
<small class="insignificant"> {instance}</small> ) : (
)} <small class="insignificant"> {instance}</small>
</header>
<main>
<div class="custom-emojis-list">
{uiState === 'error' && (
<div class="ui-state">
<p>Error loading custom emojis</p>
</div>
)} )}
{uiState === 'default' &&
Object.entries(customEmojis).map(
([category, emojis]) =>
!!emojis?.length && (
<>
<div class="section-header">
{{
'--recent--': 'Recently used',
'--others--': 'Others',
}[category] || category}
</div>
<section>
{emojis.map((emoji) => (
<button
key={emoji}
type="button"
class="plain4"
onClick={() => {
onClose();
requestAnimationFrame(() => {
onSelect(`:${emoji.shortcode}:`);
});
let recentlyUsedCustomEmojis =
store.account.get('recentlyUsedCustomEmojis') ||
[];
const recentlyUsedEmojiIndex =
recentlyUsedCustomEmojis.findIndex(
(e) => e.shortcode === emoji.shortcode,
);
if (recentlyUsedEmojiIndex !== -1) {
// Move emoji to index 0
recentlyUsedCustomEmojis.splice(
recentlyUsedEmojiIndex,
1,
);
recentlyUsedCustomEmojis.unshift(emoji);
} else {
recentlyUsedCustomEmojis.unshift(emoji);
// Remove unavailable ones
recentlyUsedCustomEmojis =
recentlyUsedCustomEmojis.filter((e) =>
customEmojisList.current?.find?.(
(emoji) => emoji.shortcode === e.shortcode,
),
);
// Limit to 10
recentlyUsedCustomEmojis =
recentlyUsedCustomEmojis.slice(0, 10);
}
// Store back
store.account.set(
'recentlyUsedCustomEmojis',
recentlyUsedCustomEmojis,
);
}}
title={`:${emoji.shortcode}:`}
>
<picture>
{!!emoji.staticUrl && (
<source
srcset={emoji.staticUrl}
media="(prefers-reduced-motion: reduce)"
/>
)}
<img
class="shortcode-emoji"
src={emoji.url || emoji.staticUrl}
alt={emoji.shortcode}
width="16"
height="16"
loading="lazy"
decoding="async"
/>
</picture>
</button>
))}
</section>
</>
),
)}
</div> </div>
<form
onSubmit={(e) => {
e.preventDefault();
const emoji = matches[0];
if (emoji) {
onSelectEmoji(`:${emoji.shortcode}:`);
}
}}
>
<input
type="search"
placeholder="Search emoji"
onInput={onFind}
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellCheck="false"
dir="auto"
/>
</form>
</header>
<main ref={scrollableRef}>
{matches !== null ? (
<ul class="custom-emojis-matches custom-emojis-list">
{matches.map((emoji) => (
<li key={emoji.shortcode} class="custom-emojis-match">
<CustomEmojiButton
emoji={emoji}
onClick={() => {
onSelectEmoji(`:${emoji.shortcode}:`);
}}
showCode
/>
</li>
))}
</ul>
) : (
<div class="custom-emojis-list">
{uiState === 'error' && (
<div class="ui-state">
<p>Error loading custom emojis</p>
</div>
)}
{uiState === 'default' &&
Object.entries(customEmojisCatList).map(
([category, emojis]) =>
!!emojis?.length && (
<>
<div class="section-header">
{{
'--recent--': 'Recently used',
'--others--': 'Others',
}[category] || category}
</div>
<CustomEmojisList
emojis={emojis}
onSelect={onSelectEmoji}
/>
</>
),
)}
</div>
)}
</main> </main>
</div> </div>
); );
} }
const CustomEmojisList = memo(({ emojis, onSelect }) => {
const [max, setMax] = useState(CUSTOM_EMOJIS_COUNT);
const showMore = emojis.length > max;
return (
<section>
{emojis.slice(0, max).map((emoji) => (
<CustomEmojiButton
key={emoji.shortcode}
emoji={emoji}
onClick={() => {
onSelect(`:${emoji.shortcode}:`);
}}
/>
))}
{showMore && (
<button
type="button"
class="plain small"
onClick={() => setMax(max + CUSTOM_EMOJIS_COUNT)}
>
{(emojis.length - max).toLocaleString()} more
</button>
)}
</section>
);
});
const CustomEmojiButton = memo(({ emoji, onClick, showCode }) => {
const addEdges = (e) => {
// Add edge-left or edge-right class based on self position relative to scrollable parent
// If near left edge, add edge-left, if near right edge, add edge-right
const buffer = 88;
const parent = e.currentTarget.closest('main');
if (parent) {
const rect = parent.getBoundingClientRect();
const selfRect = e.currentTarget.getBoundingClientRect();
const targetClassList = e.currentTarget.classList;
if (selfRect.left < rect.left + buffer) {
targetClassList.add('edge-left');
targetClassList.remove('edge-right');
} else if (selfRect.right > rect.right - buffer) {
targetClassList.add('edge-right');
targetClassList.remove('edge-left');
} else {
targetClassList.remove('edge-left', 'edge-right');
}
}
};
return (
<button
type="button"
className="plain4"
onClick={onClick}
data-title={showCode ? undefined : emoji.shortcode}
onPointerEnter={addEdges}
onFocus={addEdges}
>
<picture>
{!!emoji.staticUrl && (
<source
srcSet={emoji.staticUrl}
media="(prefers-reduced-motion: reduce)"
/>
)}
<img
className="shortcode-emoji"
src={emoji.url || emoji.staticUrl}
alt={emoji.shortcode}
width="24"
height="24"
loading="lazy"
decoding="async"
/>
</picture>
{showCode && (
<>
{' '}
<code>{emoji.shortcode}</code>
</>
)}
</button>
);
});
const GIFS_PER_PAGE = 20; const GIFS_PER_PAGE = 20;
function GIFPickerModal({ onClose = () => {}, onSelect = () => {} }) { function GIFPickerModal({ onClose = () => {}, onSelect = () => {} }) {
const [uiState, setUIState] = useState('default'); const [uiState, setUIState] = useState('default');

View file

@ -17,6 +17,21 @@
); );
filter: saturate(0.5); filter: saturate(0.5);
} }
&:is(a) {
pointer-events: auto;
display: block;
text-decoration: none;
color: inherit;
&:hover {
border-color: var(--outline-hover-color);
}
> * {
pointer-events: none;
}
}
} }
.accounts-list { .accounts-list {

View file

@ -11,6 +11,7 @@ import useLocationChange from '../utils/useLocationChange';
import AccountBlock from './account-block'; import AccountBlock from './account-block';
import Icon from './icon'; import Icon from './icon';
import Link from './link';
import Loader from './loader'; import Loader from './loader';
import Status from './status'; import Status from './status';
@ -143,9 +144,12 @@ export default function GenericAccounts({
</header> </header>
<main> <main>
{post && ( {post && (
<div class="post-preview"> <Link
to={`/${instance || currentInstance}/s/${post.id}`}
class="post-preview"
>
<Status status={post} size="s" readOnly /> <Status status={post} size="s" readOnly />
</div> </Link>
)} )}
{accounts.length > 0 ? ( {accounts.length > 0 ? (
<> <>

View file

@ -7,10 +7,13 @@ import { useInView } from 'react-intersection-observer';
// The sticky header, usually at the top // The sticky header, usually at the top
const TOP = 48; const TOP = 48;
export default function LazyShazam({ children }) { const shazamIDs = {};
export default function LazyShazam({ id, children }) {
const containerRef = useRef(); const containerRef = useRef();
const hasID = !!shazamIDs[id];
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [visibleStart, setVisibleStart] = useState(false); const [visibleStart, setVisibleStart] = useState(hasID || false);
const { ref } = useInView({ const { ref } = useInView({
root: null, root: null,
@ -20,6 +23,7 @@ export default function LazyShazam({ children }) {
onChange: (inView) => { onChange: (inView) => {
if (inView) { if (inView) {
setVisible(true); setVisible(true);
if (id) shazamIDs[id] = true;
} }
}, },
triggerOnce: true, triggerOnce: true,
@ -35,6 +39,7 @@ export default function LazyShazam({ children }) {
} else { } else {
setVisibleStart(true); setVisibleStart(true);
} }
if (id) shazamIDs[id] = true;
} }
}, []); }, []);

View file

@ -28,6 +28,7 @@ const NOTIFICATION_ICONS = {
'admin.signup': 'account-edit', 'admin.signup': 'account-edit',
'admin.report': 'account-warning', 'admin.report': 'account-warning',
severed_relationships: 'heart-break', severed_relationships: 'heart-break',
moderation_warning: 'alert',
emoji_reaction: 'emoji2', emoji_reaction: 'emoji2',
'pleroma:emoji_reaction': 'emoji2', 'pleroma:emoji_reaction': 'emoji2',
}; };
@ -45,6 +46,8 @@ poll = A poll you have voted in or created has ended
update = A status you interacted with has been edited update = A status you interacted with has been edited
admin.sign_up = Someone signed up (optionally sent to admins) admin.sign_up = Someone signed up (optionally sent to admins)
admin.report = A new report has been filed admin.report = A new report has been filed
severed_relationships = Severed relationships
moderation_warning = Moderation warning
*/ */
function emojiText(emoji, emoji_url) { function emojiText(emoji, emoji_url) {
@ -91,6 +94,7 @@ const contentText = {
Lost connections with <i>{name}</i>. Lost connections with <i>{name}</i>.
</> </>
), ),
moderation_warning: <b>Moderation warning</b>,
emoji_reaction: emojiText, emoji_reaction: emojiText,
'pleroma:emoji_reaction': emojiText, 'pleroma:emoji_reaction': emojiText,
}; };
@ -117,6 +121,17 @@ const SEVERED_RELATIONSHIPS_TEXT = {
), ),
}; };
const MODERATION_WARNING_TEXT = {
none: 'Your account has received a moderation warning.',
disable: 'Your account has been disabled.',
mark_statuses_as_sensitive:
'Some of your posts have been marked as sensitive.',
delete_statuses: 'Some of your posts have been deleted.',
sensitive: 'Your posts will be marked as sensitive from now on.',
silence: 'Your account has been limited.',
suspend: 'Your account has been suspended.',
};
const AVATARS_LIMIT = 50; const AVATARS_LIMIT = 50;
function Notification({ function Notification({
@ -125,8 +140,16 @@ function Notification({
isStatic, isStatic,
disableContextMenu, disableContextMenu,
}) { }) {
const { id, status, account, report, event, _accounts, _statuses } = const {
notification; id,
status,
account,
report,
event,
moderation_warning,
_accounts,
_statuses,
} = notification;
let { type } = notification; let { type } = notification;
// status = Attached when type of the notification is favourite, reblog, status, mention, poll, or update // status = Attached when type of the notification is favourite, reblog, status, mention, poll, or update
@ -314,6 +337,20 @@ function Notification({
. .
</div> </div>
)} )}
{type === 'moderation_warning' && !!moderation_warning && (
<div>
{MODERATION_WARNING_TEXT[moderation_warning.action]}
<br />
<a
href={`/disputes/strikes/${moderation_warning.id}`}
target="_blank"
rel="noopener noreferrer"
>
Learn more <Icon icon="external" size="s" />
</a>
.
</div>
)}
</> </>
)} )}
{_accounts?.length > 1 && ( {_accounts?.length > 1 && (

View file

@ -569,8 +569,15 @@
font-weight: bold; font-weight: bold;
vertical-align: middle; vertical-align: middle;
display: inline-block; display: inline-block;
&.horizontal {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 100%;
}
} }
.status-filtered-badge.badge-meta { .status-filtered-badge:not(.horizontal).badge-meta {
display: inline-flex; display: inline-flex;
flex-direction: column; flex-direction: column;
position: relative; position: relative;
@ -584,10 +591,10 @@
border-color: var(--text-color); border-color: var(--text-color);
background: var(--bg-color); background: var(--bg-color);
} }
.status-filtered-badge.badge-meta > span:first-child { .status-filtered-badge:not(.horizontal).badge-meta > span:first-child {
white-space: nowrap; white-space: nowrap;
} }
.status-filtered-badge.badge-meta > span + span { .status-filtered-badge:not(.horizontal).badge-meta > span + span {
display: block; display: block;
font-size: 9px; font-size: 9px;
font-weight: normal; font-weight: normal;
@ -601,6 +608,10 @@
left: 0; left: 0;
text-align: center; text-align: center;
} }
.status-filtered-badge.horizontal.badge-meta > span + span {
font-weight: normal;
text-transform: none;
}
.status.large > .container > .content-container { .status.large > .container > .content-container {
margin-left: calc(-50px - 16px); margin-left: calc(-50px - 16px);
@ -825,6 +836,12 @@
.timeline-deck .status .content.truncated ~ .card { .timeline-deck .status .content.truncated ~ .card {
display: none; display: none;
} }
.status .content .inner-content {
> img[height] {
height: auto;
aspect-ratio: var(--original-aspect-ratio);
}
}
.status .content .inner-content a:not(.mention, .has-url-text) { .status .content .inner-content a:not(.mention, .has-url-text) {
color: var(--link-text-color); color: var(--link-text-color);
} }
@ -2380,8 +2397,8 @@ a.card:is(:hover, :focus):visited {
max-width: 100%; max-width: 100%;
height: 1.2em; height: 1.2em;
vertical-align: text-bottom; vertical-align: text-bottom;
object-fit: cover; object-fit: contain;
object-position: left; /* object-position: left; */
} }
/* EDIT HISTORY */ /* EDIT HISTORY */

View file

@ -3337,7 +3337,7 @@ const QuoteStatuses = memo(({ id, instance, level = 0 }) => {
return uniqueQuotes.map((q) => { return uniqueQuotes.map((q) => {
return ( return (
<LazyShazam> <LazyShazam id={q.instance + q.id}>
<Link <Link
key={q.instance + q.id} key={q.instance + q.id}
to={`${q.instance ? `/${q.instance}` : ''}/s/${q.id}`} to={`${q.instance ? `/${q.instance}` : ''}/s/${q.id}`}

View file

@ -209,8 +209,8 @@ function Timeline({
const oRef = useHotkeys(['enter', 'o'], () => { const oRef = useHotkeys(['enter', 'o'], () => {
// open active status // open active status
const activeItem = document.activeElement.closest(itemsSelector); const activeItem = document.activeElement;
if (activeItem) { if (activeItem?.matches(itemsSelector)) {
activeItem.click(); activeItem.click();
} }
}); });
@ -646,7 +646,11 @@ const TimelineItem = memo(
> >
<Link class="status-link timeline-item" to={url}> <Link class="status-link timeline-item" to={url}>
{showCompact ? ( {showCompact ? (
<TimelineStatusCompact status={item} instance={instance} /> <TimelineStatusCompact
status={item}
instance={instance}
filterContext={filterContext}
/>
) : useItemID ? ( ) : useItemID ? (
<Status <Status
statusID={statusID} statusID={statusID}
@ -820,11 +824,12 @@ function StatusCarousel({ title, class: className, children }) {
); );
} }
function TimelineStatusCompact({ status, instance }) { function TimelineStatusCompact({ status, instance, filterContext }) {
const snapStates = useSnapshot(states); const snapStates = useSnapshot(states);
const { id, visibility, language } = status; const { id, visibility, language } = status;
const statusPeekText = statusPeek(status); const statusPeekText = statusPeek(status);
const sKey = statusKey(id, instance); const sKey = statusKey(id, instance);
const filterInfo = isFiltered(status.filtered, filterContext);
return ( return (
<article <article
class={`status compact-thread ${ class={`status compact-thread ${
@ -850,13 +855,24 @@ function TimelineStatusCompact({ status, instance }) {
lang={language} lang={language}
dir="auto" dir="auto"
> >
{statusPeekText} {!!filterInfo ? (
{status.sensitive && status.spoilerText && ( <b
class="status-filtered-badge badge-meta horizontal"
title={filterInfo?.titlesStr || ''}
>
<span>Filtered</span>: <span>{filterInfo?.titlesStr || ''}</span>
</b>
) : (
<> <>
{' '} {statusPeekText}
<span class="spoiler-badge"> {status.sensitive && status.spoilerText && (
<Icon icon="eye-close" size="s" /> <>
</span> {' '}
<span class="spoiler-badge">
<Icon icon="eye-close" size="s" />
</span>
</>
)}
</> </>
)} )}
</div> </div>

View file

@ -7,6 +7,7 @@ import { lazy } from 'preact/compat';
import { useEffect, useState } from 'preact/hooks'; import { useEffect, useState } from 'preact/hooks';
import IntlSegmenterSuspense from './components/intl-segmenter-suspense'; import IntlSegmenterSuspense from './components/intl-segmenter-suspense';
import { initStates } from './utils/states';
// import Compose from './components/compose'; // import Compose from './components/compose';
import useTitle from './utils/useTitle'; import useTitle from './utils/useTitle';
@ -31,6 +32,10 @@ function App() {
: 'Compose', : 'Compose',
); );
useEffect(() => {
initStates();
}, []);
useEffect(() => { useEffect(() => {
if (uiState === 'closed') { if (uiState === 'closed') {
try { try {

View file

@ -286,7 +286,13 @@ function FiltersAddEdit({ filter, onClose }) {
// Preserve existing expiry if not specified // Preserve existing expiry if not specified
// Seconds from now to expiresAtDate // Seconds from now to expiresAtDate
// Other clients don't do this // Other clients don't do this
expiresIn = Math.floor((expiresAtDate - new Date()) / 1000); if (hasExpiry) {
expiresIn = Math.floor(
(expiresAtDate - new Date()) / 1000,
);
} else {
expiresIn = null;
}
} else if (expiresIn === '0' || expiresIn === 0) { } else if (expiresIn === '0' || expiresIn === 0) {
// 0 = Never // 0 = Never
expiresIn = null; expiresIn = null;

View file

@ -1,5 +1,6 @@
import './login.css'; import './login.css';
import Fuse from 'fuse.js';
import { useEffect, useRef, useState } from 'preact/hooks'; import { useEffect, useRef, useState } from 'preact/hooks';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
@ -28,12 +29,14 @@ function Login() {
); );
const [instancesList, setInstancesList] = useState([]); const [instancesList, setInstancesList] = useState([]);
const searcher = useRef();
useEffect(() => { useEffect(() => {
(async () => { (async () => {
try { try {
const res = await fetch(instancesListURL); const res = await fetch(instancesListURL);
const data = await res.json(); const data = await res.json();
setInstancesList(data); setInstancesList(data);
searcher.current = new Fuse(data);
} catch (e) { } catch (e) {
// Silently fail // Silently fail
console.error(e); console.error(e);
@ -91,21 +94,11 @@ function Login() {
!/[\s\/\\@]/.test(cleanInstanceText); !/[\s\/\\@]/.test(cleanInstanceText);
const instancesSuggestions = cleanInstanceText const instancesSuggestions = cleanInstanceText
? instancesList ? searcher.current
.filter((instance) => instance.includes(instanceText)) ?.search(cleanInstanceText, {
.sort((a, b) => { limit: 10,
// Move text that starts with instanceText to the start
const aStartsWith = a
.toLowerCase()
.startsWith(instanceText.toLowerCase());
const bStartsWith = b
.toLowerCase()
.startsWith(instanceText.toLowerCase());
if (aStartsWith && !bStartsWith) return -1;
if (!aStartsWith && bStartsWith) return 1;
return 0;
}) })
.slice(0, 10) ?.map((match) => match.item)
: []; : [];
const selectedInstanceText = instanceTextLooksLikeDomain const selectedInstanceText = instanceTextLooksLikeDomain

View file

@ -102,6 +102,17 @@ function Notifications({ columnMode }) {
// }, // },
// }); // });
// TEST: Slot in a fake notification to test 'moderation_warning'
// notifications.unshift({
// id: '123123',
// type: 'moderation_warning',
// createdAt: new Date().toISOString(),
// moderation_warning: {
// id: '1231234',
// action: 'mark_statuses_as_sensitive',
// },
// });
// console.log({ notifications }); // console.log({ notifications });
const groupedNotifications = groupNotifications(notifications); const groupedNotifications = groupNotifications(notifications);

View file

@ -242,6 +242,17 @@ function _enhanceContent(content, opts = {}) {
} }
} }
// ADD ASPECT RATIO TO ALL IMAGES
if (enhancedContent.includes('<img')) {
dom.querySelectorAll('img').forEach((img) => {
const width = img.getAttribute('width') || img.naturalWidth;
const height = img.getAttribute('height') || img.naturalHeight;
if (width && height) {
img.style.setProperty('--original-aspect-ratio', `${width}/${height}`);
}
});
}
if (postEnhanceDOM) { if (postEnhanceDOM) {
queueMicrotask(() => postEnhanceDOM(dom)); queueMicrotask(() => postEnhanceDOM(dom));
// postEnhanceDOM(dom); // mutate dom // postEnhanceDOM(dom); // mutate dom

View file

@ -1,5 +1,6 @@
const { locale } = Intl.NumberFormat().resolvedOptions(); const { locale } = Intl.NumberFormat().resolvedOptions();
const shortenNumber = Intl.NumberFormat(locale, { const shortenNumber = Intl.NumberFormat(locale, {
notation: 'compact', notation: 'compact',
roundingMode: 'floor',
}).format; }).format;
export default shortenNumber; export default shortenNumber;