Porównaj commity

...

656 Commity

Autor SHA1 Wiadomość Data
miklobit 7dc1cca40c Merge remote-tracking branch 'upstream/develop' into develop 2023-03-31 22:02:56 +02:00
Chewbacca b277143e3a Merge branch 'chunk' into 'develop'
Change discover chunk to groups

See merge request soapbox-pub/soapbox!2392
2023-03-31 12:01:37 +00:00
Chewbacca eeafb3073e Change discover chunk to groups 2023-03-31 08:00:21 -04:00
Chewbacca ccd97e1405 Merge branch 'group-dark-mode' into 'develop'
Improve Dark mode support for Groups + Tests

See merge request soapbox-pub/soapbox!2390
2023-03-31 11:44:28 +00:00
Chewbacca a994d1c33e i18n 2023-03-30 20:46:18 -04:00
Chewbacca 681eacf827 Improve dark mode support of Groups + bug fixes 2023-03-30 15:19:14 -04:00
Chewbacca 4b3b601659 Add tests for GroupOptionsButton 2023-03-30 12:57:42 -04:00
Chewbacca 697791fc5d Add tests for GroupRelationship 2023-03-30 12:57:23 -04:00
Alex Gleason a976b542e1 Merge branch 'edit-group-page' into 'develop'
Edit group page

See merge request soapbox-pub/soapbox!2389
2023-03-30 13:57:57 +00:00
Chewbacca e32ea32f15 Merge branch 'group-name-validation' into 'develop'
Group name validation

See merge request soapbox-pub/soapbox!2388
2023-03-30 13:28:03 +00:00
Chewbacca ff3c0c5cd7 i18n 2023-03-30 09:25:15 -04:00
Chewbacca 01458c3003 Merge branch 'reply-popover' into 'develop'
Add popover when trying to reply to Group status

See merge request soapbox-pub/soapbox!2386
2023-03-30 13:23:47 +00:00
Alex Gleason 453420796b
yarn i18n 2023-03-29 20:49:04 -05:00
Alex Gleason e47b9300f0
EditGroup: add "cannot change" hint 2023-03-29 20:45:40 -05:00
Alex Gleason ddf433a5c9
FormGroup: move hintText above input 2023-03-29 20:42:57 -05:00
Alex Gleason 79a33d0f1d
Input: improve disabled style 2023-03-29 20:37:22 -05:00
Alex Gleason 9e60d90812
We do a little refactoring 2023-03-29 20:30:05 -05:00
Alex Gleason f3727440ff
Move form hooks into their own files 2023-03-29 20:14:41 -05:00
Alex Gleason eb055339d8
react-hook-form SUX 2023-03-29 20:00:04 -05:00
Alex Gleason 9c78a37844
Merge remote-tracking branch 'origin/develop' into edit-group-page 2023-03-29 19:31:01 -05:00
Alex Gleason 8e6dfe6395 Merge branch 'textarea-counter' into 'develop'
Textarea: add a character counter

See merge request soapbox-pub/soapbox!2373
2023-03-30 00:30:39 +00:00
Alex Gleason 7ec51778f8 Apply 1 suggestion(s) to 1 file(s) 2023-03-30 00:30:27 +00:00
Alex Gleason eb6c82a867
Make EditGroup page work, pretty much 2023-03-29 19:18:20 -05:00
Alex Gleason bfd40fa373
Boilerplate Edit Group page by copying code from the modal 2023-03-29 14:51:29 -05:00
Chewbacca 455030ef5b Add validation support to features 2023-03-29 15:44:00 -04:00
Chewbacca 4886548889 Add validation support to Group names 2023-03-29 15:42:20 -04:00
Chewbacca 85e5780645 Deprecate old Icon component 2023-03-29 15:42:05 -04:00
Chewbacca 20960d7238 Improve UI of List component 2023-03-29 15:41:49 -04:00
Alex Gleason 319d47b36f Merge branch 'pending-memberships-dot' into 'develop'
Groups: add dot to group with pending membership requests

See merge request soapbox-pub/soapbox!2387
2023-03-29 19:13:54 +00:00
Alex Gleason 609a25fd8d
Groups: add dot to group with pending membership requests 2023-03-29 12:21:43 -05:00
marcin mikołajczak 55c0f8d6a1 Merge branch 'friendica' into 'develop'
Friendica dislikes

See merge request soapbox-pub/soapbox!2381
2023-03-28 22:13:24 +00:00
Chewbacca 1e69812078 Add popover when trying to reply to Group status 2023-03-28 15:39:41 -04:00
Chewbacca 89ab02224f Merge branch 'group-improvements' into 'develop'
Group improvements

See merge request soapbox-pub/soapbox!2385
2023-03-28 17:21:10 +00:00
Chewbacca af9439f1d3 Use entity store for Group Search 2023-03-28 12:57:44 -04:00
Chewbacca a916056367 Re-use GroupActionButton component 2023-03-28 11:53:13 -04:00
Chewbacca 9fe2d4f92c Remove unused query 2023-03-28 11:53:13 -04:00
Chewbacca ca0987dec2 Update variables to use proper naming 2023-03-28 11:53:13 -04:00
Chewbacca 6458ebbb11 Ensure button text stays centered 2023-03-28 11:53:13 -04:00
Chewbacca 966fcc617a Merge branch 'fix-loading-state' into 'develop'
Fix loading animation for Carousel

See merge request soapbox-pub/soapbox!2383
2023-03-28 15:52:56 +00:00
Chewbacca e12450ee5d Merge branch 'group-improvements' into 'develop'
Remove mock data for Group Discovery

See merge request soapbox-pub/soapbox!2384
2023-03-28 14:33:49 +00:00
Chewbacca 29c913859e Remove mock data for Group Discovery 2023-03-28 10:32:56 -04:00
Chewbacca be4a7e45e9 Fix loading animation for Carousel 2023-03-28 10:30:34 -04:00
Chewbacca 1abcc95a0a Merge branch 'search-my-groups' into 'develop'
Add ability to search my Groups

See merge request soapbox-pub/soapbox!2377
2023-03-28 14:02:25 +00:00
Chewbacca e6252070a6 Fix test 2023-03-28 09:46:55 -04:00
Alex Gleason f2744cdf98 Merge branch 'pending-timer' into 'develop'
AuthorizeRejectButtons: add a countdown timer, remove rejectUserModal

See merge request soapbox-pub/soapbox!2382
2023-03-28 13:35:38 +00:00
Chewbacca 3a2b4c6efb Add ability to search my Groups 2023-03-28 09:09:09 -04:00
Alex Gleason 232e387a50
yarn i18n 2023-03-27 17:24:24 -05:00
Alex Gleason d39e2cc7e0
UnapprovedAccount: use countdown, remove rejectUserModal 2023-03-27 17:14:22 -05:00
Alex Gleason 0522f333c3
Make follow requests use AuthorizeRejectButtons countdown 2023-03-27 17:13:44 -05:00
Alex Gleason 22474e3ca9
MembershipRequest: add 3s countdown on authorize/reject actions 2023-03-27 17:09:10 -05:00
Alex Gleason f216b52b36
AuthorizeRejectButtons: skip animations if countdown is undefined 2023-03-27 17:08:24 -05:00
Alex Gleason 09ed0bccab
AuthorizeRejectButtons: refactor ActionEmblem into a separate component 2023-03-27 17:03:19 -05:00
Alex Gleason 63394bb1b4
AuthorizeRejectButtons: refactor button into a separate component 2023-03-27 16:59:55 -05:00
Alex Gleason d08a2e215b
AuthorizeRejectButtons: add a loading animation 2023-03-27 16:45:41 -05:00
Alex Gleason 3b8f43f02d
AuthorizeRejectButtons: fix onReject never being called 2023-03-27 15:36:55 -05:00
Alex Gleason 9367b16200
AuthorizeRejectButtons: swap out icon during action countdown 2023-03-27 15:34:02 -05:00
Alex Gleason 6f705a827e
AuthorizeRejectButtons: add countdown functionality 2023-03-27 15:31:13 -05:00
Chewbacca ff5eb736fc Merge branch 'report-group' into 'develop'
Add ability to report a Group

See merge request soapbox-pub/soapbox!2375
2023-03-27 18:06:35 +00:00
marcin mikołajczak f6cee79c0e lint
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-26 21:58:46 +02:00
marcin mikołajczak bf8c454c23 Update en.json
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-25 23:18:38 +01:00
marcin mikołajczak 12f3b4fbc3 Support Friendica dislikes, quotes
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-25 23:16:40 +01:00
marcin mikołajczak 4bee42f86d Update changelog
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-25 12:15:56 +01:00
marcin mikołajczak 96a8bcdf82 Friendica dislikes
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-25 12:14:53 +01:00
marcin mikołajczak a45be78b97 Merge branch 'friendica' into 'develop'
Update features.ts for Friendica

See merge request soapbox-pub/soapbox!2380
2023-03-25 10:59:06 +00:00
marcin mikołajczak 4990e1eaa7 Check version for Friendica features
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-25 10:02:21 +01:00
marcin mikołajczak 52172c923f Actually fix version parsing for Friendica
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-25 09:43:27 +01:00
marcin mikołajczak 01359ca592 Update features.ts for Friendica
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-25 09:01:58 +01:00
Alex Gleason 7c1182bfb3 Merge branch 'entity-request' into 'develop'
EntityStore: refactor all hooks to use callbacks

See merge request soapbox-pub/soapbox!2379
2023-03-24 14:44:47 +00:00
Alex Gleason 818b10efc3
Remove stray whitespace 2023-03-23 19:24:20 -05:00
Alex Gleason a530ec0202
EntityStore: switch all hooks to use a callback function 2023-03-23 19:22:26 -05:00
Alex Gleason 9d12173b87
Add useLoading hook 2023-03-23 18:44:44 -05:00
Alex Gleason aa7e2f6965
Refactor hooks with useEntityRequest 2023-03-23 16:22:15 -05:00
Alex Gleason 45c12e9b65
Make EntityCallbacks a generic function 2023-03-23 16:09:04 -05:00
Alex Gleason 7248331742
Add useEntityRequest hook 2023-03-23 16:04:42 -05:00
Alex Gleason ac9718e6ed
Return isLoading from useCreateEntity and useDeleteEntity 2023-03-23 15:30:45 -05:00
Alex Gleason 1b569b6c82
useEntity: accept an EntityRequest object 2023-03-23 15:15:04 -05:00
Alex Gleason b4c3248791
useEntityActions: fix isLoading 2023-03-23 15:09:00 -05:00
Alex Gleason 1c5a6d8b41
useDeleteEntity: refactor with EntityRequest 2023-03-23 15:05:34 -05:00
Alex Gleason 50f65bc7c9
useCreateEntity: pass an EntityRequest, refactor 2023-03-23 14:52:38 -05:00
Chewbacca 30ef70440f Add ability to report a Group 2023-03-23 15:17:44 -04:00
Alex Gleason ad3f8acbe5
EntityStore: allow passing an EntityRequest object to useEntities 2023-03-23 14:14:53 -05:00
Alex Gleason 948d66bcab Merge branch 'group-requests' into 'develop'
Groups: improve pending memberships, standardize "authorize/reject" buttons throughout the UI

See merge request soapbox-pub/soapbox!2370
2023-03-23 17:05:14 +00:00
Alex Gleason 4783a41b78
useDeleteEntity: support onSuccess callback 2023-03-23 10:45:49 -05:00
Alex Gleason 1949651b9a
Merge remote-tracking branch 'origin/develop' into group-requests 2023-03-23 10:23:06 -05:00
Alex Gleason 75b0262f9a
Move pendingCount logic to useEntities 2023-03-22 21:28:48 -05:00
Alex Gleason 2674c060ad
GroupMembers: showLoading if pending members are being fetched 2023-03-22 20:26:53 -05:00
Alex Gleason 6929975aaa
useIncrementEntity: fix optimistic counter 2023-03-22 19:58:40 -05:00
Alex Gleason 402daec9c3
Add useIncrementEntity hook 2023-03-22 19:45:34 -05:00
Alex Gleason c5b1f23bda
GroupMembers: use X-Total-Count if available 2023-03-22 19:22:12 -05:00
Alex Gleason c4d0dd568e
EntityStore: let totalCount be undefined, don't try to set it from the local count 2023-03-22 19:11:11 -05:00
Alex Gleason f016ac1e6d
GroupMembershipRequests: invalidate query upon authorize/reject 2023-03-22 18:48:24 -05:00
Alex Gleason cb8363d179
EntityStore: make fetching the first page override the old list 2023-03-22 18:47:10 -05:00
Alex Gleason e2510489c5
EntityStore: support query invalidation 2023-03-22 18:17:28 -05:00
Alex Gleason a256665aad
EntityStore: add support for X-Total-Count from the API 2023-03-22 17:39:58 -05:00
Alex Gleason 1eed61c386
GroupMembershipRequests: don't clear dismissed entries until new content is fetched 2023-03-22 16:42:08 -05:00
Alex Gleason b47cdb368f
useGroupMembershipRequests: use useDismissEntity hooks 2023-03-22 16:31:49 -05:00
Alex Gleason 61fb434a54
Improve API of parseEntitiesPath 2023-03-22 16:12:05 -05:00
Alex Gleason 8f67d2c76f
EntityStore: consolidate types, fix type of "path" 2023-03-22 16:06:10 -05:00
Alex Gleason d2fd9e0387
Export new entity hooks 2023-03-22 15:32:56 -05:00
Alex Gleason b127025167
Move useCreateEntity into its own hook as well, because why not 2023-03-22 15:31:58 -05:00
Alex Gleason b76559f24a
Add useDismissEntity hook, update useDeleteEntity to match 2023-03-22 14:40:18 -05:00
Alex Gleason 3d72e6305f
EntityStory: add dismissEntities action for deleting ids from a list 2023-03-22 14:34:10 -05:00
Alex Gleason 4049de50aa
Add separate useDeleteEntity hook accepting a callback 2023-03-22 14:08:08 -05:00
Alex Gleason ee1b1b4397
GroupMembers: fix pending row borders and dark mode 2023-03-22 13:47:18 -05:00
Alex Gleason 1954848c65
Tabs: vertically center the counter 2023-03-22 13:23:45 -05:00
Alex Gleason 71c7c4adc7 Merge branch 'group-widgets' into 'develop'
Add MyGroupsPanel, improve layout on various group pages

See merge request soapbox-pub/soapbox!2374
2023-03-22 16:03:02 +00:00
Alex Gleason cc2fbc0208 Merge branch 'redirect-group-posts' into 'develop'
Redirect group statuses to a custom path

See merge request soapbox-pub/soapbox!2371
2023-03-22 16:02:58 +00:00
Chewbacca f7a964e6ee Merge branch 'delete-group' into 'develop'
Add hook to delete Group

See merge request soapbox-pub/soapbox!2369
2023-03-22 15:58:27 +00:00
Chewbacca 32ca5f09ee I18n 2023-03-22 11:23:35 -04:00
Chewbacca ad98bf45cc Add hook to delete Group 2023-03-22 11:20:03 -04:00
Alex Gleason 65070f6519
Add MyGroupsPanel, improve layout on various group pages 2023-03-21 16:34:15 -05:00
Alex Gleason ec72ac0db8
yarn i18n 2023-03-21 16:09:40 -05:00
Alex Gleason be32a0c1a0
Textarea: add a character counter 2023-03-21 15:39:08 -05:00
Alex Gleason 69d667d6c6
"Authorized" --> "Approved" 2023-03-21 11:56:48 -05:00
Alex Gleason be7a462fc4 Merge branch 'edit-group-fixes' into 'develop'
Edit group fixes

See merge request soapbox-pub/soapbox!2372
2023-03-21 03:16:08 +00:00
Alex Gleason f61e0d889a
"Blocked members" --> "Banned members" 2023-03-20 21:56:15 -05:00
Alex Gleason cc3585f319
Manage group: add headers 2023-03-20 21:52:44 -05:00
Alex Gleason f369a7c765
Use "danger" text for deleting account and group 2023-03-20 21:42:54 -05:00
Alex Gleason a8be701ea0
Fix PendingGroupsRow test 2023-03-20 21:31:07 -05:00
Alex Gleason 2196d9e3e5
yarn i18n 2023-03-20 21:23:10 -05:00
Alex Gleason fb8d543f7c
Redirect group statuses to a custom path 2023-03-20 20:57:49 -05:00
Alex Gleason b87af6a71c
AuthorizeRejectButtons: fix styles, make less fragile 2023-03-20 20:11:21 -05:00
Alex Gleason 3a12b316d9
GroupPage: add pending members counter 2023-03-20 20:02:58 -05:00
Alex Gleason 9ca384dcd7
Card: fix back button not having a rounded border 2023-03-20 19:37:46 -05:00
Alex Gleason 4f5866d43f
CHANGELOG: authorize/reject buttons 2023-03-20 19:33:29 -05:00
Alex Gleason 5774516ea0
Reorganize GroupMembershipRequests a little 2023-03-20 19:32:24 -05:00
Alex Gleason d4e9fddd02
Update usage of AuthorizeRejectButtons in group membership requests 2023-03-20 19:24:06 -05:00
Alex Gleason 7a06c7f92c
Use AuthorizeReject buttons for follow requests 2023-03-20 19:23:11 -05:00
Alex Gleason 28f5a88848
Use AuthorizeReject buttons for account approval 2023-03-20 19:22:55 -05:00
Alex Gleason 4de8926445
IAuthorizeRejectButtons: simplify component 2023-03-20 19:22:00 -05:00
Alex Gleason f9ab9a45c2
AuthorizeRejectButtons: improve style of result message 2023-03-20 18:49:41 -05:00
Alex Gleason fe64f9f84b
AuthorizeRejectButtons: improve button styles 2023-03-20 18:35:31 -05:00
Alex Gleason 7c7855e7a1
Groups: make authorize/reject use component state, update designs 2023-03-20 17:46:10 -05:00
Alex Gleason 1d9ed41fec
Add AuthorizeRejectButtons component 2023-03-20 17:45:52 -05:00
marcin mikołajczak 282afaa47f Merge branch 'use-groups-condition' into 'develop'
Do not make requests to api/v1/groups if feature not available

See merge request soapbox-pub/soapbox!2363
2023-03-20 21:57:35 +00:00
marcin mikołajczak 7329c0bf25 Do not make requests to api/v1/groups if fffeature not available
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-20 22:42:27 +01:00
Alex Gleason ca9a41f102
Use EntityStore for pending group requests 2023-03-20 16:41:12 -05:00
Chewbacca babaa979f5 Merge branch 'move-mutations-to-entities' into 'develop'
Move Group mutations to entities

See merge request soapbox-pub/soapbox!2368
2023-03-20 21:35:37 +00:00
Alex Gleason 143a9eda44
Use PendingItemsRow for pending members, pass a prop to control its size 2023-03-20 16:26:40 -05:00
Alex Gleason f6b28dd9c3
Abstract PendingItemsRow into its own component 2023-03-20 16:09:19 -05:00
Alex Gleason 3c06ba734b
Display pending counter in group member list 2023-03-20 16:03:41 -05:00
Alex Gleason d08178f5fc
Groups: use entity store for pending requests 2023-03-20 15:54:06 -05:00
Alex Gleason 28a69ad88b
Ensure group_visibility param is passed when creating group 2023-03-20 15:30:13 -05:00
Chewbacca e46a7e8f4a Merge branch 'promote-to-admin' into 'develop'
Move Promote/Demote admin into entity store

See merge request soapbox-pub/soapbox!2359
2023-03-20 20:08:51 +00:00
Chewbacca 8b478c939a Revert change 2023-03-20 16:08:29 -04:00
Alex Gleason 396f6ada1a Merge branch 'emoji-floating-ui' into 'develop'
Emoji floating ui

Closes #1390 and #1398

See merge request soapbox-pub/soapbox!2367
2023-03-20 20:03:41 +00:00
Chewbacca e42e0577f4 Move Group mutations to entities 2023-03-20 15:41:18 -04:00
Chewbacca 4a6433433f Update translations 2023-03-20 15:41:12 -04:00
Chewbacca 4985db7dea Update group roles to owner/admin/user 2023-03-20 15:41:12 -04:00
Chewbacca 89bdc9b4a1 Move Promote/Demote admin into entity store 2023-03-20 15:41:12 -04:00
Alex Gleason ea4f707413
Fix offset of chat reaction wrapper 2023-03-20 13:57:29 -05:00
Alex Gleason bc457b61d1 Merge branch 'edit-group' into 'develop'
Edit group

See merge request soapbox-pub/soapbox!2357
2023-03-20 18:41:52 +00:00
Alex Gleason f8b20858a3
Chats: fix crash in emoji autosuggest 2023-03-19 19:59:46 -05:00
Alex Gleason 67ffe9609f
ChatTextarea: pass ref to child
Fixes https://gitlab.com/soapbox-pub/soapbox/-/issues/1390
2023-03-19 18:03:44 -05:00
Alex Gleason 47561e5c01
Enable custom emoji reacts on custom Pleroma forks 2023-03-19 17:53:14 -05:00
Alex Gleason 5c7c0ea1dd
EmojiSelector: switch to floating-ui 2023-03-19 17:52:45 -05:00
Alex Gleason 2b75dcacd2
EmojiPickerDropdownContainer: switch to floating-ui 2023-03-19 17:24:39 -05:00
Alex Gleason 9dc12f0994 Merge branch 'changelog' into 'develop'
Update CHANGELOG

See merge request soapbox-pub/soapbox!2365
2023-03-19 20:48:02 +00:00
Alex Gleason f7c7b6ce6c
Update CHANGELOG 2023-03-19 15:47:40 -05:00
Alex Gleason da7284212e Merge branch 'group-svg' into 'develop'
Delete proprietary groups image

See merge request soapbox-pub/soapbox!2364
2023-03-19 20:40:34 +00:00
Alex Gleason 813fd7f8ee
Delete proprietary groups image 2023-03-19 15:39:58 -05:00
marcin mikołajczak b62db891fe Merge branch 'mkljczk-develop-patch-27878' into 'develop'
Update CHANGELOG.md

See merge request soapbox-pub/soapbox!2362
2023-03-19 20:15:28 +00:00
marcin mikołajczak 9c80a50b95 Merge branch 'pending-status-gap' into 'develop'
Add missing gap to PendingStatus

See merge request soapbox-pub/soapbox!2361
2023-03-19 19:50:27 +00:00
marcin mikołajczak 8634c5f91e Update CHANGELOG.md 2023-03-19 19:26:17 +00:00
marcin mikołajczak 049554db84 Add missing gap to PendingStatus
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-19 20:23:36 +01:00
marcin mikołajczak 4124b85ede Merge branch 'custom-emoji-reacts' into 'develop'
Support custom emoji reacts

See merge request soapbox-pub/soapbox!2360
2023-03-19 19:03:53 +00:00
marcin mikołajczak 38c99dbc48 Add tests from custom reacts
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-19 19:22:05 +01:00
marcin mikołajczak 09a0a36935 Update tests
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-18 13:27:27 +01:00
marcin mikołajczak 179eb7fc99 Fix preview in reactions modal
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-18 11:55:00 +01:00
marcin mikołajczak 8b81838f2f Support custom emoji reacts
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-18 00:07:18 +01:00
Alex Gleason c0a22205f7
Fix GroupActionButton test 2023-03-17 16:14:23 -05:00
Alex Gleason 3a94736285 Merge branch 'store-optimistic-delete' into 'develop'
EntityStore: optimistic deletion

See merge request soapbox-pub/soapbox!2356
2023-03-17 20:40:55 +00:00
Alex Gleason e6621a802b
Make popular and suggested groups share the Group store 2023-03-16 13:18:36 -05:00
Alex Gleason 181bf23c34
Importer: use EntityStore enums 2023-03-16 13:15:00 -05:00
Alex Gleason c51870af6e
Update some more groups stuff to use entities 2023-03-15 19:26:37 -05:00
Alex Gleason 1518e88904
Retrofit old Group actions to EntityStore 2023-03-15 18:37:50 -05:00
Alex Gleason 5871abd786
Make "Manage Group" use the EntityStore 2023-03-15 17:59:37 -05:00
Alex Gleason 7fffe59fb9
Allow "owner" permissions on group pages 2023-03-15 17:26:07 -05:00
Alex Gleason 1ab9b1d75c
EntityStore: optimistic deletion 2023-03-15 16:52:09 -05:00
Chewbacca 709edaefad Merge branch 'block-group-members' into 'develop'
Use Entity Hooks for Blocking Group members

See merge request soapbox-pub/soapbox!2353
2023-03-15 20:42:40 +00:00
Chewbacca 41ab40485e Merge branch 'renovate/floating-ui-react-0.x' into 'develop'
fix(deps): update dependency @floating-ui/react to ^0.21.0

See merge request soapbox-pub/soapbox!2348
2023-03-15 20:42:27 +00:00
Alex Gleason a83cfe7ddd Merge branch 'entity-actions-delete' into 'develop'
useEntityActions: ensure the delete gets dispatched

See merge request soapbox-pub/soapbox!2354
2023-03-15 20:08:51 +00:00
Chewbacca 0fe48840e1 Merge branch 'popovers' into 'develop'
Add Popovers component

See merge request soapbox-pub/soapbox!2352
2023-03-15 20:06:50 +00:00
Alex Gleason 74ebd560e6
Revert useEntity array changes, do that in the schema parser 2023-03-15 14:19:13 -05:00
Alex Gleason 602a670b2e
useEntityActions: ensure the delete gets dispatched 2023-03-15 14:04:29 -05:00
Alex Gleason 79aa8e210f Merge branch 'weblate-soapbox-pub-soapbox' into 'develop'
Translations update from Hosted Weblate

