diff --git a/app/soapbox/__fixtures__/notification-favourite.json b/app/soapbox/__fixtures__/notification-favourite.json
new file mode 100644
index 000000000..00da9c8f9
--- /dev/null
+++ b/app/soapbox/__fixtures__/notification-favourite.json
@@ -0,0 +1,290 @@
+{
+ "account": {
+ "acct": "Hollahollara@spinster.xyz",
+ "avatar": "https://gleasonator.com/proxy/LArKQiIrW265rGIJGwdgX7rRsao/aHR0cHM6Ly9tZWRpYS5zcGluc3Rlci54eXovYWNjb3VudHMvYXZhdGFycy8wMDAvMTQxLzI5NC9vcmlnaW5hbC9lNjA1NjljMjBjNGY3ODNjLnBuZw/e60569c20c4f783c.png",
+ "avatar_static": "https://gleasonator.com/proxy/LArKQiIrW265rGIJGwdgX7rRsao/aHR0cHM6Ly9tZWRpYS5zcGluc3Rlci54eXovYWNjb3VudHMvYXZhdGFycy8wMDAvMTQxLzI5NC9vcmlnaW5hbC9lNjA1NjljMjBjNGY3ODNjLnBuZw/e60569c20c4f783c.png",
+ "bot": false,
+ "created_at": "2020-05-29T03:15:59.000Z",
+ "display_name": "Hollahollara",
+ "emojis": [],
+ "fields": [],
+ "followers_count": 0,
+ "following_count": 0,
+ "fqn": "Hollahollara@spinster.xyz",
+ "header": "https://gleasonator.com/proxy/XSANC57uDBL3tM0LBLEer7yMyaA/aHR0cHM6Ly9tZWRpYS5zcGluc3Rlci54eXovYWNjb3VudHMvaGVhZGVycy8wMDAvMTQxLzI5NC9vcmlnaW5hbC84NTMzMWEzMjJkMTIyN2Q0LnBuZw/85331a322d1227d4.png",
+ "header_static": "https://gleasonator.com/proxy/XSANC57uDBL3tM0LBLEer7yMyaA/aHR0cHM6Ly9tZWRpYS5zcGluc3Rlci54eXovYWNjb3VudHMvaGVhZGVycy8wMDAvMTQxLzI5NC9vcmlnaW5hbC84NTMzMWEzMjJkMTIyN2Q0LnBuZw/85331a322d1227d4.png",
+ "id": "9vWfJdLwuJSyJXqCeG",
+ "last_status_at": "2022-04-16T20:33:32",
+ "locked": true,
+ "note": "Adult human female. Artist. Evil terv. Millennial, killing all the things. Public Universal Friend.
www.jenniferaldridge.com
",
+ "pleroma": {
+ "accepts_chat_messages": true,
+ "also_known_as": [],
+ "ap_id": "https://spinster.xyz/users/Hollahollara",
+ "background_image": null,
+ "deactivated": false,
+ "favicon": "https://gleasonator.com/proxy/owo6QgsHm_0ogz5enHyvD68wDUA/aHR0cHM6Ly9zcGluc3Rlci54eXovZmF2aWNvbi5wbmc/favicon.png",
+ "hide_favorites": true,
+ "hide_followers": true,
+ "hide_followers_count": false,
+ "hide_follows": true,
+ "hide_follows_count": false,
+ "is_admin": false,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": false,
+ "location": null,
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": []
+ },
+ "source": {
+ "fields": [],
+ "note": "",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false
+ },
+ "sensitive": false
+ },
+ "statuses_count": 7191,
+ "url": "https://spinster.xyz/users/Hollahollara",
+ "username": "Hollahollara"
+ },
+ "created_at": "2022-04-14T20:36:52.000Z",
+ "id": "427825",
+ "pleroma": {
+ "is_muted": false,
+ "is_seen": true
+ },
+ "status": {
+ "account": {
+ "acct": "alex",
+ "avatar": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
+ "avatar_static": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
+ "bot": false,
+ "created_at": "2020-01-08T01:25:43.000Z",
+ "display_name": "Alex Gleason",
+ "emojis": [],
+ "fields": [
+ {
+ "name": "Website",
+ "value": "https://alexgleason.me"
+ },
+ {
+ "name": "Soapbox",
+ "value": "https://soapbox.pub"
+ },
+ {
+ "name": "Email",
+ "value": "alex@alexgleason.me"
+ },
+ {
+ "name": "Gender identity",
+ "value": "Soyboy"
+ },
+ {
+ "name": "Donate (PayPal)",
+ "value": "https://paypal.me/gleasonator"
+ },
+ {
+ "name": "$BTC",
+ "value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
+ },
+ {
+ "name": "$ETH",
+ "value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
+ },
+ {
+ "name": "$DOGE",
+ "value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
+ },
+ {
+ "name": "$XMR",
+ "value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
+ }
+ ],
+ "follow_requests_count": 0,
+ "followers_count": 2602,
+ "following_count": 1603,
+ "fqn": "alex@gleasonator.com",
+ "header": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
+ "header_static": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
+ "id": "9v5bmRalQvjOy0ECcC",
+ "last_status_at": "2022-04-16T19:23:50",
+ "locked": false,
+ "note": "I create Fediverse software that empowers people online.
I'm vegan btw
Note: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
+ "pleroma": {
+ "accepts_chat_messages": true,
+ "accepts_email_list": true,
+ "allow_following_move": true,
+ "also_known_as": [
+ "https://mitra.social/users/alex"
+ ],
+ "ap_id": "https://gleasonator.com/users/alex",
+ "background_image": null,
+ "birthday": "1993-07-03",
+ "deactivated": false,
+ "email": "alex@alexgleason.me",
+ "favicon": "https://gleasonator.com/favicon.png",
+ "hide_favorites": true,
+ "hide_followers": false,
+ "hide_followers_count": false,
+ "hide_follows": false,
+ "hide_follows_count": false,
+ "is_admin": true,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": true,
+ "location": "Texas",
+ "notification_settings": {
+ "block_from_strangers": false,
+ "hide_notification_contents": false
+ },
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": [],
+ "unread_conversation_count": 392,
+ "unread_notifications_count": 2
+ },
+ "source": {
+ "fields": [
+ {
+ "name": "Website",
+ "value": "https://alexgleason.me"
+ },
+ {
+ "name": "Soapbox",
+ "value": "https://soapbox.pub"
+ },
+ {
+ "name": "Email",
+ "value": "alex@alexgleason.me"
+ },
+ {
+ "name": "Gender identity",
+ "value": "Soyboy"
+ },
+ {
+ "name": "Donate (PayPal)",
+ "value": "https://paypal.me/gleasonator"
+ },
+ {
+ "name": "$BTC",
+ "value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
+ },
+ {
+ "name": "$ETH",
+ "value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
+ },
+ {
+ "name": "$DOGE",
+ "value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
+ },
+ {
+ "name": "$XMR",
+ "value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
+ }
+ ],
+ "note": "I create Fediverse software that empowers people online.\r\n\r\nI'm vegan btw\r\n\r\nNote: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false,
+ "no_rich_text": false,
+ "show_birthday": true,
+ "show_role": true
+ },
+ "privacy": "public",
+ "sensitive": false
+ },
+ "statuses_count": 24050,
+ "url": "https://gleasonator.com/users/alex",
+ "username": "alex"
+ },
+ "application": {
+ "name": "Soapbox FE",
+ "website": "https://soapbox.pub/"
+ },
+ "bookmarked": false,
+ "card": null,
+ "content": "",
+ "created_at": "2022-04-12T01:31:00.000Z",
+ "emojis": [],
+ "favourited": false,
+ "favourites_count": 11,
+ "id": "AIMEslRcKrcu02D3HU",
+ "in_reply_to_account_id": null,
+ "in_reply_to_id": null,
+ "language": null,
+ "media_attachments": [
+ {
+ "blurhash": "etMZzVWq%1%1o#_NayWCofae_Ns:R*kDjYS5a{jYoJj]V@a}WBbGof",
+ "description": "",
+ "id": "AIMEqtBeZtvpQvqfIG",
+ "meta": {
+ "original": {
+ "aspect": 0.9726443768996961,
+ "height": 658,
+ "width": 640
+ }
+ },
+ "pleroma": {
+ "mime_type": "image/jpeg"
+ },
+ "preview_url": "https://media.gleasonator.com/6c0a1d878b7c9d1d737f415645cf34cdacdf6438c468348f4fa7534a15798023.jpg",
+ "remote_url": "https://media.gleasonator.com/6c0a1d878b7c9d1d737f415645cf34cdacdf6438c468348f4fa7534a15798023.jpg",
+ "text_url": "https://media.gleasonator.com/6c0a1d878b7c9d1d737f415645cf34cdacdf6438c468348f4fa7534a15798023.jpg",
+ "type": "image",
+ "url": "https://media.gleasonator.com/6c0a1d878b7c9d1d737f415645cf34cdacdf6438c468348f4fa7534a15798023.jpg"
+ }
+ ],
+ "mentions": [],
+ "muted": false,
+ "pinned": false,
+ "pleroma": {
+ "content": {
+ "text/plain": ""
+ },
+ "content_type": null,
+ "conversation_id": "AIMEslPqRSCzuXNdWC",
+ "direct_conversation_id": null,
+ "emoji_reactions": [
+ {
+ "count": 4,
+ "me": false,
+ "name": "😆"
+ },
+ {
+ "count": 1,
+ "me": false,
+ "name": "🤢"
+ }
+ ],
+ "expires_at": null,
+ "in_reply_to_account_acct": null,
+ "local": true,
+ "parent_visible": false,
+ "pinned_at": null,
+ "quote": null,
+ "quote_url": null,
+ "quote_visible": false,
+ "spoiler_text": {
+ "text/plain": ""
+ },
+ "thread_muted": false
+ },
+ "poll": null,
+ "reblog": null,
+ "reblogged": false,
+ "reblogs_count": 4,
+ "replies_count": 0,
+ "sensitive": false,
+ "spoiler_text": "",
+ "tags": [],
+ "text": null,
+ "uri": "https://gleasonator.com/objects/7953f9fb-d3d7-4f50-b9d8-27e311ac1f5e",
+ "url": "https://gleasonator.com/notice/AIMEslRcKrcu02D3HU",
+ "visibility": "public"
+ },
+ "type": "favourite"
+}
diff --git a/app/soapbox/__fixtures__/notification-follow.json b/app/soapbox/__fixtures__/notification-follow.json
new file mode 100644
index 000000000..f563646ad
--- /dev/null
+++ b/app/soapbox/__fixtures__/notification-follow.json
@@ -0,0 +1,61 @@
+{
+ "account": {
+ "acct": "neko@rdrama.cc",
+ "avatar": "https://gleasonator.com/proxy/QJ3einzsXdobgWPsyZowxnor1zY/aHR0cHM6Ly9yZHJhbWEuY2MvbWVkaWEvODcyNDhjYjctZWYwNC00ZThjLWEwYzEtNTYxNWMyNWM0MTk1L2Jsb2I/blob",
+ "avatar_static": "https://gleasonator.com/proxy/QJ3einzsXdobgWPsyZowxnor1zY/aHR0cHM6Ly9yZHJhbWEuY2MvbWVkaWEvODcyNDhjYjctZWYwNC00ZThjLWEwYzEtNTYxNWMyNWM0MTk1L2Jsb2I/blob",
+ "bot": false,
+ "created_at": "2022-04-16T20:23:16.000Z",
+ "display_name": "Nekobit",
+ "emojis": [],
+ "fields": [],
+ "followers_count": 19,
+ "following_count": 357,
+ "fqn": "neko@rdrama.cc",
+ "header": "https://gleasonator.com/proxy/ojpBSVKfePvLnb7pwqepQspzIko/aHR0cHM6Ly9yZHJhbWEuY2MvbWVkaWEvNjBkMTJjOWYtOTNkNi00ODBmLThhMGUtMTE3M2ZkNjg5MzhmL3dhbGxwYXBlcmZsYXJlLmNvbV93YWxscGFwZXItd2ViLmpwZw/wallpaperflare.com_wallpaper-web.jpg",
+ "header_static": "https://gleasonator.com/proxy/ojpBSVKfePvLnb7pwqepQspzIko/aHR0cHM6Ly9yZHJhbWEuY2MvbWVkaWEvNjBkMTJjOWYtOTNkNi00ODBmLThhMGUtMTE3M2ZkNjg5MzhmL3dhbGxwYXBlcmZsYXJlLmNvbV93YWxscGFwZXItd2ViLmpwZw/wallpaperflare.com_wallpaper-web.jpg",
+ "id": "AIW9zGESDwdT27vk0W",
+ "last_status_at": "2022-04-16T21:49:29",
+ "locked": false,
+ "note": "New instance, hello!
Please follow if you followed my desuposter.club alt",
+ "pleroma": {
+ "accepts_chat_messages": true,
+ "also_known_as": [],
+ "ap_id": "https://rdrama.cc/users/neko",
+ "background_image": null,
+ "deactivated": false,
+ "favicon": "https://gleasonator.com/proxy/dbCdmChqVRi0vjYTCpRj5lDLtNM/aHR0cHM6Ly9yZHJhbWEuY2MvZmF2aWNvbi5wbmc/favicon.png",
+ "hide_favorites": true,
+ "hide_followers": false,
+ "hide_followers_count": false,
+ "hide_follows": false,
+ "hide_follows_count": false,
+ "is_admin": false,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": false,
+ "location": null,
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": []
+ },
+ "source": {
+ "fields": [],
+ "note": "",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false
+ },
+ "sensitive": false
+ },
+ "statuses_count": 6,
+ "url": "https://rdrama.cc/users/neko",
+ "username": "neko"
+ },
+ "created_at": "2022-04-16T20:24:03.000Z",
+ "id": "429280",
+ "pleroma": {
+ "is_muted": false,
+ "is_seen": true
+ },
+ "type": "follow"
+}
diff --git a/app/soapbox/__fixtures__/notification-follow_request.json b/app/soapbox/__fixtures__/notification-follow_request.json
new file mode 100644
index 000000000..391dfec50
--- /dev/null
+++ b/app/soapbox/__fixtures__/notification-follow_request.json
@@ -0,0 +1,61 @@
+{
+ "account": {
+ "acct": "alex@spinster.xyz",
+ "avatar": "https://gleasonator.com/images/avi.png",
+ "avatar_static": "https://gleasonator.com/images/avi.png",
+ "bot": false,
+ "created_at": "2020-01-08T03:08:22.000Z",
+ "display_name": "**MOVED**",
+ "emojis": [],
+ "fields": [],
+ "followers_count": 1005,
+ "following_count": 724,
+ "fqn": "alex@spinster.xyz",
+ "header": "https://gleasonator.com/proxy/yxa7ucolLFAsmBHYJzksSh_zoao/aHR0cHM6Ly9tZWRpYS5zcGluc3Rlci54eXovYWNjb3VudHMvaGVhZGVycy8wMDAvMDAwLzAwMS9vcmlnaW5hbC83ZmE4MWY5ZmZiYWVjZDk3LnBuZw/7fa81f9ffbaecd97.png",
+ "header_static": "https://gleasonator.com/proxy/yxa7ucolLFAsmBHYJzksSh_zoao/aHR0cHM6Ly9tZWRpYS5zcGluc3Rlci54eXovYWNjb3VudHMvaGVhZGVycy8wMDAvMDAwLzAwMS9vcmlnaW5hbC83ZmE4MWY5ZmZiYWVjZDk3LnBuZw/7fa81f9ffbaecd97.png",
+ "id": "9v5bmXkCYkqU30gp9s",
+ "last_status_at": null,
+ "locked": true,
+ "note": "Moved to https://spinster.xyz/@alex@gleasonator.com",
+ "pleroma": {
+ "accepts_chat_messages": true,
+ "also_known_as": [],
+ "ap_id": "https://spinster.xyz/users/alex",
+ "background_image": null,
+ "deactivated": false,
+ "favicon": "https://gleasonator.com/proxy/owo6QgsHm_0ogz5enHyvD68wDUA/aHR0cHM6Ly9zcGluc3Rlci54eXovZmF2aWNvbi5wbmc/favicon.png",
+ "hide_favorites": true,
+ "hide_followers": false,
+ "hide_followers_count": false,
+ "hide_follows": false,
+ "hide_follows_count": false,
+ "is_admin": false,
+ "is_confirmed": false,
+ "is_moderator": false,
+ "is_suggested": false,
+ "location": null,
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": []
+ },
+ "source": {
+ "fields": [],
+ "note": "",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false
+ },
+ "sensitive": false
+ },
+ "statuses_count": 2687,
+ "url": "https://spinster.xyz/users/alex",
+ "username": "alex"
+ },
+ "created_at": "2020-12-30T02:23:35.000Z",
+ "id": "87967",
+ "pleroma": {
+ "is_muted": false,
+ "is_seen": true
+ },
+ "type": "follow_request"
+}
diff --git a/app/soapbox/__fixtures__/notification-mention.json b/app/soapbox/__fixtures__/notification-mention.json
new file mode 100644
index 000000000..d2ad0a265
--- /dev/null
+++ b/app/soapbox/__fixtures__/notification-mention.json
@@ -0,0 +1,226 @@
+{
+ "account": {
+ "acct": "silverpill@mitra.social",
+ "avatar": "https://gleasonator.com/proxy/ZbLqy9s8Hxn9I5K23y2mffsL6iY/aHR0cHM6Ly9taXRyYS5zb2NpYWwvbWVkaWEvNmE3ODViZjdkZDA1ZjYxYzM1OTBlODkzNWFhNDkxNTZhNDk5YWMzMGZkMWU0MDJmNzllN2UxNjRhZGIzNmUyYy5wbmc/6a785bf7dd05f61c3590e8935aa49156a499ac30fd1e402f79e7e164adb36e2c.png",
+ "avatar_static": "https://gleasonator.com/proxy/ZbLqy9s8Hxn9I5K23y2mffsL6iY/aHR0cHM6Ly9taXRyYS5zb2NpYWwvbWVkaWEvNmE3ODViZjdkZDA1ZjYxYzM1OTBlODkzNWFhNDkxNTZhNDk5YWMzMGZkMWU0MDJmNzllN2UxNjRhZGIzNmUyYy5wbmc/6a785bf7dd05f61c3590e8935aa49156a499ac30fd1e402f79e7e164adb36e2c.png",
+ "bot": false,
+ "created_at": "2021-11-11T22:31:51.000Z",
+ "display_name": "silverpill",
+ "emojis": [],
+ "fields": [
+ {
+ "name": "Matrix",
+ "value": "@silverpill:poa.st"
+ },
+ {
+ "name": "Alt",
+ "value": "@silverpill@poa.st"
+ },
+ {
+ "name": "Code",
+ "value": "https://codeberg.org/silverpill/"
+ },
+ {
+ "name": "$XMR",
+ "value": "884y9LmsWY7PQNsyR7bJy1dvj91tuF5spVabyCnPk4KfQtSuzFbQobTFC7xSemJgVW1FWAwnJbjTZX5zZWbBrfkv62DB62d"
+ }
+ ],
+ "followers_count": 0,
+ "following_count": 0,
+ "fqn": "silverpill@mitra.social",
+ "header": "https://gleasonator.com/images/banner.png",
+ "header_static": "https://gleasonator.com/images/banner.png",
+ "id": "ADIzJ7q9gExPvDKBCS",
+ "last_status_at": "2022-04-15T11:27:33",
+ "locked": false,
+ "note": "",
+ "pleroma": {
+ "accepts_chat_messages": false,
+ "also_known_as": [],
+ "ap_id": "https://mitra.social/users/silverpill",
+ "background_image": null,
+ "deactivated": false,
+ "favicon": "https://gleasonator.com/proxy/XSE9_kQbQyYcSFWszWx2GgCbBuY/aHR0cHM6Ly9taXRyYS5zb2NpYWwvZmF2aWNvbi5pY28/favicon.ico",
+ "hide_favorites": true,
+ "hide_followers": true,
+ "hide_followers_count": false,
+ "hide_follows": true,
+ "hide_follows_count": false,
+ "is_admin": false,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": false,
+ "location": null,
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": []
+ },
+ "source": {
+ "fields": [],
+ "note": "",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false
+ },
+ "sensitive": false
+ },
+ "statuses_count": 135,
+ "url": "https://mitra.social/users/silverpill",
+ "username": "silverpill"
+ },
+ "created_at": "2022-04-15T11:27:33.000Z",
+ "id": "428172",
+ "pleroma": {
+ "is_muted": false,
+ "is_seen": true
+ },
+ "status": {
+ "account": {
+ "acct": "silverpill@mitra.social",
+ "avatar": "https://gleasonator.com/proxy/ZbLqy9s8Hxn9I5K23y2mffsL6iY/aHR0cHM6Ly9taXRyYS5zb2NpYWwvbWVkaWEvNmE3ODViZjdkZDA1ZjYxYzM1OTBlODkzNWFhNDkxNTZhNDk5YWMzMGZkMWU0MDJmNzllN2UxNjRhZGIzNmUyYy5wbmc/6a785bf7dd05f61c3590e8935aa49156a499ac30fd1e402f79e7e164adb36e2c.png",
+ "avatar_static": "https://gleasonator.com/proxy/ZbLqy9s8Hxn9I5K23y2mffsL6iY/aHR0cHM6Ly9taXRyYS5zb2NpYWwvbWVkaWEvNmE3ODViZjdkZDA1ZjYxYzM1OTBlODkzNWFhNDkxNTZhNDk5YWMzMGZkMWU0MDJmNzllN2UxNjRhZGIzNmUyYy5wbmc/6a785bf7dd05f61c3590e8935aa49156a499ac30fd1e402f79e7e164adb36e2c.png",
+ "bot": false,
+ "created_at": "2021-11-11T22:31:51.000Z",
+ "display_name": "silverpill",
+ "emojis": [],
+ "fields": [
+ {
+ "name": "Matrix",
+ "value": "@silverpill:poa.st"
+ },
+ {
+ "name": "Alt",
+ "value": "@silverpill@poa.st"
+ },
+ {
+ "name": "Code",
+ "value": "https://codeberg.org/silverpill/"
+ },
+ {
+ "name": "$XMR",
+ "value": "884y9LmsWY7PQNsyR7bJy1dvj91tuF5spVabyCnPk4KfQtSuzFbQobTFC7xSemJgVW1FWAwnJbjTZX5zZWbBrfkv62DB62d"
+ }
+ ],
+ "followers_count": 0,
+ "following_count": 0,
+ "fqn": "silverpill@mitra.social",
+ "header": "https://gleasonator.com/images/banner.png",
+ "header_static": "https://gleasonator.com/images/banner.png",
+ "id": "ADIzJ7q9gExPvDKBCS",
+ "last_status_at": "2022-04-15T11:27:33",
+ "locked": false,
+ "note": "",
+ "pleroma": {
+ "accepts_chat_messages": false,
+ "also_known_as": [],
+ "ap_id": "https://mitra.social/users/silverpill",
+ "background_image": null,
+ "deactivated": false,
+ "favicon": "https://gleasonator.com/proxy/XSE9_kQbQyYcSFWszWx2GgCbBuY/aHR0cHM6Ly9taXRyYS5zb2NpYWwvZmF2aWNvbi5pY28/favicon.ico",
+ "hide_favorites": true,
+ "hide_followers": true,
+ "hide_followers_count": false,
+ "hide_follows": true,
+ "hide_follows_count": false,
+ "is_admin": false,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": false,
+ "location": null,
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": []
+ },
+ "source": {
+ "fields": [],
+ "note": "",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false
+ },
+ "sensitive": false
+ },
+ "statuses_count": 135,
+ "url": "https://mitra.social/users/silverpill",
+ "username": "silverpill"
+ },
+ "application": null,
+ "bookmarked": true,
+ "card": {
+ "author_name": "",
+ "author_url": "",
+ "blurhash": null,
+ "description": "The ActivityPub protocol is a decentralized social networking protocol\n based upon the [ActivityStreams] 2.0 data format.\n It provides a client to server API for creating, updating and deleting\n content, as well as a federated server to server API for delivering\n notifications and content.",
+ "embed_url": "",
+ "height": 0,
+ "html": "",
+ "image": null,
+ "provider_name": "www.w3.org",
+ "provider_url": "https://www.w3.org",
+ "title": "ActivityPub",
+ "type": "link",
+ "url": "https://www.w3.org/TR/activitypub/#retrieving-objects",
+ "width": 0
+ },
+ "content": "@alex @lain The second one is suggested by ActivityPub spec: https://www.w3.org/TR/activitypub/#retrieving-objects
\nThe first one is likely a legacy of earlier ActivityStreams standards, I'm not sure",
+ "created_at": "2022-04-15T11:27:28.000Z",
+ "emojis": [],
+ "favourited": true,
+ "favourites_count": 2,
+ "id": "AITJf9Wpr0msWChNBI",
+ "in_reply_to_account_id": "9v5bmRalQvjOy0ECcC",
+ "in_reply_to_id": "AISPFI5nnPaS7J94rI",
+ "language": null,
+ "media_attachments": [],
+ "mentions": [
+ {
+ "acct": "alex",
+ "id": "9v5bmRalQvjOy0ECcC",
+ "url": "https://gleasonator.com/users/alex",
+ "username": "alex"
+ },
+ {
+ "acct": "lain@lain.com",
+ "id": "9v5bqYwY2jfmvPNhTM",
+ "url": "https://lain.com/users/lain",
+ "username": "lain"
+ }
+ ],
+ "muted": false,
+ "pinned": false,
+ "pleroma": {
+ "content": {
+ "text/plain": "@alex @lain The second one is suggested by ActivityPub spec: https://www.w3.org/TR/activitypub/#retrieving-objects\nThe first one is likely a legacy of earlier ActivityStreams standards, I'm not sure"
+ },
+ "content_type": null,
+ "conversation_id": "AISPFI2bzH2DxPeWsy",
+ "direct_conversation_id": null,
+ "emoji_reactions": [],
+ "expires_at": null,
+ "in_reply_to_account_acct": "alex",
+ "local": false,
+ "parent_visible": true,
+ "pinned_at": null,
+ "quote": null,
+ "quote_url": null,
+ "quote_visible": false,
+ "spoiler_text": {
+ "text/plain": ""
+ },
+ "thread_muted": false
+ },
+ "poll": null,
+ "reblog": null,
+ "reblogged": false,
+ "reblogs_count": 0,
+ "replies_count": 0,
+ "sensitive": false,
+ "spoiler_text": "",
+ "tags": [],
+ "text": null,
+ "uri": "https://mitra.social/objects/01802cfa-633c-1c2c-e9cf-e6e0ffef0afe",
+ "url": "https://mitra.social/objects/01802cfa-633c-1c2c-e9cf-e6e0ffef0afe",
+ "visibility": "public"
+ },
+ "type": "mention"
+}
diff --git a/app/soapbox/__fixtures__/notification-move.json b/app/soapbox/__fixtures__/notification-move.json
new file mode 100644
index 000000000..4bffd1906
--- /dev/null
+++ b/app/soapbox/__fixtures__/notification-move.json
@@ -0,0 +1,119 @@
+{
+ "account": {
+ "acct": "alex@fedibird.com",
+ "avatar": "https://gleasonator.com/images/avi.png",
+ "avatar_static": "https://gleasonator.com/images/avi.png",
+ "bot": false,
+ "created_at": "2022-01-24T21:25:37.000Z",
+ "display_name": "alex@fedibird.com",
+ "emojis": [],
+ "fields": [],
+ "followers_count": 0,
+ "following_count": 2,
+ "fqn": "alex@fedibird.com",
+ "header": "https://gleasonator.com/images/banner.png",
+ "header_static": "https://gleasonator.com/images/banner.png",
+ "id": "AFmHQ18XZ7Lco68MW8",
+ "last_status_at": "2022-03-16T22:07:53",
+ "locked": false,
+ "note": "
",
+ "pleroma": {
+ "accepts_chat_messages": null,
+ "also_known_as": [],
+ "ap_id": "https://fedibird.com/users/alex",
+ "background_image": null,
+ "birthday": "1993-07-03",
+ "deactivated": false,
+ "favicon": "https://gleasonator.com/proxy/HzfsidHss3CuA7aM2zxXN-tAjF8/aHR0cHM6Ly9mZWRpYmlyZC5jb20vZmF2aWNvbi5pY28/favicon.ico",
+ "hide_favorites": true,
+ "hide_followers": false,
+ "hide_followers_count": false,
+ "hide_follows": false,
+ "hide_follows_count": false,
+ "is_admin": false,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": false,
+ "location": "Texas",
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": []
+ },
+ "source": {
+ "fields": [],
+ "note": "",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false
+ },
+ "sensitive": false
+ },
+ "statuses_count": 5,
+ "url": "https://fedibird.com/@alex",
+ "username": "alex"
+ },
+ "created_at": "2022-03-17T00:08:48.000Z",
+ "id": "406814",
+ "pleroma": {
+ "is_muted": false,
+ "is_seen": true
+ },
+ "target": {
+ "acct": "benis911",
+ "avatar": "https://gleasonator.com/images/avi.png",
+ "avatar_static": "https://gleasonator.com/images/avi.png",
+ "bot": false,
+ "created_at": "2021-03-26T20:42:11.000Z",
+ "display_name": "benis911",
+ "emojis": [],
+ "fields": [],
+ "followers_count": 0,
+ "following_count": 0,
+ "fqn": "benis911@gleasonator.com",
+ "header": "https://media.gleasonator.com/fc595bbbcf5aabefecd1c2adfe5b7f5457db59847992881668653a0338ba25bd.jpg",
+ "header_static": "https://media.gleasonator.com/fc595bbbcf5aabefecd1c2adfe5b7f5457db59847992881668653a0338ba25bd.jpg",
+ "id": "A5c5LK7EJTFR0u26Pg",
+ "last_status_at": "2022-03-19T22:33:38",
+ "locked": false,
+ "note": "hello world 2",
+ "pleroma": {
+ "accepts_chat_messages": true,
+ "also_known_as": [
+ "https://gleasonator.com/users/alex",
+ "https://poa.st/users/alex",
+ "https://fedibird.com/users/alex"
+ ],
+ "ap_id": "https://gleasonator.com/users/benis911",
+ "background_image": null,
+ "birthday": "2000-01-25",
+ "deactivated": false,
+ "favicon": "https://gleasonator.com/favicon.png",
+ "hide_favorites": true,
+ "hide_followers": true,
+ "hide_followers_count": true,
+ "hide_follows": true,
+ "hide_follows_count": true,
+ "is_admin": false,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": false,
+ "location": null,
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": []
+ },
+ "source": {
+ "fields": [],
+ "note": "hello world 2",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false
+ },
+ "sensitive": false
+ },
+ "statuses_count": 174,
+ "url": "https://gleasonator.com/users/benis911",
+ "username": "benis911"
+ },
+ "type": "move"
+}
diff --git a/app/soapbox/__fixtures__/notification-pleroma-chat_mention.json b/app/soapbox/__fixtures__/notification-pleroma-chat_mention.json
new file mode 100644
index 000000000..c90cc7bb9
--- /dev/null
+++ b/app/soapbox/__fixtures__/notification-pleroma-chat_mention.json
@@ -0,0 +1,73 @@
+{
+ "account": {
+ "acct": "dave",
+ "avatar": "https://media.gleasonator.com/68c29c30c18f30dd2898f85466bf1670312dda816617e6d31421c7e4c30a8265.png",
+ "avatar_static": "https://media.gleasonator.com/68c29c30c18f30dd2898f85466bf1670312dda816617e6d31421c7e4c30a8265.png",
+ "bot": false,
+ "created_at": "2020-02-01T07:28:46.000Z",
+ "display_name": "Elden Beedle 🇺🇦 🇫🇷",
+ "emojis": [],
+ "fields": [],
+ "followers_count": 490,
+ "following_count": 367,
+ "fqn": "dave@gleasonator.com",
+ "header": "https://media.gleasonator.com/47e8907c322a0e55d12b211846aa27c6b386e947326fe14bb09c89ef7317901d.jpg",
+ "header_static": "https://media.gleasonator.com/47e8907c322a0e55d12b211846aa27c6b386e947326fe14bb09c89ef7317901d.jpg",
+ "id": "9v5c0Pkz3MT5KTfam8",
+ "last_status_at": "2022-04-16T19:57:10",
+ "locked": false,
+ "note": "Beedle is back, baby!
Mostly just crosspost memes and stuff I find on the internet",
+ "pleroma": {
+ "accepts_chat_messages": true,
+ "also_known_as": [],
+ "ap_id": "https://gleasonator.com/users/dave",
+ "background_image": null,
+ "birthday": "1990-01-01",
+ "deactivated": false,
+ "favicon": "https://gleasonator.com/favicon.png",
+ "hide_favorites": true,
+ "hide_followers": false,
+ "hide_followers_count": false,
+ "hide_follows": false,
+ "hide_follows_count": false,
+ "is_admin": false,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": true,
+ "location": null,
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": []
+ },
+ "source": {
+ "fields": [],
+ "note": "Beedle is back, baby!\r\n\r\nMostly just crosspost memes and stuff I find on the internet",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false
+ },
+ "sensitive": false
+ },
+ "statuses_count": 16758,
+ "url": "https://gleasonator.com/users/dave",
+ "username": "dave"
+ },
+ "chat_message": {
+ "account_id": "9v5c0Pkz3MT5KTfam8",
+ "attachment": null,
+ "card": null,
+ "chat_id": "9yX4Q9DiC2te6lvk5g",
+ "content": "Cool, it works, I'll keep letting you know when I find broken stuff",
+ "created_at": "2022-04-16T19:22:54.000Z",
+ "emojis": [],
+ "id": "AIW4bHoICoZ9CsRTW4",
+ "unread": false
+ },
+ "created_at": "2022-04-16T19:22:55.000Z",
+ "id": "429247",
+ "pleroma": {
+ "is_muted": false,
+ "is_seen": true
+ },
+ "type": "pleroma:chat_mention"
+}
diff --git a/app/soapbox/__fixtures__/notification-pleroma-emoji_reaction.json b/app/soapbox/__fixtures__/notification-pleroma-emoji_reaction.json
new file mode 100644
index 000000000..cc988d3a3
--- /dev/null
+++ b/app/soapbox/__fixtures__/notification-pleroma-emoji_reaction.json
@@ -0,0 +1,301 @@
+{
+ "account": {
+ "acct": "dave",
+ "avatar": "https://media.gleasonator.com/68c29c30c18f30dd2898f85466bf1670312dda816617e6d31421c7e4c30a8265.png",
+ "avatar_static": "https://media.gleasonator.com/68c29c30c18f30dd2898f85466bf1670312dda816617e6d31421c7e4c30a8265.png",
+ "bot": false,
+ "created_at": "2020-02-01T07:28:46.000Z",
+ "display_name": "Elden Beedle 🇺🇦 🇫🇷",
+ "emojis": [],
+ "fields": [],
+ "followers_count": 490,
+ "following_count": 367,
+ "fqn": "dave@gleasonator.com",
+ "header": "https://media.gleasonator.com/47e8907c322a0e55d12b211846aa27c6b386e947326fe14bb09c89ef7317901d.jpg",
+ "header_static": "https://media.gleasonator.com/47e8907c322a0e55d12b211846aa27c6b386e947326fe14bb09c89ef7317901d.jpg",
+ "id": "9v5c0Pkz3MT5KTfam8",
+ "last_status_at": "2022-04-16T19:57:10",
+ "locked": false,
+ "note": "Beedle is back, baby!
Mostly just crosspost memes and stuff I find on the internet",
+ "pleroma": {
+ "accepts_chat_messages": true,
+ "also_known_as": [],
+ "ap_id": "https://gleasonator.com/users/dave",
+ "background_image": null,
+ "birthday": "1990-01-01",
+ "deactivated": false,
+ "favicon": "https://gleasonator.com/favicon.png",
+ "hide_favorites": true,
+ "hide_followers": false,
+ "hide_followers_count": false,
+ "hide_follows": false,
+ "hide_follows_count": false,
+ "is_admin": false,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": true,
+ "location": null,
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": []
+ },
+ "source": {
+ "fields": [],
+ "note": "Beedle is back, baby!\r\n\r\nMostly just crosspost memes and stuff I find on the internet",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false
+ },
+ "sensitive": false
+ },
+ "statuses_count": 16758,
+ "url": "https://gleasonator.com/users/dave",
+ "username": "dave"
+ },
+ "created_at": "2022-04-16T16:52:15.000Z",
+ "emoji": "😮",
+ "id": "429071",
+ "pleroma": {
+ "is_muted": false,
+ "is_seen": true
+ },
+ "status": {
+ "account": {
+ "acct": "alex",
+ "avatar": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
+ "avatar_static": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
+ "bot": false,
+ "created_at": "2020-01-08T01:25:43.000Z",
+ "display_name": "Alex Gleason",
+ "emojis": [],
+ "fields": [
+ {
+ "name": "Website",
+ "value": "https://alexgleason.me"
+ },
+ {
+ "name": "Soapbox",
+ "value": "https://soapbox.pub"
+ },
+ {
+ "name": "Email",
+ "value": "alex@alexgleason.me"
+ },
+ {
+ "name": "Gender identity",
+ "value": "Soyboy"
+ },
+ {
+ "name": "Donate (PayPal)",
+ "value": "https://paypal.me/gleasonator"
+ },
+ {
+ "name": "$BTC",
+ "value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
+ },
+ {
+ "name": "$ETH",
+ "value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
+ },
+ {
+ "name": "$DOGE",
+ "value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
+ },
+ {
+ "name": "$XMR",
+ "value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
+ }
+ ],
+ "follow_requests_count": 0,
+ "followers_count": 2602,
+ "following_count": 1603,
+ "fqn": "alex@gleasonator.com",
+ "header": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
+ "header_static": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
+ "id": "9v5bmRalQvjOy0ECcC",
+ "last_status_at": "2022-04-16T19:23:50",
+ "locked": false,
+ "note": "I create Fediverse software that empowers people online.
I'm vegan btw
Note: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
+ "pleroma": {
+ "accepts_chat_messages": true,
+ "accepts_email_list": true,
+ "allow_following_move": true,
+ "also_known_as": [
+ "https://mitra.social/users/alex"
+ ],
+ "ap_id": "https://gleasonator.com/users/alex",
+ "background_image": null,
+ "birthday": "1993-07-03",
+ "deactivated": false,
+ "email": "alex@alexgleason.me",
+ "favicon": "https://gleasonator.com/favicon.png",
+ "hide_favorites": true,
+ "hide_followers": false,
+ "hide_followers_count": false,
+ "hide_follows": false,
+ "hide_follows_count": false,
+ "is_admin": true,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": true,
+ "location": "Texas",
+ "notification_settings": {
+ "block_from_strangers": false,
+ "hide_notification_contents": false
+ },
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": [],
+ "unread_conversation_count": 392,
+ "unread_notifications_count": 0
+ },
+ "source": {
+ "fields": [
+ {
+ "name": "Website",
+ "value": "https://alexgleason.me"
+ },
+ {
+ "name": "Soapbox",
+ "value": "https://soapbox.pub"
+ },
+ {
+ "name": "Email",
+ "value": "alex@alexgleason.me"
+ },
+ {
+ "name": "Gender identity",
+ "value": "Soyboy"
+ },
+ {
+ "name": "Donate (PayPal)",
+ "value": "https://paypal.me/gleasonator"
+ },
+ {
+ "name": "$BTC",
+ "value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
+ },
+ {
+ "name": "$ETH",
+ "value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
+ },
+ {
+ "name": "$DOGE",
+ "value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
+ },
+ {
+ "name": "$XMR",
+ "value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
+ }
+ ],
+ "note": "I create Fediverse software that empowers people online.\r\n\r\nI'm vegan btw\r\n\r\nNote: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false,
+ "no_rich_text": false,
+ "show_birthday": true,
+ "show_role": true
+ },
+ "privacy": "public",
+ "sensitive": false
+ },
+ "statuses_count": 24050,
+ "url": "https://gleasonator.com/users/alex",
+ "username": "alex"
+ },
+ "application": {
+ "name": "Soapbox FE",
+ "website": "https://soapbox.pub/"
+ },
+ "bookmarked": false,
+ "card": {
+ "author_name": "Kaze Emanuar",
+ "author_url": "https://www.youtube.com/c/KazeEmanuar",
+ "blurhash": null,
+ "description": "",
+ "embed_url": null,
+ "height": 113,
+ "html": "",
+ "image": "https://gleasonator.com/proxy/mI004Vq00johZtAUmMp0fC_XAuM/aHR0cHM6Ly9pLnl0aW1nLmNvbS92aS90X3J6WW5YRVFsRS9ocWRlZmF1bHQuanBn/hqdefault.jpg",
+ "provider_name": "YouTube",
+ "provider_url": "https://www.youtube.com/",
+ "title": "FIXING the ENTIRE SM64 Source Code (INSANE N64 performance)",
+ "type": "video",
+ "url": "https://youtu.be/t_rzYnXEQlE",
+ "width": 200
+ },
+ "content": "Bruh. This guy rewrote the reversed engineered Super Mario 64 code for 10x performance. Games need to be open source. https://youtu.be/t_rzYnXEQlE
",
+ "created_at": "2022-04-16T16:40:28.000Z",
+ "emojis": [],
+ "favourited": false,
+ "favourites_count": 11,
+ "id": "AIVq6SrJg5yb8eGVsm",
+ "in_reply_to_account_id": null,
+ "in_reply_to_id": null,
+ "language": null,
+ "media_attachments": [],
+ "mentions": [],
+ "muted": false,
+ "pinned": false,
+ "pleroma": {
+ "content": {
+ "text/plain": "Bruh. This guy rewrote the reversed engineered Super Mario 64 code for 10x performance. Games need to be open source. https://youtu.be/t_rzYnXEQlE"
+ },
+ "content_type": null,
+ "conversation_id": "AIVq6SqFk37r5LlfE0",
+ "direct_conversation_id": null,
+ "emoji_reactions": [
+ {
+ "count": 1,
+ "me": false,
+ "name": "❤️"
+ },
+ {
+ "count": 2,
+ "me": false,
+ "name": "😮"
+ },
+ {
+ "count": 1,
+ "me": false,
+ "name": "😆"
+ },
+ {
+ "count": 1,
+ "me": false,
+ "name": "👍🏻"
+ },
+ {
+ "count": 1,
+ "me": false,
+ "name": "🔥"
+ }
+ ],
+ "expires_at": null,
+ "in_reply_to_account_acct": null,
+ "local": true,
+ "parent_visible": false,
+ "pinned_at": null,
+ "quote": null,
+ "quote_url": null,
+ "quote_visible": false,
+ "spoiler_text": {
+ "text/plain": ""
+ },
+ "thread_muted": false
+ },
+ "poll": null,
+ "reblog": null,
+ "reblogged": false,
+ "reblogs_count": 7,
+ "replies_count": 2,
+ "sensitive": false,
+ "spoiler_text": "",
+ "tags": [],
+ "text": null,
+ "uri": "https://gleasonator.com/objects/160dcbb2-73bc-4cd2-971e-e7f6a38602a0",
+ "url": "https://gleasonator.com/notice/AIVq6SrJg5yb8eGVsm",
+ "visibility": "public"
+ },
+ "type": "pleroma:emoji_reaction"
+}
diff --git a/app/soapbox/__fixtures__/notification-poll.json b/app/soapbox/__fixtures__/notification-poll.json
new file mode 100644
index 000000000..fe582f249
--- /dev/null
+++ b/app/soapbox/__fixtures__/notification-poll.json
@@ -0,0 +1,202 @@
+{
+ "account": {
+ "acct": "dave",
+ "avatar": "https://media.gleasonator.com/68c29c30c18f30dd2898f85466bf1670312dda816617e6d31421c7e4c30a8265.png",
+ "avatar_static": "https://media.gleasonator.com/68c29c30c18f30dd2898f85466bf1670312dda816617e6d31421c7e4c30a8265.png",
+ "bot": false,
+ "created_at": "2020-02-01T07:28:46.000Z",
+ "display_name": "Elden Beedle 🇺🇦 🇫🇷",
+ "emojis": [],
+ "fields": [],
+ "followers_count": 490,
+ "following_count": 367,
+ "fqn": "dave@gleasonator.com",
+ "header": "https://media.gleasonator.com/47e8907c322a0e55d12b211846aa27c6b386e947326fe14bb09c89ef7317901d.jpg",
+ "header_static": "https://media.gleasonator.com/47e8907c322a0e55d12b211846aa27c6b386e947326fe14bb09c89ef7317901d.jpg",
+ "id": "9v5c0Pkz3MT5KTfam8",
+ "last_status_at": "2022-04-16T19:57:10",
+ "locked": false,
+ "note": "Beedle is back, baby!
Mostly just crosspost memes and stuff I find on the internet",
+ "pleroma": {
+ "accepts_chat_messages": true,
+ "also_known_as": [],
+ "ap_id": "https://gleasonator.com/users/dave",
+ "background_image": null,
+ "birthday": "1990-01-01",
+ "deactivated": false,
+ "favicon": "https://gleasonator.com/favicon.png",
+ "hide_favorites": true,
+ "hide_followers": false,
+ "hide_followers_count": false,
+ "hide_follows": false,
+ "hide_follows_count": false,
+ "is_admin": false,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": true,
+ "location": null,
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": []
+ },
+ "source": {
+ "fields": [],
+ "note": "Beedle is back, baby!\r\n\r\nMostly just crosspost memes and stuff I find on the internet",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false
+ },
+ "sensitive": false
+ },
+ "statuses_count": 16758,
+ "url": "https://gleasonator.com/users/dave",
+ "username": "dave"
+ },
+ "created_at": "2022-04-14T01:12:27.000Z",
+ "id": "427339",
+ "pleroma": {
+ "is_muted": false,
+ "is_seen": true
+ },
+ "status": {
+ "account": {
+ "acct": "dave",
+ "avatar": "https://media.gleasonator.com/68c29c30c18f30dd2898f85466bf1670312dda816617e6d31421c7e4c30a8265.png",
+ "avatar_static": "https://media.gleasonator.com/68c29c30c18f30dd2898f85466bf1670312dda816617e6d31421c7e4c30a8265.png",
+ "bot": false,
+ "created_at": "2020-02-01T07:28:46.000Z",
+ "display_name": "Elden Beedle 🇺🇦 🇫🇷",
+ "emojis": [],
+ "fields": [],
+ "followers_count": 490,
+ "following_count": 367,
+ "fqn": "dave@gleasonator.com",
+ "header": "https://media.gleasonator.com/47e8907c322a0e55d12b211846aa27c6b386e947326fe14bb09c89ef7317901d.jpg",
+ "header_static": "https://media.gleasonator.com/47e8907c322a0e55d12b211846aa27c6b386e947326fe14bb09c89ef7317901d.jpg",
+ "id": "9v5c0Pkz3MT5KTfam8",
+ "last_status_at": "2022-04-16T19:57:10",
+ "locked": false,
+ "note": "Beedle is back, baby!
Mostly just crosspost memes and stuff I find on the internet",
+ "pleroma": {
+ "accepts_chat_messages": true,
+ "also_known_as": [],
+ "ap_id": "https://gleasonator.com/users/dave",
+ "background_image": null,
+ "birthday": "1990-01-01",
+ "deactivated": false,
+ "favicon": "https://gleasonator.com/favicon.png",
+ "hide_favorites": true,
+ "hide_followers": false,
+ "hide_followers_count": false,
+ "hide_follows": false,
+ "hide_follows_count": false,
+ "is_admin": false,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": true,
+ "location": null,
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": []
+ },
+ "source": {
+ "fields": [],
+ "note": "Beedle is back, baby!\r\n\r\nMostly just crosspost memes and stuff I find on the internet",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false
+ },
+ "sensitive": false
+ },
+ "statuses_count": 16758,
+ "url": "https://gleasonator.com/users/dave",
+ "username": "dave"
+ },
+ "application": {
+ "name": "Soapbox FE",
+ "website": "https://soapbox.pub/"
+ },
+ "bookmarked": false,
+ "card": null,
+ "content": "Focusing on just the look, what do you guys think?
",
+ "created_at": "2022-04-13T01:12:26.000Z",
+ "emojis": [],
+ "favourited": false,
+ "favourites_count": 1,
+ "id": "AIOHjtGEaqUHoXGVf6",
+ "in_reply_to_account_id": "9v5c0Pkz3MT5KTfam8",
+ "in_reply_to_id": "AIOFTLqQrljhdNBNHE",
+ "language": null,
+ "media_attachments": [],
+ "mentions": [
+ {
+ "acct": "dave",
+ "id": "9v5c0Pkz3MT5KTfam8",
+ "url": "https://gleasonator.com/users/dave",
+ "username": "dave"
+ }
+ ],
+ "muted": false,
+ "pinned": false,
+ "pleroma": {
+ "content": {
+ "text/plain": "Focusing on just the look, what do you guys think?"
+ },
+ "content_type": null,
+ "conversation_id": "AIOFTLp0x2bNYyWF4C",
+ "direct_conversation_id": null,
+ "emoji_reactions": [],
+ "expires_at": null,
+ "in_reply_to_account_acct": "dave",
+ "local": true,
+ "parent_visible": true,
+ "pinned_at": null,
+ "quote": null,
+ "quote_url": null,
+ "quote_visible": false,
+ "spoiler_text": {
+ "text/plain": ""
+ },
+ "thread_muted": false
+ },
+ "poll": {
+ "emojis": [],
+ "expired": true,
+ "expires_at": "2022-04-14T01:12:26.000Z",
+ "id": "AIOHjtAuucEZY2mGNE",
+ "multiple": false,
+ "options": [
+ {
+ "title": "Looks good, looking forward to wider deployment",
+ "votes_count": 10
+ },
+ {
+ "title": "Not a fan, l'll stick to the current UI thanks",
+ "votes_count": 1
+ },
+ {
+ "title": "Hard to say, need to actually try to decide honestly",
+ "votes_count": 1
+ }
+ ],
+ "own_votes": [
+ 0
+ ],
+ "voted": true,
+ "voters_count": 12,
+ "votes_count": 12
+ },
+ "reblog": null,
+ "reblogged": false,
+ "reblogs_count": 1,
+ "replies_count": 1,
+ "sensitive": false,
+ "spoiler_text": "",
+ "tags": [],
+ "text": null,
+ "uri": "https://gleasonator.com/objects/a8465271-a48d-4c39-a0a9-d3eda3ab2735",
+ "url": "https://gleasonator.com/notice/AIOHjtGEaqUHoXGVf6",
+ "visibility": "public"
+ },
+ "type": "poll"
+}
diff --git a/app/soapbox/__fixtures__/notification-reblog.json b/app/soapbox/__fixtures__/notification-reblog.json
new file mode 100644
index 000000000..94638b8cd
--- /dev/null
+++ b/app/soapbox/__fixtures__/notification-reblog.json
@@ -0,0 +1,284 @@
+{
+ "account": {
+ "acct": "rob@nicecrew.digital",
+ "avatar": "https://gleasonator.com/proxy/RcEgR4-0InIpw_sCpDWV-XrAbmY/aHR0cHM6Ly9uaWNlY3Jldy5kaWdpdGFsL21lZGlhL2M0MTllMTk1Nzg0MmEzMTY5M2MzNDExNTZlMTBhNmQwMTY2ZTM5YzQzM2ExZTczMmVmYWNlYmJkYjAyMDYzZjEucG5n/c419e1957842a31693c341156e10a6d0166e39c433a1e732efacebbdb02063f1.png",
+ "avatar_static": "https://gleasonator.com/proxy/RcEgR4-0InIpw_sCpDWV-XrAbmY/aHR0cHM6Ly9uaWNlY3Jldy5kaWdpdGFsL21lZGlhL2M0MTllMTk1Nzg0MmEzMTY5M2MzNDExNTZlMTBhNmQwMTY2ZTM5YzQzM2ExZTczMmVmYWNlYmJkYjAyMDYzZjEucG5n/c419e1957842a31693c341156e10a6d0166e39c433a1e732efacebbdb02063f1.png",
+ "bot": false,
+ "created_at": "2022-03-10T12:30:41.000Z",
+ "display_name": "Rob Colbert",
+ "emojis": [],
+ "fields": [
+ {
+ "name": "Shing.tv",
+ "value": "https://shing.tv"
+ },
+ {
+ "name": "LibertyLinks",
+ "value": "https://libertylinks.io"
+ },
+ {
+ "name": "GiveSendGo",
+ "value": "https://givesendgo.com/dtp"
+ }
+ ],
+ "followers_count": 0,
+ "following_count": 0,
+ "fqn": "rob@nicecrew.digital",
+ "header": "https://gleasonator.com/proxy/t4--aro68-XZlasaR2bYiuiZMcA/aHR0cHM6Ly9uaWNlY3Jldy5kaWdpdGFsL21lZGlhL2E5ODYzYWE4YjEzM2QwMzkxNmU1N2MzNDgzMzBhZmE5MTM5MDFlNGZiMDEwYjk1Y2FiZjlmYmZiZTA4N2QxODMucG5n/a9863aa8b133d03916e57c348330afa913901e4fb010b95cabf9fbfbe087d183.png",
+ "header_static": "https://gleasonator.com/proxy/t4--aro68-XZlasaR2bYiuiZMcA/aHR0cHM6Ly9uaWNlY3Jldy5kaWdpdGFsL21lZGlhL2E5ODYzYWE4YjEzM2QwMzkxNmU1N2MzNDgzMzBhZmE5MTM5MDFlNGZiMDEwYjk1Y2FiZjlmYmZiZTA4N2QxODMucG5n/a9863aa8b133d03916e57c348330afa913901e4fb010b95cabf9fbfbe087d183.png",
+ "id": "AHGmnebARD1aa1IiBc",
+ "last_status_at": "2022-04-16T21:08:35",
+ "locked": false,
+ "note": "Creator and CTO of the Digital Telepresence Platform and DTP Technologies, LLC.",
+ "pleroma": {
+ "accepts_chat_messages": true,
+ "also_known_as": [],
+ "ap_id": "https://nicecrew.digital/users/rob",
+ "background_image": null,
+ "deactivated": false,
+ "favicon": "https://gleasonator.com/proxy/gb2NPo0Kv_svADN1_J9_9iSwlrY/aHR0cHM6Ly9uaWNlY3Jldy5kaWdpdGFsL2Zhdmljb24ucG5n/favicon.png",
+ "hide_favorites": true,
+ "hide_followers": true,
+ "hide_followers_count": false,
+ "hide_follows": true,
+ "hide_follows_count": false,
+ "is_admin": false,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": false,
+ "location": null,
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": []
+ },
+ "source": {
+ "fields": [],
+ "note": "",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false
+ },
+ "sensitive": false
+ },
+ "statuses_count": 761,
+ "url": "https://nicecrew.digital/users/rob",
+ "username": "rob"
+ },
+ "created_at": "2022-04-16T03:43:24.000Z",
+ "id": "428608",
+ "pleroma": {
+ "is_muted": false,
+ "is_seen": true
+ },
+ "status": {
+ "account": {
+ "acct": "alex",
+ "avatar": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
+ "avatar_static": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
+ "bot": false,
+ "created_at": "2020-01-08T01:25:43.000Z",
+ "display_name": "Alex Gleason",
+ "emojis": [],
+ "fields": [
+ {
+ "name": "Website",
+ "value": "https://alexgleason.me"
+ },
+ {
+ "name": "Soapbox",
+ "value": "https://soapbox.pub"
+ },
+ {
+ "name": "Email",
+ "value": "alex@alexgleason.me"
+ },
+ {
+ "name": "Gender identity",
+ "value": "Soyboy"
+ },
+ {
+ "name": "Donate (PayPal)",
+ "value": "https://paypal.me/gleasonator"
+ },
+ {
+ "name": "$BTC",
+ "value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
+ },
+ {
+ "name": "$ETH",
+ "value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
+ },
+ {
+ "name": "$DOGE",
+ "value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
+ },
+ {
+ "name": "$XMR",
+ "value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
+ }
+ ],
+ "follow_requests_count": 0,
+ "followers_count": 2602,
+ "following_count": 1603,
+ "fqn": "alex@gleasonator.com",
+ "header": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
+ "header_static": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
+ "id": "9v5bmRalQvjOy0ECcC",
+ "last_status_at": "2022-04-16T19:23:50",
+ "locked": false,
+ "note": "I create Fediverse software that empowers people online.
I'm vegan btw
Note: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
+ "pleroma": {
+ "accepts_chat_messages": true,
+ "accepts_email_list": true,
+ "allow_following_move": true,
+ "also_known_as": [
+ "https://mitra.social/users/alex"
+ ],
+ "ap_id": "https://gleasonator.com/users/alex",
+ "background_image": null,
+ "birthday": "1993-07-03",
+ "deactivated": false,
+ "email": "alex@alexgleason.me",
+ "favicon": "https://gleasonator.com/favicon.png",
+ "hide_favorites": true,
+ "hide_followers": false,
+ "hide_followers_count": false,
+ "hide_follows": false,
+ "hide_follows_count": false,
+ "is_admin": true,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": true,
+ "location": "Texas",
+ "notification_settings": {
+ "block_from_strangers": false,
+ "hide_notification_contents": false
+ },
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": [],
+ "unread_conversation_count": 392,
+ "unread_notifications_count": 0
+ },
+ "source": {
+ "fields": [
+ {
+ "name": "Website",
+ "value": "https://alexgleason.me"
+ },
+ {
+ "name": "Soapbox",
+ "value": "https://soapbox.pub"
+ },
+ {
+ "name": "Email",
+ "value": "alex@alexgleason.me"
+ },
+ {
+ "name": "Gender identity",
+ "value": "Soyboy"
+ },
+ {
+ "name": "Donate (PayPal)",
+ "value": "https://paypal.me/gleasonator"
+ },
+ {
+ "name": "$BTC",
+ "value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
+ },
+ {
+ "name": "$ETH",
+ "value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
+ },
+ {
+ "name": "$DOGE",
+ "value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
+ },
+ {
+ "name": "$XMR",
+ "value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
+ }
+ ],
+ "note": "I create Fediverse software that empowers people online.\r\n\r\nI'm vegan btw\r\n\r\nNote: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false,
+ "no_rich_text": false,
+ "show_birthday": true,
+ "show_role": true
+ },
+ "privacy": "public",
+ "sensitive": false
+ },
+ "statuses_count": 24050,
+ "url": "https://gleasonator.com/users/alex",
+ "username": "alex"
+ },
+ "application": {
+ "name": "Soapbox FE",
+ "website": "https://soapbox.pub/"
+ },
+ "bookmarked": false,
+ "card": null,
+ "content": "The @fsf needs to give out an award to every American who has never downloaded TikTok.
",
+ "created_at": "2022-04-16T03:42:50.000Z",
+ "emojis": [],
+ "favourited": false,
+ "favourites_count": 15,
+ "id": "AIUihbqUEe5Uvv7P9s",
+ "in_reply_to_account_id": null,
+ "in_reply_to_id": null,
+ "language": null,
+ "media_attachments": [],
+ "mentions": [
+ {
+ "acct": "fsf@status.fsf.org",
+ "id": "9v5boQSsaxVc3AU8u0",
+ "url": "https://status.fsf.org/fsf",
+ "username": "fsf"
+ }
+ ],
+ "muted": false,
+ "pinned": false,
+ "pleroma": {
+ "content": {
+ "text/plain": "The @fsf needs to give out an award to every American who has never downloaded TikTok."
+ },
+ "content_type": null,
+ "conversation_id": "AIUihbp4JuxArWSGwq",
+ "direct_conversation_id": null,
+ "emoji_reactions": [
+ {
+ "count": 2,
+ "me": false,
+ "name": "🔥"
+ }
+ ],
+ "expires_at": null,
+ "in_reply_to_account_acct": null,
+ "local": true,
+ "parent_visible": false,
+ "pinned_at": null,
+ "quote": null,
+ "quote_url": null,
+ "quote_visible": false,
+ "spoiler_text": {
+ "text/plain": ""
+ },
+ "thread_muted": false
+ },
+ "poll": null,
+ "reblog": null,
+ "reblogged": false,
+ "reblogs_count": 8,
+ "replies_count": 4,
+ "sensitive": false,
+ "spoiler_text": "",
+ "tags": [],
+ "text": null,
+ "uri": "https://gleasonator.com/objects/6be95787-fb9c-41cd-96cf-9652b2680863",
+ "url": "https://gleasonator.com/notice/AIUihbqUEe5Uvv7P9s",
+ "visibility": "public"
+ },
+ "type": "reblog"
+}
diff --git a/app/soapbox/actions/settings.js b/app/soapbox/actions/settings.js
index 2338a4c4f..098cdfdb1 100644
--- a/app/soapbox/actions/settings.js
+++ b/app/soapbox/actions/settings.js
@@ -84,7 +84,7 @@ export const defaultSettings = ImmutableMap({
shows: ImmutableMap({
follow: true,
- follow_request: false,
+ follow_request: true,
favourite: true,
reblog: true,
mention: true,
diff --git a/app/soapbox/components/poll.tsx b/app/soapbox/components/poll.tsx
index b7bcbf547..e95b4342e 100644
--- a/app/soapbox/components/poll.tsx
+++ b/app/soapbox/components/poll.tsx
@@ -74,7 +74,7 @@ const PollOptionText: React.FC = ({ poll, option, index, active
{!showResults && (
{
const { account } = status;
@@ -598,7 +598,7 @@ class Status extends ImmutablePureComponent {
// const domain = getDomain(status.account);
return (
-
+
{
const me = state.get('me');
const account = getAccount(state, me);
const soapbox = getSoapboxConfig(state);
+ const features = getFeatures(state.instance);
return {
account,
+ features,
maxFields: state.getIn(['instance', 'pleroma', 'metadata', 'fields_limits', 'max_fields'], 4),
verifiedCanEditName: soapbox.get('verifiedCanEditName'),
- supportsEmailList: getFeatures(state.get('instance')).emailList,
};
};
@@ -242,7 +243,7 @@ class EditProfile extends ImmutablePureComponent {
}
render() {
- const { intl, account, verifiedCanEditName, supportsEmailList /* maxFields */ } = this.props;
+ const { intl, account, verifiedCanEditName, features /* maxFields */ } = this.props;
const verified = account.get('verified');
const canEditName = verifiedCanEditName || !verified;
@@ -262,27 +263,31 @@ class EditProfile extends ImmutablePureComponent {
/>
-
}
- >
-
-
+ {features.accountLocation && (
+
}
+ >
+
+
+ )}
-
}
- >
-
-
+ {features.accountWebsite && (
+
}
+ >
+
+
+ )}
}
@@ -351,13 +356,15 @@ class EditProfile extends ImmutablePureComponent {
checked={this.state.discoverable}
onChange={this.handleCheckboxChange}
/>*/}
- {supportsEmailList &&
}
- hint={
}
- name='accepts_email_list'
- checked={this.state.accepts_email_list}
- onChange={this.handleCheckboxChange}
- />}
+ {features.emailList && (
+
}
+ hint={
}
+ name='accepts_email_list'
+ checked={this.state.accepts_email_list}
+ onChange={this.handleCheckboxChange}
+ />
+ )}
{/* */}
{/*
diff --git a/app/soapbox/features/notifications/components/__tests__/notification.test.tsx b/app/soapbox/features/notifications/components/__tests__/notification.test.tsx
new file mode 100644
index 000000000..f11d43f09
--- /dev/null
+++ b/app/soapbox/features/notifications/components/__tests__/notification.test.tsx
@@ -0,0 +1,103 @@
+import * as React from 'react';
+
+import { updateNotifications } from '../../../../actions/notifications';
+import { render, screen, rootState, createTestStore } from '../../../../jest/test-helpers';
+import { makeGetNotification } from '../../../../selectors';
+import Notification from '../notification';
+
+const getNotification = makeGetNotification();
+
+/** Prepare the notification for use by the component */
+const normalize = (notification: any) => {
+ const store = createTestStore(rootState);
+ store.dispatch(updateNotifications(notification) as any);
+ const state = store.getState();
+
+ return {
+ // @ts-ignore
+ notification: getNotification(state, state.notifications.items.get(notification.id)),
+ state,
+ };
+};
+
+describe('
', () => {
+ it('renders a follow notification', async() => {
+ const { notification, state } = normalize(require('soapbox/__fixtures__/notification-follow.json'));
+
+ render(
, undefined, state);
+
+ expect(screen.getByTestId('notification')).toBeInTheDocument();
+ expect(screen.getByTestId('account')).toContainHTML('neko@rdrama.cc');
+ });
+
+ it('renders a favourite notification', async() => {
+ const { notification, state } = normalize(require('soapbox/__fixtures__/notification-favourite.json'));
+
+ render(
, undefined, state);
+
+ expect(screen.getByTestId('notification')).toContainHTML('Hollahollara@spinster.xyz');
+ expect(screen.getByTestId('status')).toContainHTML('https://media.gleasonator.com');
+ });
+
+ it('renders a follow_request notification', async() => {
+ const { notification, state } = normalize(require('soapbox/__fixtures__/notification-follow_request.json'));
+
+ render(
, undefined, state);
+
+ expect(screen.getByTestId('notification')).toBeInTheDocument();
+ expect(screen.getByTestId('account')).toContainHTML('alex@spinster.xyz');
+ });
+
+ it('renders a mention notification', async() => {
+ const { notification, state } = normalize(require('soapbox/__fixtures__/notification-mention.json'));
+
+ render(
, undefined, state);
+
+ expect(screen.getByTestId('notification')).toContainHTML('silverpill@mitra.social');
+ expect(screen.getByTestId('status')).toContainHTML('ActivityPub spec');
+ });
+
+ it('renders a move notification', async() => {
+ const { notification, state } = normalize(require('soapbox/__fixtures__/notification-move.json'));
+
+ render(
, undefined, state);
+
+ expect(screen.getByTestId('notification')).toContainHTML('alex@fedibird.com');
+ expect(screen.getByTestId('account')).toContainHTML('benis911');
+ });
+
+ it('renders a pleroma:emoji_reaction notification', async() => {
+ const { notification, state } = normalize(require('soapbox/__fixtures__/notification-pleroma-emoji_reaction.json'));
+
+ render(
, undefined, state);
+
+ expect(screen.getByTestId('notification')).toContainHTML('😮');
+ expect(screen.getByTestId('status')).toContainHTML('Super Mario 64');
+ });
+
+ it('renders a pleroma:chat_mention notification', async() => {
+ const { notification, state } = normalize(require('soapbox/__fixtures__/notification-pleroma-chat_mention.json'));
+
+ render(
, undefined, state);
+
+ expect(screen.getByTestId('notification')).toContainHTML('dave');
+ });
+
+ it('renders a poll notification', async() => {
+ const { notification, state } = normalize(require('soapbox/__fixtures__/notification-poll.json'));
+
+ render(
, undefined, state);
+
+ expect(screen.getByTestId('notification')).toBeInTheDocument();
+ expect(screen.getByTestId('status')).toContainHTML('what do you guys think?');
+ });
+
+ it('renders a reblog notification', async() => {
+ const { notification, state } = normalize(require('soapbox/__fixtures__/notification-reblog.json'));
+
+ render(
, undefined, state);
+
+ expect(screen.getByTestId('notification')).toContainHTML('rob@nicecrew.digital');
+ expect(screen.getByTestId('status')).toContainHTML('never downloaded TikTok');
+ });
+});
diff --git a/app/soapbox/features/notifications/components/notification.js b/app/soapbox/features/notifications/components/notification.js
deleted file mode 100644
index 00911e68e..000000000
--- a/app/soapbox/features/notifications/components/notification.js
+++ /dev/null
@@ -1,251 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import { HotKeys } from 'react-hotkeys';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { FormattedMessage, useIntl } from 'react-intl';
-import { useHistory } from 'react-router-dom';
-
-import Icon from '../../../components/icon';
-import Permalink from '../../../components/permalink';
-import { HStack, Text } from '../../../components/ui';
-import AccountContainer from '../../../containers/account_container';
-import StatusContainer from '../../../containers/status_container';
-
-const notificationForScreenReader = (intl, message, timestamp) => {
- const output = [message];
-
- output.push(intl.formatDate(timestamp, { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }));
-
- return output.join(', ');
-};
-
-// Workaround for dynamic messages (https://github.com/formatjs/babel-plugin-react-intl/issues/119#issuecomment-326202499)
-function FormattedMessageFixed(props) {
- return
;
-}
-
-const buildLink = (account) => (
-
-
-
-);
-
-export const NOTIFICATION_TYPES = ['follow', 'mention', 'favourite', 'reblog', 'status'];
-
-const icons = {
- follow: require('@tabler/icons/icons/user-plus.svg'),
- mention: require('@tabler/icons/icons/at.svg'),
- favourite: require('@tabler/icons/icons/heart.svg'),
- reblog: require('@tabler/icons/icons/repeat.svg'),
- status: require('@tabler/icons/icons/home.svg'),
-};
-
-const messages = {
- follow: {
- id: 'notification.follow',
- defaultMessage: '{name} followed you',
- },
- mention: {
- id: 'notification.mentioned',
- defaultMessage: '{name} mentioned you',
- },
- favourite: {
- id: 'notification.favourite',
- defaultMessage: '{name} liked your TRUTH',
- },
- reblog: {
- id: 'notification.reblog',
- defaultMessage: '{name} re-TRUTH your TRUTH',
- },
- status: {
- id: 'notification.status',
- defaultMessage: '{name} just posted',
- },
-};
-
-const buildMessage = (type, account) => {
- const link = buildLink(account);
-
- return (
-
- );
-};
-
-const Notification = (props) => {
- const { hidden, notification, onMoveUp, onMoveDown } = props;
-
- const history = useHistory();
- const intl = useIntl();
-
- const type = notification.get('type');
- const timestamp = notification.get('created_at');
- const account = notification.get('account');
-
- const getHandlers = () => ({
- reply: handleMention,
- favourite: handleHotkeyFavourite,
- boost: handleHotkeyBoost,
- mention: handleMention,
- open: handleOpen,
- openProfile: handleOpenProfile,
- moveUp: handleMoveUp,
- moveDown: handleMoveDown,
- toggleHidden: handleHotkeyToggleHidden,
- });
-
- const handleOpen = () => {
- if (notification.get('status')) {
- history.push(`/@${notification.getIn(['account', 'acct'])}/posts/${notification.getIn(['status', 'id'])}`);
- } else {
- handleOpenProfile();
- }
- };
-
- const handleOpenProfile = () => {
- history.push(`/@${notification.getIn(['account', 'acct'])}`);
- };
-
- const handleMention = (event) => {
- event.preventDefault();
-
- props.onMention(notification.get('account'), history);
- };
-
- const handleHotkeyFavourite = () => {
- const status = notification.get('status');
- if (status) props.onFavourite(status);
- };
-
- const handleHotkeyBoost = (e) => {
- const status = notification.get('status');
- if (status) props.onReblog(status, e);
- };
-
- const handleHotkeyToggleHidden = () => {
- const status = notification.get('status');
- if (status) props.onToggleHidden(status);
- };
-
- const handleMoveUp = () => {
- onMoveUp(notification.get('id'));
- };
-
- const handleMoveDown = () => {
- onMoveDown(notification.get('id'));
- };
-
- const renderContent = () => {
- switch (type) {
- case 'follow':
- return (
-
- );
- case 'favourite':
- case 'mention':
- case 'reblog':
- case 'status':
- return (
-
- );
- default:
- return null;
- }
- };
-
- if (!NOTIFICATION_TYPES.includes(type)) {
- return null;
- }
-
- const message = buildMessage(type, account);
-
- return (
-
-
-
-
-
-
-
-
-
- {message}
-
-
-
-
-
-
- {renderContent()}
-
-
-
-
- );
-};
-
-Notification.propTypes = {
- hidden: PropTypes.bool,
- notification: ImmutablePropTypes.record.isRequired,
- onMoveUp: PropTypes.func.isRequired,
- onMoveDown: PropTypes.func.isRequired,
- onMention: PropTypes.func.isRequired,
- onFavourite: PropTypes.func.isRequired,
- onReblog: PropTypes.func.isRequired,
- onToggleHidden: PropTypes.func.isRequired,
- getScrollPosition: PropTypes.func,
- updateScrollBottom: PropTypes.func,
- cacheMediaWidth: PropTypes.func,
- cachedMediaWidth: PropTypes.number,
- siteTitle: PropTypes.string,
-};
-
-export default Notification;
diff --git a/app/soapbox/features/notifications/components/notification.tsx b/app/soapbox/features/notifications/components/notification.tsx
new file mode 100644
index 000000000..fa85cc134
--- /dev/null
+++ b/app/soapbox/features/notifications/components/notification.tsx
@@ -0,0 +1,307 @@
+import React from 'react';
+import { HotKeys } from 'react-hotkeys';
+import { FormattedMessage, useIntl } from 'react-intl';
+import { useHistory } from 'react-router-dom';
+
+import Icon from '../../../components/icon';
+import Permalink from '../../../components/permalink';
+import { HStack, Text, Emoji } from '../../../components/ui';
+import AccountContainer from '../../../containers/account_container';
+import StatusContainer from '../../../containers/status_container';
+
+import type { History } from 'history';
+import type { ScrollPosition } from 'soapbox/components/status';
+import type { NotificationType } from 'soapbox/normalizers/notification';
+import type { Account, Status, Notification as NotificationEntity } from 'soapbox/types/entities';
+
+const notificationForScreenReader = (intl: ReturnType
, message: string, timestamp: Date) => {
+ const output = [message];
+
+ output.push(intl.formatDate(timestamp, { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }));
+
+ return output.join(', ');
+};
+
+// Workaround for dynamic messages (https://github.com/formatjs/babel-plugin-react-intl/issues/119#issuecomment-326202499)
+function FormattedMessageFixed(props: any) {
+ return ;
+}
+
+const buildLink = (account: Account): JSX.Element => (
+
+
+
+);
+
+const icons: Record = {
+ follow: require('@tabler/icons/icons/user-plus.svg'),
+ follow_request: require('@tabler/icons/icons/user-plus.svg'),
+ mention: require('@tabler/icons/icons/at.svg'),
+ favourite: require('@tabler/icons/icons/heart.svg'),
+ reblog: require('@tabler/icons/icons/repeat.svg'),
+ status: require('@tabler/icons/icons/home.svg'),
+ poll: require('@tabler/icons/icons/chart-bar.svg'),
+ move: require('@tabler/icons/icons/briefcase.svg'),
+ 'pleroma:chat_mention': require('@tabler/icons/icons/messages.svg'),
+ 'pleroma:emoji_reaction': require('@tabler/icons/icons/mood-happy.svg'),
+};
+
+const messages: Record = {
+ follow: {
+ id: 'notification.follow',
+ defaultMessage: '{name} followed you',
+ },
+ follow_request: {
+ id: 'notification.follow_request',
+ defaultMessage: '{name} has requested to follow you',
+ },
+ mention: {
+ id: 'notification.mentioned',
+ defaultMessage: '{name} mentioned you',
+ },
+ favourite: {
+ id: 'notification.favourite',
+ defaultMessage: '{name} liked your post',
+ },
+ reblog: {
+ id: 'notification.reblog',
+ defaultMessage: '{name} reposted your post',
+ },
+ status: {
+ id: 'notification.status',
+ defaultMessage: '{name} just posted',
+ },
+ poll: {
+ id: 'notification.poll',
+ defaultMessage: 'A poll you have voted in has ended',
+ },
+ move: {
+ id: 'notification.move',
+ defaultMessage: '{name} moved to {targetName}',
+ },
+ 'pleroma:chat_mention': {
+ id: 'notification.chat_mention',
+ defaultMessage: '{name} sent you a message',
+ },
+ 'pleroma:emoji_reaction': {
+ id: 'notification.pleroma:emoji_reaction',
+ defaultMessage: '{name} reacted to your post',
+ },
+};
+
+const buildMessage = (type: NotificationType, account: Account): JSX.Element => {
+ const link = buildLink(account);
+
+ return (
+
+ );
+};
+
+interface INotificaton {
+ hidden?: boolean,
+ notification: NotificationEntity,
+ onMoveUp: (notificationId: string) => void,
+ onMoveDown: (notificationId: string) => void,
+ onMention: (account: Account, history: History) => void,
+ onFavourite: (status: Status) => void,
+ onReblog: (status: Status, e?: KeyboardEvent) => void,
+ onToggleHidden: (status: Status) => void,
+ getScrollPosition?: () => ScrollPosition | undefined,
+ updateScrollBottom?: (bottom: number) => void,
+ cacheMediaWidth: () => void,
+ cachedMediaWidth: number,
+ siteTitle?: string,
+}
+
+const Notification: React.FC = (props) => {
+ const { hidden = false, notification, onMoveUp, onMoveDown } = props;
+
+ const history = useHistory();
+ const intl = useIntl();
+
+ const type = notification.type;
+ const { account, status } = notification;
+
+ const getHandlers = () => ({
+ reply: handleMention,
+ favourite: handleHotkeyFavourite,
+ boost: handleHotkeyBoost,
+ mention: handleMention,
+ open: handleOpen,
+ openProfile: handleOpenProfile,
+ moveUp: handleMoveUp,
+ moveDown: handleMoveDown,
+ toggleHidden: handleHotkeyToggleHidden,
+ });
+
+ const handleOpen = () => {
+ if (status && typeof status === 'object' && account && typeof account === 'object') {
+ history.push(`/@${account.acct}/posts/${status.id}`);
+ } else {
+ handleOpenProfile();
+ }
+ };
+
+ const handleOpenProfile = () => {
+ if (account && typeof account === 'object') {
+ history.push(`/@${account.acct}`);
+ }
+ };
+
+ const handleMention = (e?: KeyboardEvent) => {
+ e?.preventDefault();
+
+ if (account && typeof account === 'object') {
+ props.onMention(account, history);
+ }
+ };
+
+ const handleHotkeyFavourite = (e?: KeyboardEvent) => {
+ if (status && typeof status === 'object') {
+ props.onFavourite(status);
+ }
+ };
+
+ const handleHotkeyBoost = (e?: KeyboardEvent) => {
+ if (status && typeof status === 'object') {
+ props.onReblog(status, e);
+ }
+ };
+
+ const handleHotkeyToggleHidden = (e?: KeyboardEvent) => {
+ if (status && typeof status === 'object') {
+ props.onToggleHidden(status);
+ }
+ };
+
+ const handleMoveUp = () => {
+ onMoveUp(notification.id);
+ };
+
+ const handleMoveDown = () => {
+ onMoveDown(notification.id);
+ };
+
+ const renderIcon = (): React.ReactNode => {
+ if (type === 'pleroma:emoji_reaction' && notification.emoji) {
+ return (
+
+ );
+ } else if (type) {
+ return (
+
+ );
+ } else {
+ return null;
+ }
+ };
+
+ const renderContent = () => {
+ switch (type) {
+ case 'follow':
+ case 'follow_request':
+ return account && typeof account === 'object' ? (
+
+ ) : null;
+ case 'move':
+ return account && typeof account === 'object' && notification.target && typeof notification.target === 'object' ? (
+
+ ) : null;
+ case 'favourite':
+ case 'mention':
+ case 'reblog':
+ case 'status':
+ case 'poll':
+ case 'pleroma:emoji_reaction':
+ return status && typeof status === 'object' ? (
+
+ ) : null;
+ default:
+ return null;
+ }
+ };
+
+ const message: React.ReactNode = type && account && typeof account === 'object' ? buildMessage(type, account) : null;
+
+ return (
+
+
+
+
+
+ {renderIcon()}
+
+
+
+ {message}
+
+
+
+
+
+
+ {renderContent()}
+
+
+
+
+ );
+};
+
+export default Notification;
diff --git a/app/soapbox/features/notifications/index.js b/app/soapbox/features/notifications/index.js
index fe77bee19..fce474a96 100644
--- a/app/soapbox/features/notifications/index.js
+++ b/app/soapbox/features/notifications/index.js
@@ -23,7 +23,6 @@ import ScrollableList from '../../components/scrollable_list';
import TimelineQueueButtonHeader from '../../components/timeline_queue_button_header';
import { Column } from '../../components/ui';
-import { NOTIFICATION_TYPES } from './components/notification';
import FilterBarContainer from './containers/filter_bar_container';
import NotificationContainer from './containers/notification_container';
@@ -200,13 +199,12 @@ class Notifications extends React.PureComponent {
}
this.scrollableContent = scrollableContent;
- const notificationsToRender = notifications.filter((notification) => NOTIFICATION_TYPES.includes(notification.get('type')));
const scrollContainer = (
0,
- 'space-y-2': notificationsToRender.size === 0,
+ 'divide-y divide-gray-200 dark:divide-gray-600 divide-solid': notifications.size > 0,
+ 'space-y-2': notifications.size === 0,
})}
>
{scrollableContent}
diff --git a/app/soapbox/features/status/components/quoted_status.tsx b/app/soapbox/features/status/components/quoted_status.tsx
index 0b715a86f..ce1d60ead 100644
--- a/app/soapbox/features/status/components/quoted_status.tsx
+++ b/app/soapbox/features/status/components/quoted_status.tsx
@@ -39,6 +39,7 @@ class QuotedStatus extends ImmutablePureComponent {
this.props.history.push(`/@${account.acct}/posts/${status.id}`);
+ e.stopPropagation();
e.preventDefault();
}
}
@@ -153,4 +154,4 @@ class QuotedStatus extends ImmutablePureComponent {
}
-export default withRouter(injectIntl(QuotedStatus) as any);
\ No newline at end of file
+export default withRouter(injectIntl(QuotedStatus) as any);
diff --git a/app/soapbox/jest/test-helpers.tsx b/app/soapbox/jest/test-helpers.tsx
index 7a4f8f53b..72f35756e 100644
--- a/app/soapbox/jest/test-helpers.tsx
+++ b/app/soapbox/jest/test-helpers.tsx
@@ -70,4 +70,5 @@ export {
applyActions,
rootState,
rootReducer,
+ createTestStore,
};
diff --git a/app/soapbox/normalizers/notification.ts b/app/soapbox/normalizers/notification.ts
index ea2f922e9..dd4ad7933 100644
--- a/app/soapbox/normalizers/notification.ts
+++ b/app/soapbox/normalizers/notification.ts
@@ -11,8 +11,8 @@ import {
import type { Account, Status, EmbeddedEntity } from 'soapbox/types/entities';
-type NotificationType = ''
- | 'follow'
+export type NotificationType =
+ 'follow'
| 'follow_request'
| 'mention'
| 'reblog'
@@ -32,7 +32,7 @@ export const NotificationRecord = ImmutableRecord({
id: '',
status: null as EmbeddedEntity,
target: null as EmbeddedEntity, // move
- type: '' as NotificationType,
+ type: '' as NotificationType | '',
});
export const normalizeNotification = (notification: Record) => {
diff --git a/app/soapbox/reducers/__tests__/notifications-test.js b/app/soapbox/reducers/__tests__/notifications-test.js
index 446cfbc68..aa7d6f714 100644
--- a/app/soapbox/reducers/__tests__/notifications-test.js
+++ b/app/soapbox/reducers/__tests__/notifications-test.js
@@ -188,6 +188,16 @@ describe('notifications reducer', () => {
expect(result.items.get('10743').type).toEqual('favourite');
});
+ it('imports follow_request notification', () => {
+ const action = {
+ type: NOTIFICATIONS_UPDATE,
+ notification: require('soapbox/__fixtures__/notification-follow_request.json'),
+ };
+
+ const result = reducer(initialState, action);
+ expect(result.items.get('87967').type).toEqual('follow_request');
+ });
+
it('increments `unread` counter when top is false', () => {
const action = { type: NOTIFICATIONS_UPDATE, notification };
const result = reducer(initialState, action);
diff --git a/app/soapbox/reducers/user_lists.js b/app/soapbox/reducers/user_lists.js
index 076144f75..ff9a63865 100644
--- a/app/soapbox/reducers/user_lists.js
+++ b/app/soapbox/reducers/user_lists.js
@@ -1,4 +1,7 @@
-import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
+import {
+ Map as ImmutableMap,
+ OrderedSet as ImmutableOrderedSet,
+} from 'immutable';
import {
FOLLOWERS_FETCH_SUCCESS,
@@ -68,13 +71,13 @@ const normalizeList = (state, type, id, accounts, next) => {
const appendToList = (state, type, id, accounts, next) => {
return state.updateIn([type, id], map => {
- return map.set('next', next).update('items', list => list.concat(accounts.map(item => item.id)));
+ return map.set('next', next).update('items', ImmutableOrderedSet(), list => list.concat(accounts.map(item => item.id)));
});
};
const normalizeFollowRequest = (state, notification) => {
- return state.updateIn(['follow_requests', 'items'], list => {
- return list.filterNot(item => item === notification.account.id).unshift(notification.account.id);
+ return state.updateIn(['follow_requests', 'items'], ImmutableOrderedSet(), list => {
+ return ImmutableOrderedSet([notification.account.id]).union(list);
});
};
diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts
index 94c0da574..2fdc310f9 100644
--- a/app/soapbox/utils/features.ts
+++ b/app/soapbox/utils/features.ts
@@ -142,6 +142,8 @@ const getInstanceFeatures = (instance: Instance) => {
trendingTruths: v.software === TRUTHSOCIAL,
trendingStatuses: v.software === MASTODON && gte(v.compatVersion, '3.5.0'),
pepe: v.software === TRUTHSOCIAL,
+ accountLocation: v.software === TRUTHSOCIAL,
+ accountWebsite: v.software === TRUTHSOCIAL,
// FIXME: long-term this shouldn't be a feature,
// but for now we want it to be overrideable in the build