See merge request soapbox-pub/soapbox!2350
2023-03-15 19:02:36 +00:00
Chewbacca 6b30671875 Add Account entity and improve Block/Ban support for Groups 2023-03-15 14:55:43 -04:00
Chewbacca a99a7b2af5 Improve focus design for Danger buttons 2023-03-15 14:55:43 -04:00
Chewbacca 9dde71716f Improve DropdownMenu API 2023-03-15 14:55:43 -04:00
Chewbacca 20ccd26a6e Update Entity Store with bug fixes 2023-03-15 14:55:43 -04:00
Chewbacca 283935e837 Add popover component 2023-03-15 14:55:26 -04:00
gallegonovato bbda49a31c
Translated using Weblate (Spanish)
Currently translated at 100.0% (1520 of 1520 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/es/
2023-03-15 19:55:03 +01:00
Poesty Li 471f275dcb
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1520 of 1520 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-03-15 19:55:03 +01:00
Hosted Weblate c5a548fa83
Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/
2023-03-15 19:55:03 +01:00
Poesty Li e28e12cd60
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1520 of 1520 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-03-15 19:55:03 +01:00
gallegonovato c9bd4291bb
Translated using Weblate (Spanish)
Currently translated at 100.0% (1520 of 1520 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/es/
2023-03-15 19:55:03 +01:00
Chewbacca af69c7564e Merge branch 'group-avatar-ring' into 'develop'
Add GroupAvatar component

See merge request soapbox-pub/soapbox!2349
2023-03-15 18:54:47 +00:00
Alex Gleason 822ab987a1 Merge branch 'create-group-hook' into 'develop'
Add useEntityActions hooks

See merge request soapbox-pub/soapbox!2351
2023-03-15 18:54:28 +00:00
Alex Gleason 463dcd2c1e
Merge remote-tracking branch 'origin/develop' into create-group-hook 2023-03-14 14:56:46 -05:00
Chewbacca c6c12fa60f Merge branch 'group-composer' into 'develop'
Update Group Members to use Entity Store

See merge request soapbox-pub/soapbox!2342
2023-03-14 19:31:52 +00:00
Alex Gleason ac76af41b2
Add preliminary useEntityActions hook 2023-03-14 14:24:11 -05:00
Chewbacca c8a4d63fc8 Add GroupAvatar component 2023-03-14 15:17:05 -04:00
Chewbacca 7070630eaf useVersion -> useBackend 2023-03-14 15:16:01 -04:00
Chewbacca 821b90c372 Add pages for Popular / Suggested Groups 2023-03-14 15:16:01 -04:00
Chewbacca 1b542c3ed7 Use Entities enum 2023-03-14 15:16:01 -04:00
Chewbacca f4d2f42c01 Use new schema arg 2023-03-14 15:16:01 -04:00
Chewbacca 7be8218f0c Convert popular/suggested Groups to use Entities 2023-03-14 15:16:01 -04:00
Alex Gleason 50dadeb1b8 useEntities: support multiple list keys 2023-03-14 15:16:01 -04:00
Chewbacca 99b7a1bdd7 Fix i18n 2023-03-14 15:16:01 -04:00
Chewbacca 1d53f48904 Fix parser 2023-03-14 15:16:01 -04:00
Chewbacca 9d1c2df1a2 Use ZOD for group-members 2023-03-14 15:16:00 -04:00
Chewbacca 08f97a133e Update blankslate for Group Timeline 2023-03-14 15:15:26 -04:00
Chewbacca 8a36561ec8 Use entities with Group Members 2023-03-14 15:15:26 -04:00
Alex Gleason 11d06e6b6e useEntities: support multiple list keys 2023-03-14 15:15:26 -04:00
Alex Gleason 8f8807eb76
EntityStore: allow deleting entities 2023-03-14 14:14:48 -05:00
Soapbox Bot 3b1f1cf789 Update dependency @floating-ui/react to ^0.21.0 2023-03-14 18:09:10 +00:00
Chewbacca 7e74e215cc Merge branch 'fix-export' into 'develop'
Fix exporting of types

See merge request soapbox-pub/soapbox!2347
2023-03-14 17:50:06 +00:00
Alex Gleason fafd27d79a Merge branch 'renovate/fork-ts-checker-webpack-plugin-8.x' into 'develop'
Update dependency fork-ts-checker-webpack-plugin to v8

See merge request soapbox-pub/soapbox!2335
2023-03-14 17:33:32 +00:00
Chewbacca 3ca168dc8c Fix exporting of types 2023-03-14 13:10:00 -04:00
Alex Gleason 6ac57910bf Merge branch 'groupschema-tests' into 'develop'
Add group factory functions for tests, add a groupSchema test

See merge request soapbox-pub/soapbox!2344
2023-03-14 14:16:05 +00:00
Chewbacca e173418041 Merge branch 'update-group-comps' into 'develop'
Update Group component with Join buttons

See merge request soapbox-pub/soapbox!2330
2023-03-14 14:13:46 +00:00
Chewbacca 39d61eabda Merge branch 'entity-store-refactoring' into 'develop'
Entity store refactoring (improve performance, etc)

See merge request soapbox-pub/soapbox!2346
2023-03-14 14:12:14 +00:00
Alex Gleason 9df2bb4a86
EntityStore: add tests for reducer, improve types, ensure error gets added to state 2023-03-13 18:42:46 -05:00
Alex Gleason b93a299009
useEntities(): refactor into smaller performant selectors and hooks 2023-03-13 17:53:54 -05:00
Alex Gleason 8547aeb517
Add useGetState hook 2023-03-13 17:45:35 -05:00
marcin mikołajczak 8c6ce74c46 Merge branch 'fix-locale-parser' into 'develop'
Fix locale parser

Closes #1393

See merge request soapbox-pub/soapbox!2343
2023-03-13 22:27:22 +00:00
Alex Gleason a19b1e83a9
EntityStore: parse entities after fetch, not during render (performance) 2023-03-13 16:39:23 -05:00
Alex Gleason e9ae8d2c45
Fix filteredArray logic 2023-03-13 16:37:40 -05:00
Alex Gleason d0ceac9987
Pass zodSchema directly to entity hooks for safeParse validation 2023-03-13 16:23:11 -05:00
Alex Gleason d12078a687
Use group factory functions in tests instead of normalizers 2023-03-13 14:55:59 -05:00
marcin mikołajczak 61ece4d271 Merge remote-tracking branch 'takver/fix-locale-parser' into fix-locale-parser
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-13 20:44:09 +01:00
Chewbacca bced3d6632 Merge branch 'pending-groups' into 'develop'
Add support for pending Group Requests

See merge request soapbox-pub/soapbox!2327
2023-03-13 19:20:16 +00:00
Chewbacca 879ac883aa Merge branch 'my-groups-blankslate' into 'develop'
Update blankslate to allow Group Creation

See merge request soapbox-pub/soapbox!2326
2023-03-13 19:10:14 +00:00
Soapbox Bot fadceaac45 fix(deps): update dependency fork-ts-checker-webpack-plugin to v8 2023-03-13 19:08:19 +00:00
Alex Gleason d747e323c6
Add basic groupSchema test 2023-03-13 14:03:50 -05:00
Alex Gleason c3728dbddd Merge branch 'weblate-soapbox-pub-soapbox' into 'develop'
Translations update from Hosted Weblate

See merge request soapbox-pub/soapbox!2336
2023-03-13 18:29:09 +00:00
Alex Gleason 1922e889f7 Merge branch 'create-group' into 'develop'
Create group

See merge request soapbox-pub/soapbox!2325
2023-03-13 18:28:43 +00:00
Chewbacca 58527b0656 Update Group component with Join buttons 2023-03-13 14:27:35 -04:00
Chewbacca 737c43d847 Fix i18n 2023-03-13 14:27:21 -04:00
Chewbacca f21f72461a Add support for pending Group Requests 2023-03-13 14:27:21 -04:00
Chewbacca 83532aedba Update blankslate to allow Group Creation 2023-03-13 14:27:21 -04:00
Chewbacca cced90a780 Update blankslate to allow Group Creation 2023-03-13 14:26:42 -04:00
Poesty Li 466cf91b31
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1508 of 1508 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-03-13 19:08:29 +01:00
Hosted Weblate c3dd9515bf
Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/
2023-03-13 19:08:29 +01:00
Poesty Li bd7710677f
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1502 of 1502 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-03-13 19:08:28 +01:00
gallegonovato 8baecde992
Translated using Weblate (Spanish)
Currently translated at 100.0% (1502 of 1502 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/es/
2023-03-13 19:08:28 +01:00
Hosted Weblate 43a204db50
Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/
2023-03-13 19:08:28 +01:00
Alex Gleason a40222c2de Merge branch 'groups-zod' into 'develop'
Groups zod

See merge request soapbox-pub/soapbox!2338
2023-03-13 18:08:16 +00:00
Alex Gleason 607e6b1808
groupsSchema: refine --> transform 2023-03-13 12:05:56 -05:00
Chewbacca a9b79f72b4 Revert "Fix useGroupsPath test"
This reverts commit 487604b15a.
2023-03-13 13:02:41 -04:00
Chewbacca a0c67c9b6f Fix Zod parsing error 2023-03-13 13:02:37 -04:00
Alex Gleason 487604b15a
Fix useGroupsPath test 2023-03-13 11:54:22 -05:00
Alex Gleason 5278c8eb0f
Merge remote-tracking branch 'origin/develop' into groups-zod 2023-03-13 11:05:21 -05:00
Chewbacca 55bacbbf05 Merge branch 'group-actions' into 'develop'
Group actions

See merge request soapbox-pub/soapbox!2324
2023-03-13 16:01:34 +00:00
Chewbacca a71aaca719 Fix translations 2023-03-13 11:36:08 -04:00
Chewbacca bd4c99b697 Fix tests 2023-03-13 11:36:08 -04:00
Chewbacca 7ad2696f85 Move Join / Leave / Cancel group/request to mutations 2023-03-13 11:36:08 -04:00
Chewbacca 287fda6d6c Improve error handling for Groups Discover page 2023-03-13 11:36:08 -04:00
Chewbacca e7b3af5260 Merge branch 'fix-tests' into 'develop'
Fix mock in tests

See merge request soapbox-pub/soapbox!2341
2023-03-13 15:35:40 +00:00
Chewbacca 82da9ceeeb Fix mock in tests 2023-03-13 10:07:23 -04:00
marcin mikołajczak 5148d913a7 Merge branch 'follow-requests-styles' into 'develop'
Fix: Long handles don't get truncated in Follow Requests menu

Closes #1389

See merge request soapbox-pub/soapbox!2340
2023-03-13 11:10:27 +00:00
marcin mikołajczak ea6be269bb Fix: Long handles don't get truncated in Follow Requests menu
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-13 00:00:07 +01:00
Alex Gleason ef10e2a699 Merge branch 'test-fixes' into 'develop'
Upgrade testing-library deps for sanity, fix DurationSelector time logic, comment out 2 failing tests

See merge request soapbox-pub/soapbox!2339
2023-03-10 23:00:11 +00:00
Alex Gleason ccec7f43e5
DurationSelector: actually, don't even do weird date stuff at all, LOL 2023-03-10 15:07:25 -06:00
Alex Gleason bd49417210
Also, fix the variable names in DurationSelector 2023-03-10 15:04:44 -06:00
Alex Gleason 2b137c12cf
Comment out failing tests ¯\_(ツ)_/¯ 2023-03-10 15:00:53 -06:00
Alex Gleason de89a438cc
Make DurationSelector test work locally 2023-03-10 15:00:29 -06:00
Alex Gleason 4031e4624c
Upgrade testing-library deps for sanity 2023-03-10 15:00:21 -06:00
Alex Gleason 1af67c3a25
members_count will never be null 2023-03-10 13:46:20 -06:00
Alex Gleason 6a2c64ae45
groupSchema: catch emojis 2023-03-10 13:41:47 -06:00
Alex Gleason 3d2331d20b
Make useGroups hooks use zod parser, update group types 2023-03-10 13:36:00 -06:00
Alex Gleason 5e8c92ed4d
groupSchema: refine --> transform, fix type of members_count 2023-03-10 13:13:54 -06:00
Alex Gleason 6be8d4d46e
Add groups schemas with zod 2023-03-10 12:42:49 -06:00
Alex Gleason 37f5b35aab
Add zod 2023-03-10 11:35:18 -06:00
Alex Gleason a0c1bd84c9 Merge branch 'group-entities' into 'develop'
EntityStore: Groups

See merge request soapbox-pub/soapbox!2333
2023-03-10 17:22:55 +00:00
Alex Gleason cf541e83b3
Fix useGroupsPath test 2023-03-10 10:56:00 -06:00
marcin mikołajczak b1471be142 Merge branch 'filters-v2' into 'develop'
filters v2

See merge request soapbox-pub/soapbox!2321
2023-03-10 11:03:46 +00:00
marcin mikołajczak 51524118d4 Merge branch 'gallery-load-more' into 'develop'
Fix load more button height on account gallery page

See merge request soapbox-pub/soapbox!2318
2023-03-10 10:52:00 +00:00
Alex Gleason c55154daaf Merge branch 'renovate/testing-library-react-14.x' into 'develop'
Update dependency @testing-library/react to v14

See merge request soapbox-pub/soapbox!2332
2023-03-10 01:46:02 +00:00
Alex Gleason 00c00b31fc Merge branch 'compareDate' into 'develop'
Sort conversations by last status date rather than id

See merge request soapbox-pub/soapbox!2319
2023-03-10 01:45:18 +00:00
Alex Gleason 074c3429cd Merge branch 'develop-patch-51d8' into 'develop'
Dictate Pleroma Chat and Quote Post compatibility by advertised features in the api

See merge request soapbox-pub/soapbox!2334
2023-03-10 01:41:46 +00:00
rurai10 29b75a29f0 Update file features.ts 2023-03-10 00:15:45 +00:00
Alex Gleason be9d922047
Actually, do put relationships in their own list. And fix parsers not doing the right thing. 2023-03-09 15:35:50 -06:00
Alex Gleason 14a84e557c
Update useGroups queries with EntityStore improvements 2023-03-09 15:05:54 -06:00
Alex Gleason fa2884c11b
EntityStore: fetch with useEntity automatically, accept refetch opt 2023-03-09 15:05:27 -06:00
marcin mikołajczak 1d64f934d9 Merge remote-tracking branch 'soapbox/develop' into filters-v2 2023-03-09 21:49:57 +01:00
Alex Gleason ad583c89f8
EntityStore: allow passing an undefined endpoint (to skip fetch), prevent race conditions in isFetching 2023-03-09 14:44:54 -06:00
marcin mikołajczak 94d248fb79 Update README.md
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-09 21:39:31 +01:00
Alex Gleason a3b1f541bc
EntityStore: support staleTime option (automatically fetch) 2023-03-09 14:20:04 -06:00
Alex Gleason d883f2f5bd
Group hooks: use new parser opt 2023-03-09 12:36:20 -06:00
Alex Gleason 250b009635
EntityStore: allow passing a parser function to parse the entities 2023-03-09 12:32:50 -06:00
Alex Gleason 9964491da5
First draft of GroupRelationship entity hooks 2023-03-09 11:47:24 -06:00
Alex Gleason 8923e7b5d0
Combine group hooks into one useGroups file 2023-03-09 11:24:53 -06:00
Alex Gleason 4c6d13e4ef
Use EntityStore for Groups 2023-03-09 11:21:27 -06:00
Soapbox Bot 31351700e0 Update dependency @testing-library/react to v14 2023-03-09 17:09:36 +00:00
Alex Gleason f2983b96fb Merge branch 'weblate-soapbox-pub-soapbox' into 'develop'
Translations update from Hosted Weblate

See merge request soapbox-pub/soapbox!2329
2023-03-09 17:08:09 +00:00
Alex Gleason c492af7042
Merge remote-tracking branch 'origin/develop' into entity-store 2023-03-09 10:43:58 -06:00
Tassoman ae1287ae82
Translated using Weblate (Italian)
Currently translated at 99.8% (1486 of 1488 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/it/
2023-03-09 17:37:54 +01:00
Alex Gleason 68144e4f82 Merge branch 'renovate/eslint-plugin-jsdoc-40.x' into 'develop'
Update dependency eslint-plugin-jsdoc to v40

See merge request soapbox-pub/soapbox!2323
2023-03-09 16:37:48 +00:00
Alex Gleason bed26727c1
yarn i18n 2023-03-09 10:16:04 -06:00
oakes 332be25784 Sort conversations by last status date rather than id 2023-03-09 10:49:59 -05:00
Alex Gleason a8b0dc93f2 Merge branch 'nostr' into 'develop'
Truncate Nostr pubkeys in reply mentions

See merge request soapbox-pub/soapbox!2328
2023-03-09 02:16:05 +00:00
Alex Gleason d1531b832d
Truncate Nostr pubkeys in reply mentions 2023-03-08 19:56:24 -06:00
Alex Gleason c75ce58792
Fix instance test 2023-03-08 14:50:15 -06:00
Alex Gleason 01343bbe0a
Add copy and share links to group confirmation step 2023-03-08 14:46:24 -06:00
Alex Gleason 2a9f05a765
Create Group: add info items to confirmation step 2023-03-08 14:10:09 -06:00
Alex Gleason d7f5e210d8
Groups: scaffold the confirmation step 2023-03-08 13:43:16 -06:00
Alex Gleason 925509a985
Fix groups modal bailing too soon 2023-03-08 12:20:20 -06:00
Alex Gleason 63df638630
Create Group: enforce max limit on description and name 2023-03-08 12:00:28 -06:00
Alex Gleason 08014a678d Merge branch 'weblate-soapbox-pub-soapbox' into 'develop'
Translations update from Hosted Weblate

See merge request soapbox-pub/soapbox!2293
2023-03-08 04:33:30 +00:00
gallegonovato b9ac2084c2
Translated using Weblate (Spanish)
Currently translated at 100.0% (1488 of 1488 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/es/
2023-03-08 00:38:28 +01:00
marcin mikołajczak d969c91c76 Update filter preview
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-08 00:02:17 +01:00
Poesty Li 334f93f2b1
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1488 of 1488 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-03-06 19:46:15 +01:00
Poesty Li 1d94d2ec9f
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1488 of 1488 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-03-06 19:46:15 +01:00
Poesty Li 57da9102cf
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1488 of 1488 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-03-06 19:46:15 +01:00
Hosted Weblate f9523d7afd
Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/
2023-03-06 19:46:15 +01:00
Poesty Li 050f4130bb
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1479 of 1479 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-03-06 19:46:15 +01:00
Isabell De Inschnitzel 3ef34b1695
Translated using Weblate (German)
Currently translated at 97.3% (1440 of 1479 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/de/
2023-03-06 19:46:15 +01:00
Poesty Li aca7bb63a5
Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (1476 of 1479 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-03-06 19:46:14 +01:00
Hosted Weblate 41a1923ca5
Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/
2023-03-06 19:46:14 +01:00
marcin mikołajczak d2ca2da10a
Translated using Weblate (Polish)
Currently translated at 92.3% (1357 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/pl/
2023-03-06 19:46:14 +01:00
Tassoman e7e3ef86a3
Translated using Weblate (Italian)
Currently translated at 100.0% (1470 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/it/
2023-03-06 19:46:14 +01:00
Simen f2eaccf0f2
Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (1470 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/nb_NO/
2023-03-06 19:46:14 +01:00
Simen e87bb3d0ae
Translated using Weblate (Norwegian Bokmål)
Currently translated at 79.5% (1169 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/nb_NO/
2023-03-06 19:46:14 +01:00
ruine 4e9e85a60b
Translated using Weblate (Japanese)
Currently translated at 82.7% (1217 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ja/
2023-03-06 19:46:14 +01:00
ruine 905c1a47ac
Translated using Weblate (Japanese)
Currently translated at 82.7% (1217 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ja/
2023-03-06 19:46:14 +01:00
marcin mikołajczak 4e779742fc
Translated using Weblate (Polish)
Currently translated at 92.3% (1357 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/pl/
2023-03-06 19:46:14 +01:00
Tassoman 3b3a7653b1
Translated using Weblate (Italian)
Currently translated at 100.0% (1470 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/it/
2023-03-06 19:46:14 +01:00
Oukiki Saleh 1e6be8fa0f
Translated using Weblate (Arabic)
Currently translated at 99.3% (1460 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ar/
2023-03-06 19:46:14 +01:00
Oukiki Saleh d6d7398576
Translated using Weblate (Arabic)
Currently translated at 99.1% (1458 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ar/
2023-03-06 19:46:14 +01:00
Oukiki Saleh edf3b093af
Translated using Weblate (Arabic)
Currently translated at 93.3% (1372 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ar/
2023-03-06 19:46:14 +01:00
gallegonovato 95f4aee356
Translated using Weblate (Spanish)
Currently translated at 100.0% (1470 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/es/
2023-03-06 19:46:14 +01:00
Mr.Narsus f8e05013fd
Translated using Weblate (Arabic)
Currently translated at 91.9% (1351 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ar/
2023-03-06 19:46:14 +01:00
Oukiki Saleh 08b59ea58f
Translated using Weblate (Arabic)
Currently translated at 91.9% (1351 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ar/
2023-03-06 19:46:14 +01:00
Mr.Narsus 483aa96b33
Translated using Weblate (Arabic)
Currently translated at 89.3% (1313 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ar/
2023-03-06 19:46:14 +01:00
Oukiki Saleh fce046df8f
Translated using Weblate (Arabic)
Currently translated at 89.3% (1313 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ar/
2023-03-06 19:46:14 +01:00
Oukiki Saleh 5ab98da220
Translated using Weblate (Arabic)
Currently translated at 89.3% (1313 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ar/
2023-03-06 19:46:13 +01:00
Mr.Narsus 4e8130667a
Translated using Weblate (Arabic)
Currently translated at 89.3% (1313 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ar/
2023-03-06 19:46:13 +01:00
Mr.Narsus 725750ca7d
Translated using Weblate (Arabic)
Currently translated at 89.3% (1313 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ar/
2023-03-06 19:46:13 +01:00
Oukiki Saleh 3f13f8e370
Translated using Weblate (Arabic)
Currently translated at 89.3% (1313 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ar/
2023-03-06 19:46:13 +01:00
Tassoman 605ea95ee4
Translated using Weblate (Italian)
Currently translated at 98.2% (1445 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/it/
2023-03-06 19:46:13 +01:00
Oukiki Saleh f9541b5c52
Translated using Weblate (Arabic)
Currently translated at 88.2% (1297 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ar/
2023-03-06 19:46:13 +01:00
Poesty Li 63a42aac67
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1470 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-03-06 19:46:13 +01:00
Poesty Li fb1c36b1c8
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1470 of 1470 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-03-06 19:46:13 +01:00
Alex Gleason ca9e59b56b Merge branch 'chat-multi-upload' into 'develop'
Chats: allow uploading multiple attachments at once (if the backend supports it)

See merge request soapbox-pub/soapbox!2313
2023-03-06 18:46:01 +00:00
Alex Gleason 65d1c66aad
Add group icon to create screen 2023-03-06 12:42:13 -06:00
Alex Gleason d16bce0ecc
Merge remote-tracking branch 'origin/develop' into create-group 2023-03-06 12:10:06 -06:00
Chewbacca 4e2213aba8 Merge branch 'my-groups' into 'develop'
Add Trending and Suggested Groups to discovery

See merge request soapbox-pub/soapbox!2312
2023-03-06 16:44:11 +00:00
Chewbacca 721b5dafcd Merge branch 'group-search' into 'my-groups'
Add support for Group search

See merge request soapbox-pub/soapbox!2314
2023-03-06 16:25:07 +00:00
Chewbacca 31653a5a54 Merge branch 'default-group-tabs' into 'group-search'
Use Groups Discovery if user has no groups

See merge request soapbox-pub/soapbox!2317
2023-03-06 16:04:44 +00:00
Chewbacca bdaf6e4cd6 Merge branch 'group-profile-header' into 'default-group-tabs'
Improve Group Header with latest designs

See merge request soapbox-pub/soapbox!2320
2023-03-06 15:45:42 +00:00
Soapbox Bot 266dd3d110 Update dependency eslint-plugin-jsdoc to v40 2023-03-06 05:10:38 +00:00
Alex Gleason a8c4be01ec Merge branch 'renovate/floating-ui-react-0.x' into 'develop'
Update dependency @floating-ui/react to ^0.20.0

See merge request soapbox-pub/soapbox!2322
2023-03-06 04:38:17 +00:00
marcin mikołajczak 1d4d9c2732 Filters expiration, restyle filters list, fix keywords deletion
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-05 19:49:40 +01:00
Soapbox Bot 3c336cbeb4 Update dependency @floating-ui/react to ^0.20.0 2023-03-05 11:05:00 +00:00
marcin mikołajczak b54a466bfd Merge branch 'update-emoji-mart-2' into 'develop'
Update emoji mart

See merge request soapbox-pub/soapbox!2309
2023-03-05 10:05:08 +00:00
marcin mikołajczak af314ee55d Allow editing filters
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-04 21:22:59 +01:00
marcin mikołajczak ebe4f9373b Remove console.log
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-04 12:44:04 +01:00
marcin mikołajczak 4c92f581c4 Allow creating v2 filters
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-04 12:43:27 +01:00
marcin mikołajczak 4200fa2df4 filters v2
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-03 22:40:39 +01:00
marcin mikołajczak c9f2cc0ae2 Hide emoji-mart preview footer
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-02 23:38:17 +01:00
marcin mikołajczak 1f6328c9c6 Fix emoji picker button color
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-02 23:35:56 +01:00
Chewbacca 3cc4f8b64b Improve Group Header with latest designs 2023-03-02 14:42:45 -05:00
Alex Gleason 55c8887a08
Small design tweaks for create group modal 2023-03-02 12:17:25 -06:00
Alex Gleason d3d363e12f
Allow all permissions if unset 2023-03-02 10:28:31 -06:00
marcin mikołajczak 2ce98055d8 Fix load more button height on account gallery page
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-01 21:02:04 +01:00
marcin mikołajczak eb93cb39fd Fix emoji selector on touchscreen
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-03-01 20:27:16 +01:00
Chewbacca 8fd3b99887 Use Groups Discovery if user has no groups 2023-03-01 14:16:09 -05:00
Chewbacca 01dfbad7bc Add i18n keys to en.json 2023-03-01 11:18:35 -05:00
Chewbacca 0b7a2ac19b Add support for i18n in Group Search 2023-03-01 10:40:07 -05:00
Chewbacca d6d7807807 Add tests for RecentSearches 2023-03-01 09:43:30 -05:00
Alex Gleason aad7df89a5 Merge branch 'pagination' into 'develop'
Fix conversation list pagination

See merge request soapbox-pub/soapbox!2316
2023-02-28 20:42:26 +00:00
oakes f1a14efc58 Remove unnecessary equality test 2023-02-28 15:30:01 -05:00
oakes 1b00de14a6 Fix conversation list pagination 2023-02-28 15:24:04 -05:00
Chewbacca 5acc231cda Add tests for Search component 2023-02-28 15:03:03 -05:00
marcin mikołajczak 277045c7a1 Remove custom emojis from reaction picker
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-28 20:52:15 +01:00
marcin mikołajczak d6732955de Merge remote-tracking branch 'soapbox/develop' into update-emoji-mart-2 2023-02-28 20:48:21 +01:00
marcin mikołajczak 9aef41eab1 Cleanup
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-28 19:13:34 +01:00
marcin mikołajczak e6d6ac6d44 Update en.json
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-28 18:59:32 +01:00
Alex Gleason f450c42048 Merge branch 'i18n-fix' into 'develop'
Add `yarn i18n` command to fix translation files

See merge request soapbox-pub/soapbox!2315
2023-02-28 17:13:56 +00:00
marcin mikołajczak dfa5d3ec8e Restore icon picker
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-28 18:04:24 +01:00
Alex Gleason 18bcd1c084
Add `yarn i18n` command to fix translation files 2023-02-28 10:13:53 -06:00
Alex Gleason a6c5d468f4
Enable groups for v.build === UNRELEASED 2023-02-28 10:05:23 -06:00
Chewbacca e7897228c6 Add support for Group search 2023-02-28 10:26:27 -05:00
Alex Gleason 83dab22371
Chats: allow uploading multiple attachments at once (if the backend supports it) 2023-02-27 09:59:00 -06:00
Chewbacca 0414c36a1e Add Trending and Suggested Groups to discovery 2023-02-27 08:26:59 -05:00
marcin mikołajczak 01a4e7370f Types, update styles
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-26 20:34:57 +01:00
marcin mikołajczak 2bbbcd625e WIP
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-26 19:29:37 +01:00
Alex Gleason b7c1d7d44a Merge branch 'media-rounded' into 'develop'
Improve rounded courners in MediaGallery and Chats

See merge request soapbox-pub/soapbox!2310
2023-02-24 16:06:40 +00:00
Alex Gleason 1b020b2a9b
Improve rounded courners in MediaGallery and Chats 2023-02-23 16:05:24 -06:00
marcin mikołajczak 528acb8ac5 Merge remote-tracking branch 'soapbox/develop' into update-emoji-mart
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-23 17:42:31 +01:00
marcin mikołajczak 5a90bb4456 Merge branch 'mkljczk-develop-patch-61732' into 'develop'
Compose: Only send group_id when not null

Closes rebased#184

See merge request soapbox-pub/soapbox!2307
2023-02-22 19:09:23 +00:00
marcin mikołajczak 28d84dedff Compose: Only send group_id when not null 2023-02-22 17:35:48 +00:00
Alex Gleason 6a3618da9a Merge branch 'docker-release-only' into 'develop'
GitLab CI: only run Docker job when a tag is pushed

See merge request soapbox-pub/soapbox!2299
2023-02-21 17:10:07 +00:00
Alex Gleason 02cf175e1f Merge branch 'empty-p' into 'develop'
Replace `<p></p>` with empty string in chats and statuses

See merge request soapbox-pub/soapbox!2306
2023-02-21 16:38:14 +00:00
Alex Gleason dc8952ad18
Replace `<p></p>` with empty string in chats and statuses 2023-02-21 09:00:16 -06:00
Alex Gleason 6413bed23f Merge branch 'renovate/tabler-icons-2.x' into 'develop'
Update dependency @tabler/icons to v2

See merge request soapbox-pub/soapbox!2218
2023-02-20 18:05:26 +00:00
Alex Gleason 93f873fce9
Tabler: ballon.svg --> balloon.svg 2023-02-20 11:37:06 -06:00
marcin mikołajczak eaa060c4bd Merge branch 'audio-styles' into 'develop'
Fix: audio player track position opacity missing

Closes #1381

See merge request soapbox-pub/soapbox!2302
2023-02-19 21:01:28 +00:00
marcin mikołajczak 9d6c698d31 Fix: audio player track position opacity missing
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-19 20:23:20 +01:00
miklobit eb188b0dfa Add custom logo 2023-02-18 15:06:29 +01:00
Alex Gleason d7c9c035a2 Merge branch 'alexgleason-develop-patch-82538' into 'develop'
Remove groups from changelog

See merge request soapbox-pub/soapbox!2301
2023-02-17 12:58:50 +00:00
Alex Gleason c6d89fae38 Remove groups from changelog 2023-02-17 12:58:13 +00:00
Alex Gleason 52b24bb013 Merge branch 'alexgleason-develop-patch-02881' into 'develop'
Update CHANGELOG.md

See merge request soapbox-pub/soapbox!2300
2023-02-16 04:37:42 +00:00
Alex Gleason c9285c7abe Update CHANGELOG.md 2023-02-16 04:37:11 +00:00
Alex Gleason 09baa262f9
GitLab CI: only run Docker job when a tag is pushed 2023-02-15 19:57:40 -06:00
Alex Gleason 10c24f01d9 Merge branch 'fix-react-icons' into 'develop'
Fix Emoji Icon Positioning

Closes #1363

See merge request soapbox-pub/soapbox!2297
2023-02-16 01:52:51 +00:00
marcin mikołajczak b3585bb348 Merge branch 'eslint-rules' into 'develop'
Change ESLint rules, lint

See merge request soapbox-pub/soapbox!2298
2023-02-16 00:06:57 +00:00
marcin mikołajczak 81de0014d3 Change ESLint rules, lint
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-16 00:12:02 +01:00
Alex Gleason 2e27886230 Alex's suggestion (brap) 2023-02-15 21:34:26 +00:00
matty 4b99ab7d35 fixes the stuff 2023-02-15 16:14:03 -05:00
Soapbox Bot a6b96c88aa Update dependency @tabler/icons to v2 2023-02-15 19:09:14 +00:00
Alex Gleason ad07305417 Merge branch 'changelog-unreleased' into 'develop'
Prep CHANGELOG for next release

See merge request soapbox-pub/soapbox!2296
2023-02-15 19:01:18 +00:00
Alex Gleason 445379f180
Prep CHANGELOG for next release 2023-02-15 13:00:59 -06:00
Alex Gleason c108c8cfbd Merge branch 'release-v3.2' into 'develop'
Bump version to v3.2.0

See merge request soapbox-pub/soapbox!2295
2023-02-15 18:58:18 +00:00
Alex Gleason e3307193e7
Bump version to v3.2.0 2023-02-15 12:57:36 -06:00
Alex Gleason 67bf9a7b20 Merge branch 'picker-mobile' into 'develop'
EmojiSelector: ensure the full picker fits on the screen on mobile

See merge request soapbox-pub/soapbox!2291
2023-02-15 18:48:08 +00:00
Alex Gleason 5f55e71fc0 Merge branch 'bump-sentry' into 'develop'
Bump Sentry versions

See merge request soapbox-pub/soapbox!2279
2023-02-15 18:47:43 +00:00
Alex Gleason 919b545e82 Merge branch 'fixes-1371' into 'develop'
DropdownMenu: close only if open in Redux

Closes #1371

See merge request soapbox-pub/soapbox!2294
2023-02-15 18:47:04 +00:00
Alex Gleason d92a4fd239
DropdownMenu: close only if open in Redux 2023-02-15 12:18:00 -06:00
marcin mikołajczak dec2cc69e8 Merge branch 'quoted-status-card' into 'develop'
Don't display card for quoted posts

See merge request soapbox-pub/soapbox!2281
2023-02-15 16:53:05 +00:00
marcin mikołajczak 2cfd402bec Merge branch 'admin-announcements' into 'develop'
Dashboard: Allow to create announcements

See merge request soapbox-pub/soapbox!2276
2023-02-14 23:05:07 +00:00
marcin mikołajczak a4681fddf8 Announcements: Feature-gate link
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-14 23:35:04 +01:00
marcin mikołajczak 45cf156a79 Add link to announcements
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-14 23:34:27 +01:00
Alex Gleason 1c46820d03 Merge branch 'i-1329-2' into 'develop'
hovering reportee shows reportee card, not reporter's

Closes #1329

See merge request soapbox-pub/soapbox!2292
2023-02-14 22:10:24 +00:00
marcin mikołajczak a85bf8d284 Merge branch 'cleanup' into 'develop'
Remove unused styles

See merge request soapbox-pub/soapbox!2289
2023-02-14 21:28:37 +00:00
tassoman 4a205fddc1 hovering reportee shows reportee card, not reporter's 2023-02-14 22:22:57 +01:00
Alex Gleason 0e7136ec04
EmojiSelector: ensure the full picker fits on the screen on mobile 2023-02-14 15:20:23 -06:00
Alex Gleason 23acbc115c Merge branch 'weblate-soapbox-pub-soapbox' into 'develop'
Translations update from Hosted Weblate

See merge request soapbox-pub/soapbox!2272
2023-02-14 21:08:43 +00:00
HiSubway 8437ef4f4b
Translated using Weblate (Japanese)
Currently translated at 84.2% (1217 of 1445 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ja/
2023-02-14 21:50:56 +01:00
HiSubway 25d65a0524
Translated using Weblate (Japanese)
Currently translated at 83.8% (1212 of 1445 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ja/
2023-02-14 21:50:56 +01:00
HiSubway cdeb5c12fa
Translated using Weblate (Japanese)
Currently translated at 82.9% (1199 of 1445 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ja/
2023-02-14 21:50:56 +01:00
HiSubway 73948374c4
Translated using Weblate (Japanese)
Currently translated at 82.8% (1197 of 1445 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ja/
2023-02-14 21:50:56 +01:00
HiSubway d959136f36
Translated using Weblate (Japanese)
Currently translated at 82.6% (1195 of 1445 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ja/
2023-02-14 21:50:56 +01:00
HiSubway 0ac5eda63e
Translated using Weblate (Japanese)
Currently translated at 82.6% (1194 of 1445 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ja/
2023-02-14 21:50:55 +01:00
Tassoman 9d0b034c0f
Translated using Weblate (Italian)
Currently translated at 100.0% (1445 of 1445 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/it/
2023-02-14 21:50:55 +01:00
Tassoman e8e4d4d5bc
Translated using Weblate (Italian)
Currently translated at 100.0% (1445 of 1445 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/it/
2023-02-14 21:50:55 +01:00
Hosted Weblate f5b60f25b5
Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/
2023-02-14 21:50:55 +01:00
Hosted Weblate c1858f32f0
Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/
2023-02-14 21:50:55 +01:00
Ahmad Dakhlallah bee84db236
Translated using Weblate (Arabic)
Currently translated at 89.1% (1290 of 1447 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/ar/
2023-02-14 21:50:55 +01:00
Chewbacca 5eea84c5fb Merge branch 'prevent-excess-redux-actions' into 'develop'
Prevent unnecessary calls to redux action

See merge request soapbox-pub/soapbox!2290
2023-02-14 20:50:44 +00:00
Alex Gleason ed8f48816e Merge branch 'i-1244' into 'develop'
verified badge on profile's header

Closes #1244

See merge request soapbox-pub/soapbox!2212
2023-02-14 20:28:16 +00:00
Tassoman 5828e239b1 verified badge on profile's header 2023-02-14 20:28:16 +00:00
Alex Gleason 78328a0ce2 Merge branch 'gate-chat-reactions' into 'develop'
Fix chatEmojiReactions feature gating

See merge request soapbox-pub/soapbox!2286
2023-02-14 20:21:53 +00:00
Alex Gleason 63ca831d16 Merge branch 'chats-scrollbars' into 'develop'
Fix chat overflow-x

See merge request soapbox-pub/soapbox!2285
2023-02-14 20:21:40 +00:00
Alex Gleason 6fc8b181e6 Merge branch 'status-list-null' into 'develop'
StatusList: don't push null item to Virtuoso (LoadGap)

See merge request soapbox-pub/soapbox!2283
2023-02-14 20:21:21 +00:00
Alex Gleason 2a50a6c26f Merge branch 'sentry-localforage' into 'develop'
Sentry: filter out localForage "No storage found" error

See merge request soapbox-pub/soapbox!2282
2023-02-14 20:21:03 +00:00
Chewbacca f3469cf972 Prevent unnecessary calls to redux action 2023-02-14 15:04:26 -05:00
marcin mikołajczak 4a183e3ba6 Remove unused styles
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-14 20:56:12 +01:00
Chewbacca 225df82be8 Merge branch 'use-floating-ui-middleware' into 'develop'
Use flip and arrow middleware

See merge request soapbox-pub/soapbox!2288
2023-02-14 19:53:54 +00:00
marcin mikołajczak 75fc756bd1 Merge branch 'prefer-clsx' into 'develop'
Use clsx instead of template strings

See merge request soapbox-pub/soapbox!2287
2023-02-14 19:25:16 +00:00
Chewbacca 5b7c6793e6 Remove z-index 2023-02-14 14:01:11 -05:00
Chewbacca ce2e7f3785 Use flip and arrow middleware 2023-02-14 13:58:10 -05:00
Chewbacca 9b74f9264d Merge branch 'fix-pointer-events-bug' into 'develop'
Close dropdown-menu when component unmounts

See merge request soapbox-pub/soapbox!2284
2023-02-14 18:47:48 +00:00
Alex Gleason f271553213
Fix type of chat_message.emoji_reactions, fix tests with +unreleased 2023-02-14 12:36:15 -06:00
Alex Gleason 2a131a8407
Add "unreleased" build to features for tests 2023-02-14 12:35:27 -06:00
Chewbacca f068d6280d prevent scrolling when focused 2023-02-14 13:28:11 -05:00
marcin mikołajczak 3aba4218c5 Use clsx instead of template strings
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-14 19:00:43 +01:00
Alex Gleason d273947f70
Fix chatEmojiReactions feature gating 2023-02-14 11:41:27 -06:00
Alex Gleason 0aeeeb1dc4
Performance: hide EmojiSelector until portaled 2023-02-14 11:27:32 -06:00
Alex Gleason ec2235011f
ChatMessageReactionWrapper: put EmojiSelector in a portal to avoid overflow-x issues 2023-02-14 11:27:28 -06:00
Chewbacca d19f8a267a Close dropdown-menu when component unmounts 2023-02-14 12:18:26 -05:00
Alex Gleason 41b4a229b6
StatusList: don't push null item to Virtuoso (LoadGap) 2023-02-14 10:44:00 -06:00
Alex Gleason 55f99980e6
Sentry: filter out localForage "No storage found" error 2023-02-14 10:30:14 -06:00
Alex Gleason 783a79d306 Merge branch 'multiple-attachments' into 'develop'
Chats: allow uploading multiple attachments

See merge request soapbox-pub/soapbox!2268
2023-02-14 15:54:31 +00:00
Alex Gleason 261d900b51 Merge branch 'fix-dropdown' into 'develop'
Fix Profile Dropdown

See merge request soapbox-pub/soapbox!2261
2023-02-14 15:27:51 +00:00
Alex Gleason 14efff51ff
Merge remote-tracking branch 'origin/develop' into multiple-attachments 2023-02-14 09:24:12 -06:00
marcin mikołajczak 9958b66409 Don't display card for quoted posts
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-14 14:57:39 +01:00
Chewbacca 66f78144ea Bump Sentry versions 2023-02-14 07:39:26 -05:00
Alex Gleason 4ce28750c2 Merge branch 'fix-test' into 'develop'
ChatMessage: remove testid from emoji button resulting in failing test

See merge request soapbox-pub/soapbox!2280
2023-02-13 20:47:37 +00:00
Alex Gleason 04ae0f2ee4
ChatMessage: remove testid from emoji button resulting in failing test 2023-02-13 14:46:15 -06:00
Alex Gleason 01952c8fad Merge branch 'bugfixes' into 'develop'
Fix pipeline, fix emoji selector position

See merge request soapbox-pub/soapbox!2278
2023-02-13 20:28:39 +00:00
Alex Gleason 45657c7a2a
EmojiSelector: pass offset as a prop 2023-02-13 13:17:05 -06:00
Alex Gleason ae606a1db0
StatusReactionWrapper: switch to our Portal component 2023-02-13 13:16:57 -06:00
Chewbacca 0b9fe2cfca Merge branch 'hoverable-menu' into 'develop'
Add new hoverable menu for Chat Messages (and rebuild DropdownMenu component with Popper)

See merge request soapbox-pub/soapbox!2274
2023-02-13 17:32:02 +00:00
Chewbacca 8290b54dd5 Fix commented out code 2023-02-13 08:01:47 -05:00
Alex Gleason 998ff44e80 Merge branch 'react-any-emoji' into 'develop'
React with any emoji

See merge request soapbox-pub/soapbox!2271
2023-02-13 02:48:01 +00:00
Alex Gleason cc2eafdfac
Merge remote-tracking branch 'origin/develop' into react-any-emoji 2023-02-12 20:30:33 -06:00
Alex Gleason f9cb9d468b
EmojiSelector: improve style of 3-dots icon 2023-02-12 20:29:33 -06:00
Alex Gleason 16179a6371
Transpile emoji-mart for jest 2023-02-12 20:23:29 -06:00
marcin mikołajczak 195ec1b67d Merge branch 'chat-resize-reset' into 'develop'
Chats: reset chat message field height after sending a message

See merge request soapbox-pub/soapbox!2277
2023-02-12 22:14:03 +00:00
marcin mikołajczak 35bf20507d Merge branch 'cleanup' into 'develop'
Fixes, styles improvements, cleanup, enforce classes order for classNames

See merge request soapbox-pub/soapbox!2258
2023-02-12 21:59:53 +00:00
marcin mikołajczak 00657c201f Sort en.json keys
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-12 22:29:20 +01:00
marcin mikołajczak 4936c3ed38 Merge remote-tracking branch 'soapbox/develop' into cleanup
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-12 22:26:55 +01:00
marcin mikołajczak 7759f64fed Chats: reset chat message field height after sending a message
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-12 22:23:44 +01:00
marcin mikołajczak 8f79ef2bcd Merge branch 'filters' into 'develop'
Fix filters, restyle filters page

See merge request soapbox-pub/soapbox!2262
2023-02-12 21:08:15 +00:00
marcin mikołajczak 0872a17542 Fix en.json
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-12 22:06:37 +01:00
marcin mikołajczak 9dc7c92c5a add sort-keys rule to modal-root.tsx
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-12 18:07:30 +01:00
marcin mikołajczak 8e3a879841 Update en.json
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-12 18:06:36 +01:00
marcin mikołajczak 9ced32405e Update CHANGELOG.MD
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-12 17:59:53 +01:00
marcin mikołajczak 64c4847f9d Dashboard: Allow to create announcements
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-12 17:54:13 +01:00
Chewbacca f46086d52a Cleanup a few things 2023-02-10 14:43:41 -05:00
Chewbacca 04e5e06d26 Make sure DOM is ready for Portal 2023-02-10 13:49:59 -05:00
Chewbacca 21a525ba8d Fix dropdown-menu reducer test 2023-02-10 13:39:16 -05:00
Chewbacca 59ce093b31 Replace @reach/portal with custom Portal 2023-02-10 13:23:48 -05:00
Chewbacca 60d7ff8395 Re-build dropdown-menu using FloatingUI 2023-02-10 13:17:39 -05:00
Chewbacca 380d2b763f Merge branch 'improve-reactions' into 'develop'
Improve Emoji Reactions and add support for Chat Reactions

See merge request soapbox-pub/soapbox!2267
2023-02-09 21:37:57 +00:00
Alex Gleason 1dda90da10 Merge branch 'emoji-compat' into 'develop'
Invert features.emojiReactsRGI

See merge request soapbox-pub/soapbox!2266
2023-02-09 20:44:29 +00:00
Chewbacca 48eb42687a Disable chatEmojiReacts 2023-02-09 13:16:06 -05:00
Chewbacca 2def1d0170 Discard changes to 'emojiReactsRGI' feature 2023-02-09 13:13:01 -05:00
Chewbacca 9d08467b07 Update Changelog 2023-02-09 13:02:03 -05:00
Chewbacca 17684571af Update hoverable menu on Chat Messages 2023-02-09 12:42:00 -05:00
Alex Gleason 8bdebd229d Merge branch 'display-all-reactions' into 'develop'
Display all emoji reactions

See merge request soapbox-pub/soapbox!2270
2023-02-09 14:00:38 +00:00
Alex Gleason 78c52956ed
Merge remote-tracking branch 'origin/develop' into react-any-emoji 2023-02-08 21:06:15 -06:00
Alex Gleason 1461113f7f Merge branch 'weblate-soapbox-pub-soapbox' into 'develop'
Translations update from Hosted Weblate

See merge request soapbox-pub/soapbox!2269
2023-02-09 03:04:22 +00:00
Alex Gleason c95261fcf1
Display all emoji reactions 2023-02-08 21:00:17 -06:00
Alex Gleason cf487b901d
Display all emoji reactions 2023-02-08 20:59:26 -06:00
Tassoman 4ac6e66bbb
Translated using Weblate (Italian)
Currently translated at 100.0% (1447 of 1447 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/it/
2023-02-09 03:40:19 +01:00
Alex Gleason e2e3af4a8b
Don't allow reacting with any emoji to a chat for now 2023-02-08 20:28:44 -06:00
Alex Gleason 6f15b5f42f
StatusReactionWrapper: put the picker in a portal 2023-02-08 20:26:37 -06:00
Alex Gleason 06ea520e89
EmojiSelector: allow reacting from full picker 2023-02-08 20:23:26 -06:00
Alex Gleason aab6ee34c2
EmojiSelector: render full picker 2023-02-08 19:20:17 -06:00
marcin mikołajczak 0ae3cd853e Update CHANGELOG.md
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-08 23:45:03 +01:00
marcin mikołajczak 47403872fa Merge remote-tracking branch 'soapbox/develop' into filters 2023-02-08 23:40:40 +01:00
Alex Gleason f730897323
Fix instance tests 2023-02-08 14:41:10 -06:00
Alex Gleason 772c53a6ac Merge branch 'weblate-soapbox-pub-soapbox' into 'develop'
Translations update from Hosted Weblate

See merge request soapbox-pub/soapbox!2260
2023-02-08 19:46:26 +00:00
gallegonovato 861509baa5
Translated using Weblate (Spanish)
Currently translated at 100.0% (1447 of 1447 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/es/
2023-02-08 20:39:21 +01:00
Alex Gleason 50457d3b8d
Chats: disable adding media (and submitting) while media is uploading 2023-02-08 13:07:54 -06:00
Chewbacca 3ca491c03f classNames -> clsx 2023-02-08 14:06:41 -05:00
Alex Gleason 6eadaf2942
CHats: fix deleting a specific attachment 2023-02-08 13:02:48 -06:00
Alex Gleason 64f17ef013
ChatTextarea: add gaps between uploads, put pending upload at end of list 2023-02-08 12:53:07 -06:00
Alex Gleason 072e058764
ChatUploadPreview: support gif attachments 2023-02-08 12:50:18 -06:00
Alex Gleason ef5001d38b
Chats: support multiple attachments 2023-02-08 12:30:20 -06:00
Chewbacca e255bfac3d Improve Emoji Reactions and add support for Chat Reactions 2023-02-08 12:58:01 -05:00
Alex Gleason 81bb8b933a
Invert features.emojiReactsRGI 2023-02-08 11:57:23 -06:00
Poesty Li 7373a650f1
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1447 of 1447 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-02-08 13:40:02 +01:00
gallegonovato 7f03d3bfe9
Translated using Weblate (Spanish)
Currently translated at 100.0% (1446 of 1446 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/es/
2023-02-07 23:53:30 +01:00
Poesty Li 8404be2745
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1446 of 1446 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-02-07 23:53:30 +01:00
Poesty Li ad8ad9cae8
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1443 of 1443 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/zh_Hans/
2023-02-07 23:53:30 +01:00
forenta 4a3877d442
Translated using Weblate (German)
Currently translated at 100.0% (1443 of 1443 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/de/
2023-02-07 23:53:30 +01:00
marcin mikołajczak dbf2e53b93 Merge branch 'rss-button' into 'develop'
Add RSS link to account menu

See merge request soapbox-pub/soapbox!1881
2023-02-07 22:53:23 +00:00
marcin mikołajczak b19de058a1 Update changelog
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-07 22:24:58 +01:00
marcin mikołajczak 07b7d1ec2a Merge remote-tracking branch 'soapbox/develop' into rss-button 2023-02-07 22:24:15 +01:00
Chewbacca df19b4e4a3 Merge branch 'add-intl-to-storybook' into 'develop'
Add support for react-intl for Storybook

See merge request soapbox-pub/soapbox!2264
2023-02-07 21:16:20 +00:00
Chewbacca 674ff4aa65 Add support for react-intl for Storybook 2023-02-07 14:23:28 -05:00
Alex Gleason f68ce9c47b
Account: rip out calculations on display name, truncate with CSS 2023-02-07 12:03:12 -06:00
Alex Gleason 0631657278
ProfileDropdown: enforce a max width 2023-02-07 12:02:37 -06:00
Chewbacca 800b9e7742 Merge branch 'improve-tabs' into 'develop'
Update default border color for Tabs

See merge request soapbox-pub/soapbox!2263
2023-02-07 17:34:07 +00:00
Chewbacca 0cc4188cad Update default border color for Tabs 2023-02-07 12:00:14 -05:00
marcin mikołajczak 1bbbaf8f4b Update en.json
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-07 17:43:37 +01:00
marcin mikołajczak 49a7d40efb Fix filters, restyle filters page
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-07 15:38:31 +01:00
marcin mikołajczak 71a2e679a0 Update .eslintrc.cjs
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-07 09:42:08 +01:00
Alex Gleason 9ac2764bfa
CHANGELOG: profile dropdown 2023-02-06 17:19:12 -06:00
Alex Gleason dc597ac765
Add useClickOutside hook for Floating UI elements 2023-02-06 17:17:12 -06:00
Alex Gleason ec7f9b9950
ProfileDropdown: fix hover styles for menu items with `action` 2023-02-06 17:00:25 -06:00
Alex Gleason 53a930c75c
ProfileDropdown: dismiss when clicked outside 2023-02-06 16:53:56 -06:00
marcin mikołajczak bfe1401a34 Fix imports
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-06 23:48:44 +01:00
Alex Gleason 7f6b19aa4a
ProfileDropdown: refactor with Floating UI 2023-02-06 16:33:08 -06:00
Alex Gleason aa8f84d352
Menu: move <hr> styles to component 2023-02-06 16:15:00 -06:00
Alex Gleason f6f3973eac
Account: let avatar and display name be truncated 2023-02-06 16:09:59 -06:00
Alex Gleason a433d22ba3
Account: don't calculate max-width unnecessarily 2023-02-06 16:09:25 -06:00
Alex Gleason ab8d162f03
Account: useLayoutEffect, refactor function calls, fix types, prevent negative maxWidth 2023-02-06 15:53:58 -06:00
Alex Gleason cb74b0a37c
useOnScreen: memoize IntersectionObserver, fix types 2023-02-06 15:41:09 -06:00
Alex Gleason d44be7fbf8 Merge branch 'chat-composer' into 'develop'
Chats: move chat attachments into composer

See merge request soapbox-pub/soapbox!2229
2023-02-06 21:05:42 +00:00
marcin mikołajczak d62290f020 Merge branch 'events' into 'develop'
Add NewEventPanel to events list page

See merge request soapbox-pub/soapbox!2255
2023-02-06 20:53:49 +00:00
Alex Gleason e3f168f050
Merge remote-tracking branch 'origin/develop' into chat-composer 2023-02-06 14:25:37 -06:00
marcin mikołajczak 227f28d013 update eslint config
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-06 20:50:05 +01:00
Alex Gleason 76c76688e9 Merge branch 'renovate/react-sticky-box-2.x' into 'develop'
Update dependency react-sticky-box to v2

See merge request soapbox-pub/soapbox!2257
2023-02-06 19:38:33 +00:00
marcin mikołajczak 4721452986 Merge remote-tracking branch 'soapbox/develop' into cleanup
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-06 20:31:20 +01:00
marcin mikołajczak c56728b722 classNames() ==> clsx()
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-06 20:28:18 +01:00
marcin mikołajczak 75076abeac more styles cleanup
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-06 20:20:14 +01:00
Alex Gleason e5f90e4733 Merge branch 'clsx' into 'develop'
classNames() --> clsx(), lint

See merge request soapbox-pub/soapbox!2259
2023-02-06 19:15:43 +00:00
Chewbacca ea075c3799 Merge branch 'improve-logo-alignment' into 'develop'
Improve navbar alignment

See merge request soapbox-pub/soapbox!2253
2023-02-06 18:31:49 +00:00
Alex Gleason 785612691a
yarn lint:js --fix 2023-02-06 12:06:44 -06:00
Alex Gleason 701215d7bc
classNames() --> clsx() 2023-02-06 12:05:19 -06:00
Alex Gleason 89d55b1b70
ChatComposer: shrink "x" icon, fix pending upload jump 2023-02-06 11:30:36 -06:00
marcin mikołajczak adfbff858d remove comment
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-06 18:22:16 +01:00
Alex Gleason 6743439efd Merge branch 'weblate-soapbox-pub-soapbox' into 'develop'
Translations update from Hosted Weblate

See merge request soapbox-pub/soapbox!2254
2023-02-06 17:21:48 +00:00
marcin mikołajczak 49ba4c7a9d Fixes, styles improvements, cleanup, enforce classes order for classNames
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-06 18:18:49 +01:00
Soapbox Bot 8369648c4c Update dependency react-sticky-box to v2 2023-02-06 17:10:57 +00:00
Chewbacca 81d7d3a7a4 Merge branch 'develop' into 'improve-logo-alignment'
# Conflicts:
#   app/soapbox/features/ui/components/navbar.tsx
2023-02-06 15:38:14 +00:00
marcin mikołajczak 8c3f1eb412 Update en.json
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-06 14:48:09 +01:00
gallegonovato 9f97d575f5
Translated using Weblate (Spanish)
Currently translated at 100.0% (1443 of 1443 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/es/
2023-02-06 04:37:06 +01:00
Isabell De Inschnitzel 5e7745cbab
Translated using Weblate (German)
Currently translated at 94.7% (1367 of 1443 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/de/
2023-02-06 04:37:05 +01:00
marcin mikołajczak b529ec8a07 Add NewEventPanel to events list page
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-05 23:14:21 +01:00
Adrien Bourmault f2b042d473
Translated using Weblate (French)
Currently translated at 92.6% (1337 of 1443 strings)

Translation: Soapbox/Soapbox
Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/fr/
2023-02-05 07:35:39 +01:00
Alex Gleason 25277b0b73
Merge remote-tracking branch 'origin/develop' into chat-composer 2023-02-03 12:03:45 -06:00
Chewbacca dadeb43c74 Improve navbar alignment 2023-02-03 11:01:25 -05:00
Alex Gleason 4c025bb2bc
Refactor ProgressBar 2023-02-02 18:03:22 -06:00
Alex Gleason e203e0d0d2
Chats: move uploading progress into the composer text box 2023-02-02 17:37:37 -06:00
Alex Gleason 6b1882d7a9
Merge remote-tracking branch 'origin/develop' into chat-composer 2023-02-02 15:19:54 -06:00
Alex Gleason b9474e61de
Test normalizeChatMessage 2023-02-02 15:17:06 -06:00
Alex Gleason 3df63e59d3
Submit `media_ids` when creating a message 2023-02-02 14:59:23 -06:00
Alex Gleason 1b8d118217
Chats: support displaying multiple attachments 2023-02-02 14:43:47 -06:00
Alex Gleason 7fe4b34e6f
Add type-aware UploadPreview component, fix ChatComposer bg on dark mode 2023-02-02 14:20:40 -06:00
Alex Gleason ec634f4fa1
ChatUpload: allow clicking upload to view in modal 2023-02-02 13:54:36 -06:00
Alex Gleason cb715f4f05
ChatUpload: ensure X icon is always white 2023-02-02 13:33:18 -06:00
Alex Gleason 747868edb3
ChatTextarea: fix focus border and ring styles 2023-02-02 13:27:38 -06:00
Alex Gleason ea8561ca19
ChatUpload: add blurhash 2023-02-02 13:06:27 -06:00
marcin mikołajczak 0e66e230a7 Update en.json
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-02-01 23:00:19 +01:00
Alex Gleason 382bc6732d
Merge remote-tracking branch 'origin/develop' into chat-composer 2023-02-01 15:28:33 -06:00
marcin mikołajczak db15858b9f Merge remote-tracking branch 'soapbox/develop' into rss-button
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2023-01-30 23:16:25 +01:00
Alex Gleason 7b32373694
Move chat attachments into composer 2023-01-26 12:30:26 -06:00
Alex Gleason 382e464390
Move chat attachments into the chat composer 2023-01-25 15:45:33 -06:00
Alex Gleason 27500193d8
EntityStore: incorporate Notifications, go back to using POJOs instead of Maps 2022-12-04 18:54:54 -06:00
Alex Gleason f7bfc40b70
EntityStore: proper pagination support 2022-12-04 17:53:56 -06:00
Alex Gleason 52059f6f37
EntityStore: add request/success/fail actions 2022-12-04 17:26:28 -06:00
Alex Gleason 3b067c6fab
Scaffold entity store library 2022-12-04 17:05:01 -06:00
marcin mikołajczak 5e05c32eed Remove unused config
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2022-11-21 21:59:35 +01:00
marcin mikołajczak fe50acf0c3 Move RSS button to account menu
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2022-11-21 21:03:35 +01:00
marcin mikołajczak 8be2ee918d Merge remote-tracking branch 'soapbox/develop' into rss-button 2022-11-21 20:41:25 +01:00
marcin mikołajczak 5d10324127 Allow displaying RSS button to unauthenticated users
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2022-11-11 11:59:06 +01:00
Alex Gleason 8534cabc0a
Fix ComposeForm collapsing when clicking within emoji picker 2022-07-13 20:41:39 -05:00
Alex Gleason a8ebbc15c8
EmojiPickerDropdown: shrink emoji size, cleanup 2022-07-13 20:37:28 -05:00
Alex Gleason 26b5ca8b6e
Fix linter errors 2022-07-11 15:19:37 -05:00
Alex Gleason 00802b01e9
EmojiPicker: fix asset paths 2022-07-11 15:09:41 -05:00
ewwwwwwww 3879dad039 scroll fix 2022-07-10 04:42:22 -07:00
ewwwwwwww a1f0f75f60 abstracted dropdown so it can be reused 2022-07-10 02:06:37 -07:00
ewwwwwwww b8a335a166 add comments and fix index 2022-07-09 16:32:34 -07:00
ewwwwwwww 63ee482982 oops 2022-07-09 15:37:13 -07:00
ewwwwwwww 3725db9789 oops 2022-07-09 15:36:24 -07:00
ewwwwwwww 540b28364b oops 2022-07-09 15:35:50 -07:00
ewwwwwwww 317672e6ec initial changes for fixing image locations 2022-07-09 15:31:14 -07:00
Alex Gleason b7b0ad06dc
Merge remote-tracking branch 'origin/develop' into update-emoji-mart 2022-07-09 15:21:17 -05:00
ewwwwwwww 34d77df8d7 fix lint 2022-07-09 12:16:42 -07:00
ewwwwwwww d5ca6b58fb i18n support 2022-07-09 11:58:09 -07:00
ewwwwwwww 403db1d46e mobile fixes 2022-07-09 10:52:08 -07:00
ewwwwwwww 9770209f00 lint fixes 2022-07-09 10:02:21 -07:00
ewwwwwwww 2f579839d8 rebased 2022-07-09 09:03:22 -07:00
ewwwwwwww 1249b24ba4 fix all tests 2022-07-09 08:33:14 -07:00
ewwwwwwww f3c350aeef fix some texts 2022-07-09 08:33:14 -07:00
ewwwwwwww 8ef60cdf0b add angry note about twemoji breaking spec 2022-07-09 08:33:14 -07:00
ewwwwwwww 13e76b8022 unqualified fixes 2022-07-09 08:33:14 -07:00
ewwwwwwww b1f5902397 added tests 2022-07-09 08:33:14 -07:00
ewwwwwwww a6e56b131c unicode fixes 2022-07-09 08:33:14 -07:00
ewwwwwwww 4a73412142 small fix 2022-07-09 08:33:14 -07:00
ewwwwwwww 16f06cd554 small fix 2022-07-09 08:33:14 -07:00
ewwwwwwww 1962c6544e emoji-picker fixes 2022-07-09 08:33:14 -07:00
ewwwwwwww 84267d3ffe fix emoji-picker key input 2022-07-09 08:33:14 -07:00
ewwwwwwww 41eebfdbda remove dead code 2022-07-09 08:33:14 -07:00
ewwwwwwww 891ce09443 small fixes 2022-07-09 08:33:14 -07:00
ewwwwwwww 0de246fcae remove old todo 2022-07-09 08:33:14 -07:00
ewwwwwwww c183ab0f06 fix some tests 2022-07-09 08:33:14 -07:00
ewwwwwwww f10001fbfd fix some tests 2022-07-09 08:33:13 -07:00
ewwwwwwww 9d0a3b7a69 remove cheerio and add custom html parser 2022-07-09 08:31:31 -07:00
ewwwwwwww a2fab1285d fix types 2022-07-09 08:31:31 -07:00
ewwwwwwww bd9a33201a convert type to union 2022-07-09 08:31:31 -07:00
ewwwwwwww 8d8cf53ac4 remove console log 2022-07-09 08:31:31 -07:00
ewwwwwwww f08d43ecb3 small fixes 2022-07-09 08:31:31 -07:00
ewwwwwwww e2d6a5d41d remove useless file 2022-07-09 08:31:31 -07:00
ewwwwwwww 485095e502 fixed types 2022-07-09 08:31:31 -07:00
ewwwwwwww bfa8331f96 new emoji search 2022-07-09 08:31:31 -07:00
ewwwwwwww 36d6275107 type fixes 2022-07-09 08:31:31 -07:00
ewwwwwwww d98371bf6a migrate emoji types 2022-07-09 08:31:27 -07:00
ewwwwwwww 6ded0afc1e remove files 2022-07-09 08:30:44 -07:00
ewwwwwwww ad523574e2 small fixes 2022-07-09 08:30:44 -07:00
ewwwwwwww d86b7ba333 new emojifier 2022-07-09 08:30:44 -07:00
ewwwwwwww 88f5739769 new emojifier 2022-07-09 08:30:44 -07:00
ewwwwwwww a8e7e10f61 new emojifier 2022-07-09 08:30:44 -07:00
ewwwwwwww 89bba0e2e3 lint fixes 2022-07-09 08:30:44 -07:00
ewwwwwwww f8bd30a5f7 remove consolelog 2022-07-09 08:30:44 -07:00
ewwwwwwww eeb30b5492 migrated emoji picker to typescript 2022-07-09 08:30:44 -07:00
ewwwwwwww 4b7876f1a6 enabled async component 2022-07-09 08:30:44 -07:00
ewwwwwwww 170d3f748e more emoji fixes 2022-07-09 08:30:44 -07:00
ewwwwwwww 2727fb8f20 lint fixes 2022-07-09 08:30:44 -07:00
Shevek 6a3e66bc7e properly fallback on locale 2021-11-08 21:24:07 -05:00
705 zmienionych plików z 19644 dodań i 10810 usunięć

Wyświetl plik

@ -56,6 +56,7 @@ module.exports = {
},
polyfills: [
'es:all', // core-js
'fetch', // not polyfilled, but ignore it
'IntersectionObserver', // npm:intersection-observer
'Promise', // core-js
'ResizeObserver', // npm:resize-observer-polyfill
@ -260,12 +261,29 @@ module.exports = {
},
],
'@typescript-eslint/no-duplicate-imports': 'error',
'@typescript-eslint/member-delimiter-style': [
'error',
{
multiline: {
delimiter: 'none',
},
singleline: {
delimiter: 'comma',
},
},
],
'promise/catch-or-return': 'error',
'react-hooks/rules-of-hooks': 'error',
'tailwindcss/classnames-order': 'error',
'tailwindcss/classnames-order': [
'error',
{
classRegex: '^(base|container|icon|item|list|outer|wrapper)?[c|C]lass(Name)?$',
config: 'tailwind.config.cjs',
},
],
'tailwindcss/migration-from-tailwind-2': 'error',
},
overrides: [

Wyświetl plik

@ -157,11 +157,11 @@ docker:
# https://medium.com/devops-with-valentine/how-to-build-a-docker-image-and-push-it-to-the-gitlab-container-registry-from-a-gitlab-ci-pipeline-acac0d1f26df
script:
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER $CI_REGISTRY --password-stdin
- docker build -t $CI_REGISTRY_IMAGE .
- docker push $CI_REGISTRY_IMAGE
only:
variables:
- $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
rules:
- if: $CI_COMMIT_TAG
interruptible: false
release:
stage: release

Wyświetl plik

@ -11,6 +11,7 @@ const config: StorybookConfig = {
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'storybook-react-intl',
{
name: '@storybook/addon-postcss',
options: {

Wyświetl plik

@ -1,12 +0,0 @@
import '../app/styles/tailwind.css';
import '../stories/theme.css';
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
}

Wyświetl plik

@ -0,0 +1,22 @@
import '../app/styles/tailwind.css';
import '../stories/theme.css';
import { addDecorator, Story } from '@storybook/react';
import { IntlProvider } from 'react-intl';
import React from 'react';
const withProvider = (Story: Story) => (
<IntlProvider locale='en'><Story /></IntlProvider>
);
addDecorator(withProvider);
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};

Wyświetl plik

@ -6,26 +6,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Posts: Support posts filtering on recent Mastodon versions
- Reactions: Support custom emoji reactions
- Compatbility: Support Mastodon v2 timeline filters.
- Posts: Support dislikes on Friendica.
- UI: added a character counter to some textareas.
### Changed
- Posts: truncate Nostr pubkeys in reply mentions.
- Posts: upgraded emoji picker component.
- UI: unified design of "approve" and "reject" buttons in follow requests and waitlist.
### Fixed
- Posts: fixed emojis being cut off in reactions modal.
- Posts: fix audio player progress bar visibility.
- Posts: added missing gap in pending status.
- Compatibility: fixed quote posting compatibility with custom Pleroma forks.
- Profile: fix "load more" button height on account gallery page.
- 18n: fixed Chinese language being detected from the browser.
- Conversations: fixed pagination (Mastodon).
- Compatibility: fix version parsing for Friendica.
## [3.2.0] - 2023-02-15
### Added
- Admin: redirect the homepage to any URL.
- Compatibility: added compatibility with Friendica.
- Posts: bot badge on statuses from bot accounts.
- Compatibility: improved browser support for older browsers.
- Events: allow to repost events in event menu.
- Groups: Initial support for groups.
- Profile: Add RSS link to user profiles.
- Reactions: adds support for reacting to chat messages.
- Groups: initial support for groups.
- Profile: add RSS link to user profiles.
- Chats: reset chat message field height after sending a message.
- Admin: allow to manage announcements.
### Changed
- Chats: improved display of media attachments.
- ServiceWorker: switch to a network-first strategy. The "An update is available!" prompt goes away.
- Posts: increased font size of focused status in threads.
- Posts: let "mute conversation" be clicked from any feed, not just noficiations.
- Posts: display all emoji reactions.
- Reactions: improved UI of reactions on statuses.
- Profile: make verified badge more prominent, overlapping with avatar.
### Fixed
- Admin: fixed hover card in reports modal shows reporter not reportee
- Chats: media attachments rendering at the wrong size and/or causing the chat to scroll on load.
- Chats: don't display "copy" button for messages without text.
- Posts: don't have to click the play button twice for embedded videos.
- index.html: remove `referrer` meta tag so it doesn't conflict with backend's `Referrer-Policy` header.
- Modals: fix media modal automatically switching to video.
- Navigation: profile dropdown erratic behavior.
- Posts: fix posts filtering.
### Removed
- Admin: single user mode. Now the homepage can be redirected to any URL.

Wyświetl plik

@ -75,7 +75,7 @@ One disadvantage of this approach is that it does not help the software spread.
© Alex Gleason & other Soapbox contributors
© Eugen Rochko & other Mastodon contributors
© Trump Media & Technology Group
© Gab AI, Inc.
© Gab AI, Inc.
Soapbox is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by

Wyświetl plik

@ -2,4 +2,4 @@
- verified.svg - Created by Alex Gleason. CC0
Fediverse logo: https://en.wikipedia.org/wiki/Fediverse#/media/File:Fediverse_logo_proposal.svg
Fediverse logo: https://en.wikipedia.org/wiki/Fediverse#/media/File:Fediverse_logo_proposal.svg

Wyświetl plik

@ -1 +1,107 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 95 95" width="100" height="100"><path d="M94.909 19.374C94.909 8.674 86.235 0 75.534 0c-10.647 0-19.28 8.591-19.365 19.217l-15.631 2.09c-1.961-6.007-7.598-10.35-14.258-10.35-8.284 0-15.002 6.716-15.002 15.002 0 6.642 4.321 12.267 10.303 14.24l-2.205 16.056c-10.66.049-19.285 8.7-19.285 19.37C.091 86.325 8.765 95 19.466 95c10.677 0 19.332-8.638 19.37-19.304l18.093-2.501c1.979 5.972 7.598 10.285 14.234 10.285 8.284 0 15.002-6.716 15.002-15.002 0-6.891-4.652-12.682-10.983-14.441l1.365-15.339c10.229-.53 18.363-8.966 18.363-19.324zM56.194 67.8l-18.116 2.505a19.39 19.39 0 0 0-13.312-13.3l2.205-16.077a14.98 14.98 0 0 0 14.27-14.222l15.655-2.094c1.894 6.757 7.351 12.009 14.225 13.612l-1.365 15.322c-7.4.688-13.224 6.753-13.562 14.254z" fill="#ffffff"/></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="svg2"
style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"
sodipodi:docname="soapbox-logo-white.svg"
xml:space="preserve"
version="1.1"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
viewBox="0 0 100 100"
width="100"
height="100"
inkscape:export-filename="/home/miklobit/Downloads/citizen4/logo/citizen4-logo-250px.png"
inkscape:export-xdpi="63.5"
inkscape:export-ydpi="63.5"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><sodipodi:namedview
id="namedview12"
bordercolor="#666666"
inkscape:pageshadow="2"
guidetolerance="10"
pagecolor="#ffffff"
gridtolerance="10"
inkscape:zoom="5.0135101"
objecttolerance="10"
borderopacity="1"
inkscape:current-layer="g1133"
inkscape:cx="54.253406"
inkscape:cy="42.086282"
inkscape:window-width="1920"
showgrid="false"
inkscape:pageopacity="0"
inkscape:window-height="1016"
inkscape:document-rotation="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-x="0"
inkscape:window-y="36"
inkscape:window-maximized="1"
units="mm"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px" />
<defs
id="defs4">
<style
id="style6"
type="text/css">
.fil0 {fill:black}
</style>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath932"><g
id="g936"
style="fill:#ffffff;stroke:#000000;stroke-width:0.468608;stroke-miterlimit:4;stroke-dasharray:none"
transform="matrix(40.327178,0,0,40.327178,-206.26309,-5.404074)">
<path
id="path934"
sodipodi:nodetypes="ccccc"
style="fill:#ffffff;stroke:#000000;stroke-width:0.468608;stroke-miterlimit:4;stroke-dasharray:none"
d="M 4.25,1.3382 0.4955,2.4662 C 0.58759,5.2588 1.2884,8.6655 4.25,9.6618 7.2442,8.694 7.8774,5.2291 8.0045,2.4662 Z" />
</g></clipPath><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath932-3"><g
id="g936-6"
style="fill:#ffffff;stroke:#000000;stroke-width:0.468608;stroke-miterlimit:4;stroke-dasharray:none"
transform="matrix(40.327178,0,0,40.327178,-206.26309,-5.404074)"><path
id="path934-7"
sodipodi:nodetypes="ccccc"
style="fill:#ffffff;stroke:#000000;stroke-width:0.468608;stroke-miterlimit:4;stroke-dasharray:none"
d="M 4.25,1.3382 0.4955,2.4662 C 0.58759,5.2588 1.2884,8.6655 4.25,9.6618 7.2442,8.694 7.8774,5.2291 8.0045,2.4662 Z" /></g></clipPath>
</defs>
<metadata
id="metadata7"><rdf:RDF><cc:Work><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" /><dc:date>2023-02-18T14:20:55</dc:date><dc:source>https://soc.citizen4.eu</dc:source><dc:subject><rdf:Bag><rdf:li>citizen4</rdf:li><rdf:li>logo</rdf:li><rdf:li>shield</rdf:li></rdf:Bag></dc:subject><dc:creator><cc:Agent><dc:title>miklo</dc:title></cc:Agent></dc:creator><dc:description>Citizen4 logo</dc:description></cc:Work><cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/"><cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" /><cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><g
inkscape:groupmode="layer"
id="g1133"
inkscape:label="citizen4"
style="display:inline;fill:#ffffff"
transform="translate(9.1709534,9.343974)"><g
id="g1149"
transform="matrix(0.28130772,0,0,0.28130772,-1.9206898,-6.7154381)"
style="fill:#ffffff"><path
id="path1127"
style="fill:#ffffff;fill-opacity:1;stroke-width:2.298"
d="m 233.61288,175.15307 h -30.0693 v -8.40148 h -35.71547 v -22.85127 h 35.71317 v -8.40148 h 30.0716 c 1.31445,0 2.42898,0.4596 3.34818,1.3765 0.9169,0.9215 1.3788,2.03832 1.3788,3.34818 v 30.20487 c 0,1.22484 -0.4596,2.32098 -1.3788,3.28154 -0.9192,0.95827 -2.03602,1.44314 -3.34818,1.44314 z m 18.90793,-30.07388 c 6.91237,0 10.37315,3.41253 10.37315,10.24447 0,6.83195 -3.45618,10.24217 -10.37315,10.24217 -6.82735,0 -10.24218,-3.41482 -10.24218,-10.24217 0,-6.82734 3.41483,-10.24447 10.24218,-10.24447 z M 70.322893,175.15307 h 30.069297 v -8.40148 h 35.71546 v -22.85127 h -35.71317 v -8.40148 H 70.322893 c -1.31446,0 -2.42898,0.4596 -3.34818,1.3765 -0.9169,0.9215 -1.3788,2.03832 -1.3788,3.34818 v 30.20487 c 0,1.22484 0.4596,2.32098 1.3788,3.28154 0.9192,0.95827 2.03602,1.44314 3.34818,1.44314 z m -18.90793,-30.07388 c -6.91237,0 -10.37315,3.41253 -10.37315,10.24447 0,6.83195 3.45618,10.24217 10.37315,10.24217 6.82735,0 10.24218,-3.41482 10.24218,-10.24217 0,-6.82734 -3.41483,-10.24447 -10.24218,-10.24447 z M 171.79501,73.680969 v 30.069301 h -8.40148 v 35.71546 h -22.85128 v -35.71317 h -8.40147 V 73.680969 c 0,-1.31445 0.45959,-2.42898 1.37649,-3.34818 0.9215,-0.9169 2.03832,-1.3788 3.34819,-1.3788 h 30.20487 c 1.22483,0 2.32097,0.4596 3.28153,1.3788 0.95827,0.9192 1.44315,2.03602 1.44315,3.34818 z m -30.07388,-18.90793 c 0,-6.91237 3.41252,-10.37315 10.24446,-10.37315 6.83195,0 10.24218,3.45618 10.24218,10.37315 0,6.82735 -3.41482,10.24218 -10.24218,10.24218 -6.82734,0 -10.24446,-3.41483 -10.24446,-10.24218 z m 30.07388,182.197911 v -30.06929 h -8.40148 v -35.71547 h -22.85128 v 35.71317 h -8.40147 v 30.07159 c 0,1.31446 0.45959,2.42898 1.37649,3.34818 0.9215,0.9169 2.03832,1.3788 3.34819,1.3788 h 30.20487 c 1.22483,0 2.32097,-0.4596 3.28153,-1.3788 0.95827,-0.9192 1.44315,-2.03602 1.44315,-3.34818 z m -30.07388,18.90793 c 0,6.91237 3.41252,10.37315 10.24446,10.37315 6.83195,0 10.24218,-3.45618 10.24218,-10.37315 0,-6.82735 -3.41482,-10.24218 -10.24218,-10.24218 -6.82734,0 -10.24446,3.41483 -10.24446,10.24218 z M 151.92079,0.52207567 0.51240486,46.01113 C 4.2261345,158.6288 32.487823,296.01139 151.92079,336.18936 272.66842,297.16072 298.20359,157.43109 303.32917,46.01113 Z" /></g></g></svg>

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 812 B

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 6.7 KiB

Wyświetl plik

@ -1 +1,127 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 95 95" width="100" height="100"><path d="M94.909 19.374C94.909 8.674 86.235 0 75.534 0c-10.647 0-19.28 8.591-19.365 19.217l-15.631 2.09c-1.961-6.007-7.598-10.35-14.258-10.35-8.284 0-15.002 6.716-15.002 15.002 0 6.642 4.321 12.267 10.303 14.24l-2.205 16.056c-10.66.049-19.285 8.7-19.285 19.37C.091 86.325 8.765 95 19.466 95c10.677 0 19.332-8.638 19.37-19.304l18.093-2.501c1.979 5.972 7.598 10.285 14.234 10.285 8.284 0 15.002-6.716 15.002-15.002 0-6.891-4.652-12.682-10.983-14.441l1.365-15.339c10.229-.53 18.363-8.966 18.363-19.324zM56.194 67.8l-18.116 2.505a19.39 19.39 0 0 0-13.312-13.3l2.205-16.077a14.98 14.98 0 0 0 14.27-14.222l15.655-2.094c1.894 6.757 7.351 12.009 14.225 13.612l-1.365 15.322c-7.4.688-13.224 6.753-13.562 14.254z" fill="#0482d8"/></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="svg2"
style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"
sodipodi:docname="soapbox-logo.svg"
xml:space="preserve"
version="1.1"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
viewBox="0 0 100 100"
width="100"
height="100"
inkscape:export-filename="/home/miklobit/Downloads/citizen4/logo/citizen4-logo-250px.png"
inkscape:export-xdpi="63.5"
inkscape:export-ydpi="63.5"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><sodipodi:namedview
id="namedview12"
bordercolor="#666666"
inkscape:pageshadow="2"
guidetolerance="10"
pagecolor="#ffffff"
gridtolerance="10"
inkscape:zoom="5.0135101"
objecttolerance="10"
borderopacity="1"
inkscape:current-layer="svg2"
inkscape:cx="54.253406"
inkscape:cy="42.086282"
inkscape:window-width="1920"
showgrid="false"
inkscape:pageopacity="0"
inkscape:window-height="1016"
inkscape:document-rotation="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-x="0"
inkscape:window-y="36"
inkscape:window-maximized="1"
units="mm"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px" />
<defs
id="defs4">
<style
id="style6"
type="text/css">
.fil0 {fill:black}
</style>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath932"><g
id="g936"
style="fill:#ffffff;stroke:#000000;stroke-width:0.468608;stroke-miterlimit:4;stroke-dasharray:none"
transform="matrix(40.327178,0,0,40.327178,-206.26309,-5.404074)">
<path
id="path934"
sodipodi:nodetypes="ccccc"
style="fill:#ffffff;stroke:#000000;stroke-width:0.468608;stroke-miterlimit:4;stroke-dasharray:none"
d="M 4.25,1.3382 0.4955,2.4662 C 0.58759,5.2588 1.2884,8.6655 4.25,9.6618 7.2442,8.694 7.8774,5.2291 8.0045,2.4662 Z" />
</g></clipPath><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath932-3"><g
id="g936-6"
style="fill:#ffffff;stroke:#000000;stroke-width:0.468608;stroke-miterlimit:4;stroke-dasharray:none"
transform="matrix(40.327178,0,0,40.327178,-206.26309,-5.404074)"><path
id="path934-7"
sodipodi:nodetypes="ccccc"
style="fill:#ffffff;stroke:#000000;stroke-width:0.468608;stroke-miterlimit:4;stroke-dasharray:none"
d="M 4.25,1.3382 0.4955,2.4662 C 0.58759,5.2588 1.2884,8.6655 4.25,9.6618 7.2442,8.694 7.8774,5.2291 8.0045,2.4662 Z" /></g></clipPath>
</defs>
<metadata
id="metadata7"><rdf:RDF><cc:Work><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" /><dc:date>2023-02-18T14:20:55</dc:date><dc:source>https://soc.citizen4.eu</dc:source><dc:subject><rdf:Bag><rdf:li>citizen4</rdf:li><rdf:li>logo</rdf:li><rdf:li>shield</rdf:li></rdf:Bag></dc:subject><dc:creator><cc:Agent><dc:title>miklo</dc:title></cc:Agent></dc:creator><dc:description>Citizen4 logo</dc:description></cc:Work><cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/"><cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" /><cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="shield"
style="display:inline"
transform="translate(9.1709534,9.343974)"><g
id="g912"
style="clip-rule:evenodd;fill:#003399;fill-opacity:1;fill-rule:evenodd;stroke:#888888;stroke-width:0.468608;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"
transform="matrix(11.344346,0,0,11.344346,-7.3976698,-21.749578)"><path
id="path910"
sodipodi:nodetypes="ccccc"
style="fill:#003399;fill-opacity:1;stroke:#888888;stroke-width:0.468608;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 4.25,1.3382 0.4955,2.4662 C 0.58759,5.2588 1.2884,8.6655 4.25,9.6618 7.2442,8.694 7.8774,5.2291 8.0045,2.4662 Z" /></g></g><g
inkscape:groupmode="layer"
id="g1133"
inkscape:label="citizen2"
style="display:inline"
transform="translate(9.1709534,9.343974)"><g
id="g1149"
transform="matrix(0.28130772,0,0,0.28130772,-1.9206898,-6.7154381)"><path
id="path1119"
style="fill:#ffcc00;fill-opacity:1;stroke-width:2.298"
d="m 171.79501,236.97095 v -30.06929 h -8.40148 v -35.71547 h -22.85128 v 35.71317 h -8.40147 v 30.07159 c 0,1.31446 0.45959,2.42898 1.37649,3.34818 0.9215,0.9169 2.03832,1.3788 3.34819,1.3788 h 30.20487 c 1.22483,0 2.32097,-0.4596 3.28153,-1.3788 0.95827,-0.9192 1.44315,-2.03602 1.44315,-3.34818 z m -30.07388,18.90793 c 0,6.91237 3.41252,10.37315 10.24446,10.37315 6.83195,0 10.24218,-3.45618 10.24218,-10.37315 0,-6.82735 -3.41482,-10.24218 -10.24218,-10.24218 -6.82734,0 -10.24446,3.41483 -10.24446,10.24218 z" /><path
id="path1121"
style="fill:#ffcc00;fill-opacity:1;stroke-width:2.298"
d="m 171.79501,73.680969 v 30.069301 h -8.40148 v 35.71546 h -22.85128 v -35.71317 h -8.40147 V 73.680969 c 0,-1.31445 0.45959,-2.42898 1.37649,-3.34818 0.9215,-0.9169 2.03832,-1.3788 3.34819,-1.3788 h 30.20487 c 1.22483,0 2.32097,0.4596 3.28153,1.3788 0.95827,0.9192 1.44315,2.03602 1.44315,3.34818 z m -30.07388,-18.90793 c 0,-6.91237 3.41252,-10.37315 10.24446,-10.37315 6.83195,0 10.24218,3.45618 10.24218,10.37315 0,6.82735 -3.41482,10.24218 -10.24218,10.24218 -6.82734,0 -10.24446,-3.41483 -10.24446,-10.24218 z" /><path
id="path1125"
style="fill:#ffcc00;fill-opacity:1;stroke-width:2.298"
d="m 70.322893,175.15307 h 30.069297 v -8.40148 h 35.71546 v -22.85127 h -35.71317 v -8.40148 H 70.322893 c -1.31446,0 -2.42898,0.4596 -3.34818,1.3765 -0.9169,0.9215 -1.3788,2.03832 -1.3788,3.34818 v 30.20487 c 0,1.22484 0.4596,2.32098 1.3788,3.28154 0.9192,0.95827 2.03602,1.44314 3.34818,1.44314 z m -18.90793,-30.07388 c -6.91237,0 -10.37315,3.41253 -10.37315,10.24447 0,6.83195 3.45618,10.24217 10.37315,10.24217 6.82735,0 10.24218,-3.41482 10.24218,-10.24217 0,-6.82734 -3.41483,-10.24447 -10.24218,-10.24447 z" /><path
id="path1127"
style="fill:#ffcc00;fill-opacity:1;stroke-width:2.298"
d="m 233.61288,175.15307 h -30.0693 v -8.40148 h -35.71547 v -22.85127 h 35.71317 v -8.40148 h 30.0716 c 1.31445,0 2.42898,0.4596 3.34818,1.3765 0.9169,0.9215 1.3788,2.03832 1.3788,3.34818 v 30.20487 c 0,1.22484 -0.4596,2.32098 -1.3788,3.28154 -0.9192,0.95827 -2.03602,1.44314 -3.34818,1.44314 z m 18.90793,-30.07388 c 6.91237,0 10.37315,3.41253 10.37315,10.24447 0,6.83195 -3.45618,10.24217 -10.37315,10.24217 -6.82735,0 -10.24218,-3.41482 -10.24218,-10.24217 0,-6.82734 3.41483,-10.24447 10.24218,-10.24447 z" /></g></g></svg>

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 812 B

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 7.6 KiB

Wyświetl plik

@ -0,0 +1,16 @@
{
"note": "patriots 900000001",
"discoverable": true,
"id": "109989480368015378",
"domain": null,
"avatar": "https://media.covfefe.social/groups/avatars/109/989/480/368/015/378/original/50b0d899bc5aae13.jpg",
"avatar_static": "https://media.covfefe.social/groups/avatars/109/989/480/368/015/378/original/50b0d899bc5aae13.jpg",
"header": "https://media.covfefe.social/groups/headers/109/989/480/368/015/378/original/c5063b59f919cd4a.png",
"header_static": "https://media.covfefe.social/groups/headers/109/989/480/368/015/378/original/c5063b59f919cd4a.png",
"group_visibility": "everyone",
"created_at": "2023-03-08T00:00:00.000Z",
"display_name": "PATRIOT PATRIOTS",
"membership_required": true,
"members_count": 1,
"tags": []
}

Wyświetl plik

@ -228,7 +228,7 @@ const fetchAccountFail = (id: string | null, error: AxiosError) => ({
});
type FollowAccountOpts = {
reblogs?: boolean,
reblogs?: boolean
notify?: boolean
};

Wyświetl plik

@ -1,13 +1,18 @@
import { defineMessages } from 'react-intl';
import { fetchRelationships } from 'soapbox/actions/accounts';
import { importFetchedAccount, importFetchedAccounts, importFetchedStatuses } from 'soapbox/actions/importer';
import toast from 'soapbox/toast';
import { filterBadges, getTagDiff } from 'soapbox/utils/badges';
import { getFeatures } from 'soapbox/utils/features';
import api, { getLinks } from '../api';
import { openModal } from './modals';
import type { AxiosResponse } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { APIEntity } from 'soapbox/types/entities';
import type { APIEntity, Announcement } from 'soapbox/types/entities';
const ADMIN_CONFIG_FETCH_REQUEST = 'ADMIN_CONFIG_FETCH_REQUEST';
const ADMIN_CONFIG_FETCH_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS';
@ -77,16 +82,45 @@ const ADMIN_USERS_UNSUGGEST_REQUEST = 'ADMIN_USERS_UNSUGGEST_REQUEST';
const ADMIN_USERS_UNSUGGEST_SUCCESS = 'ADMIN_USERS_UNSUGGEST_SUCCESS';
const ADMIN_USERS_UNSUGGEST_FAIL = 'ADMIN_USERS_UNSUGGEST_FAIL';
const ADMIN_USER_INDEX_EXPAND_FAIL = 'ADMIN_USER_INDEX_EXPAND_FAIL';
const ADMIN_USER_INDEX_EXPAND_FAIL = 'ADMIN_USER_INDEX_EXPAND_FAIL';
const ADMIN_USER_INDEX_EXPAND_REQUEST = 'ADMIN_USER_INDEX_EXPAND_REQUEST';
const ADMIN_USER_INDEX_EXPAND_SUCCESS = 'ADMIN_USER_INDEX_EXPAND_SUCCESS';
const ADMIN_USER_INDEX_FETCH_FAIL = 'ADMIN_USER_INDEX_FETCH_FAIL';
const ADMIN_USER_INDEX_FETCH_FAIL = 'ADMIN_USER_INDEX_FETCH_FAIL';
const ADMIN_USER_INDEX_FETCH_REQUEST = 'ADMIN_USER_INDEX_FETCH_REQUEST';
const ADMIN_USER_INDEX_FETCH_SUCCESS = 'ADMIN_USER_INDEX_FETCH_SUCCESS';
const ADMIN_USER_INDEX_QUERY_SET = 'ADMIN_USER_INDEX_QUERY_SET';
const ADMIN_ANNOUNCEMENTS_FETCH_FAIL = 'ADMIN_ANNOUNCEMENTS_FETCH_FAILS';
const ADMIN_ANNOUNCEMENTS_FETCH_REQUEST = 'ADMIN_ANNOUNCEMENTS_FETCH_REQUEST';
const ADMIN_ANNOUNCEMENTS_FETCH_SUCCESS = 'ADMIN_ANNOUNCEMENTS_FETCH_SUCCESS';
const ADMIN_ANNOUNCEMENTS_EXPAND_FAIL = 'ADMIN_ANNOUNCEMENTS_EXPAND_FAILS';
const ADMIN_ANNOUNCEMENTS_EXPAND_REQUEST = 'ADMIN_ANNOUNCEMENTS_EXPAND_REQUEST';
const ADMIN_ANNOUNCEMENTS_EXPAND_SUCCESS = 'ADMIN_ANNOUNCEMENTS_EXPAND_SUCCESS';
const ADMIN_ANNOUNCEMENT_CHANGE_CONTENT = 'ADMIN_ANNOUNCEMENT_CHANGE_CONTENT';
const ADMIN_ANNOUNCEMENT_CHANGE_START_TIME = 'ADMIN_ANNOUNCEMENT_CHANGE_START_TIME';
const ADMIN_ANNOUNCEMENT_CHANGE_END_TIME = 'ADMIN_ANNOUNCEMENT_CHANGE_END_TIME';
const ADMIN_ANNOUNCEMENT_CHANGE_ALL_DAY = 'ADMIN_ANNOUNCEMENT_CHANGE_ALL_DAY';
const ADMIN_ANNOUNCEMENT_CREATE_REQUEST = 'ADMIN_ANNOUNCEMENT_CREATE_REQUEST';
const ADMIN_ANNOUNCEMENT_CREATE_SUCCESS = 'ADMIN_ANNOUNCEMENT_CREATE_REQUEST';
const ADMIN_ANNOUNCEMENT_CREATE_FAIL = 'ADMIN_ANNOUNCEMENT_CREATE_FAIL';
const ADMIN_ANNOUNCEMENT_DELETE_REQUEST = 'ADMIN_ANNOUNCEMENT_DELETE_REQUEST';
const ADMIN_ANNOUNCEMENT_DELETE_SUCCESS = 'ADMIN_ANNOUNCEMENT_DELETE_REQUEST';
const ADMIN_ANNOUNCEMENT_DELETE_FAIL = 'ADMIN_ANNOUNCEMENT_DELETE_FAIL';
const ADMIN_ANNOUNCEMENT_MODAL_INIT = 'ADMIN_ANNOUNCEMENT_MODAL_INIT';
const messages = defineMessages({
announcementCreateSuccess: { id: 'admin.edit_announcement.created', defaultMessage: 'Announcement created' },
announcementDeleteSuccess: { id: 'admin.edit_announcement.deleted', defaultMessage: 'Announcement deleted' },
announcementUpdateSuccess: { id: 'admin.edit_announcement.updated', defaultMessage: 'Announcement edited' },
});
const nicknamesFromIds = (getState: () => RootState, ids: string[]) => ids.map(id => getState().accounts.get(id)!.acct);
const fetchConfig = () =>
@ -598,6 +632,93 @@ const expandUserIndex = () =>
});
};
const fetchAdminAnnouncements = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: ADMIN_ANNOUNCEMENTS_FETCH_REQUEST });
return api(getState)
.get('/api/pleroma/admin/announcements', { params: { limit: 50 } })
.then(({ data }) => {
dispatch({ type: ADMIN_ANNOUNCEMENTS_FETCH_SUCCESS, announcements: data });
return data;
}).catch(error => {
dispatch({ type: ADMIN_ANNOUNCEMENTS_FETCH_FAIL, error });
});
};
const expandAdminAnnouncements = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
const page = getState().admin_announcements.page;
dispatch({ type: ADMIN_ANNOUNCEMENTS_EXPAND_REQUEST });
return api(getState)
.get('/api/pleroma/admin/announcements', { params: { limit: 50, offset: page * 50 } })
.then(({ data }) => {
dispatch({ type: ADMIN_ANNOUNCEMENTS_EXPAND_SUCCESS, announcements: data });
return data;
}).catch(error => {
dispatch({ type: ADMIN_ANNOUNCEMENTS_EXPAND_FAIL, error });
});
};
const changeAnnouncementContent = (content: string) => ({
type: ADMIN_ANNOUNCEMENT_CHANGE_CONTENT,
value: content,
});
const changeAnnouncementStartTime = (time: Date | null) => ({
type: ADMIN_ANNOUNCEMENT_CHANGE_START_TIME,
value: time,
});
const changeAnnouncementEndTime = (time: Date | null) => ({
type: ADMIN_ANNOUNCEMENT_CHANGE_END_TIME,
value: time,
});
const changeAnnouncementAllDay = (allDay: boolean) => ({
type: ADMIN_ANNOUNCEMENT_CHANGE_ALL_DAY,
value: allDay,
});
const handleCreateAnnouncement = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: ADMIN_ANNOUNCEMENT_CREATE_REQUEST });
const { id, content, starts_at, ends_at, all_day } = getState().admin_announcements.form;
return api(getState)[id ? 'patch' : 'post'](
id ? `/api/pleroma/admin/announcements/${id}` : '/api/pleroma/admin/announcements',
{ content, starts_at, ends_at, all_day },
).then(({ data }) => {
dispatch({ type: ADMIN_ANNOUNCEMENT_CREATE_SUCCESS, announcement: data });
toast.success(id ? messages.announcementUpdateSuccess : messages.announcementCreateSuccess);
dispatch(fetchAdminAnnouncements());
return data;
}).catch(error => {
dispatch({ type: ADMIN_ANNOUNCEMENT_CREATE_FAIL, error });
});
};
const deleteAnnouncement = (id: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: ADMIN_ANNOUNCEMENT_DELETE_REQUEST, id });
return api(getState).delete(`/api/pleroma/admin/announcements/${id}`).then(({ data }) => {
dispatch({ type: ADMIN_ANNOUNCEMENT_DELETE_SUCCESS, id });
toast.success(messages.announcementDeleteSuccess);
dispatch(fetchAdminAnnouncements());
return data;
}).catch(error => {
dispatch({ type: ADMIN_ANNOUNCEMENT_DELETE_FAIL, id, error });
});
};
const initAnnouncementModal = (announcement?: Announcement) =>
(dispatch: AppDispatch) => {
dispatch({ type: ADMIN_ANNOUNCEMENT_MODAL_INIT, announcement });
dispatch(openModal('EDIT_ANNOUNCEMENT'));
};
export {
ADMIN_CONFIG_FETCH_REQUEST,
ADMIN_CONFIG_FETCH_SUCCESS,
@ -657,6 +778,23 @@ export {
ADMIN_USER_INDEX_FETCH_REQUEST,
ADMIN_USER_INDEX_FETCH_SUCCESS,
ADMIN_USER_INDEX_QUERY_SET,
ADMIN_ANNOUNCEMENTS_FETCH_FAIL,
ADMIN_ANNOUNCEMENTS_FETCH_REQUEST,
ADMIN_ANNOUNCEMENTS_FETCH_SUCCESS,
ADMIN_ANNOUNCEMENTS_EXPAND_FAIL,
ADMIN_ANNOUNCEMENTS_EXPAND_REQUEST,
ADMIN_ANNOUNCEMENTS_EXPAND_SUCCESS,
ADMIN_ANNOUNCEMENT_CHANGE_CONTENT,
ADMIN_ANNOUNCEMENT_CHANGE_START_TIME,
ADMIN_ANNOUNCEMENT_CHANGE_END_TIME,
ADMIN_ANNOUNCEMENT_CHANGE_ALL_DAY,
ADMIN_ANNOUNCEMENT_CREATE_FAIL,
ADMIN_ANNOUNCEMENT_CREATE_REQUEST,
ADMIN_ANNOUNCEMENT_CREATE_SUCCESS,
ADMIN_ANNOUNCEMENT_DELETE_FAIL,
ADMIN_ANNOUNCEMENT_DELETE_REQUEST,
ADMIN_ANNOUNCEMENT_DELETE_SUCCESS,
ADMIN_ANNOUNCEMENT_MODAL_INIT,
fetchConfig,
updateConfig,
updateSoapboxConfig,
@ -686,4 +824,13 @@ export {
setUserIndexQuery,
fetchUserIndex,
expandUserIndex,
fetchAdminAnnouncements,
expandAdminAnnouncements,
changeAnnouncementContent,
changeAnnouncementStartTime,
changeAnnouncementEndTime,
changeAnnouncementAllDay,
handleCreateAnnouncement,
deleteAnnouncement,
initAnnouncementModal,
};

Wyświetl plik

@ -4,7 +4,8 @@ import throttle from 'lodash/throttle';
import { defineMessages, IntlShape } from 'react-intl';
import api from 'soapbox/api';
import { search as emojiSearch } from 'soapbox/features/emoji/emoji-mart-search-light';
import { isNativeEmoji } from 'soapbox/features/emoji';
import emojiSearch from 'soapbox/features/emoji/search';
import { tagHistory } from 'soapbox/settings';
import toast from 'soapbox/toast';
import { isLoggedIn } from 'soapbox/utils/auth';
@ -19,8 +20,8 @@ import { openModal, closeModal } from './modals';
import { getSettings } from './settings';
import { createStatus } from './statuses';
import type { Emoji } from 'soapbox/components/autosuggest-emoji';
import type { AutoSuggestion } from 'soapbox/components/autosuggest-input';
import type { Emoji } from 'soapbox/features/emoji';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { Account, APIEntity, Status, Tag } from 'soapbox/types/entities';
import type { History } from 'soapbox/types/history';
@ -277,7 +278,7 @@ const submitCompose = (composeId: string, routerHistory?: History, force = false
const idempotencyKey = compose.idempotencyKey;
const params = {
const params: Record<string, any> = {
status,
in_reply_to_id: compose.in_reply_to,
quote_id: compose.quote,
@ -289,9 +290,10 @@ const submitCompose = (composeId: string, routerHistory?: History, force = false
poll: compose.poll,
scheduled_at: compose.schedule,
to,
group_id: compose.privacy === 'group' ? compose.group_id : null,
};
if (compose.privacy === 'group') params.group_id = compose.group_id;
dispatch(createStatus(params, idempotencyKey, statusId)).then(function(data) {
if (!statusId && data.visibility === 'direct' && getState().conversations.mounted <= 0 && routerHistory) {
routerHistory.push('/messages');
@ -515,7 +517,9 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, composeId,
}, 200, { leading: true, trailing: true });
const fetchComposeSuggestionsEmojis = (dispatch: AppDispatch, getState: () => RootState, composeId: string, token: string) => {
const results = emojiSearch(token.replace(':', ''), { maxResults: 5 } as any);
const state = getState();
const results = emojiSearch(token.replace(':', ''), { maxResults: 5 }, state.custom_emojis);
dispatch(readyComposeSuggestionsEmojis(composeId, token, results));
};
@ -560,7 +564,7 @@ const selectComposeSuggestion = (composeId: string, position: number, token: str
let completion, startPosition;
if (typeof suggestion === 'object' && suggestion.id) {
completion = suggestion.native || suggestion.colons;
completion = isNativeEmoji(suggestion) ? suggestion.native : suggestion.colons;
startPosition = position - 1;
dispatch(useEmoji(suggestion));

Wyświetl plik

@ -1,13 +1,8 @@
import type { DropdownPlacement } from 'soapbox/components/dropdown-menu';
const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
const openDropdownMenu = (id: number, placement: DropdownPlacement, keyboard: boolean) =>
({ type: DROPDOWN_MENU_OPEN, id, placement, keyboard });
const closeDropdownMenu = (id: number) =>
({ type: DROPDOWN_MENU_CLOSE, id });
const openDropdownMenu = () => ({ type: DROPDOWN_MENU_OPEN });
const closeDropdownMenu = () => ({ type: DROPDOWN_MENU_CLOSE });
export {
DROPDOWN_MENU_OPEN,

Wyświetl plik

@ -25,7 +25,7 @@ const EMOJI_REACTS_FETCH_FAIL = 'EMOJI_REACTS_FETCH_FAIL';
const noOp = () => () => new Promise(f => f(undefined));
const simpleEmojiReact = (status: Status, emoji: string) =>
const simpleEmojiReact = (status: Status, emoji: string, custom?: string) =>
(dispatch: AppDispatch) => {
const emojiReacts: ImmutableList<ImmutableMap<string, any>> = status.pleroma.get('emoji_reactions') || ImmutableList();
@ -43,7 +43,7 @@ const simpleEmojiReact = (status: Status, emoji: string) =>
if (emoji === '👍') {
dispatch(favourite(status));
} else {
dispatch(emojiReact(status, emoji));
dispatch(emojiReact(status, emoji, custom));
}
}).catch(err => {
console.error(err);
@ -70,11 +70,11 @@ const fetchEmojiReacts = (id: string, emoji: string) =>
});
};
const emojiReact = (status: Status, emoji: string) =>
const emojiReact = (status: Status, emoji: string, custom?: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return dispatch(noOp());
dispatch(emojiReactRequest(status, emoji));
dispatch(emojiReactRequest(status, emoji, custom));
return api(getState)
.put(`/api/v1/pleroma/statuses/${status.get('id')}/reactions/${emoji}`)
@ -120,10 +120,11 @@ const fetchEmojiReactsFail = (id: string, error: AxiosError) => ({
error,
});
const emojiReactRequest = (status: Status, emoji: string) => ({
const emojiReactRequest = (status: Status, emoji: string, custom?: string) => ({
type: EMOJI_REACT_REQUEST,
status,
emoji,
custom,
skipLoading: true,
});

Wyświetl plik

@ -1,6 +1,6 @@
import { saveSettings } from './settings';
import type { Emoji } from 'soapbox/components/autosuggest-emoji';
import type { Emoji } from 'soapbox/features/emoji';
import type { AppDispatch } from 'soapbox/store';
const EMOJI_USE = 'EMOJI_USE';

Wyświetl plik

@ -569,7 +569,7 @@ const rejectEventParticipationRequestFail = (id: string, accountId: string, erro
});
const fetchEventIcs = (id: string) =>
(dispatch: any, getState: () => RootState) =>
(dispatch: AppDispatch, getState: () => RootState) =>
api(getState).get(`/api/v1/pleroma/events/${id}/ics`);
const cancelEventCompose = () => ({

Wyświetl plik

@ -34,8 +34,8 @@ type ExportDataActions = {
| typeof EXPORT_BLOCKS_FAIL
| typeof EXPORT_MUTES_REQUEST
| typeof EXPORT_MUTES_SUCCESS
| typeof EXPORT_MUTES_FAIL,
error?: any,
| typeof EXPORT_MUTES_FAIL
error?: any
}
function fileExport(content: string, fileName: string) {

Wyświetl plik

@ -11,25 +11,25 @@ export const FAMILIAR_FOLLOWERS_FETCH_SUCCESS = 'FAMILIAR_FOLLOWERS_FETCH_SUCCES
export const FAMILIAR_FOLLOWERS_FETCH_FAIL = 'FAMILIAR_FOLLOWERS_FETCH_FAIL';
type FamiliarFollowersFetchRequestAction = {
type: typeof FAMILIAR_FOLLOWERS_FETCH_REQUEST,
id: string,
type: typeof FAMILIAR_FOLLOWERS_FETCH_REQUEST
id: string
}
type FamiliarFollowersFetchRequestSuccessAction = {
type: typeof FAMILIAR_FOLLOWERS_FETCH_SUCCESS,
id: string,
accounts: Array<APIEntity>,
type: typeof FAMILIAR_FOLLOWERS_FETCH_SUCCESS
id: string
accounts: Array<APIEntity>
}
type FamiliarFollowersFetchRequestFailAction = {
type: typeof FAMILIAR_FOLLOWERS_FETCH_FAIL,
id: string,
error: any,
type: typeof FAMILIAR_FOLLOWERS_FETCH_FAIL
id: string
error: any
}
type AccountsImportAction = {
type: typeof ACCOUNTS_IMPORT,
accounts: Array<APIEntity>,
type: typeof ACCOUNTS_IMPORT
accounts: Array<APIEntity>
}
export type FamiliarFollowersActions = FamiliarFollowersFetchRequestAction | FamiliarFollowersFetchRequestSuccessAction | FamiliarFollowersFetchRequestFailAction | AccountsImportAction

Wyświetl plik

@ -12,10 +12,18 @@ const FILTERS_FETCH_REQUEST = 'FILTERS_FETCH_REQUEST';
const FILTERS_FETCH_SUCCESS = 'FILTERS_FETCH_SUCCESS';
const FILTERS_FETCH_FAIL = 'FILTERS_FETCH_FAIL';
const FILTER_FETCH_REQUEST = 'FILTER_FETCH_REQUEST';
const FILTER_FETCH_SUCCESS = 'FILTER_FETCH_SUCCESS';
const FILTER_FETCH_FAIL = 'FILTER_FETCH_FAIL';
const FILTERS_CREATE_REQUEST = 'FILTERS_CREATE_REQUEST';
const FILTERS_CREATE_SUCCESS = 'FILTERS_CREATE_SUCCESS';
const FILTERS_CREATE_FAIL = 'FILTERS_CREATE_FAIL';
const FILTERS_UPDATE_REQUEST = 'FILTERS_UPDATE_REQUEST';
const FILTERS_UPDATE_SUCCESS = 'FILTERS_UPDATE_SUCCESS';
const FILTERS_UPDATE_FAIL = 'FILTERS_UPDATE_FAIL';
const FILTERS_DELETE_REQUEST = 'FILTERS_DELETE_REQUEST';
const FILTERS_DELETE_SUCCESS = 'FILTERS_DELETE_SUCCESS';
const FILTERS_DELETE_FAIL = 'FILTERS_DELETE_FAIL';
@ -25,22 +33,16 @@ const messages = defineMessages({
removed: { id: 'filters.removed', defaultMessage: 'Filter deleted.' },
});
const fetchFilters = () =>
type FilterKeywords = { keyword: string, whole_word: boolean }[];
const fetchFiltersV1 = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return;
const state = getState();
const instance = state.instance;
const features = getFeatures(instance);
if (!features.filters) return;
dispatch({
type: FILTERS_FETCH_REQUEST,
skipLoading: true,
});
api(getState)
return api(getState)
.get('/api/v1/filters')
.then(({ data }) => dispatch({
type: FILTERS_FETCH_SUCCESS,
@ -55,15 +57,105 @@ const fetchFilters = () =>
}));
};
const createFilter = (phrase: string, expires_at: string, context: Array<string>, whole_word: boolean, irreversible: boolean) =>
const fetchFiltersV2 = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({
type: FILTERS_FETCH_REQUEST,
skipLoading: true,
});
return api(getState)
.get('/api/v2/filters')
.then(({ data }) => dispatch({
type: FILTERS_FETCH_SUCCESS,
filters: data,
skipLoading: true,
}))
.catch(err => dispatch({
type: FILTERS_FETCH_FAIL,
err,
skipLoading: true,
skipAlert: true,
}));
};
const fetchFilters = (fromFiltersPage = false) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return;
const state = getState();
const instance = state.instance;
const features = getFeatures(instance);
if (features.filtersV2 && fromFiltersPage) return dispatch(fetchFiltersV2());
if (features.filters) return dispatch(fetchFiltersV1());
};
const fetchFilterV1 = (id: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({
type: FILTER_FETCH_REQUEST,
skipLoading: true,
});
return api(getState)
.get(`/api/v1/filters/${id}`)
.then(({ data }) => dispatch({
type: FILTER_FETCH_SUCCESS,
filter: data,
skipLoading: true,
}))
.catch(err => dispatch({
type: FILTER_FETCH_FAIL,
err,
skipLoading: true,
skipAlert: true,
}));
};
const fetchFilterV2 = (id: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({
type: FILTER_FETCH_REQUEST,
skipLoading: true,
});
return api(getState)
.get(`/api/v2/filters/${id}`)
.then(({ data }) => dispatch({
type: FILTER_FETCH_SUCCESS,
filter: data,
skipLoading: true,
}))
.catch(err => dispatch({
type: FILTER_FETCH_FAIL,
err,
skipLoading: true,
skipAlert: true,
}));
};
const fetchFilter = (id: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
const instance = state.instance;
const features = getFeatures(instance);
if (features.filtersV2) return dispatch(fetchFilterV2(id));
if (features.filters) return dispatch(fetchFilterV1(id));
};
const createFilterV1 = (title: string, expires_in: string | null, context: Array<string>, hide: boolean, keywords: FilterKeywords) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: FILTERS_CREATE_REQUEST });
return api(getState).post('/api/v1/filters', {
phrase,
phrase: keywords[0].keyword,
context,
irreversible,
whole_word,
expires_at,
irreversible: hide,
whole_word: keywords[0].whole_word,
expires_in,
}).then(response => {
dispatch({ type: FILTERS_CREATE_SUCCESS, filter: response.data });
toast.success(messages.added);
@ -72,7 +164,80 @@ const createFilter = (phrase: string, expires_at: string, context: Array<string>
});
};
const deleteFilter = (id: string) =>
const createFilterV2 = (title: string, expires_in: string | null, context: Array<string>, hide: boolean, keywords_attributes: FilterKeywords) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: FILTERS_CREATE_REQUEST });
return api(getState).post('/api/v2/filters', {
title,
context,
filter_action: hide ? 'hide' : 'warn',
expires_in,
keywords_attributes,
}).then(response => {
dispatch({ type: FILTERS_CREATE_SUCCESS, filter: response.data });
toast.success(messages.added);
}).catch(error => {
dispatch({ type: FILTERS_CREATE_FAIL, error });
});
};
const createFilter = (title: string, expires_in: string | null, context: Array<string>, hide: boolean, keywords: FilterKeywords) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
const instance = state.instance;
const features = getFeatures(instance);
if (features.filtersV2) return dispatch(createFilterV2(title, expires_in, context, hide, keywords));
return dispatch(createFilterV1(title, expires_in, context, hide, keywords));
};
const updateFilterV1 = (id: string, title: string, expires_in: string | null, context: Array<string>, hide: boolean, keywords: FilterKeywords) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: FILTERS_UPDATE_REQUEST });
return api(getState).patch(`/api/v1/filters/${id}`, {
phrase: keywords[0].keyword,
context,
irreversible: hide,
whole_word: keywords[0].whole_word,
expires_in,
}).then(response => {
dispatch({ type: FILTERS_UPDATE_SUCCESS, filter: response.data });
toast.success(messages.added);
}).catch(error => {
dispatch({ type: FILTERS_UPDATE_FAIL, error });
});
};
const updateFilterV2 = (id: string, title: string, expires_in: string | null, context: Array<string>, hide: boolean, keywords_attributes: FilterKeywords) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: FILTERS_UPDATE_REQUEST });
return api(getState).patch(`/api/v2/filters/${id}`, {
title,
context,
filter_action: hide ? 'hide' : 'warn',
expires_in,
keywords_attributes,
}).then(response => {
dispatch({ type: FILTERS_UPDATE_SUCCESS, filter: response.data });
toast.success(messages.added);
}).catch(error => {
dispatch({ type: FILTERS_UPDATE_FAIL, error });
});
};
const updateFilter = (id: string, title: string, expires_in: string | null, context: Array<string>, hide: boolean, keywords: FilterKeywords) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
const instance = state.instance;
const features = getFeatures(instance);
if (features.filtersV2) return dispatch(updateFilterV2(id, title, expires_in, context, hide, keywords));
return dispatch(updateFilterV1(id, title, expires_in, context, hide, keywords));
};
const deleteFilterV1 = (id: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: FILTERS_DELETE_REQUEST });
return api(getState).delete(`/api/v1/filters/${id}`).then(response => {
@ -83,17 +248,47 @@ const deleteFilter = (id: string) =>
});
};
const deleteFilterV2 = (id: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: FILTERS_DELETE_REQUEST });
return api(getState).delete(`/api/v2/filters/${id}`).then(response => {
dispatch({ type: FILTERS_DELETE_SUCCESS, filter: response.data });
toast.success(messages.removed);
}).catch(error => {
dispatch({ type: FILTERS_DELETE_FAIL, error });
});
};
const deleteFilter = (id: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
const instance = state.instance;
const features = getFeatures(instance);
if (features.filtersV2) return dispatch(deleteFilterV2(id));
return dispatch(deleteFilterV1(id));
};
export {
FILTERS_FETCH_REQUEST,
FILTERS_FETCH_SUCCESS,
FILTERS_FETCH_FAIL,
FILTER_FETCH_REQUEST,
FILTER_FETCH_SUCCESS,
FILTER_FETCH_FAIL,
FILTERS_CREATE_REQUEST,
FILTERS_CREATE_SUCCESS,
FILTERS_CREATE_FAIL,
FILTERS_UPDATE_REQUEST,
FILTERS_UPDATE_SUCCESS,
FILTERS_UPDATE_FAIL,
FILTERS_DELETE_REQUEST,
FILTERS_DELETE_SUCCESS,
FILTERS_DELETE_FAIL,
fetchFilters,
fetchFilter,
createFilter,
updateFilter,
deleteFilter,
};
};

Wyświetl plik

@ -1,5 +1,6 @@
import { defineMessages } from 'react-intl';
import { deleteEntities } from 'soapbox/entity-store/actions';
import toast from 'soapbox/toast';
import api, { getLinks } from '../api';
@ -40,14 +41,6 @@ const GROUP_RELATIONSHIPS_FETCH_REQUEST = 'GROUP_RELATIONSHIPS_FETCH_REQUEST';
const GROUP_RELATIONSHIPS_FETCH_SUCCESS = 'GROUP_RELATIONSHIPS_FETCH_SUCCESS';
const GROUP_RELATIONSHIPS_FETCH_FAIL = 'GROUP_RELATIONSHIPS_FETCH_FAIL';
const GROUP_JOIN_REQUEST = 'GROUP_JOIN_REQUEST';
const GROUP_JOIN_SUCCESS = 'GROUP_JOIN_SUCCESS';
const GROUP_JOIN_FAIL = 'GROUP_JOIN_FAIL';
const GROUP_LEAVE_REQUEST = 'GROUP_LEAVE_REQUEST';
const GROUP_LEAVE_SUCCESS = 'GROUP_LEAVE_SUCCESS';
const GROUP_LEAVE_FAIL = 'GROUP_LEAVE_FAIL';
const GROUP_DELETE_STATUS_REQUEST = 'GROUP_DELETE_STATUS_REQUEST';
const GROUP_DELETE_STATUS_SUCCESS = 'GROUP_DELETE_STATUS_SUCCESS';
const GROUP_DELETE_STATUS_FAIL = 'GROUP_DELETE_STATUS_FAIL';
@ -148,7 +141,8 @@ const createGroup = (params: Record<string, any>, shouldReset?: boolean) =>
if (shouldReset) {
dispatch(resetGroupEditor());
}
dispatch(closeModal('MANAGE_GROUP'));
return data;
}).catch(err => dispatch(createGroupFail(err)));
};
@ -198,7 +192,7 @@ const updateGroupFail = (error: AxiosError) => ({
});
const deleteGroup = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => {
dispatch(deleteGroupRequest(id));
dispatch(deleteEntities([id], 'Group'));
return api(getState).delete(`/api/v1/groups/${id}`)
.then(() => dispatch(deleteGroupSuccess(id)))
@ -312,70 +306,6 @@ const fetchGroupRelationshipsFail = (error: AxiosError) => ({
skipNotFound: true,
});
const joinGroup = (id: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const locked = (getState().groups.items.get(id) as any).locked || false;
dispatch(joinGroupRequest(id, locked));
return api(getState).post(`/api/v1/groups/${id}/join`).then(response => {
dispatch(joinGroupSuccess(response.data));
toast.success(locked ? messages.joinRequestSuccess : messages.joinSuccess);
}).catch(error => {
dispatch(joinGroupFail(error, locked));
});
};
const leaveGroup = (id: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(leaveGroupRequest(id));
return api(getState).post(`/api/v1/groups/${id}/leave`).then(response => {
dispatch(leaveGroupSuccess(response.data));
toast.success(messages.leaveSuccess);
}).catch(error => {
dispatch(leaveGroupFail(error));
});
};
const joinGroupRequest = (id: string, locked: boolean) => ({
type: GROUP_JOIN_REQUEST,
id,
locked,
skipLoading: true,
});
const joinGroupSuccess = (relationship: APIEntity) => ({
type: GROUP_JOIN_SUCCESS,
relationship,
skipLoading: true,
});
const joinGroupFail = (error: AxiosError, locked: boolean) => ({
type: GROUP_JOIN_FAIL,
error,
locked,
skipLoading: true,
});
const leaveGroupRequest = (id: string) => ({
type: GROUP_LEAVE_REQUEST,
id,
skipLoading: true,
});
const leaveGroupSuccess = (relationship: APIEntity) => ({
type: GROUP_LEAVE_SUCCESS,
relationship,
skipLoading: true,
});
const leaveGroupFail = (error: AxiosError) => ({
type: GROUP_LEAVE_FAIL,
error,
skipLoading: true,
});
const groupDeleteStatus = (groupId: string, statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(groupDeleteStatusRequest(groupId, statusId));
@ -859,9 +789,11 @@ const submitGroupEditor = (shouldReset?: boolean) => (dispatch: AppDispatch, get
const note = getState().group_editor.note;
const avatar = getState().group_editor.avatar;
const header = getState().group_editor.header;
const visibility = getState().group_editor.locked ? 'members_only' : 'everyone'; // Truth Social
const params: Record<string, any> = {
display_name: displayName,
group_visibility: visibility,
note,
};
@ -869,9 +801,9 @@ const submitGroupEditor = (shouldReset?: boolean) => (dispatch: AppDispatch, get
if (header) params.header = header;
if (groupId === null) {
dispatch(createGroup(params, shouldReset));
return dispatch(createGroup(params, shouldReset));
} else {
dispatch(updateGroup(groupId, params, shouldReset));
return dispatch(updateGroup(groupId, params, shouldReset));
}
};
@ -895,12 +827,6 @@ export {
GROUP_RELATIONSHIPS_FETCH_REQUEST,
GROUP_RELATIONSHIPS_FETCH_SUCCESS,
GROUP_RELATIONSHIPS_FETCH_FAIL,
GROUP_JOIN_REQUEST,
GROUP_JOIN_SUCCESS,
GROUP_JOIN_FAIL,
GROUP_LEAVE_REQUEST,
GROUP_LEAVE_SUCCESS,
GROUP_LEAVE_FAIL,
GROUP_DELETE_STATUS_REQUEST,
GROUP_DELETE_STATUS_SUCCESS,
GROUP_DELETE_STATUS_FAIL,
@ -973,14 +899,6 @@ export {
fetchGroupRelationshipsRequest,
fetchGroupRelationshipsSuccess,
fetchGroupRelationshipsFail,
joinGroup,
leaveGroup,
joinGroupRequest,
joinGroupSuccess,
joinGroupFail,
leaveGroupRequest,
leaveGroupSuccess,
leaveGroupFail,
groupDeleteStatus,
groupDeleteStatusRequest,
groupDeleteStatusSuccess,

Wyświetl plik

@ -27,8 +27,8 @@ type ImportDataActions = {
| typeof IMPORT_BLOCKS_FAIL
| typeof IMPORT_MUTES_REQUEST
| typeof IMPORT_MUTES_SUCCESS
| typeof IMPORT_MUTES_FAIL,
error?: any,
| typeof IMPORT_MUTES_FAIL
error?: any
config?: string
}

Wyświetl plik

@ -1,3 +1,8 @@
import { importEntities } from 'soapbox/entity-store/actions';
import { Entities } from 'soapbox/entity-store/entities';
import { Group, groupSchema } from 'soapbox/schemas';
import { filteredArray } from 'soapbox/schemas/utils';
import { getSettings } from '../settings';
import type { AppDispatch, RootState } from 'soapbox/store';
@ -18,11 +23,11 @@ const importAccount = (account: APIEntity) =>
const importAccounts = (accounts: APIEntity[]) =>
({ type: ACCOUNTS_IMPORT, accounts });
const importGroup = (group: APIEntity) =>
({ type: GROUP_IMPORT, group });
const importGroup = (group: Group) =>
importEntities([group], Entities.GROUPS);
const importGroups = (groups: APIEntity[]) =>
({ type: GROUPS_IMPORT, groups });
const importGroups = (groups: Group[]) =>
importEntities(groups, Entities.GROUPS);
const importStatus = (status: APIEntity, idempotencyKey?: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
@ -69,17 +74,8 @@ const importFetchedGroup = (group: APIEntity) =>
importFetchedGroups([group]);
const importFetchedGroups = (groups: APIEntity[]) => {
const normalGroups: APIEntity[] = [];
const processGroup = (group: APIEntity) => {
if (!group.id) return;
normalGroups.push(group);
};
groups.forEach(processGroup);
return importGroups(normalGroups);
const entities = filteredArray(groupSchema).catch([]).parse(groups);
return importGroups(entities);
};
const importFetchedStatus = (status: APIEntity, idempotencyKey?: string) =>

Wyświetl plik

@ -20,6 +20,10 @@ const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST';
const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS';
const FAVOURITE_FAIL = 'FAVOURITE_FAIL';
const DISLIKE_REQUEST = 'DISLIKE_REQUEST';
const DISLIKE_SUCCESS = 'DISLIKE_SUCCESS';
const DISLIKE_FAIL = 'DISLIKE_FAIL';
const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST';
const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS';
const UNREBLOG_FAIL = 'UNREBLOG_FAIL';
@ -28,6 +32,10 @@ const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST';
const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS';
const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL';
const UNDISLIKE_REQUEST = 'UNDISLIKE_REQUEST';
const UNDISLIKE_SUCCESS = 'UNDISLIKE_SUCCESS';
const UNDISLIKE_FAIL = 'UNDISLIKE_FAIL';
const REBLOGS_FETCH_REQUEST = 'REBLOGS_FETCH_REQUEST';
const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS';
const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL';
@ -36,6 +44,10 @@ const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST';
const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS';
const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL';
const DISLIKES_FETCH_REQUEST = 'DISLIKES_FETCH_REQUEST';
const DISLIKES_FETCH_SUCCESS = 'DISLIKES_FETCH_SUCCESS';
const DISLIKES_FETCH_FAIL = 'DISLIKES_FETCH_FAIL';
const REACTIONS_FETCH_REQUEST = 'REACTIONS_FETCH_REQUEST';
const REACTIONS_FETCH_SUCCESS = 'REACTIONS_FETCH_SUCCESS';
const REACTIONS_FETCH_FAIL = 'REACTIONS_FETCH_FAIL';
@ -96,7 +108,7 @@ const unreblog = (status: StatusEntity) =>
};
const toggleReblog = (status: StatusEntity) =>
(dispatch: AppDispatch, getState: () => RootState) => {
(dispatch: AppDispatch) => {
if (status.reblogged) {
dispatch(unreblog(status));
} else {
@ -169,7 +181,7 @@ const unfavourite = (status: StatusEntity) =>
};
const toggleFavourite = (status: StatusEntity) =>
(dispatch: AppDispatch, getState: () => RootState) => {
(dispatch: AppDispatch) => {
if (status.favourited) {
dispatch(unfavourite(status));
} else {
@ -215,6 +227,79 @@ const unfavouriteFail = (status: StatusEntity, error: AxiosError) => ({
skipLoading: true,
});
const dislike = (status: StatusEntity) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return;
dispatch(dislikeRequest(status));
api(getState).post(`/api/friendica/statuses/${status.get('id')}/dislike`).then(function() {
dispatch(dislikeSuccess(status));
}).catch(function(error) {
dispatch(dislikeFail(status, error));
});
};
const undislike = (status: StatusEntity) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return;
dispatch(undislikeRequest(status));
api(getState).post(`/api/friendica/statuses/${status.get('id')}/undislike`).then(() => {
dispatch(undislikeSuccess(status));
}).catch(error => {
dispatch(undislikeFail(status, error));
});
};
const toggleDislike = (status: StatusEntity) =>
(dispatch: AppDispatch) => {
if (status.disliked) {
dispatch(undislike(status));
} else {
dispatch(dislike(status));
}
};
const dislikeRequest = (status: StatusEntity) => ({
type: DISLIKE_REQUEST,
status: status,
skipLoading: true,
});
const dislikeSuccess = (status: StatusEntity) => ({
type: DISLIKE_SUCCESS,
status: status,
skipLoading: true,
});
const dislikeFail = (status: StatusEntity, error: AxiosError) => ({
type: DISLIKE_FAIL,
status: status,
error: error,
skipLoading: true,
});
const undislikeRequest = (status: StatusEntity) => ({
type: UNDISLIKE_REQUEST,
status: status,
skipLoading: true,
});
const undislikeSuccess = (status: StatusEntity) => ({
type: UNDISLIKE_SUCCESS,
status: status,
skipLoading: true,
});
const undislikeFail = (status: StatusEntity, error: AxiosError) => ({
type: UNDISLIKE_FAIL,
status: status,
error: error,
skipLoading: true,
});
const bookmark = (status: StatusEntity) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(bookmarkRequest(status));
@ -351,6 +436,38 @@ const fetchFavouritesFail = (id: string, error: AxiosError) => ({
error,
});
const fetchDislikes = (id: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return;
dispatch(fetchDislikesRequest(id));
api(getState).get(`/api/friendica/statuses/${id}/disliked_by`).then(response => {
dispatch(importFetchedAccounts(response.data));
dispatch(fetchRelationships(response.data.map((item: APIEntity) => item.id)));
dispatch(fetchDislikesSuccess(id, response.data));
}).catch(error => {
dispatch(fetchDislikesFail(id, error));
});
};
const fetchDislikesRequest = (id: string) => ({
type: DISLIKES_FETCH_REQUEST,
id,
});
const fetchDislikesSuccess = (id: string, accounts: APIEntity[]) => ({
type: DISLIKES_FETCH_SUCCESS,
id,
accounts,
});
const fetchDislikesFail = (id: string, error: AxiosError) => ({
type: DISLIKES_FETCH_FAIL,
id,
error,
});
const fetchReactions = (id: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(fetchReactionsRequest(id));
@ -498,18 +615,27 @@ export {
FAVOURITE_REQUEST,
FAVOURITE_SUCCESS,
FAVOURITE_FAIL,
DISLIKE_REQUEST,
DISLIKE_SUCCESS,
DISLIKE_FAIL,
UNREBLOG_REQUEST,
UNREBLOG_SUCCESS,
UNREBLOG_FAIL,
UNFAVOURITE_REQUEST,
UNFAVOURITE_SUCCESS,
UNFAVOURITE_FAIL,
UNDISLIKE_REQUEST,
UNDISLIKE_SUCCESS,
UNDISLIKE_FAIL,
REBLOGS_FETCH_REQUEST,
REBLOGS_FETCH_SUCCESS,
REBLOGS_FETCH_FAIL,
FAVOURITES_FETCH_REQUEST,
FAVOURITES_FETCH_SUCCESS,
FAVOURITES_FETCH_FAIL,
DISLIKES_FETCH_REQUEST,
DISLIKES_FETCH_SUCCESS,
DISLIKES_FETCH_FAIL,
REACTIONS_FETCH_REQUEST,
REACTIONS_FETCH_SUCCESS,
REACTIONS_FETCH_FAIL,
@ -546,6 +672,15 @@ export {
unfavouriteRequest,
unfavouriteSuccess,
unfavouriteFail,
dislike,
undislike,
toggleDislike,
dislikeRequest,
dislikeSuccess,
dislikeFail,
undislikeRequest,
undislikeSuccess,
undislikeFail,
bookmark,
unbookmark,
toggleBookmark,
@ -563,6 +698,10 @@ export {
fetchFavouritesRequest,
fetchFavouritesSuccess,
fetchFavouritesFail,
fetchDislikes,
fetchDislikesRequest,
fetchDislikesSuccess,
fetchDislikesFail,
fetchReactions,
fetchReactionsRequest,
fetchReactionsSuccess,

Wyświetl plik

@ -112,27 +112,6 @@ const deleteUserModal = (intl: IntlShape, accountId: string, afterConfirm = () =
}));
};
const rejectUserModal = (intl: IntlShape, accountId: string, afterConfirm = () => {}) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
const acct = state.accounts.get(accountId)!.acct;
const name = state.accounts.get(accountId)!.username;
dispatch(openModal('CONFIRM', {
icon: require('@tabler/icons/user-off.svg'),
heading: intl.formatMessage(messages.rejectUserHeading, { acct }),
message: intl.formatMessage(messages.rejectUserPrompt, { acct }),
confirm: intl.formatMessage(messages.rejectUserConfirm, { name }),
onConfirm: () => {
dispatch(deleteUsers([accountId]))
.then(() => {
afterConfirm();
})
.catch(() => {});
},
}));
};
const toggleStatusSensitivityModal = (intl: IntlShape, statusId: string, sensitive: boolean, afterConfirm = () => {}) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
@ -178,7 +157,6 @@ const deleteStatusModal = (intl: IntlShape, statusId: string, afterConfirm = ()
export {
deactivateUserModal,
deleteUserModal,
rejectUserModal,
toggleStatusSensitivityModal,
deleteStatusModal,
};

Wyświetl plik

@ -37,8 +37,8 @@ const subscribe = (registration: ServiceWorkerRegistration, getState: () => Root
});
const unsubscribe = ({ registration, subscription }: {
registration: ServiceWorkerRegistration,
subscription: PushSubscription | null,
registration: ServiceWorkerRegistration
subscription: PushSubscription | null
}) =>
subscription ? subscription.unsubscribe().then(() => registration) : new Promise<ServiceWorkerRegistration>(r => r(registration));
@ -82,8 +82,8 @@ const register = () =>
.then(getPushSubscription)
// @ts-ignore
.then(({ registration, subscription }: {
registration: ServiceWorkerRegistration,
subscription: PushSubscription | null,
registration: ServiceWorkerRegistration
subscription: PushSubscription | null
}) => {
if (subscription !== null) {
// We have a subscription, check if it is still valid

Wyświetl plik

@ -4,7 +4,7 @@ import { openModal } from './modals';
import type { AxiosError } from 'axios';
import type { AppDispatch, RootState } from 'soapbox/store';
import type { Account, ChatMessage, Status } from 'soapbox/types/entities';
import type { Account, ChatMessage, Group, Status } from 'soapbox/types/entities';
const REPORT_INIT = 'REPORT_INIT';
const REPORT_CANCEL = 'REPORT_CANCEL';
@ -20,19 +20,29 @@ const REPORT_BLOCK_CHANGE = 'REPORT_BLOCK_CHANGE';
const REPORT_RULE_CHANGE = 'REPORT_RULE_CHANGE';
type ReportedEntity = {
status?: Status,
chatMessage?: ChatMessage
enum ReportableEntities {
ACCOUNT = 'ACCOUNT',
CHAT_MESSAGE = 'CHAT_MESSAGE',
GROUP = 'GROUP',
STATUS = 'STATUS'
}
const initReport = (account: Account, entities?: ReportedEntity) => (dispatch: AppDispatch) => {
const { status, chatMessage } = entities || {};
type ReportedEntity = {
status?: Status
chatMessage?: ChatMessage
group?: Group
}
const initReport = (entityType: ReportableEntities, account: Account, entities?: ReportedEntity) => (dispatch: AppDispatch) => {
const { status, chatMessage, group } = entities || {};
dispatch({
type: REPORT_INIT,
entityType,
account,
status,
chatMessage,
group,
});
return dispatch(openModal('REPORT'));
@ -56,7 +66,8 @@ const submitReport = () =>
return api(getState).post('/api/v1/reports', {
account_id: reports.getIn(['new', 'account_id']),
status_ids: reports.getIn(['new', 'status_ids']),
message_ids: [reports.getIn(['new', 'chat_message', 'id'])],
message_ids: [reports.getIn(['new', 'chat_message', 'id'])].filter(Boolean),
group_id: reports.getIn(['new', 'group', 'id']),
rule_ids: reports.getIn(['new', 'rule_ids']),
comment: reports.getIn(['new', 'comment']),
forward: reports.getIn(['new', 'forward']),
@ -97,6 +108,7 @@ const changeReportRule = (ruleId: string) => ({
});
export {
ReportableEntities,
REPORT_INIT,
REPORT_CANCEL,
REPORT_SUBMIT_REQUEST,

Wyświetl plik

@ -1,9 +1,10 @@
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet } from 'immutable';
import { defineMessages } from 'react-intl';
import { defineMessage } from 'react-intl';
import { createSelector } from 'reselect';
import { v4 as uuid } from 'uuid';
import { patchMe } from 'soapbox/actions/me';
import messages from 'soapbox/locales/messages';
import toast from 'soapbox/toast';
import { isLoggedIn } from 'soapbox/utils/auth';
@ -18,12 +19,10 @@ const FE_NAME = 'soapbox_fe';
/** Options when changing/saving settings. */
type SettingOpts = {
/** Whether to display an alert when settings are saved. */
showAlert?: boolean,
showAlert?: boolean
}
const messages = defineMessages({
saveSuccess: { id: 'settings.save.success', defaultMessage: 'Your preferences have been saved!' },
});
const saveSuccessMessage = defineMessage({ id: 'settings.save.success', defaultMessage: 'Your preferences have been saved!' });
const defaultSettings = ImmutableMap({
onboarded: false,
@ -40,7 +39,7 @@ const defaultSettings = ImmutableMap({
defaultPrivacy: 'public',
defaultContentType: 'text/plain',
themeMode: 'system',
locale: navigator.language.split(/[-_]/)[0] || 'en',
locale: navigator.language || 'en',
showExplanationBox: true,
explanationBox: true,
autoloadTimelines: true,
@ -221,7 +220,7 @@ const saveSettingsImmediate = (opts?: SettingOpts) =>
dispatch({ type: SETTING_SAVE });
if (opts?.showAlert) {
toast.success(messages.saveSuccess);
toast.success(saveSuccessMessage);
}
}).catch(error => {
toast.showAlertForError(error);
@ -231,6 +230,12 @@ const saveSettingsImmediate = (opts?: SettingOpts) =>
const saveSettings = (opts?: SettingOpts) =>
(dispatch: AppDispatch) => dispatch(saveSettingsImmediate(opts));
const getLocale = (state: RootState, fallback = 'en') => {
const localeWithVariant = (getSettings(state).get('locale') as string).replace('_', '-');
const locale = localeWithVariant.split('-')[0];
return Object.keys(messages).includes(localeWithVariant) ? localeWithVariant : Object.keys(messages).includes(locale) ? locale : fallback;
};
export {
SETTING_CHANGE,
SETTING_SAVE,
@ -242,4 +247,5 @@ export {
changeSetting,
saveSettingsImmediate,
saveSettings,
getLocale,
};

Wyświetl plik

@ -32,8 +32,8 @@ const getSoapboxConfig = createSelector([
}
// If RGI reacts aren't supported, strip VS16s
// // https://git.pleroma.social/pleroma/pleroma/-/issues/2355
if (!features.emojiReactsRGI) {
// https://git.pleroma.social/pleroma/pleroma/-/issues/2355
if (features.emojiReactsNonRGI) {
soapboxConfig.set('allowedEmoji', soapboxConfig.allowedEmoji.map(removeVS16s));
}
});

Wyświetl plik

@ -48,6 +48,8 @@ const STATUS_TRANSLATE_SUCCESS = 'STATUS_TRANSLATE_SUCCESS';
const STATUS_TRANSLATE_FAIL = 'STATUS_TRANSLATE_FAIL';
const STATUS_TRANSLATE_UNDO = 'STATUS_TRANSLATE_UNDO';
const STATUS_UNFILTER = 'STATUS_UNFILTER';
const statusExists = (getState: () => RootState, statusId: string) => {
return (getState().statuses.get(statusId) || null) !== null;
};
@ -335,6 +337,11 @@ const undoStatusTranslation = (id: string) => ({
id,
});
const unfilterStatus = (id: string) => ({
type: STATUS_UNFILTER,
id,
});
export {
STATUS_CREATE_REQUEST,
STATUS_CREATE_SUCCESS,
@ -363,6 +370,7 @@ export {
STATUS_TRANSLATE_SUCCESS,
STATUS_TRANSLATE_FAIL,
STATUS_TRANSLATE_UNDO,
STATUS_UNFILTER,
createStatus,
editStatus,
fetchStatus,
@ -381,4 +389,5 @@ export {
toggleStatusHidden,
translateStatus,
undoStatusTranslation,
unfilterStatus,
};

Wyświetl plik

@ -1,8 +1,8 @@
import { getSettings } from 'soapbox/actions/settings';
import { getLocale, getSettings } from 'soapbox/actions/settings';
import messages from 'soapbox/locales/messages';
import { ChatKeys, IChat, isLastMessage } from 'soapbox/queries/chats';
import { queryClient } from 'soapbox/queries/client';
import { getUnreadChatsCount, updateChatListItem } from 'soapbox/utils/chats';
import { getUnreadChatsCount, updateChatListItem, updateChatMessage } from 'soapbox/utils/chats';
import { removePageItem } from 'soapbox/utils/queries';
import { play, soundCache } from 'soapbox/utils/sounds';
@ -34,13 +34,6 @@ import type { APIEntity, Chat } from 'soapbox/types/entities';
const STREAMING_CHAT_UPDATE = 'STREAMING_CHAT_UPDATE';
const STREAMING_FOLLOW_RELATIONSHIPS_UPDATE = 'STREAMING_FOLLOW_RELATIONSHIPS_UPDATE';
const validLocale = (locale: string) => Object.keys(messages).includes(locale);
const getLocale = (state: RootState) => {
const locale = getSettings(state).get('locale') as string;
return validLocale(locale) ? locale : 'en';
};
const updateFollowRelationships = (relationships: APIEntity) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const me = getState().me;
@ -81,7 +74,7 @@ const updateChatQuery = (chat: IChat) => {
};
interface StreamOpts {
statContext?: IStatContext,
statContext?: IStatContext
}
const connectTimelineStream = (
@ -170,6 +163,9 @@ const connectTimelineStream = (
}
});
break;
case 'chat_message.reaction': // TruthSocial
updateChatMessage(JSON.parse(data.payload));
break;
case 'pleroma:follow_relationships_update':
dispatch(updateFollowRelationships(JSON.parse(data.payload)));
break;

Wyświetl plik

@ -31,14 +31,14 @@ const AGE: Challenge = 'age';
export type Challenge = 'age' | 'sms' | 'email'
type Challenges = {
email?: 0 | 1,
sms?: 0 | 1,
age?: 0 | 1,
email?: 0 | 1
sms?: 0 | 1
age?: 0 | 1
}
type Verification = {
token?: string,
challenges?: Challenges,
token?: string
challenges?: Challenges
challengeTypes?: Array<'age' | 'sms' | 'email'>
};

Wyświetl plik

@ -23,7 +23,12 @@ export const getLinks = (response: AxiosResponse): LinkHeader => {
export const getNextLink = (response: AxiosResponse) => {
const nextLink = new LinkHeader(response.headers?.link);
return nextLink.refs.find((ref) => ref.uri)?.uri;
return nextLink.refs.find(link => link.rel === 'next')?.uri;
};
export const getPrevLink = (response: AxiosResponse) => {
const prevLink = new LinkHeader(response.headers?.link);
return prevLink.refs.find(link => link.rel === 'prev')?.uri;
};
export const baseClient = (...params: any[]) => {

Wyświetl plik

@ -29,6 +29,10 @@ export const getNextLink = (response: AxiosResponse): string | undefined => {
return getLinks(response).refs.find(link => link.rel === 'next')?.uri;
};
export const getPrevLink = (response: AxiosResponse): string | undefined => {
return getLinks(response).refs.find(link => link.rel === 'prev')?.uri;
};
const getToken = (state: RootState, authType: string) => {
return authType === 'app' ? getAppToken(state) : getAccessToken(state);
};

Wyświetl plik

@ -1,7 +1,7 @@
import React from 'react';
interface IInlineSVG {
loader?: JSX.Element,
loader?: JSX.Element
}
const InlineSVG: React.FC<IInlineSVG> = ({ loader }): JSX.Element => {

Wyświetl plik

@ -1,16 +0,0 @@
import React from 'react';
import { render, screen } from '../../jest/test-helpers';
import EmojiSelector from '../emoji-selector';
describe('<EmojiSelector />', () => {
it('renders correctly', () => {
const children = <EmojiSelector />;
// @ts-ignore
children.__proto__.addEventListener = () => {};
render(children);
expect(screen.queryAllByRole('button')).toHaveLength(6);
});
});

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
@ -12,9 +12,9 @@ const messages = defineMessages({
interface IAccountSearch {
/** Callback when a searched account is chosen. */
onSelected: (accountId: string) => void,
onSelected: (accountId: string) => void
/** Override the default placeholder of the input. */
placeholder?: string,
placeholder?: string
}
/** Input to search for accounts. */
@ -77,12 +77,12 @@ const AccountSearch: React.FC<IAccountSearch> = ({ onSelected, ...rest }) => {
>
<SvgIcon
src={require('@tabler/icons/search.svg')}
className={classNames('h-4 w-4 text-gray-400', { hidden: !isEmpty() })}
className={clsx('h-4 w-4 text-gray-400', { hidden: !isEmpty() })}
/>
<SvgIcon
src={require('@tabler/icons/x.svg')}
className={classNames('h-4 w-4 text-gray-400', { hidden: isEmpty() })}
className={clsx('h-4 w-4 text-gray-400', { hidden: isEmpty() })}
aria-label={intl.formatMessage(messages.placeholder)}
/>
</div>

Wyświetl plik

@ -1,11 +1,11 @@
import React from 'react';
import React, { useRef } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { Link, useHistory } from 'react-router-dom';
import HoverRefWrapper from 'soapbox/components/hover-ref-wrapper';
import VerificationBadge from 'soapbox/components/verification-badge';
import ActionButton from 'soapbox/features/ui/components/action-button';
import { useAppSelector, useOnScreen } from 'soapbox/hooks';
import { useAppSelector } from 'soapbox/hooks';
import { getAcct } from 'soapbox/utils/accounts';
import { displayFqn } from 'soapbox/utils/state';
@ -14,11 +14,12 @@ import RelativeTimestamp from './relative-timestamp';
import { Avatar, Emoji, HStack, Icon, IconButton, Stack, Text } from './ui';
import type { StatusApprovalStatus } from 'soapbox/normalizers/status';
import type { Account as AccountSchema } from 'soapbox/schemas';
import type { Account as AccountEntity } from 'soapbox/types/entities';
interface IInstanceFavicon {
account: AccountEntity,
disabled?: boolean,
account: AccountEntity | AccountSchema
disabled?: boolean
}
const messages = defineMessages({
@ -53,7 +54,7 @@ const InstanceFavicon: React.FC<IInstanceFavicon> = ({ account, disabled }) => {
};
interface IProfilePopper {
condition: boolean,
condition: boolean
wrapper: (children: React.ReactNode) => React.ReactNode
children: React.ReactNode
}
@ -67,30 +68,31 @@ const ProfilePopper: React.FC<IProfilePopper> = ({ condition, wrapper, children
};
export interface IAccount {
account: AccountEntity,
action?: React.ReactElement,
actionAlignment?: 'center' | 'top',
actionIcon?: string,
actionTitle?: string,
account: AccountEntity | AccountSchema
action?: React.ReactElement
actionAlignment?: 'center' | 'top'
actionIcon?: string
actionTitle?: string
/** Override other actions for specificity like mute/unmute. */
actionType?: 'muting' | 'blocking' | 'follow_request',
avatarSize?: number,
hidden?: boolean,
hideActions?: boolean,
id?: string,
onActionClick?: (account: any) => void,
showProfileHoverCard?: boolean,
timestamp?: string,
timestampUrl?: string,
futureTimestamp?: boolean,
withAccountNote?: boolean,
withDate?: boolean,
withLinkToProfile?: boolean,
withRelationship?: boolean,
showEdit?: boolean,
approvalStatus?: StatusApprovalStatus,
emoji?: string,
note?: string,
actionType?: 'muting' | 'blocking' | 'follow_request'
avatarSize?: number
hidden?: boolean
hideActions?: boolean
id?: string
onActionClick?: (account: any) => void
showProfileHoverCard?: boolean
timestamp?: string
timestampUrl?: string
futureTimestamp?: boolean
withAccountNote?: boolean
withDate?: boolean
withLinkToProfile?: boolean
withRelationship?: boolean
showEdit?: boolean
approvalStatus?: StatusApprovalStatus
emoji?: string
emojiUrl?: string
note?: string
}
const Account = ({
@ -115,21 +117,17 @@ const Account = ({
showEdit = false,
approvalStatus,
emoji,
emojiUrl,
note,
}: IAccount) => {
const overflowRef = React.useRef<HTMLDivElement>(null);
const actionRef = React.useRef<HTMLDivElement>(null);
// @ts-ignore
const isOnScreen = useOnScreen(overflowRef);
const [style, setStyle] = React.useState<React.CSSProperties>({ visibility: 'hidden' });
const overflowRef = useRef<HTMLDivElement>(null);
const actionRef = useRef<HTMLDivElement>(null);
const me = useAppSelector((state) => state.me);
const username = useAppSelector((state) => account ? getAcct(account, displayFqn(state)) : null);
const handleAction = () => {
// @ts-ignore
onActionClick(account);
onActionClick!(account);
};
const renderAction = () => {
@ -148,7 +146,7 @@ const Account = ({
title={actionTitle}
onClick={handleAction}
className='bg-transparent text-gray-600 hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-500'
iconClassName='w-4 h-4'
iconClassName='h-4 w-4'
/>
);
}
@ -162,19 +160,6 @@ const Account = ({
const intl = useIntl();
React.useEffect(() => {
const style: React.CSSProperties = {};
const actionWidth = actionRef.current?.clientWidth || 0;
if (overflowRef.current) {
style.maxWidth = overflowRef.current.clientWidth - 30 - avatarSize - actionWidth;
} else {
style.visibility = 'hidden';
}
setStyle(style);
}, [isOnScreen, overflowRef, actionRef]);
if (!account) {
return null;
}
@ -195,7 +180,7 @@ const Account = ({
return (
<div data-testid='account' className='group block w-full shrink-0' ref={overflowRef}>
<HStack alignItems={actionAlignment} justifyContent='between'>
<HStack alignItems={withAccountNote || note ? 'top' : 'center'} space={3}>
<HStack alignItems={withAccountNote || note ? 'top' : 'center'} space={3} className='overflow-hidden'>
<ProfilePopper
condition={showProfileHoverCard}
wrapper={(children) => <HoverRefWrapper className='relative' accountId={account.id} inline>{children}</HoverRefWrapper>}
@ -208,14 +193,15 @@ const Account = ({
<Avatar src={account.avatar} size={avatarSize} />
{emoji && (
<Emoji
className='absolute -bottom-1.5 -right-1.5 h-5 w-5'
className='absolute bottom-0 -right-1.5 h-5 w-5'
emoji={emoji}
src={emojiUrl}
/>
)}
</LinkEl>
</ProfilePopper>
<div className='grow'>
<div className='grow overflow-hidden'>
<ProfilePopper
condition={showProfileHoverCard}
wrapper={(children) => <HoverRefWrapper accountId={account.id} inline>{children}</HoverRefWrapper>}
@ -225,7 +211,7 @@ const Account = ({
title={account.acct}
onClick={(event: React.MouseEvent) => event.stopPropagation()}
>
<HStack space={1} alignItems='center' grow style={style}>
<HStack space={1} alignItems='center' grow>
<Text
size='sm'
weight='semibold'
@ -241,7 +227,7 @@ const Account = ({
</ProfilePopper>
<Stack space={withAccountNote || note ? 1 : 0}>
<HStack alignItems='center' space={1} style={style}>
<HStack alignItems='center' space={1}>
<Text theme='muted' size='sm' direction='ltr' truncate>@{username}</Text>
{account.favicon && (

Wyświetl plik

@ -15,8 +15,8 @@ const obfuscatedCount = (count: number) => {
};
interface IAnimatedNumber {
value: number;
obfuscate?: boolean;
value: number
obfuscate?: boolean
}
const AnimatedNumber: React.FC<IAnimatedNumber> = ({ value, obfuscate }) => {

Wyświetl plik

@ -4,7 +4,7 @@ import { useHistory } from 'react-router-dom';
import type { Announcement as AnnouncementEntity, Mention as MentionEntity } from 'soapbox/types/entities';
interface IAnnouncementContent {
announcement: AnnouncementEntity;
announcement: AnnouncementEntity
}
const AnnouncementContent: React.FC<IAnnouncementContent> = ({ announcement }) => {

Wyświetl plik

@ -11,10 +11,10 @@ import type { Map as ImmutableMap } from 'immutable';
import type { Announcement as AnnouncementEntity } from 'soapbox/types/entities';
interface IAnnouncement {
announcement: AnnouncementEntity;
addReaction: (id: string, name: string) => void;
removeReaction: (id: string, name: string) => void;
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>;
announcement: AnnouncementEntity
addReaction: (id: string, name: string) => void
removeReaction: (id: string, name: string) => void
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>
}
const Announcement: React.FC<IAnnouncement> = ({ announcement, addReaction, removeReaction, emojiMap }) => {

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
@ -52,7 +52,7 @@ const AnnouncementsPanel = () => {
key={i}
tabIndex={0}
onClick={() => setIndex(i)}
className={classNames({
className={clsx({
'w-2 h-2 rounded-full focus:ring-primary-600 focus:ring-2 focus:ring-offset-2': true,
'bg-gray-200 hover:bg-gray-300': i !== index,
'bg-primary-600': i === index,

Wyświetl plik

@ -1,15 +1,15 @@
import React from 'react';
import unicodeMapping from 'soapbox/features/emoji/emoji-unicode-mapping-light';
import unicodeMapping from 'soapbox/features/emoji/mapping';
import { useSettings } from 'soapbox/hooks';
import { joinPublicPath } from 'soapbox/utils/static';
import type { Map as ImmutableMap } from 'immutable';
interface IEmoji {
emoji: string;
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>;
hovered: boolean;
emoji: string
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>
hovered: boolean
}
const Emoji: React.FC<IEmoji> = ({ emoji, emojiMap, hovered }) => {

Wyświetl plik

@ -1,8 +1,8 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useState } from 'react';
import AnimatedNumber from 'soapbox/components/animated-number';
import unicodeMapping from 'soapbox/features/emoji/emoji-unicode-mapping-light';
import unicodeMapping from 'soapbox/features/emoji/mapping';
import Emoji from './emoji';
@ -10,12 +10,12 @@ import type { Map as ImmutableMap } from 'immutable';
import type { AnnouncementReaction } from 'soapbox/types/entities';
interface IReaction {
announcementId: string;
reaction: AnnouncementReaction;
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>;
addReaction: (id: string, name: string) => void;
removeReaction: (id: string, name: string) => void;
style: React.CSSProperties;
announcementId: string
reaction: AnnouncementReaction
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>
addReaction: (id: string, name: string) => void
removeReaction: (id: string, name: string) => void
style: React.CSSProperties
}
const Reaction: React.FC<IReaction> = ({ announcementId, reaction, addReaction, removeReaction, emojiMap, style }) => {
@ -43,7 +43,7 @@ const Reaction: React.FC<IReaction> = ({ announcementId, reaction, addReaction,
return (
<button
className={classNames('flex shrink-0 items-center gap-1.5 bg-gray-100 dark:bg-primary-900 rounded-sm px-1.5 py-1 transition-colors', {
className={clsx('flex shrink-0 items-center gap-1.5 rounded-sm bg-gray-100 px-1.5 py-1 transition-colors dark:bg-primary-900', {
'bg-gray-200 dark:bg-primary-800': hovered,
'bg-primary-200 dark:bg-primary-500': reaction.me,
})}

Wyświetl plik

@ -1,30 +1,29 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { TransitionMotion, spring } from 'react-motion';
import { Icon } from 'soapbox/components/ui';
import EmojiPickerDropdown from 'soapbox/features/compose/components/emoji-picker/emoji-picker-dropdown';
import EmojiPickerDropdown from 'soapbox/features/emoji/containers/emoji-picker-dropdown-container';
import { useSettings } from 'soapbox/hooks';
import Reaction from './reaction';
import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import type { Emoji } from 'soapbox/components/autosuggest-emoji';
import type { Emoji, NativeEmoji } from 'soapbox/features/emoji';
import type { AnnouncementReaction } from 'soapbox/types/entities';
interface IReactionsBar {
announcementId: string;
reactions: ImmutableList<AnnouncementReaction>;
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>;
addReaction: (id: string, name: string) => void;
removeReaction: (id: string, name: string) => void;
announcementId: string
reactions: ImmutableList<AnnouncementReaction>
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>
addReaction: (id: string, name: string) => void
removeReaction: (id: string, name: string) => void
}
const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, addReaction, removeReaction, emojiMap }) => {
const reduceMotion = useSettings().get('reduceMotion');
const handleEmojiPick = (data: Emoji) => {
addReaction(announcementId, data.native.replace(/:/g, ''));
addReaction(announcementId, (data as NativeEmoji).native.replace(/:/g, ''));
};
const willEnter = () => ({ scale: reduceMotion ? 1 : 0 });
@ -42,7 +41,7 @@ const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, addR
return (
<TransitionMotion styles={styles} willEnter={willEnter} willLeave={willLeave}>
{items => (
<div className={classNames('flex flex-wrap items-center gap-1', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
<div className={clsx('flex flex-wrap items-center gap-1', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
{items.map(({ key, data, style }) => (
<Reaction
key={key}
@ -55,7 +54,7 @@ const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, addR
/>
))}
{visibleReactions.size < 8 && <EmojiPickerDropdown onPickEmoji={handleEmojiPick} button={<Icon className='h-4 w-4 text-gray-400 hover:text-gray-600 dark:hover:text-white' src={require('@tabler/icons/plus.svg')} />} />}
{visibleReactions.size < 8 && <EmojiPickerDropdown onPickEmoji={handleEmojiPick} />}
</div>
)}
</TransitionMotion>

Wyświetl plik

@ -0,0 +1,139 @@
import clsx from 'clsx';
import React, { useEffect, useRef, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { HStack, IconButton, Text } from 'soapbox/components/ui';
interface IAuthorizeRejectButtons {
onAuthorize(): Promise<unknown> | unknown
onReject(): Promise<unknown> | unknown
countdown?: number
}
/** Buttons to approve or reject a pending item, usually an account. */
const AuthorizeRejectButtons: React.FC<IAuthorizeRejectButtons> = ({ onAuthorize, onReject, countdown }) => {
const [state, setState] = useState<'authorizing' | 'rejecting' | 'authorized' | 'rejected' | 'pending'>('pending');
const timeout = useRef<NodeJS.Timeout>();
function handleAction(
present: 'authorizing' | 'rejecting',
past: 'authorized' | 'rejected',
action: () => Promise<unknown> | unknown,
): void {
if (state === present) {
if (timeout.current) {
clearTimeout(timeout.current);
}
setState('pending');
} else {
const doAction = async () => {
try {
await action();
setState(past);
} catch (e) {
console.error(e);
}
};
if (typeof countdown === 'number') {
setState(present);
timeout.current = setTimeout(doAction, countdown);
} else {
doAction();
}
}
}
const handleAuthorize = async () => handleAction('authorizing', 'authorized', onAuthorize);
const handleReject = async () => handleAction('rejecting', 'rejected', onReject);
useEffect(() => {
return () => {
if (timeout.current) {
clearTimeout(timeout.current);
}
};
}, []);
switch (state) {
case 'authorized':
return (
<ActionEmblem text={<FormattedMessage id='authorize.success' defaultMessage='Approved' />} />
);
case 'rejected':
return (
<ActionEmblem text={<FormattedMessage id='reject.success' defaultMessage='Rejected' />} />
);
default:
return (
<HStack space={3} alignItems='center'>
<AuthorizeRejectButton
theme='danger'
icon={require('@tabler/icons/x.svg')}
action={handleReject}
isLoading={state === 'rejecting'}
disabled={state === 'authorizing'}
/>
<AuthorizeRejectButton
theme='primary'
icon={require('@tabler/icons/check.svg')}
action={handleAuthorize}
isLoading={state === 'authorizing'}
disabled={state === 'rejecting'}
/>
</HStack>
);
}
};
interface IActionEmblem {
text: React.ReactNode
}
const ActionEmblem: React.FC<IActionEmblem> = ({ text }) => {
return (
<div className='rounded-full bg-gray-100 px-4 py-2 dark:bg-gray-800'>
<Text theme='muted' size='sm'>
{text}
</Text>
</div>
);
};
interface IAuthorizeRejectButton {
theme: 'primary' | 'danger'
icon: string
action(): void
isLoading?: boolean
disabled?: boolean
}
const AuthorizeRejectButton: React.FC<IAuthorizeRejectButton> = ({ theme, icon, action, isLoading, disabled }) => {
return (
<div className='relative'>
<IconButton
src={isLoading ? require('@tabler/icons/player-stop-filled.svg') : icon}
onClick={action}
theme='seamless'
className={clsx('h-10 w-10 items-center justify-center border-2', {
'border-primary-500/10 hover:border-primary-500': theme === 'primary',
'border-danger-600/10 hover:border-danger-600': theme === 'danger',
})}
iconClassName={clsx('h-6 w-6', {
'text-primary-500': theme === 'primary',
'text-danger-600': theme === 'danger',
})}
disabled={disabled}
/>
{(isLoading) && (
<div
className={clsx('pointer-events-none absolute inset-0 h-10 w-10 animate-spin rounded-full border-2 border-transparent', {
'border-t-primary-500': theme === 'primary',
'border-t-danger-600': theme === 'danger',
})}
/>
)}
</div>
);
};
export { AuthorizeRejectButtons };

Wyświetl plik

@ -12,16 +12,16 @@ import type { InputThemes } from 'soapbox/components/ui/input/input';
const noOp = () => { };
interface IAutosuggestAccountInput {
onChange: React.ChangeEventHandler<HTMLInputElement>,
onSelected: (accountId: string) => void,
autoFocus?: boolean,
value: string,
limit?: number,
className?: string,
autoSelect?: boolean,
menu?: Menu,
onKeyDown?: React.KeyboardEventHandler,
theme?: InputThemes,
onChange: React.ChangeEventHandler<HTMLInputElement>
onSelected: (accountId: string) => void
autoFocus?: boolean
value: string
limit?: number
className?: string
autoSelect?: boolean
menu?: Menu
onKeyDown?: React.KeyboardEventHandler
theme?: InputThemes
}
const AutosuggestAccountInput: React.FC<IAutosuggestAccountInput> = ({

Wyświetl plik

@ -1,38 +1,30 @@
import React from 'react';
import unicodeMapping from 'soapbox/features/emoji/emoji-unicode-mapping-light';
import { isCustomEmoji } from 'soapbox/features/emoji';
import unicodeMapping from 'soapbox/features/emoji/mapping';
import { joinPublicPath } from 'soapbox/utils/static';
export type Emoji = {
id: string,
custom: boolean,
imageUrl: string,
native: string,
colons: string,
}
type UnicodeMapping = {
filename: string,
}
import type { Emoji } from 'soapbox/features/emoji';
interface IAutosuggestEmoji {
emoji: Emoji,
emoji: Emoji
}
const AutosuggestEmoji: React.FC<IAutosuggestEmoji> = ({ emoji }) => {
let url;
let url, alt;
if (emoji.custom) {
if (isCustomEmoji(emoji)) {
url = emoji.imageUrl;
alt = emoji.colons;
} else {
// @ts-ignore
const mapping: UnicodeMapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, '')];
const mapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, '')];
if (!mapping) {
return null;
}
url = joinPublicPath(`packs/emoji/${mapping.filename}.svg`);
url = joinPublicPath(`packs/emoji/${mapping.unified}.svg`);
alt = emoji.native;
}
return (
@ -40,7 +32,7 @@ const AutosuggestEmoji: React.FC<IAutosuggestEmoji> = ({ emoji }) => {
<img
className='emojione'
src={url}
alt={emoji.native || emoji.colons}
alt={alt}
/>
{emoji.colons}

Wyświetl plik

@ -1,39 +1,39 @@
import { Portal } from '@reach/portal';
import classNames from 'clsx';
import clsx from 'clsx';
import { List as ImmutableList } from 'immutable';
import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
import AutosuggestEmoji, { Emoji } from 'soapbox/components/autosuggest-emoji';
import AutosuggestEmoji from 'soapbox/components/autosuggest-emoji';
import Icon from 'soapbox/components/icon';
import { Input } from 'soapbox/components/ui';
import { Input, Portal } from 'soapbox/components/ui';
import AutosuggestAccount from 'soapbox/features/compose/components/autosuggest-account';
import { isRtl } from 'soapbox/rtl';
import { textAtCursorMatchesToken } from 'soapbox/utils/suggestions';
import type { Menu, MenuItem } from 'soapbox/components/dropdown-menu';
import type { InputThemes } from 'soapbox/components/ui/input/input';
import type { Emoji } from 'soapbox/features/emoji';
export type AutoSuggestion = string | Emoji;
export interface IAutosuggestInput extends Pick<React.HTMLAttributes<HTMLInputElement>, 'onChange' | 'onKeyUp' | 'onKeyDown'> {
value: string,
suggestions: ImmutableList<any>,
disabled?: boolean,
placeholder?: string,
onSuggestionSelected: (tokenStart: number, lastToken: string | null, suggestion: AutoSuggestion) => void,
onSuggestionsClearRequested: () => void,
onSuggestionsFetchRequested: (token: string) => void,
autoFocus: boolean,
autoSelect: boolean,
className?: string,
id?: string,
searchTokens: string[],
maxLength?: number,
menu?: Menu,
renderSuggestion?: React.FC<{ id: string }>,
hidePortal?: boolean,
theme?: InputThemes,
value: string
suggestions: ImmutableList<any>
disabled?: boolean
placeholder?: string
onSuggestionSelected: (tokenStart: number, lastToken: string | null, suggestion: AutoSuggestion) => void
onSuggestionsClearRequested: () => void
onSuggestionsFetchRequested: (token: string) => void
autoFocus: boolean
autoSelect: boolean
className?: string
id?: string
searchTokens: string[]
maxLength?: number
menu?: Menu
renderSuggestion?: React.FC<{ id: string }>
hidePortal?: boolean
theme?: InputThemes
}
export default class AutosuggestInput extends ImmutablePureComponent<IAutosuggestInput> {
@ -199,7 +199,7 @@ export default class AutosuggestInput extends ImmutablePureComponent<IAutosugges
tabIndex={0}
key={key}
data-index={i}
className={classNames({
className={clsx({
'px-4 py-2.5 text-sm text-gray-700 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gray-100 dark:focus:bg-primary-800 group': true,
'bg-gray-100 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-800': i === selectedSuggestion,
})}
@ -235,7 +235,7 @@ export default class AutosuggestInput extends ImmutablePureComponent<IAutosugges
return menu.map((item, i) => (
<a
className={classNames('flex items-center space-x-2 px-4 py-2.5 text-sm cursor-pointer text-gray-700 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gray-100 dark:focus:bg-primary-800', { selected: suggestions.size - selectedSuggestion === i })}
className={clsx('flex cursor-pointer items-center space-x-2 px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-100 focus:bg-gray-100 dark:text-gray-500 dark:hover:bg-gray-800 dark:focus:bg-primary-800', { selected: suggestions.size - selectedSuggestion === i })}
href='#'
role='button'
tabIndex={0}
@ -302,7 +302,7 @@ export default class AutosuggestInput extends ImmutablePureComponent<IAutosugges
<Portal key='portal'>
<div
style={this.setPortalPosition()}
className={classNames({
className={clsx({
'fixed w-full z-[1001] shadow bg-white dark:bg-gray-900 rounded-lg py-1 dark:ring-2 dark:ring-primary-700 focus:outline-none': true,
hidden: !visible,
block: visible,

Wyświetl plik

@ -19,7 +19,7 @@ export const ADDRESS_ICONS: Record<string, string> = {
};
interface IAutosuggestLocation {
id: string,
id: string
}
const AutosuggestLocation: React.FC<IAutosuggestLocation> = ({ id }) => {

Wyświetl plik

@ -1,36 +1,36 @@
import { Portal } from '@reach/portal';
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Textarea from 'react-textarea-autosize';
import { Portal } from 'soapbox/components/ui';
import AutosuggestAccount from 'soapbox/features/compose/components/autosuggest-account';
import { isRtl } from 'soapbox/rtl';
import { textAtCursorMatchesToken } from 'soapbox/utils/suggestions';
import AutosuggestAccount from '../features/compose/components/autosuggest-account';
import { isRtl } from '../rtl';
import AutosuggestEmoji, { Emoji } from './autosuggest-emoji';
import AutosuggestEmoji from './autosuggest-emoji';
import type { List as ImmutableList } from 'immutable';
import type { Emoji } from 'soapbox/features/emoji';
interface IAutosuggesteTextarea {
id?: string,
value: string,
suggestions: ImmutableList<string>,
disabled: boolean,
placeholder: string,
onSuggestionSelected: (tokenStart: number, token: string | null, value: string | undefined) => void,
onSuggestionsClearRequested: () => void,
onSuggestionsFetchRequested: (token: string | number) => void,
onChange: React.ChangeEventHandler<HTMLTextAreaElement>,
onKeyUp?: React.KeyboardEventHandler<HTMLTextAreaElement>,
onKeyDown?: React.KeyboardEventHandler<HTMLTextAreaElement>,
onPaste: (files: FileList) => void,
autoFocus: boolean,
onFocus: () => void,
onBlur?: () => void,
condensed?: boolean,
children: React.ReactNode,
id?: string
value: string
suggestions: ImmutableList<string>
disabled: boolean
placeholder: string
onSuggestionSelected: (tokenStart: number, token: string | null, value: string | undefined) => void
onSuggestionsClearRequested: () => void
onSuggestionsFetchRequested: (token: string | number) => void
onChange: React.ChangeEventHandler<HTMLTextAreaElement>
onKeyUp?: React.KeyboardEventHandler<HTMLTextAreaElement>
onKeyDown?: React.KeyboardEventHandler<HTMLTextAreaElement>
onPaste: (files: FileList) => void
autoFocus: boolean
onFocus: () => void
onBlur?: () => void
condensed?: boolean
children: React.ReactNode
}
class AutosuggestTextarea extends ImmutablePureComponent<IAutosuggesteTextarea> {
@ -201,7 +201,7 @@ class AutosuggestTextarea extends ImmutablePureComponent<IAutosuggesteTextarea>
tabIndex={0}
key={key}
data-index={i}
className={classNames({
className={clsx({
'px-4 py-2.5 text-sm text-gray-700 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-gray-100 dark:focus:bg-primary-800 group': true,
'bg-gray-100 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-800': i === selectedSuggestion,
})}
@ -244,7 +244,7 @@ class AutosuggestTextarea extends ImmutablePureComponent<IAutosuggesteTextarea>
<Textarea
ref={this.setTextarea}
className={classNames('transition-[min-height] motion-reduce:transition-none dark:bg-transparent px-0 border-0 text-gray-800 dark:text-white placeholder:text-gray-600 dark:placeholder:text-gray-600 resize-none w-full focus:shadow-none focus:border-0 focus:ring-0', {
className={clsx('w-full resize-none border-0 px-0 text-gray-800 transition-[min-height] placeholder:text-gray-600 focus:border-0 focus:shadow-none focus:ring-0 motion-reduce:transition-none dark:bg-transparent dark:text-white dark:placeholder:text-gray-600', {
'min-h-[40px]': condensed,
'min-h-[100px]': !condensed,
})}
@ -271,7 +271,7 @@ class AutosuggestTextarea extends ImmutablePureComponent<IAutosuggesteTextarea>
<Portal key='portal'>
<div
style={this.setPortalPosition()}
className={classNames({
className={clsx({
'fixed z-1000 shadow bg-white dark:bg-gray-900 rounded-lg py-1 space-y-0 dark:ring-2 dark:ring-primary-700 focus:outline-none': true,
hidden: suggestionsHidden || suggestions.isEmpty(),
block: !suggestionsHidden && !suggestions.isEmpty(),

Wyświetl plik

@ -1,9 +1,9 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
interface IBadge {
title: React.ReactNode,
slug: string,
title: React.ReactNode
slug: string
}
/** Badge to display on a user's profile. */
const Badge: React.FC<IBadge> = ({ title, slug }) => {
@ -12,13 +12,13 @@ const Badge: React.FC<IBadge> = ({ title, slug }) => {
return (
<span
data-testid='badge'
className={classNames('inline-flex items-center px-2 py-0.5 rounded text-xs font-medium', {
className={clsx('inline-flex items-center rounded px-2 py-0.5 text-xs font-medium', {
'bg-fuchsia-700 text-white': slug === 'patron',
'bg-emerald-800 text-white': slug === 'badge:donor',
'bg-black text-white': slug === 'admin',
'bg-cyan-600 text-white': slug === 'moderator',
'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100': fallback,
'bg-white bg-opacity-75 text-gray-900': slug === 'opaque',
'bg-white/75 text-gray-900': slug === 'opaque',
})}
>
{title}

Wyświetl plik

@ -15,9 +15,9 @@ const messages = defineMessages({
});
interface IBirthdayInput {
value?: string,
onChange: (value: string) => void,
required?: boolean,
value?: string
onChange: (value: string) => void
required?: boolean
}
const BirthdayInput: React.FC<IBirthdayInput> = ({ value, onChange, required }) => {
@ -56,15 +56,15 @@ const BirthdayInput: React.FC<IBirthdayInput> = ({ value, onChange, required })
nextYearButtonDisabled,
date,
}: {
decreaseMonth(): void,
increaseMonth(): void,
prevMonthButtonDisabled: boolean,
nextMonthButtonDisabled: boolean,
decreaseYear(): void,
increaseYear(): void,
prevYearButtonDisabled: boolean,
nextYearButtonDisabled: boolean,
date: Date,
decreaseMonth(): void
increaseMonth(): void
prevMonthButtonDisabled: boolean
nextMonthButtonDisabled: boolean
decreaseYear(): void
increaseYear(): void
prevYearButtonDisabled: boolean
nextYearButtonDisabled: boolean
date: Date
}) => {
return (
<div className='flex flex-col gap-2'>

Wyświetl plik

@ -3,18 +3,18 @@ import React, { useRef, useEffect } from 'react';
interface IBlurhash {
/** Hash to render */
hash: string | null | undefined,
hash: string | null | undefined
/** Width of the blurred region in pixels. Defaults to 32. */
width?: number,
width?: number
/** Height of the blurred region in pixels. Defaults to width. */
height?: number,
height?: number
/**
* Whether dummy mode is enabled. If enabled, nothing is rendered
* and canvas left untouched.
*/
dummy?: boolean,
dummy?: boolean
/** className of the canvas element. */
className?: string,
className?: string
}
/**

Wyświetl plik

@ -5,7 +5,7 @@ import { Button, HStack, Input } from './ui';
interface ICopyableInput {
/** Text to be copied. */
value: string,
value: string
}
/** An input with copy abilities. */
@ -29,7 +29,7 @@ const CopyableInput: React.FC<ICopyableInput> = ({ value }) => {
type='text'
value={value}
className='rounded-r-none rtl:rounded-l-none rtl:rounded-r-lg'
outerClassName='flex-grow'
outerClassName='grow'
onClick={selectInput}
readOnly
/>

Wyświetl plik

@ -12,7 +12,7 @@ const messages = defineMessages({
});
interface IDomain {
domain: string,
domain: string
}
const Domain: React.FC<IDomain> = ({ domain }) => {

Wyświetl plik

@ -1,420 +0,0 @@
import classNames from 'clsx';
import { supportsPassiveEvents } from 'detect-passive-events';
import React from 'react';
import { spring } from 'react-motion';
// @ts-ignore: TODO: upgrade react-overlays. v3.1 and above have TS definitions
import Overlay from 'react-overlays/lib/Overlay';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { Counter, IconButton } from 'soapbox/components/ui';
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
import Motion from 'soapbox/features/ui/util/optional-motion';
import type { Status } from 'soapbox/types/entities';
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
let id = 0;
export interface MenuItem {
action?: React.EventHandler<React.KeyboardEvent | React.MouseEvent>,
middleClick?: React.EventHandler<React.MouseEvent>,
text: string,
href?: string,
to?: string,
newTab?: boolean,
isLogout?: boolean,
icon?: string,
count?: number,
destructive?: boolean,
meta?: string,
active?: boolean,
}
export type Menu = Array<MenuItem | null>;
interface IDropdownMenu extends RouteComponentProps {
items: Menu,
onClose: () => void,
style?: React.CSSProperties,
placement?: DropdownPlacement,
arrowOffsetLeft?: string,
arrowOffsetTop?: string,
openedViaKeyboard: boolean,
}
interface IDropdownMenuState {
mounted: boolean,
}
class DropdownMenu extends React.PureComponent<IDropdownMenu, IDropdownMenuState> {
static defaultProps: Partial<IDropdownMenu> = {
style: {},
placement: 'bottom',
};
state = {
mounted: false,
};
node: HTMLDivElement | null = null;
focusedItem: HTMLAnchorElement | null = null;
handleDocumentClick = (e: Event) => {
if (this.node && !this.node.contains(e.target as Node)) {
this.props.onClose();
}
};
componentDidMount() {
document.addEventListener('click', this.handleDocumentClick, false);
document.addEventListener('keydown', this.handleKeyDown, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
if (this.focusedItem && this.props.openedViaKeyboard) {
this.focusedItem.focus({ preventScroll: true });
}
this.setState({ mounted: true });
}
componentWillUnmount() {
document.removeEventListener('click', this.handleDocumentClick);
document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('touchend', this.handleDocumentClick);
}
setRef: React.RefCallback<HTMLDivElement> = c => {
this.node = c;
};
setFocusRef: React.RefCallback<HTMLAnchorElement> = c => {
this.focusedItem = c;
};
handleKeyDown = (e: KeyboardEvent) => {
if (!this.node) return;
const items = Array.from(this.node.getElementsByTagName('a'));
const index = items.indexOf(document.activeElement as any);
let element = null;
switch (e.key) {
case 'ArrowDown':
element = items[index + 1] || items[0];
break;
case 'ArrowUp':
element = items[index - 1] || items[items.length - 1];
break;
case 'Tab':
if (e.shiftKey) {
element = items[index - 1] || items[items.length - 1];
} else {
element = items[index + 1] || items[0];
}
break;
case 'Home':
element = items[0];
break;
case 'End':
element = items[items.length - 1];
break;
case 'Escape':
this.props.onClose();
break;
}
if (element) {
element.focus();
e.preventDefault();
e.stopPropagation();
}
};
handleItemKeyPress: React.EventHandler<React.KeyboardEvent> = e => {
if (e.key === 'Enter' || e.key === ' ') {
this.handleClick(e);
}
};
handleClick: React.EventHandler<React.MouseEvent | React.KeyboardEvent> = e => {
const i = Number(e.currentTarget.getAttribute('data-index'));
const item = this.props.items[i];
if (!item) return;
const { action, to } = item;
this.props.onClose();
e.stopPropagation();
if (to) {
e.preventDefault();
this.props.history.push(to);
} else if (typeof action === 'function') {
e.preventDefault();
action(e);
}
};
handleMiddleClick: React.EventHandler<React.MouseEvent> = e => {
const i = Number(e.currentTarget.getAttribute('data-index'));
const item = this.props.items[i];
if (!item) return;
const { middleClick } = item;
this.props.onClose();
if (e.button === 1 && typeof middleClick === 'function') {
e.preventDefault();
middleClick(e);
}
};
handleAuxClick: React.EventHandler<React.MouseEvent> = e => {
if (e.button === 1) {
this.handleMiddleClick(e);
}
};
renderItem(option: MenuItem | null, i: number): JSX.Element {
if (option === null) {
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
}
const { text, href, to, newTab, isLogout, icon, count, destructive } = option;
return (
<li className={classNames('dropdown-menu__item truncate', { destructive })} key={`${text}-${i}`}>
<a
href={href || to || '#'}
role='button'
tabIndex={0}
ref={i === 0 ? this.setFocusRef : null}
onClick={this.handleClick}
onAuxClick={this.handleAuxClick}
onKeyPress={this.handleItemKeyPress}
data-index={i}
target={newTab ? '_blank' : undefined}
data-method={isLogout ? 'delete' : undefined}
title={text}
>
{icon && <SvgIcon src={icon} className='mr-3 h-5 w-5 flex-none rtl:ml-3 rtl:mr-0' />}
<span className='truncate'>{text}</span>
{count ? (
<span className='ml-auto h-5 w-5 flex-none'>
<Counter count={count} />
</span>
) : null}
</a>
</li>
);
}
render() {
const { items, style, placement, arrowOffsetLeft, arrowOffsetTop } = this.props;
const { mounted } = this.state;
return (
<Motion defaultStyle={{ opacity: 0, scaleX: 1, scaleY: 1 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => (
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div
className={`dropdown-menu ${placement}`}
style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : undefined }}
ref={this.setRef}
data-testid='dropdown-menu'
>
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
<ul>
{items.map((option, i) => this.renderItem(option, i))}
</ul>
</div>
)}
</Motion>
);
}
}
const RouterDropdownMenu = withRouter(DropdownMenu);
export interface IDropdown extends RouteComponentProps {
icon?: string,
src?: string,
items: Menu,
size?: number,
active?: boolean,
pressed?: boolean,
title?: string,
disabled?: boolean,
status?: Status,
isUserTouching?: () => boolean,
isModalOpen?: boolean,
onOpen?: (
id: number,
onItemClick: React.EventHandler<React.MouseEvent | React.KeyboardEvent>,
dropdownPlacement: DropdownPlacement,
keyboard: boolean,
) => void,
onClose?: (id: number) => void,
dropdownPlacement?: string,
openDropdownId?: number | null,
openedViaKeyboard?: boolean,
text?: string,
onShiftClick?: React.EventHandler<React.MouseEvent | React.KeyboardEvent>,
children?: JSX.Element,
dropdownMenuStyle?: React.CSSProperties,
}
interface IDropdownState {
id: number,
open: boolean,
}
export type DropdownPlacement = 'top' | 'bottom';
class Dropdown extends React.PureComponent<IDropdown, IDropdownState> {
static defaultProps: Partial<IDropdown> = {
title: 'Menu',
};
state = {
id: id++,
open: false,
};
target: HTMLButtonElement | null = null;
activeElement: Element | null = null;
handleClick: React.EventHandler<React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLButtonElement>> = e => {
const { onOpen, onShiftClick, openDropdownId } = this.props;
e.stopPropagation();
if (onShiftClick && e.shiftKey) {
e.preventDefault();
onShiftClick(e);
} else if (this.state.id === openDropdownId) {
this.handleClose();
} else if (onOpen) {
const { top } = e.currentTarget.getBoundingClientRect();
const placement: DropdownPlacement = top * 2 < innerHeight ? 'bottom' : 'top';
onOpen(this.state.id, this.handleItemClick, placement, e.type !== 'click');
}
};
handleClose = () => {
if (this.activeElement && this.activeElement === this.target) {
(this.activeElement as HTMLButtonElement).focus();
this.activeElement = null;
}
if (this.props.onClose) {
this.props.onClose(this.state.id);
}
};
handleMouseDown: React.EventHandler<React.MouseEvent | React.KeyboardEvent> = () => {
if (!this.state.open) {
this.activeElement = document.activeElement;
}
};
handleButtonKeyDown: React.EventHandler<React.KeyboardEvent> = (e) => {
switch (e.key) {
case ' ':
case 'Enter':
this.handleMouseDown(e);
break;
}
};
handleKeyPress: React.EventHandler<React.KeyboardEvent<HTMLButtonElement>> = (e) => {
switch (e.key) {
case ' ':
case 'Enter':
this.handleClick(e);
e.stopPropagation();
e.preventDefault();
break;
}
};
handleItemClick: React.EventHandler<React.MouseEvent> = e => {
const i = Number(e.currentTarget.getAttribute('data-index'));
const item = this.props.items[i];
if (!item) return;
const { action, to } = item;
this.handleClose();
e.preventDefault();
e.stopPropagation();
if (typeof action === 'function') {
action(e);
} else if (to) {
this.props.history?.push(to);
}
};
setTargetRef: React.RefCallback<HTMLButtonElement> = c => {
this.target = c;
};
findTarget = () => {
return this.target;
};
componentWillUnmount = () => {
if (this.state.id === this.props.openDropdownId) {
this.handleClose();
}
};
render() {
const { src = require('@tabler/icons/dots.svg'), items, title, disabled, dropdownPlacement, openDropdownId, openedViaKeyboard = false, pressed, text, children, dropdownMenuStyle } = this.props;
const open = this.state.id === openDropdownId;
return (
<>
{children ? (
React.cloneElement(children, {
disabled,
onClick: this.handleClick,
onMouseDown: this.handleMouseDown,
onKeyDown: this.handleButtonKeyDown,
onKeyPress: this.handleKeyPress,
ref: this.setTargetRef,
})
) : (
<IconButton
disabled={disabled}
className={classNames({
'text-gray-600 hover:text-gray-700 dark:hover:text-gray-500': true,
'text-gray-700 dark:text-gray-500': open,
})}
title={title}
src={src}
aria-pressed={pressed}
text={text}
onClick={this.handleClick}
onMouseDown={this.handleMouseDown}
onKeyDown={this.handleButtonKeyDown}
onKeyPress={this.handleKeyPress}
ref={this.setTargetRef}
/>
)}
<Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
<RouterDropdownMenu items={items} onClose={this.handleClose} openedViaKeyboard={openedViaKeyboard} style={dropdownMenuStyle} />
</Overlay>
</>
);
}
}
export default withRouter(Dropdown);

Wyświetl plik

@ -0,0 +1,109 @@
import clsx from 'clsx';
import React, { useEffect, useRef } from 'react';
import { useHistory } from 'react-router-dom';
import { Counter, Icon } from '../ui';
export interface MenuItem {
action?: React.EventHandler<React.KeyboardEvent | React.MouseEvent>
active?: boolean
count?: number
destructive?: boolean
href?: string
icon?: string
meta?: string
middleClick?(event: React.MouseEvent): void
target?: React.HTMLAttributeAnchorTarget
text: string
to?: string
}
interface IDropdownMenuItem {
index: number
item: MenuItem | null
onClick?(): void
}
const DropdownMenuItem = ({ index, item, onClick }: IDropdownMenuItem) => {
const history = useHistory();
const itemRef = useRef<HTMLAnchorElement>(null);
const handleClick: React.EventHandler<React.MouseEvent | React.KeyboardEvent> = (event) => {
event.stopPropagation();
if (!item) return;
if (onClick) onClick();
if (item.to) {
event.preventDefault();
history.push(item.to);
} else if (typeof item.action === 'function') {
event.preventDefault();
item.action(event);
}
};
const handleAuxClick: React.EventHandler<React.MouseEvent> = (event) => {
if (!item) return;
if (onClick) onClick();
if (event.button === 1 && item.middleClick) {
item.middleClick(event);
}
};
const handleItemKeyPress: React.EventHandler<React.KeyboardEvent> = (event) => {
if (event.key === 'Enter' || event.key === ' ') {
handleClick(event);
}
};
useEffect(() => {
const firstItem = index === 0;
if (itemRef.current && firstItem) {
itemRef.current.focus({ preventScroll: true });
}
}, [itemRef.current, index]);
if (item === null) {
return <li className='my-1 mx-2 h-[2px] bg-gray-100 dark:bg-gray-800' />;
}
return (
<li className='truncate focus-visible:ring-2 focus-visible:ring-primary-500'>
<a
href={item.href || item.to || '#'}
role='button'
tabIndex={0}
ref={itemRef}
data-index={index}
onClick={handleClick}
onAuxClick={handleAuxClick}
onKeyPress={handleItemKeyPress}
target={item.target}
title={item.text}
className={
clsx({
'flex px-4 py-2.5 text-sm text-gray-700 dark:text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none cursor-pointer': true,
'text-danger-600 dark:text-danger-400': item.destructive,
})
}
>
{item.icon && <Icon src={item.icon} className='mr-3 h-5 w-5 flex-none rtl:ml-3 rtl:mr-0' />}
<span className='truncate'>{item.text}</span>
{item.count ? (
<span className='ml-auto h-5 w-5 flex-none'>
<Counter count={item.count} />
</span>
) : null}
</a>
</li>
);
};
export default DropdownMenuItem;

Wyświetl plik

@ -0,0 +1,346 @@
import { offset, Placement, useFloating, flip, arrow } from '@floating-ui/react';
import clsx from 'clsx';
import { supportsPassiveEvents } from 'detect-passive-events';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useHistory } from 'react-router-dom';
import {
closeDropdownMenu as closeDropdownMenuRedux,
openDropdownMenu,
} from 'soapbox/actions/dropdown-menu';
import { closeModal, openModal } from 'soapbox/actions/modals';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { isUserTouching } from 'soapbox/is-mobile';
import { IconButton, Portal } from '../ui';
import DropdownMenuItem, { MenuItem } from './dropdown-menu-item';
import type { Status } from 'soapbox/types/entities';
export type Menu = Array<MenuItem | null>;
interface IDropdownMenu {
children?: React.ReactElement
disabled?: boolean
items: Menu
onClose?: () => void
onOpen?: () => void
onShiftClick?: React.EventHandler<React.MouseEvent | React.KeyboardEvent>
placement?: Placement
src?: string
status?: Status
title?: string
}
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
const DropdownMenu = (props: IDropdownMenu) => {
const {
children,
disabled,
items,
onClose,
onOpen,
onShiftClick,
placement: initialPlacement = 'top',
src = require('@tabler/icons/dots.svg'),
title = 'Menu',
...filteredProps
} = props;
const dispatch = useAppDispatch();
const history = useHistory();
const [isOpen, setIsOpen] = useState<boolean>(false);
const isOpenRedux = useAppSelector(state => state.dropdown_menu.isOpen);
const arrowRef = useRef<HTMLDivElement>(null);
const activeElement = useRef<Element | null>(null);
const isOnMobile = isUserTouching();
const { x, y, strategy, refs, middlewareData, placement } = useFloating<HTMLButtonElement>({
placement: initialPlacement,
middleware: [
offset(12),
flip(),
arrow({
element: arrowRef,
}),
],
});
const handleClick: React.EventHandler<
React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLButtonElement>
> = (event) => {
event.stopPropagation();
if (onShiftClick && event.shiftKey) {
event.preventDefault();
onShiftClick(event);
return;
}
if (isOpen) {
handleClose();
} else {
handleOpen();
}
};
/**
* On mobile screens, let's replace the Popper dropdown with a Modal.
*/
const handleOpen = () => {
if (isOnMobile) {
dispatch(
openModal('ACTIONS', {
status: filteredProps.status,
actions: items,
onClick: handleItemClick,
}),
);
} else {
dispatch(openDropdownMenu());
setIsOpen(true);
}
if (onOpen) {
onOpen();
}
};
const handleClose = () => {
if (activeElement.current && activeElement.current === refs.reference.current) {
(activeElement.current as any).focus();
activeElement.current = null;
}
if (isOnMobile) {
dispatch(closeModal('ACTIONS'));
} else {
closeDropdownMenu();
setIsOpen(false);
}
if (onClose) {
onClose();
}
};
const closeDropdownMenu = () => {
if (isOpenRedux) {
dispatch(closeDropdownMenuRedux());
}
};
const handleMouseDown: React.EventHandler<React.MouseEvent | React.KeyboardEvent> = () => {
if (!isOpen) {
activeElement.current = document.activeElement;
}
};
const handleButtonKeyDown: React.EventHandler<React.KeyboardEvent> = (event) => {
switch (event.key) {
case ' ':
case 'Enter':
handleMouseDown(event);
break;
}
};
const handleKeyPress: React.EventHandler<React.KeyboardEvent<HTMLButtonElement>> = (event) => {
switch (event.key) {
case ' ':
case 'Enter':
event.stopPropagation();
event.preventDefault();
handleClick(event);
break;
}
};
const handleItemClick: React.EventHandler<React.MouseEvent> = (event) => {
event.preventDefault();
event.stopPropagation();
const i = Number(event.currentTarget.getAttribute('data-index'));
const item = items[i];
if (!item) return;
const { action, to } = item;
handleClose();
if (typeof action === 'function') {
action(event);
} else if (to) {
history.push(to);
}
};
const handleDocumentClick = (event: Event) => {
if (refs.floating.current && !refs.floating.current.contains(event.target as Node)) {
handleClose();
}
};
const handleKeyDown = (e: KeyboardEvent) => {
if (!refs.floating.current) return;
const items = Array.from(refs.floating.current.getElementsByTagName('a'));
const index = items.indexOf(document.activeElement as any);
let element = null;
switch (e.key) {
case 'ArrowDown':
element = items[index + 1] || items[0];
break;
case 'ArrowUp':
element = items[index - 1] || items[items.length - 1];
break;
case 'Tab':
if (e.shiftKey) {
element = items[index - 1] || items[items.length - 1];
} else {
element = items[index + 1] || items[0];
}
break;
case 'Home':
element = items[0];
break;
case 'End':
element = items[items.length - 1];
break;
case 'Escape':
handleClose();
break;
}
if (element) {
element.focus();
e.preventDefault();
e.stopPropagation();
}
};
const arrowProps: React.CSSProperties = useMemo(() => {
if (middlewareData.arrow) {
const { x, y } = middlewareData.arrow;
const staticPlacement = {
top: 'bottom',
right: 'left',
bottom: 'top',
left: 'right',
}[placement.split('-')[0]];
return {
left: x !== null ? `${x}px` : '',
top: y !== null ? `${y}px` : '',
// Ensure the static side gets unset when
// flipping to other placements' axes.
right: '',
bottom: '',
[staticPlacement as string]: `${(-(arrowRef.current?.offsetWidth || 0)) / 2}px`,
transform: 'rotate(45deg)',
};
}
return {};
}, [middlewareData.arrow, placement]);
useEffect(() => {
return () => {
closeDropdownMenu();
};
}, []);
useEffect(() => {
document.addEventListener('click', handleDocumentClick, false);
document.addEventListener('keydown', handleKeyDown, false);
document.addEventListener('touchend', handleDocumentClick, listenerOptions);
return () => {
document.removeEventListener('click', handleDocumentClick);
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('touchend', handleDocumentClick);
};
}, [refs.floating.current]);
if (items.length === 0) {
return null;
}
return (
<>
{children ? (
React.cloneElement(children, {
disabled,
onClick: handleClick,
onMouseDown: handleMouseDown,
onKeyDown: handleButtonKeyDown,
onKeyPress: handleKeyPress,
ref: refs.setReference,
})
) : (
<IconButton
disabled={disabled}
className={clsx({
'text-gray-600 hover:text-gray-700 dark:hover:text-gray-500': true,
'text-gray-700 dark:text-gray-500': isOpen,
})}
title={title}
src={src}
onClick={handleClick}
onMouseDown={handleMouseDown}
onKeyDown={handleButtonKeyDown}
onKeyPress={handleKeyPress}
ref={refs.setReference}
/>
)}
{isOpen ? (
<Portal>
<div
data-testid='dropdown-menu'
ref={refs.setFloating}
className={
clsx('z-[1001] w-56 rounded-md bg-white py-1 shadow-lg transition-opacity duration-100 focus:outline-none dark:bg-gray-900 dark:ring-2 dark:ring-primary-700', {
'opacity-0 pointer-events-none': !isOpen,
})
}
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
}}
>
<ul>
{items.map((item, idx) => (
<DropdownMenuItem
key={idx}
item={item}
index={idx}
onClick={handleClose}
/>
))}
</ul>
{/* Arrow */}
<div
ref={arrowRef}
style={arrowProps}
className='pointer-events-none absolute z-[-1] h-3 w-3 bg-white dark:bg-gray-900'
/>
</div>
</Portal>
) : null}
</>
);
};
export default DropdownMenu;

Wyświetl plik

@ -0,0 +1,3 @@
export { default } from './dropdown-menu';
export type { Menu } from './dropdown-menu';
export type { MenuItem } from './dropdown-menu-item';

Wyświetl plik

@ -1,142 +0,0 @@
// import classNames from 'clsx';
import React from 'react';
import { HotKeys } from 'react-hotkeys';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
import { EmojiSelector as RealEmojiSelector } from 'soapbox/components/ui';
import type { List as ImmutableList } from 'immutable';
import type { RootState } from 'soapbox/store';
const mapStateToProps = (state: RootState) => ({
allowedEmoji: getSoapboxConfig(state).allowedEmoji,
});
interface IEmojiSelector {
allowedEmoji: ImmutableList<string>,
onReact: (emoji: string) => void,
onUnfocus: () => void,
visible: boolean,
focused?: boolean,
}
class EmojiSelector extends ImmutablePureComponent<IEmojiSelector> {
static defaultProps: Partial<IEmojiSelector> = {
onReact: () => { },
onUnfocus: () => { },
visible: false,
};
node?: HTMLDivElement = undefined;
handleBlur: React.FocusEventHandler<HTMLDivElement> = e => {
const { focused, onUnfocus } = this.props;
if (focused && (!e.currentTarget || !e.currentTarget.classList.contains('emoji-react-selector__emoji'))) {
onUnfocus();
}
};
_selectPreviousEmoji = (i: number): void => {
if (!this.node) return;
if (i !== 0) {
const button: HTMLButtonElement | null = this.node.querySelector(`.emoji-react-selector__emoji:nth-child(${i})`);
button?.focus();
} else {
const button: HTMLButtonElement | null = this.node.querySelector('.emoji-react-selector__emoji:last-child');
button?.focus();
}
};
_selectNextEmoji = (i: number) => {
if (!this.node) return;
if (i !== this.props.allowedEmoji.size - 1) {
const button: HTMLButtonElement | null = this.node.querySelector(`.emoji-react-selector__emoji:nth-child(${i + 2})`);
button?.focus();
} else {
const button: HTMLButtonElement | null = this.node.querySelector('.emoji-react-selector__emoji:first-child');
button?.focus();
}
};
handleKeyDown = (i: number): React.KeyboardEventHandler => e => {
const { onUnfocus } = this.props;
switch (e.key) {
case 'Tab':
e.preventDefault();
if (e.shiftKey) this._selectPreviousEmoji(i);
else this._selectNextEmoji(i);
break;
case 'Left':
case 'ArrowLeft':
this._selectPreviousEmoji(i);
break;
case 'Right':
case 'ArrowRight':
this._selectNextEmoji(i);
break;
case 'Escape':
onUnfocus();
break;
}
};
handleReact = (emoji: string) => (): void => {
const { onReact, focused, onUnfocus } = this.props;
onReact(emoji);
if (focused) {
onUnfocus();
}
};
handlers = {
open: () => { },
};
setRef = (c: HTMLDivElement): void => {
this.node = c;
};
render() {
const { visible, focused, allowedEmoji, onReact } = this.props;
return (
<HotKeys handlers={this.handlers}>
{/*<div
className={classNames('flex absolute bg-white dark:bg-gray-500 px-2 py-3 rounded-full shadow-md opacity-0 pointer-events-none duration-100 w-max', { 'opacity-100 pointer-events-auto z-[999]': visible || focused })}
onBlur={this.handleBlur}
ref={this.setRef}
>
{allowedEmoji.map((emoji, i) => (
<button
key={i}
className='emoji-react-selector__emoji'
onClick={this.handleReact(emoji)}
onKeyDown={this.handleKeyDown(i)}
tabIndex={(visible || focused) ? 0 : -1}
>
<Emoji emoji={emoji} />
</button>
))}
</div>*/}
<RealEmojiSelector
emojis={allowedEmoji.toArray()}
onReact={onReact}
visible={visible}
focused={focused}
/>
</HotKeys>
);
}
}
export default connect(mapStateToProps)(EmojiSelector);

Wyświetl plik

@ -31,10 +31,10 @@ interface Props extends ReturnType<typeof mapStateToProps> {
}
type State = {
hasError: boolean,
error: any,
componentStack: any,
browser?: Bowser.Parser.Parser,
hasError: boolean
error: any
componentStack: any
browser?: Bowser.Parser.Parser
}
class ErrorBoundary extends React.PureComponent<Props, State> {

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@ -51,7 +51,7 @@ const EventPreview: React.FC<IEventPreview> = ({ status, className, hideAction,
));
return (
<div className={classNames('w-full rounded-lg bg-gray-100 dark:bg-primary-800 relative overflow-hidden', className)}>
<div className={clsx('relative w-full overflow-hidden rounded-lg bg-gray-100 dark:bg-primary-800', className)}>
<div className='absolute top-28 right-3'>
{floatingAction && action}
</div>

Wyświetl plik

@ -3,14 +3,14 @@ import React, { useEffect, useRef } from 'react';
import { isIOS } from 'soapbox/is-mobile';
interface IExtendedVideoPlayer {
src: string,
alt?: string,
width?: number,
height?: number,
time?: number,
controls?: boolean,
muted?: boolean,
onClick?: () => void,
src: string
alt?: string
width?: number
height?: number
time?: number
controls?: boolean
muted?: boolean
onClick?: () => void
}
const ExtendedVideoPlayer: React.FC<IExtendedVideoPlayer> = ({ src, alt, time, controls, muted, onClick }) => {

Wyświetl plik

@ -5,13 +5,13 @@
* @see soapbox/components/icon
*/
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
export interface IForkAwesomeIcon extends React.HTMLAttributes<HTMLLIElement> {
id: string,
className?: string,
fixedWidth?: boolean,
id: string
className?: string
fixedWidth?: boolean
}
const ForkAwesomeIcon: React.FC<IForkAwesomeIcon> = ({ id, className, fixedWidth, ...rest }) => {
@ -25,7 +25,7 @@ const ForkAwesomeIcon: React.FC<IForkAwesomeIcon> = ({ id, className, fixedWidth
<i
role='img'
// alt={alt}
className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })}
className={clsx('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })}
{...rest}
/>
);

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
@ -30,7 +30,7 @@ const GdprBanner: React.FC = () => {
}
return (
<Banner theme='opaque' className={classNames('transition-transform', { 'translate-y-full': slideout })}>
<Banner theme='opaque' className={clsx('transition-transform', { 'translate-y-full': slideout })}>
<div className='flex flex-col space-y-4 rtl:space-x-reverse lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:space-x-4'>
<Stack space={2}>
<Text size='xl' weight='bold'>

Wyświetl plik

@ -1,7 +1,12 @@
import React from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { defineMessages, useIntl } from 'react-intl';
import { Avatar, HStack, Icon, Stack, Text } from './ui';
import GroupMemberCount from 'soapbox/features/group/components/group-member-count';
import GroupPrivacy from 'soapbox/features/group/components/group-privacy';
import GroupRelationship from 'soapbox/features/group/components/group-relationship';
import GroupAvatar from './groups/group-avatar';
import { HStack, Stack, Text } from './ui';
import type { Group as GroupEntity } from 'soapbox/types/entities';
@ -17,43 +22,42 @@ const GroupCard: React.FC<IGroupCard> = ({ group }) => {
const intl = useIntl();
return (
<div className='overflow-hidden'>
<Stack className='rounded-lg border border-solid border-gray-300 bg-white dark:border-primary-800 dark:bg-primary-900 sm:rounded-xl'>
<div className='relative -m-[1px] mb-0 h-[120px] rounded-t-lg bg-primary-100 dark:bg-gray-800 sm:rounded-t-xl'>
{group.header && <img className='h-full w-full rounded-t-lg object-cover sm:rounded-t-xl' src={group.header} alt={intl.formatMessage(messages.groupHeader)} />}
<div className='absolute left-1/2 bottom-0 -translate-x-1/2 translate-y-1/2'>
<Avatar className='ring-2 ring-white dark:ring-primary-900' src={group.avatar} size={64} />
</div>
</div>
<Stack className='p-3 pt-9' alignItems='center' space={3}>
<Text size='lg' weight='bold' dangerouslySetInnerHTML={{ __html: group.display_name_html }} />
<HStack className='text-gray-700 dark:text-gray-600' space={3} wrap>
{group.relationship?.role === 'admin' ? (
<HStack space={1} alignItems='center'>
<Icon className='h-4 w-4' src={require('@tabler/icons/users.svg')} />
<span><FormattedMessage id='group.role.admin' defaultMessage='Admin' /></span>
</HStack>
) : group.relationship?.role === 'moderator' && (
<HStack space={1} alignItems='center'>
<Icon className='h-4 w-4' src={require('@tabler/icons/gavel.svg')} />
<span><FormattedMessage id='group.role.moderator' defaultMessage='Moderator' /></span>
</HStack>
)}
{group.locked ? (
<HStack space={1} alignItems='center'>
<Icon className='h-4 w-4' src={require('@tabler/icons/lock.svg')} />
<span><FormattedMessage id='group.privacy.locked' defaultMessage='Private' /></span>
</HStack>
) : (
<HStack space={1} alignItems='center'>
<Icon className='h-4 w-4' src={require('@tabler/icons/world.svg')} />
<span><FormattedMessage id='group.privacy.public' defaultMessage='Public' /></span>
</HStack>
)}
</HStack>
</Stack>
<Stack
className='relative h-[240px] rounded-lg border border-solid border-gray-300 bg-white dark:border-primary-800 dark:bg-primary-900'
data-testid='group-card'
>
{/* Group Cover Image */}
<Stack grow className='relative basis-1/2 rounded-t-lg bg-primary-100 dark:bg-gray-800'>
{group.header && (
<img
className='absolute inset-0 h-full w-full rounded-t-lg object-cover'
src={group.header} alt={intl.formatMessage(messages.groupHeader)}
/>
)}
</Stack>
</div>
{/* Group Avatar */}
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
<GroupAvatar group={group} size={64} withRing />
</div>
{/* Group Info */}
<Stack alignItems='center' justifyContent='end' grow className='basis-1/2 py-4' space={0.5}>
<HStack alignItems='center' space={1.5}>
<Text size='lg' weight='bold' dangerouslySetInnerHTML={{ __html: group.display_name_html }} />
{group.relationship?.pending_requests && (
<div className='h-2 w-2 rounded-full bg-secondary-500' />
)}
</HStack>
<HStack className='text-gray-700 dark:text-gray-600' space={2} wrap>
<GroupRelationship group={group} />
<GroupPrivacy group={group} />
<GroupMemberCount group={group} />
</HStack>
</Stack>
</Stack>
);
};

Wyświetl plik

@ -0,0 +1,37 @@
import clsx from 'clsx';
import React from 'react';
import { GroupRoles } from 'soapbox/schemas/group-member';
import { Avatar } from '../ui';
import type { Group } from 'soapbox/schemas';
interface IGroupAvatar {
group: Group
size: number
withRing?: boolean
}
const GroupAvatar = (props: IGroupAvatar) => {
const { group, size, withRing = false } = props;
const isOwner = group.relationship?.role === GroupRoles.OWNER;
return (
<Avatar
className={
clsx('relative rounded-full', {
'shadow-[0_0_0_2px_theme(colors.primary.600),0_0_0_4px_theme(colors.white)]': isOwner && withRing,
'dark:shadow-[0_0_0_2px_theme(colors.primary.600),0_0_0_4px_theme(colors.gray.800)]': isOwner && withRing,
'shadow-[0_0_0_2px_theme(colors.primary.600)]': isOwner && !withRing,
'shadow-[0_0_0_2px_theme(colors.white)] dark:shadow-[0_0_0_2px_theme(colors.gray.800)]': !isOwner && withRing,
})
}
src={group.avatar}
size={size}
/>
);
};
export default GroupAvatar;

Wyświetl plik

@ -0,0 +1,99 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { Button, Divider, HStack, Popover, Stack, Text } from 'soapbox/components/ui';
import GroupMemberCount from 'soapbox/features/group/components/group-member-count';
import GroupPrivacy from 'soapbox/features/group/components/group-privacy';
import GroupAvatar from '../group-avatar';
import type { Group } from 'soapbox/schemas';
interface IGroupPopoverContainer {
children: React.ReactElement<any, string | React.JSXElementConstructor<any>>
isEnabled: boolean
group: Group
}
const messages = defineMessages({
title: { id: 'group.popover.title', defaultMessage: 'Membership required' },
summary: { id: 'group.popover.summary', defaultMessage: 'You must be a member of the group in order to reply to this status.' },
action: { id: 'group.popover.action', defaultMessage: 'View Group' },
});
const GroupPopover = (props: IGroupPopoverContainer) => {
const { children, group, isEnabled } = props;
const intl = useIntl();
if (!isEnabled) {
return children;
}
return (
<Popover
interaction='click'
referenceElementClassName='cursor-pointer'
content={
<Stack space={4} className='w-80'>
<Stack
className='relative h-60 rounded-lg bg-white dark:border-primary-800 dark:bg-primary-900'
data-testid='group-card'
>
{/* Group Cover Image */}
<Stack grow className='relative basis-1/2 rounded-t-lg bg-primary-100 dark:bg-gray-800'>
{group.header && (
<img
className='absolute inset-0 h-full w-full rounded-t-lg object-cover'
src={group.header}
alt=''
/>
)}
</Stack>
{/* Group Avatar */}
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
<GroupAvatar group={group} size={64} withRing />
</div>
{/* Group Info */}
<Stack alignItems='center' justifyContent='end' grow className='basis-1/2 py-4' space={0.5}>
<Text size='lg' weight='bold' dangerouslySetInnerHTML={{ __html: group.display_name_html }} />
<HStack className='text-gray-700 dark:text-gray-600' space={2} wrap>
<GroupPrivacy group={group} />
<GroupMemberCount group={group} />
</HStack>
</Stack>
</Stack>
<Divider />
<Stack space={0.5} className='px-4'>
<Text weight='semibold'>
{intl.formatMessage(messages.title)}
</Text>
<Text theme='muted'>
{intl.formatMessage(messages.summary)}
</Text>
</Stack>
<div className='px-4 pb-4'>
<Link to={`/groups/${group.id}`}>
<Button type='button' theme='secondary' block>
{intl.formatMessage(messages.action)}
</Button>
</Link>
</div>
</Stack>
}
isFlush
children={
<div className='inline-block'>{children}</div>
}
/>
);
};
export default GroupPopover;

Wyświetl plik

@ -10,7 +10,7 @@ import { HStack, Stack, Text } from './ui';
import type { Tag } from 'soapbox/types/entities';
interface IHashtag {
hashtag: Tag,
hashtag: Tag
}
const Hashtag: React.FC<IHashtag> = ({ hashtag }) => {

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import debounce from 'lodash/debounce';
import React, { useRef } from 'react';
@ -15,10 +15,10 @@ const showProfileHoverCard = debounce((dispatch, ref, accountId) => {
}, 600);
interface IHoverRefWrapper {
accountId: string,
inline?: boolean,
className?: string,
children: React.ReactNode,
accountId: string
inline?: boolean
className?: string
children: React.ReactNode
}
/** Makes a profile hover card appear when the wrapped element is hovered. */
@ -47,7 +47,7 @@ export const HoverRefWrapper: React.FC<IHoverRefWrapper> = ({ accountId, childre
return (
<Elem
ref={ref}
className={classNames('hover-ref-wrapper', className)}
className={clsx('hover-ref-wrapper', className)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import debounce from 'lodash/debounce';
import React, { useRef } from 'react';
import { useDispatch } from 'react-redux';
@ -14,10 +14,10 @@ const showStatusHoverCard = debounce((dispatch, ref, statusId) => {
}, 300);
interface IHoverStatusWrapper {
statusId: any,
inline: boolean,
className?: string,
children: React.ReactNode,
statusId: any
inline: boolean
className?: string
children: React.ReactNode
}
/** Makes a status hover card appear when the wrapped element is hovered. */
@ -45,7 +45,7 @@ export const HoverStatusWrapper: React.FC<IHoverStatusWrapper> = ({ statusId, ch
return (
<Elem
ref={ref}
className={classNames('hover-status-wrapper', className)}
className={clsx('hover-status-wrapper', className)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import Icon from 'soapbox/components/icon';
@ -66,7 +66,7 @@ const IconButton: React.FC<IIconButton> = ({
}
};
const classes = classNames(className, 'icon-button', {
const classes = clsx(className, 'icon-button', {
active,
disabled,
});

Wyświetl plik

@ -4,10 +4,10 @@ import Icon, { IIcon } from 'soapbox/components/icon';
import { Counter } from 'soapbox/components/ui';
interface IIconWithCounter extends React.HTMLAttributes<HTMLDivElement> {
count: number,
count: number
countMax?: number
icon?: string;
src?: string;
icon?: string
src?: string
}
const IconWithCounter: React.FC<IIconWithCounter> = ({ icon, count, countMax, ...rest }) => {

Wyświetl plik

@ -3,21 +3,24 @@
* @module soapbox/components/icon
*/
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import InlineSVG from 'react-inlinesvg'; // eslint-disable-line no-restricted-imports
export interface IIcon extends React.HTMLAttributes<HTMLDivElement> {
src: string,
id?: string,
alt?: string,
className?: string,
src: string
id?: string
alt?: string
className?: string
}
/**
* @deprecated Use the UI Icon component directly.
*/
const Icon: React.FC<IIcon> = ({ src, alt, className, ...rest }) => {
return (
<div
className={classNames('svg-icon', className)}
className={clsx('svg-icon', className)}
{...rest}
>
<InlineSVG src={src} title={alt} loader={<></>} />

Wyświetl plik

@ -1,11 +1,10 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { v4 as uuidv4 } from 'uuid';
import { SelectDropdown } from '../features/forms';
import Icon from './icon';
import { HStack, Select } from './ui';
import { Icon, HStack, Select } from './ui';
interface IList {
children: React.ReactNode
@ -16,9 +15,9 @@ const List: React.FC<IList> = ({ children }) => (
);
interface IListItem {
label: React.ReactNode,
hint?: React.ReactNode,
onClick?(): void,
label: React.ReactNode
hint?: React.ReactNode
onClick?(): void
onSelect?(): void
isSelected?: boolean
children?: React.ReactNode
@ -45,7 +44,7 @@ const ListItem: React.FC<IListItem> = ({ label, hint, children, onClick, onSelec
return React.cloneElement(child, {
id: domId,
className: classNames({
className: clsx({
'w-auto': isSelect,
}, child.props.className),
});
@ -57,14 +56,14 @@ const ListItem: React.FC<IListItem> = ({ label, hint, children, onClick, onSelec
return (
<Comp
className={classNames({
'flex items-center justify-between px-3 py-2 first:rounded-t-lg last:rounded-b-lg bg-gradient-to-r from-gradient-start/10 to-gradient-end/10': true,
'cursor-pointer hover:from-gradient-start/20 hover:to-gradient-end/20 dark:hover:from-gradient-start/5 dark:hover:to-gradient-end/5': typeof onClick !== 'undefined' || typeof onSelect !== 'undefined',
className={clsx({
'flex items-center justify-between px-4 py-2 first:rounded-t-lg last:rounded-b-lg bg-gradient-to-r from-gradient-start/20 to-gradient-end/20 dark:from-gradient-start/10 dark:to-gradient-end/10': true,
'cursor-pointer hover:from-gradient-start/30 hover:to-gradient-end/30 dark:hover:from-gradient-start/5 dark:hover:to-gradient-end/5': typeof onClick !== 'undefined' || typeof onSelect !== 'undefined',
})}
{...linkProps}
>
<div className='flex flex-col py-1.5 pr-4 rtl:pl-4 rtl:pr-0'>
<LabelComp className='text-gray-900 dark:text-gray-100' htmlFor={domId}>{label}</LabelComp>
<LabelComp className='font-medium text-gray-900 dark:text-gray-100' htmlFor={domId}>{label}</LabelComp>
{hint ? (
<span className='text-sm text-gray-700 dark:text-gray-600'>{hint}</span>
@ -83,9 +82,26 @@ const ListItem: React.FC<IListItem> = ({ label, hint, children, onClick, onSelec
<div className='flex flex-row items-center text-gray-700 dark:text-gray-600'>
{children}
{isSelected ? (
<Icon src={require('@tabler/icons/check.svg')} className='ml-1 text-primary-500 dark:text-primary-400' />
) : null}
<div
className={
clsx({
'flex h-6 w-6 items-center justify-center rounded-full border-2 border-solid border-primary-500 dark:border-primary-400 transition': true,
'bg-primary-500 dark:bg-primary-400': isSelected,
'bg-transparent': !isSelected,
})
}
>
<Icon
src={require('@tabler/icons/check.svg')}
className={
clsx({
'h-4 w-4 text-white dark:text-white transition-all duration-500': true,
'opacity-0 scale-50': !isSelected,
'opacity-100 scale-100': isSelected,
})
}
/>
</div>
</div>
) : null}

Wyświetl plik

@ -8,9 +8,9 @@ const messages = defineMessages({
});
interface ILoadGap {
disabled?: boolean,
maxId: string,
onClick: (id: string) => void,
disabled?: boolean
maxId: string
onClick: (id: string) => void
}
const LoadGap: React.FC<ILoadGap> = ({ disabled, maxId, onClick }) => {

Wyświetl plik

@ -4,18 +4,19 @@ import { FormattedMessage } from 'react-intl';
import { Button } from 'soapbox/components/ui';
interface ILoadMore {
onClick: React.MouseEventHandler,
disabled?: boolean,
visible?: Boolean,
onClick: React.MouseEventHandler
disabled?: boolean
visible?: boolean
className?: string
}
const LoadMore: React.FC<ILoadMore> = ({ onClick, disabled, visible = true }) => {
const LoadMore: React.FC<ILoadMore> = ({ onClick, disabled, visible = true, className }) => {
if (!visible) {
return null;
}
return (
<Button theme='primary' block disabled={disabled || !visible} onClick={onClick}>
<Button className={className} theme='primary' block disabled={disabled || !visible} onClick={onClick}>
<FormattedMessage id='status.load_more' defaultMessage='Load more' />
</Button>
);

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
import throttle from 'lodash/throttle';
import React, { useCallback, useEffect, useRef, useState } from 'react';
@ -18,7 +18,7 @@ const messages = defineMessages({
});
interface ILocationSearch {
onSelected: (locationId: string) => void,
onSelected: (locationId: string) => void
}
const LocationSearch: React.FC<ILocationSearch> = ({ onSelected }) => {
@ -100,8 +100,8 @@ const LocationSearch: React.FC<ILocationSearch> = ({ onSelected }) => {
renderSuggestion={AutosuggestLocation}
/>
<div role='button' tabIndex={0} className='search__icon' onClick={handleClear}>
<Icon src={require('@tabler/icons/search.svg')} className={classNames('svg-icon--search', { active: isEmpty() })} />
<Icon src={require('@tabler/icons/backspace.svg')} className={classNames('svg-icon--backspace', { active: !isEmpty() })} aria-label={intl.formatMessage(messages.placeholder)} />
<Icon src={require('@tabler/icons/search.svg')} className={clsx('svg-icon--search', { active: isEmpty() })} />
<Icon src={require('@tabler/icons/backspace.svg')} className={clsx('svg-icon--backspace', { active: !isEmpty() })} aria-label={intl.formatMessage(messages.placeholder)} />
</div>
</div>
);

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useState, useRef, useLayoutEffect } from 'react';
import Blurhash from 'soapbox/components/blurhash';
@ -19,21 +19,21 @@ const ATTACHMENT_LIMIT = 4;
const MAX_FILENAME_LENGTH = 45;
interface Dimensions {
w: Property.Width | number,
h: Property.Height | number,
t?: Property.Top,
r?: Property.Right,
b?: Property.Bottom,
l?: Property.Left,
float?: Property.Float,
pos?: Property.Position,
w: Property.Width | number
h: Property.Height | number
t?: Property.Top
r?: Property.Right
b?: Property.Bottom
l?: Property.Left
float?: Property.Float
pos?: Property.Position
}
interface SizeData {
style: React.CSSProperties,
itemsDimensions: Dimensions[],
size: number,
width: number,
style: React.CSSProperties
itemsDimensions: Dimensions[]
size: number
width: number
}
const withinLimits = (aspectRatio: number) => {
@ -48,16 +48,16 @@ const shouldLetterbox = (attachment: Attachment): boolean => {
};
interface IItem {
attachment: Attachment,
standalone?: boolean,
index: number,
size: number,
onClick: (index: number) => void,
displayWidth?: number,
visible: boolean,
dimensions: Dimensions,
last?: boolean,
total: number,
attachment: Attachment
standalone?: boolean
index: number
size: number
onClick: (index: number) => void
displayWidth?: number
visible: boolean
dimensions: Dimensions
last?: boolean
total: number
}
const Item: React.FC<IItem> = ({
@ -152,7 +152,14 @@ const Item: React.FC<IItem> = ({
);
return (
<div className={classNames('media-gallery__item', { standalone })} key={attachment.id} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}>
<div
className={clsx('media-gallery__item', {
standalone,
'rounded-md': total > 1,
})}
key={attachment.id}
style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}
>
<a className='media-gallery__item-thumbnail' href={attachment.url} target='_blank' style={{ cursor: 'pointer' }}>
<Blurhash hash={attachment.blurhash} className='media-gallery__preview' />
<span className='media-gallery__item__icons'>{attachmentIcon}</span>
@ -189,7 +196,7 @@ const Item: React.FC<IItem> = ({
}
thumbnail = (
<div className={classNames('media-gallery__gifv', { autoplay: autoPlayGif })}>
<div className={clsx('media-gallery__gifv', { autoplay: autoPlayGif })}>
<video
className='media-gallery__item-gifv-thumbnail'
aria-label={attachment.description}
@ -211,7 +218,7 @@ const Item: React.FC<IItem> = ({
const ext = attachment.url.split('.').pop()?.toUpperCase();
thumbnail = (
<a
className={classNames('media-gallery__item-thumbnail')}
className={clsx('media-gallery__item-thumbnail')}
href={attachment.url}
onClick={handleClick}
target='_blank'
@ -225,7 +232,7 @@ const Item: React.FC<IItem> = ({
const ext = attachment.url.split('.').pop()?.toUpperCase();
thumbnail = (
<a
className={classNames('media-gallery__item-thumbnail')}
className={clsx('media-gallery__item-thumbnail')}
href={attachment.url}
onClick={handleClick}
target='_blank'
@ -245,7 +252,14 @@ const Item: React.FC<IItem> = ({
}
return (
<div className={classNames('media-gallery__item', `media-gallery__item--${attachment.type}`, { standalone })} key={attachment.id} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}>
<div
className={clsx('media-gallery__item', `media-gallery__item--${attachment.type}`, {
standalone,
'rounded-md': total > 1,
})}
key={attachment.id}
style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}
>
{last && total > ATTACHMENT_LIMIT && (
<div className='media-gallery__item-overflow'>
+{total - ATTACHMENT_LIMIT + 1}
@ -260,23 +274,25 @@ const Item: React.FC<IItem> = ({
);
};
interface IMediaGallery {
sensitive?: boolean,
media: ImmutableList<Attachment>,
height?: number,
onOpenMedia: (media: ImmutableList<Attachment>, index: number) => void,
defaultWidth?: number,
cacheWidth?: (width: number) => void,
visible?: boolean,
onToggleVisibility?: () => void,
displayMedia?: string,
compact: boolean,
export interface IMediaGallery {
sensitive?: boolean
media: ImmutableList<Attachment>
height?: number
onOpenMedia: (media: ImmutableList<Attachment>, index: number) => void
defaultWidth?: number
cacheWidth?: (width: number) => void
visible?: boolean
onToggleVisibility?: () => void
displayMedia?: string
compact?: boolean
className?: string
}
const MediaGallery: React.FC<IMediaGallery> = (props) => {
const {
media,
defaultWidth = 0,
className,
onOpenMedia,
cacheWidth,
compact,
@ -546,7 +562,11 @@ const MediaGallery: React.FC<IMediaGallery> = (props) => {
}, [node.current]);
return (
<div className={classNames('media-gallery', { 'media-gallery--compact': compact })} style={sizeData.style} ref={node}>
<div
className={clsx(className, 'media-gallery', { 'media-gallery--compact': compact })}
style={sizeData.style}
ref={node}
>
{children}
</div>
);

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import 'wicg-inert';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
@ -39,10 +39,10 @@ export const checkEventComposeContent = (compose?: ReturnType<typeof ReducerComp
};
interface IModalRoot {
onCancel?: () => void,
onClose: (type?: ModalType) => void,
type: ModalType,
children: React.ReactNode,
onCancel?: () => void
onClose: (type?: ModalType) => void
type: ModalType
children: React.ReactNode
}
const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type }) => {
@ -232,7 +232,7 @@ const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type })
return (
<div
ref={ref}
className={classNames({
className={clsx({
'fixed top-0 left-0 z-[100] w-full h-full overflow-x-hidden overflow-y-auto': true,
'pointer-events-none': !visible,
})}
@ -247,7 +247,7 @@ const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type })
<div
role='dialog'
className={classNames({
className={clsx({
'my-2 mx-auto relative pointer-events-none flex items-center min-h-[calc(100%-3.5rem)]': true,
'p-4 md:p-0': type !== 'MEDIA',
})}

Wyświetl plik

@ -1,16 +1,16 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
interface IOutlineBox extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode,
className?: string,
children: React.ReactNode
className?: string
}
/** Wraps children in a container with an outline. */
const OutlineBox: React.FC<IOutlineBox> = ({ children, className, ...rest }) => {
return (
<div
className={classNames('p-4 rounded-lg border border-solid border-gray-300 dark:border-gray-800', className)}
className={clsx('rounded-lg border border-solid border-gray-300 p-4 dark:border-gray-800', className)}
{...rest}
>
{children}
@ -18,4 +18,4 @@ const OutlineBox: React.FC<IOutlineBox> = ({ children, className, ...rest }) =>
);
};
export default OutlineBox;
export default OutlineBox;

Wyświetl plik

@ -0,0 +1,54 @@
import clsx from 'clsx';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { HStack, Icon, Text } from 'soapbox/components/ui';
interface IPendingItemsRow {
/** Path to navigate the user when clicked. */
to: string
/** Number of pending items. */
count: number
/** Size of the icon. */
size?: 'md' | 'lg'
}
const PendingItemsRow: React.FC<IPendingItemsRow> = ({ to, count, size = 'md' }) => {
return (
<Link to={to} className='group' data-testid='pending-items-row'>
<HStack alignItems='center' justifyContent='between'>
<HStack alignItems='center' space={2}>
<div className={clsx('rounded-full bg-primary-200 text-primary-500 dark:bg-primary-800 dark:text-primary-200', {
'p-3': size === 'lg',
'p-2.5': size === 'md',
})}
>
<Icon
src={require('@tabler/icons/exclamation-circle.svg')}
className={clsx({
'h-5 w-5': size === 'md',
'h-7 w-7': size === 'lg',
})}
/>
</div>
<Text weight='bold' size='md'>
<FormattedMessage
id='groups.pending.count'
defaultMessage='{number, plural, one {# pending request} other {# pending requests}}'
values={{ number: count }}
/>
</Text>
</HStack>
<Icon
src={require('@tabler/icons/chevron-right.svg')}
className='h-5 w-5 text-gray-600 transition-colors group-hover:text-gray-700 dark:text-gray-600 dark:group-hover:text-gray-500'
/>
</HStack>
</Link>
);
};
export { PendingItemsRow };

Wyświetl plik

@ -16,9 +16,9 @@ const messages = defineMessages({
});
interface IPollFooter {
poll: PollEntity,
showResults: boolean,
selected: Selected,
poll: PollEntity
showResults: boolean
selected: Selected
}
const PollFooter: React.FC<IPollFooter> = ({ poll, showResults, selected }): JSX.Element => {

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { Motion, presets, spring } from 'react-motion';
@ -29,7 +29,7 @@ const PollPercentageBar: React.FC<{ percent: number, leading: boolean }> = ({ pe
};
interface IPollOptionText extends IPollOption {
percent: number,
percent: number
}
const PollOptionText: React.FC<IPollOptionText> = ({ poll, option, index, active, onToggle }) => {
@ -46,7 +46,7 @@ const PollOptionText: React.FC<IPollOptionText> = ({ poll, option, index, active
return (
<label
className={
classNames('flex relative p-2 bg-white dark:bg-primary-900 cursor-pointer rounded-3xl border border-solid hover:bg-primary-50 dark:hover:bg-primary-800/50', {
clsx('relative flex cursor-pointer rounded-3xl border border-solid bg-white p-2 hover:bg-primary-50 dark:bg-primary-900 dark:hover:bg-primary-800/50', {
'border-primary-600 ring-1 ring-primary-600 bg-primary-50 dark:bg-primary-800/50 dark:border-primary-300 dark:ring-primary-300': active,
'border-primary-300 dark:border-primary-500': !active,
})
@ -74,7 +74,7 @@ const PollOptionText: React.FC<IPollOptionText> = ({ poll, option, index, active
<div className='col-start-1 row-start-1 flex items-center justify-self-end'>
<span
className={classNames('flex items-center justify-center w-6 h-6 flex-none border border-solid rounded-full', {
className={clsx('flex h-6 w-6 flex-none items-center justify-center rounded-full border border-solid', {
'bg-primary-600 border-primary-600 dark:bg-primary-300 dark:border-primary-300': active,
'border-primary-300 bg-white dark:bg-primary-900 dark:border-primary-500': !active,
})}
@ -95,12 +95,12 @@ const PollOptionText: React.FC<IPollOptionText> = ({ poll, option, index, active
};
interface IPollOption {
poll: PollEntity,
option: PollOptionEntity,
index: number,
showResults?: boolean,
active: boolean,
onToggle: (value: number) => void,
poll: PollEntity
option: PollOptionEntity
index: number
showResults?: boolean
active: boolean
onToggle: (value: number) => void
}
const PollOption: React.FC<IPollOption> = (props): JSX.Element | null => {

Wyświetl plik

@ -13,8 +13,8 @@ import PollOption from './poll-option';
export type Selected = Record<number, boolean>;
interface IPoll {
id: string,
status?: string,
id: string
status?: string
}
const messages = defineMessages({

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import { useIntl, FormattedMessage } from 'react-intl';
import { usePopper } from 'react-popper';
@ -54,7 +54,7 @@ const handleMouseLeave = (dispatch: AppDispatch): React.MouseEventHandler => {
};
interface IProfileHoverCard {
visible: boolean,
visible: boolean
}
/** Popup profile preview that appears when hovering avatars and display names. */
@ -95,7 +95,7 @@ export const ProfileHoverCard: React.FC<IProfileHoverCard> = ({ visible = true }
return (
<div
className={classNames({
className={clsx({
'absolute transition-opacity w-[320px] z-[101] top-0 left-0': true,
'opacity-100': visible,
'opacity-0 pointer-events-none': !visible,

Wyświetl plik

@ -1,11 +1,11 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
interface IProgressCircle {
progress: number,
radius?: number,
stroke?: number,
title?: string,
progress: number
radius?: number
stroke?: number
title?: string
}
const ProgressCircle: React.FC<IProgressCircle> = ({ progress, radius = 12, stroke = 4, title }) => {
@ -30,7 +30,7 @@ const ProgressCircle: React.FC<IProgressCircle> = ({ progress, radius = 12, stro
strokeWidth={stroke}
/>
<circle
className={classNames('stroke-primary-500', {
className={clsx('stroke-primary-500', {
'stroke-secondary-500': progress > 1,
})}
style={{

Wyświetl plik

@ -4,10 +4,10 @@ import PTRComponent from 'react-simple-pull-to-refresh';
import { Spinner } from 'soapbox/components/ui';
interface IPullToRefresh {
onRefresh?: () => Promise<any>;
refreshingContent?: JSX.Element | string;
pullingContent?: JSX.Element | string;
children: React.ReactNode;
onRefresh?: () => Promise<any>
refreshingContent?: JSX.Element | string
pullingContent?: JSX.Element | string
children: React.ReactNode
}
/**

Wyświetl plik

@ -3,7 +3,7 @@ import React from 'react';
import PullToRefresh from './pull-to-refresh';
interface IPullable {
children: React.ReactNode,
children: React.ReactNode
}
/**

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React, { MouseEventHandler, useEffect, useRef, useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
@ -23,11 +23,11 @@ const messages = defineMessages({
interface IQuotedStatus {
/** The quoted status entity. */
status?: StatusEntity,
status?: StatusEntity
/** Callback when cancelled (during compose). */
onCancel?: Function,
onCancel?: Function
/** Whether the status is shown in the post composer. */
compose?: boolean,
compose?: boolean
}
/** Status embedded in a quote post. */
@ -94,7 +94,7 @@ const QuotedStatus: React.FC<IQuotedStatus> = ({ status, onCancel, compose }) =>
return (
<OutlineBox
data-testid='quoted-status'
className={classNames('cursor-pointer', {
className={clsx('cursor-pointer', {
'hover:bg-gray-100 dark:hover:bg-gray-800': !compose,
})}
>
@ -133,7 +133,7 @@ const QuotedStatus: React.FC<IQuotedStatus> = ({ status, onCancel, compose }) =>
collapsable
/>
{(status.card || status.media_attachments.size > 0) && (
{status.media_attachments.size > 0 && (
<StatusMedia
status={status}
muted={compose}

Wyświetl plik

@ -16,11 +16,11 @@ const RadioGroup = ({ onChange, children }: IRadioGroup) => {
};
interface IRadioItem {
label: React.ReactNode,
hint?: React.ReactNode,
value: string,
checked: boolean,
onChange?: React.ChangeEventHandler,
label: React.ReactNode
hint?: React.ReactNode
value: string
checked: boolean
onChange?: React.ChangeEventHandler
}
const RadioItem: React.FC<IRadioItem> = ({ label, hint, checked = false, onChange, value }) => {

Wyświetl plik

@ -113,14 +113,14 @@ const timeRemainingString = (intl: IntlShape, date: Date, now: number) => {
};
interface RelativeTimestampProps extends IText {
intl: IntlShape,
timestamp: string,
year?: number,
futureDate?: boolean,
intl: IntlShape
timestamp: string
year?: number
futureDate?: boolean
}
interface RelativeTimestampState {
now: number,
now: number
}
/** Displays a timestamp compared to the current time, eg "1m" for one minute ago. */

Wyświetl plik

@ -2,13 +2,13 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
interface ISafeEmbed {
/** Styles for the outer frame element. */
className?: string,
className?: string
/** Space-separate list of restrictions to ALLOW for the iframe. */
sandbox?: string,
sandbox?: string
/** Unique title for the iframe. */
title: string,
title: string
/** HTML body to embed. */
html?: string,
html?: string
}
/** Safely embeds arbitrary HTML content on the page (by putting it in an iframe). */

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import throttle from 'lodash/throttle';
import React, { useState, useEffect, useCallback } from 'react';
import { useIntl, MessageDescriptor } from 'react-intl';
@ -9,15 +9,15 @@ import { useSettings } from 'soapbox/hooks';
interface IScrollTopButton {
/** Callback when clicked, and also when scrolled to the top. */
onClick: () => void,
onClick: () => void
/** Number of unread items. */
count: number,
count: number
/** Message to display in the button (should contain a `{count}` value). */
message: MessageDescriptor,
message: MessageDescriptor
/** Distance from the top of the screen (scrolling down) before the button appears. */
threshold?: number,
threshold?: number
/** Distance from the top of the screen (scrolling up) before the action is triggered. */
autoloadThreshold?: number,
autoloadThreshold?: number
}
/** Floating new post counter above timelines, clicked to scroll to top. */
@ -36,7 +36,7 @@ const ScrollTopButton: React.FC<IScrollTopButton> = ({
const visible = count > 0 && scrolled;
const classes = classNames('left-1/2 -translate-x-1/2 fixed top-20 z-50', {
const classes = clsx('fixed left-1/2 top-20 z-50 -translate-x-1/2', {
'hidden': !visible,
});

Wyświetl plik

@ -10,14 +10,14 @@ import { Card, Spinner } from './ui';
/** Custom Viruoso component context. */
type Context = {
itemClassName?: string,
listClassName?: string,
itemClassName?: string
listClassName?: string
}
/** Scroll position saved in sessionStorage. */
type SavedScrollPosition = {
index: number,
offset: number,
index: number
offset: number
}
/** Custom Virtuoso Item component representing a single scrollable item. */
@ -37,44 +37,46 @@ const List: Components<JSX.Element, Context>['List'] = React.forwardRef((props,
interface IScrollableList extends VirtuosoProps<any, any> {
/** Unique key to preserve the scroll position when navigating back. */
scrollKey?: string,
scrollKey?: string
/** Pagination callback when the end of the list is reached. */
onLoadMore?: () => void,
onLoadMore?: () => void
/** Whether the data is currently being fetched. */
isLoading?: boolean,
isLoading?: boolean
/** Whether to actually display the loading state. */
showLoading?: boolean,
showLoading?: boolean
/** Whether we expect an additional page of data. */
hasMore?: boolean,
hasMore?: boolean
/** Additional element to display at the top of the list. */
prepend?: React.ReactNode,
prepend?: React.ReactNode
/** Whether to display the prepended element. */
alwaysPrepend?: boolean,
alwaysPrepend?: boolean
/** Message to display when the list is loaded but empty. */
emptyMessage?: React.ReactNode,
emptyMessage?: React.ReactNode
/** Should the empty message be displayed in a Card */
emptyMessageCard?: boolean
/** Scrollable content. */
children: Iterable<React.ReactNode>,
children: Iterable<React.ReactNode>
/** Callback when the list is scrolled to the top. */
onScrollToTop?: () => void,
onScrollToTop?: () => void
/** Callback when the list is scrolled. */
onScroll?: () => void,
onScroll?: () => void
/** Placeholder component to render while loading. */
placeholderComponent?: React.ComponentType | React.NamedExoticComponent,
placeholderComponent?: React.ComponentType | React.NamedExoticComponent
/** Number of placeholders to render while loading. */
placeholderCount?: number,
placeholderCount?: number
/**
* Pull to refresh callback.
* @deprecated Put a PTR around the component instead.
*/
onRefresh?: () => Promise<any>,
onRefresh?: () => Promise<any>
/** Extra class names on the Virtuoso element. */
className?: string,
className?: string
/** Class names on each item container. */
itemClassName?: string,
itemClassName?: string
/** `id` attribute on the Virtuoso element. */
id?: string,
id?: string
/** CSS styles on the Virtuoso element. */
style?: React.CSSProperties,
style?: React.CSSProperties
/** Whether to use the window to scroll the content instead of Virtuoso's container. */
useWindowScroll?: boolean
}
@ -87,6 +89,7 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
children,
isLoading,
emptyMessage,
emptyMessageCard = true,
showLoading,
onRefresh,
onScroll,
@ -158,13 +161,17 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
<div className='mt-2'>
{alwaysPrepend && prepend}
<Card variant='rounded' size='lg'>
{isLoading ? (
<Spinner />
) : (
emptyMessage
)}
</Card>
{isLoading ? (
<Spinner />
) : (
<>
{emptyMessageCard ? (
<Card variant='rounded' size='lg'>
{emptyMessage}
</Card>
) : emptyMessage}
</>
)}
</div>
);
};

Wyświetl plik

@ -1,5 +1,5 @@
/* eslint-disable jsx-a11y/interactive-supports-focus */
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { Link, NavLink } from 'react-router-dom';
@ -10,7 +10,7 @@ import { closeSidebar } from 'soapbox/actions/sidebar';
import Account from 'soapbox/components/account';
import { Stack } from 'soapbox/components/ui';
import ProfileStats from 'soapbox/features/ui/components/profile-stats';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useGroupsPath, useFeatures } from 'soapbox/hooks';
import { makeGetAccount, makeGetOtherAccounts } from 'soapbox/selectors';
import { Divider, HStack, Icon, IconButton, Text } from './ui';
@ -43,11 +43,11 @@ const messages = defineMessages({
});
interface ISidebarLink {
href?: string,
to?: string,
icon: string,
text: string | JSX.Element,
onClick: React.EventHandler<React.MouseEvent>,
href?: string
to?: string
icon: string
text: string | JSX.Element
onClick: React.EventHandler<React.MouseEvent>
}
const SidebarLink: React.FC<ISidebarLink> = ({ href, to, icon, text, onClick }) => {
@ -90,6 +90,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
const sidebarOpen = useAppSelector((state) => state.sidebar.sidebarOpen);
const settings = useAppSelector((state) => getSettings(state));
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count());
const groupsPath = useGroupsPath();
const closeButtonRef = React.useRef(null);
@ -138,7 +139,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
<div
aria-expanded={sidebarOpen}
className={
classNames({
clsx({
'z-[1000]': sidebarOpen,
hidden: !sidebarOpen,
})
@ -153,7 +154,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
<div className='fixed inset-0 z-[1000] flex'>
<div
className={
classNames({
clsx({
'flex flex-col flex-1 bg-white dark:bg-primary-900 -translate-x-full rtl:translate-x-full w-full max-w-xs': true,
'!translate-x-0': sidebarOpen,
})
@ -210,7 +211,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
{features.groups && (
<SidebarLink
to='/groups'
to={groupsPath}
icon={require('@tabler/icons/circles.svg')}
text={intl.formatMessage(messages.groups)}
onClick={onClose}
@ -296,7 +297,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
/>
)}
{features.filters && (
{(features.filters || features.filtersV2) && (
<SidebarLink
to='/filters'
icon={require('@tabler/icons/filter.svg')}
@ -334,7 +335,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
<Icon
src={require('@tabler/icons/chevron-down.svg')}
className={classNames('w-4 h-4 text-gray-900 dark:text-gray-100 transition-transform', {
className={clsx('h-4 w-4 text-gray-900 transition-transform dark:text-gray-100', {
'rotate-180': switcher,
})}
/>

Wyświetl plik

@ -1,4 +1,4 @@
import classNames from 'clsx';
import clsx from 'clsx';
import React from 'react';
import { NavLink } from 'react-router-dom';
@ -6,17 +6,17 @@ import { Icon, Text } from './ui';
interface ISidebarNavigationLink {
/** Notification count, if any. */
count?: number,
count?: number
/** Optional max to cap count (ie: N+) */
countMax?: number
/** URL to an SVG icon. */
icon: string,
icon: string
/** Link label. */
text: React.ReactNode,
text: React.ReactNode
/** Route to an internal page. */
to?: string,
to?: string
/** Callback when the link is clicked. */
onClick?: React.EventHandler<React.MouseEvent>,
onClick?: React.EventHandler<React.MouseEvent>
}
/** Desktop sidebar navigation link. */
@ -38,7 +38,7 @@ const SidebarNavigationLink = React.forwardRef((props: ISidebarNavigationLink, r
to={to}
ref={ref}
onClick={handleClick}
className={classNames({
className={clsx({
'flex items-center px-4 py-3.5 text-base font-semibold space-x-4 rtl:space-x-reverse rounded-full group text-gray-600 hover:text-gray-900 dark:text-gray-500 dark:hover:text-gray-100 hover:bg-primary-200 dark:hover:bg-primary-900': true,
'dark:text-gray-100 text-gray-900': isActive,
})}
@ -48,7 +48,7 @@ const SidebarNavigationLink = React.forwardRef((props: ISidebarNavigationLink, r
src={icon}
count={count}
countMax={countMax}
className={classNames('h-5 w-5', {
className={clsx('h-5 w-5', {
'text-gray-600 dark:text-gray-500 group-hover:text-primary-500 dark:group-hover:text-primary-400': !isActive,
'text-primary-500 dark:text-primary-400': isActive,
})}

Wyświetl plik

@ -2,15 +2,13 @@ import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { Stack } from 'soapbox/components/ui';
import DropdownMenu from 'soapbox/containers/dropdown-menu-container';
import { useStatContext } from 'soapbox/contexts/stat-context';
import ComposeButton from 'soapbox/features/ui/components/compose-button';
import { useAppSelector, useFeatures, useOwnAccount, useSettings } from 'soapbox/hooks';
import { useAppSelector, useGroupsPath, useFeatures, useOwnAccount, useSettings } from 'soapbox/hooks';
import DropdownMenu, { Menu } from './dropdown-menu';
import SidebarNavigationLink from './sidebar-navigation-link';
import type { Menu } from 'soapbox/components/dropdown-menu';
const messages = defineMessages({
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
bookmarks: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
@ -27,6 +25,8 @@ const SidebarNavigation = () => {
const features = useFeatures();
const settings = useSettings();
const account = useOwnAccount();
const groupsPath = useGroupsPath();
const notificationCount = useAppSelector((state) => state.notifications.unread);
const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count());
const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
@ -137,7 +137,7 @@ const SidebarNavigation = () => {
{features.groups && (
<SidebarNavigationLink
to='/groups'
to={groupsPath}
icon={require('@tabler/icons/circles.svg')}
text={<FormattedMessage id='tabs_bar.groups' defaultMessage='Groups' />}
/>
@ -185,7 +185,7 @@ const SidebarNavigation = () => {
)}
{menu.length > 0 && (
<DropdownMenu items={menu}>
<DropdownMenu items={menu} placement='top'>
<SidebarNavigationLink
icon={require('@tabler/icons/dots-circle-horizontal.svg')}
text={<FormattedMessage id='tabs_bar.more' defaultMessage='More' />}

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