Merge remote-tracking branch 'origin/develop' into next
This commit is contained in:
commit
630736757d
27 changed files with 2766 additions and 4906 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -11,3 +11,4 @@ yarn-error.log*
|
|||
/static/
|
||||
/static-test/
|
||||
/public/
|
||||
/dist/
|
||||
|
|
|
@ -8,8 +8,12 @@
|
|||
<!--server-generated-meta-->
|
||||
<link rel="icon" type="image/png" href="/favicon.png">
|
||||
</head>
|
||||
<body class="app-body">
|
||||
<body class="app-body app-body--loading theme-mode-light no-reduce-motion">
|
||||
<div class="app-holder" id="soapbox">
|
||||
<div class="loading-indicator">
|
||||
<div class="loading-indicator__figure"></div>
|
||||
</div>
|
||||
</div>
|
||||
<noscript>To use Soapbox, please enable JavaScript.</noscript>
|
||||
<div class="app-holder" id="soapbox"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
228
app/soapbox/__fixtures__/mastodon_initial_state.json
Normal file
228
app/soapbox/__fixtures__/mastodon_initial_state.json
Normal file
|
@ -0,0 +1,228 @@
|
|||
{
|
||||
"meta": {
|
||||
"streaming_api_base_url": "wss://mastodon.social",
|
||||
"access_token": "Nh15V9JWyY5Fshf2OJ_feNvOIkTV7YGVfEJFr0Y0D6Q",
|
||||
"locale": "en",
|
||||
"domain": "mastodon.social",
|
||||
"title": "Mastodon",
|
||||
"admin": "1",
|
||||
"search_enabled": true,
|
||||
"repository": "mastodon/mastodon",
|
||||
"source_url": "https://github.com/mastodon/mastodon",
|
||||
"version": "3.4.1",
|
||||
"invites_enabled": true,
|
||||
"limited_federation_mode": false,
|
||||
"mascot": null,
|
||||
"profile_directory": true,
|
||||
"trends": true,
|
||||
"me": "106801667066418367",
|
||||
"unfollow_modal": false,
|
||||
"boost_modal": false,
|
||||
"delete_modal": true,
|
||||
"auto_play_gif": false,
|
||||
"display_media": "default",
|
||||
"expand_spoilers": false,
|
||||
"reduce_motion": false,
|
||||
"disable_swiping": false,
|
||||
"advanced_layout": false,
|
||||
"use_blurhash": true,
|
||||
"use_pending_items": false,
|
||||
"is_staff": false,
|
||||
"crop_images": true
|
||||
},
|
||||
"compose": {
|
||||
"me": "106801667066418367",
|
||||
"default_privacy": "public",
|
||||
"default_sensitive": false,
|
||||
"text": ""
|
||||
},
|
||||
"accounts": {
|
||||
"1": {
|
||||
"id": "1",
|
||||
"username": "Gargron",
|
||||
"acct": "Gargron",
|
||||
"display_name": "Eugen",
|
||||
"locked": false,
|
||||
"bot": false,
|
||||
"discoverable": true,
|
||||
"group": false,
|
||||
"created_at": "2016-03-16T00:00:00.000Z",
|
||||
"note": "\\u003cp\\u003eDeveloper of Mastodon and administrator of mastodon.social. I post service announcements, development updates, and personal stuff.\\u003c/p\\u003e",
|
||||
"url": "https://mastodon.social/@Gargron",
|
||||
"avatar": "https://files.mastodon.social/accounts/avatars/000/000/001/original/d96d39a0abb45b92.jpg",
|
||||
"avatar_static": "https://files.mastodon.social/accounts/avatars/000/000/001/original/d96d39a0abb45b92.jpg",
|
||||
"header": "https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png",
|
||||
"header_static": "https://files.mastodon.social/accounts/headers/000/000/001/original/c91b871f294ea63e.png",
|
||||
"followers_count": 469426,
|
||||
"following_count": 459,
|
||||
"statuses_count": 70336,
|
||||
"last_status_at": "2021-09-15",
|
||||
"emojis": [],
|
||||
"fields": [
|
||||
{
|
||||
"name": "Patreon",
|
||||
"value": "\\u003ca href=\"https://www.patreon.com/mastodon\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"\\u003e\\u003cspan class=\"invisible\"\\u003ehttps://www.\\u003c/span\\u003e\\u003cspan class=\"\"\\u003epatreon.com/mastodon\\u003c/span\\u003e\\u003cspan class=\"invisible\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e",
|
||||
"verified_at": null
|
||||
},
|
||||
{
|
||||
"name": "Homepage",
|
||||
"value": "\\u003ca href=\"https://zeonfederated.com\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"\\u003e\\u003cspan class=\"invisible\"\\u003ehttps://\\u003c/span\\u003e\\u003cspan class=\"\"\\u003ezeonfederated.com\\u003c/span\\u003e\\u003cspan class=\"invisible\"\\u003e\\u003c/span\\u003e\\u003c/a\\u003e",
|
||||
"verified_at": "2019-07-15T18:29:57.191+00:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
"106801667066418367": {
|
||||
"id": "106801667066418367",
|
||||
"username": "benis911",
|
||||
"acct": "benis911",
|
||||
"display_name": "",
|
||||
"locked": false,
|
||||
"bot": false,
|
||||
"discoverable": null,
|
||||
"group": false,
|
||||
"created_at": "2021-08-22T00:00:00.000Z",
|
||||
"note": "\\u003cp\\u003e\\u003c/p\\u003e",
|
||||
"url": "https://mastodon.social/@benis911",
|
||||
"avatar": "https://mastodon.social/avatars/original/missing.png",
|
||||
"avatar_static": "https://mastodon.social/avatars/original/missing.png",
|
||||
"header": "https://mastodon.social/headers/original/missing.png",
|
||||
"header_static": "https://mastodon.social/headers/original/missing.png",
|
||||
"followers_count": 0,
|
||||
"following_count": 0,
|
||||
"statuses_count": 0,
|
||||
"last_status_at": null,
|
||||
"emojis": [],
|
||||
"fields": []
|
||||
}
|
||||
},
|
||||
"media_attachments": {
|
||||
"accept_content_types": [
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".png",
|
||||
".gif",
|
||||
".webm",
|
||||
".mp4",
|
||||
".m4v",
|
||||
".mov",
|
||||
".ogg",
|
||||
".oga",
|
||||
".mp3",
|
||||
".wav",
|
||||
".flac",
|
||||
".opus",
|
||||
".aac",
|
||||
".m4a",
|
||||
".3gp",
|
||||
".wma",
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"video/webm",
|
||||
"video/mp4",
|
||||
"video/quicktime",
|
||||
"video/ogg",
|
||||
"audio/wave",
|
||||
"audio/wav",
|
||||
"audio/x-wav",
|
||||
"audio/x-pn-wave",
|
||||
"audio/ogg",
|
||||
"audio/mpeg",
|
||||
"audio/mp3",
|
||||
"audio/webm",
|
||||
"audio/flac",
|
||||
"audio/aac",
|
||||
"audio/m4a",
|
||||
"audio/x-m4a",
|
||||
"audio/mp4",
|
||||
"audio/3gpp",
|
||||
"video/x-ms-asf"
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"known_fediverse": false,
|
||||
"notifications": {
|
||||
"alerts": {
|
||||
"follow": false,
|
||||
"follow_request": false,
|
||||
"favourite": false,
|
||||
"reblog": false,
|
||||
"mention": false,
|
||||
"poll": false,
|
||||
"status": false
|
||||
},
|
||||
"quickFilter": {
|
||||
"active": "all",
|
||||
"show": true,
|
||||
"advanced": false
|
||||
},
|
||||
"dismissPermissionBanner": false,
|
||||
"showUnread": true,
|
||||
"shows": {
|
||||
"follow": true,
|
||||
"follow_request": false,
|
||||
"favourite": true,
|
||||
"reblog": true,
|
||||
"mention": true,
|
||||
"poll": true,
|
||||
"status": true
|
||||
},
|
||||
"sounds": {
|
||||
"follow": true,
|
||||
"follow_request": false,
|
||||
"favourite": true,
|
||||
"reblog": true,
|
||||
"mention": true,
|
||||
"poll": true,
|
||||
"status": true
|
||||
}
|
||||
},
|
||||
"public": {
|
||||
"regex": {
|
||||
"body": ""
|
||||
}
|
||||
},
|
||||
"direct": {
|
||||
"regex": {
|
||||
"body": ""
|
||||
}
|
||||
},
|
||||
"community": {
|
||||
"regex": {
|
||||
"body": ""
|
||||
}
|
||||
},
|
||||
"skinTone": 1,
|
||||
"trends": {
|
||||
"show": true
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"id": "COMPOSE",
|
||||
"uuid": "b6dce3ed-c6cc-4446-8981-f08f8461ae8d",
|
||||
"params": {}
|
||||
},
|
||||
{
|
||||
"id": "HOME",
|
||||
"uuid": "e89b270b-6e79-4956-98fb-e8bf0aff098c",
|
||||
"params": {}
|
||||
},
|
||||
{
|
||||
"id": "NOTIFICATIONS",
|
||||
"uuid": "d359cdfa-e074-44ba-bde5-f46867a3bca6",
|
||||
"params": {}
|
||||
}
|
||||
],
|
||||
"introductionVersion": 20181216044202,
|
||||
"home": {
|
||||
"shows": {
|
||||
"reblog": true,
|
||||
"reply": true
|
||||
},
|
||||
"regex": {
|
||||
"body": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"push_subscription": null
|
||||
}
|
6
app/soapbox/__fixtures__/pleroma_initial_results.json
Normal file
6
app/soapbox/__fixtures__/pleroma_initial_results.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"/api/pleroma/frontend_configurations": "eyJtYXN0b19mZSI6eyJzaG93SW5zdGFuY2VTcGVjaWZpY1BhbmVsIjp0cnVlfSwicGxlcm9tYV9mZSI6eyJhbHdheXNTaG93U3ViamVjdElucHV0Ijp0cnVlLCJiYWNrZ3JvdW5kIjoiL2ltYWdlcy9jaXR5LmpwZyIsImNvbGxhcHNlTWVzc2FnZVdpdGhTdWJqZWN0IjpmYWxzZSwiZGlzYWJsZUNoYXQiOmZhbHNlLCJncmVlbnRleHQiOmZhbHNlLCJoaWRlRmlsdGVyZWRTdGF0dXNlcyI6ZmFsc2UsImhpZGVNdXRlZFBvc3RzIjpmYWxzZSwiaGlkZVBvc3RTdGF0cyI6ZmFsc2UsImhpZGVTaXRlbmFtZSI6ZmFsc2UsImhpZGVVc2VyU3RhdHMiOmZhbHNlLCJsb2dpbk1ldGhvZCI6InBhc3N3b3JkIiwibG9nbyI6Ii9zdGF0aWMvbG9nby5zdmciLCJsb2dvTWFyZ2luIjoiLjFlbSIsImxvZ29NYXNrIjp0cnVlLCJtaW5pbWFsU2NvcGVzTW9kZSI6ZmFsc2UsIm5vQXR0YWNobWVudExpbmtzIjpmYWxzZSwibnNmd0NlbnNvckltYWdlIjoiIiwicG9zdENvbnRlbnRUeXBlIjoidGV4dC9wbGFpbiIsInJlZGlyZWN0Um9vdExvZ2luIjoiL21haW4vZnJpZW5kcyIsInJlZGlyZWN0Um9vdE5vTG9naW4iOiIvbWFpbi9hbGwiLCJzY29wZUNvcHkiOnRydWUsInNob3dGZWF0dXJlc1BhbmVsIjp0cnVlLCJzaG93SW5zdGFuY2VTcGVjaWZpY1BhbmVsIjpmYWxzZSwic2lkZWJhclJpZ2h0IjpmYWxzZSwic3ViamVjdExpbmVCZWhhdmlvciI6ImVtYWlsIiwidGhlbWUiOiJwbGVyb21hLWRhcmsiLCJ3ZWJQdXNoTm90aWZpY2F0aW9ucyI6ZmFsc2V9LCJzb2FwYm94X2ZlIjp7ImJyYW5kQ29sb3IiOiIjMWNhODJiIiwiY3J5cHRvQWRkcmVzc2VzIjpbeyJhZGRyZXNzIjoiYmMxcTljeDM1YWRwbTczYXEyZnc0MHllNnRzOGhmeHF6anI1dW53ZzBuIiwibm90ZSI6IiIsInRpY2tlciI6ImJ0YyJ9LHsiYWRkcmVzcyI6IjB4QWM5YUI1RmMwNERjMWNCMTc4OUFmNzViNTIzQmQyM0M3MEIyRDcxNyIsInRpY2tlciI6ImV0aCJ9LHsiYWRkcmVzcyI6IkQ1elZaczZqclJha2FQVkdpRXJrUWlIdDlzYXl6bTZWNUQiLCJ0aWNrZXIiOiJkb2dlIn0seyJhZGRyZXNzIjoiMHg1NDFhNDVjYjIxMmI1N2Y0MTM5MzQyN2ZiMTUzMzVmYzg5YzM1ODUxIiwidGlja2VyIjoidWJxIn0seyJhZGRyZXNzIjoiNDVKRENMcmpKNGJnVlVTYmJzMnlqeTltNU1mNFZMUFc4Zkc3anc5c3E1dTY5clhaWm9wUW9nWk5leVlrTUJuWHBrYWlwNHA0UXdhYUpOaGRUb3RQYTlnNDREQkN6ZEsiLCJub3RlIjoiIiwidGlja2VyIjoieG1yIn0seyJhZGRyZXNzIjoibHRjMXFkYTY0NWpkZjRqc3p3eGN2c24zMnlrZGhlbXZseDd5bDluNWd6OSIsIm5vdGUiOiIiLCJ0aWNrZXIiOiJsdGMifSx7ImFkZHJlc3MiOiJiaXRjb2luY2FzaDpxcGNmbm05dzh1ZW1heDM4eXFoeWc1OHpuMnB0cGY2c3p2a3IwbjQ4YTciLCJub3RlIjoiIiwidGlja2VyIjoiYmNoIn0seyJhZGRyZXNzIjoiWG5CNXA0SnZMM1NvOTFBMWMxTUVSb3paRWplTVNzQUQ3SiIsIm5vdGUiOiIiLCJ0aWNrZXIiOiJkYXNoIn0seyJhZGRyZXNzIjoidDFQSFpYNVpqWTd5NjFpQzE5QTk1OFc5aGR5SDNTaUxKdUYiLCJub3RlIjoiIiwidGlja2VyIjoiemVjIn0seyJhZGRyZXNzIjoiMHhCODFCQUVFMTBkMTYzNDA0YTFjNjAwNDVhODcyYTBkYTlFMjU4NDY1Iiwibm90ZSI6IiIsInRpY2tlciI6ImV0YyJ9LHsiYWRkcmVzcyI6IkFHVExSWGFwUFlweHQzUExkaVhFczh5NGtMdzZReTNDNHQiLCJub3RlIjoiIiwidGlja2VyIjoiYnRnIn0seyJhZGRyZXNzIjoiU2JRY0ZVRGk3a0t5eGttc2t6VzN3NzR4NjhINWVVcmc3NiIsIm5vdGUiOiIiLCJ0aWNrZXIiOiJkZ2IifSx7ImFkZHJlc3MiOiJON25vbXBVVnh6NUFUcnpSVlR6dzdDYUFKb1NpVnRFY1F4Iiwibm90ZSI6IiIsInRpY2tlciI6Im5tYyJ9LHsiYWRkcmVzcyI6IjNBUWNVZ0NiRjZ5bWlSNEhHQ1U4QU54OVNxYnpMNm54OHIiLCJub3RlIjoiIiwidGlja2VyIjoidnRjIn1dLCJjcnlwdG9Eb25hdGVQYW5lbCI6eyJsaW1pdCI6MX0sImRlZmF1bHRTZXR0aW5ncyI6eyJ0aGVtZU1vZGUiOiJsaWdodCJ9LCJleHRlbnNpb25zIjp7InBhdHJvbiI6eyJlbmFibGVkIjp0cnVlfX0sImdyZWVudGV4dCI6dHJ1ZSwibG9nbyI6Imh0dHBzOi8vbWVkaWEuZ2xlYXNvbmF0b3IuY29tLzBjNzYwYjNlY2RiYzk5M2JhNDdiNzg1ZDBhZGVjZjBlYzcxZmQ5YzU5ODA4ZTI3ZDA2NjViOWY3N2EzMmQ4ZGUucG5nIiwibmF2bGlua3MiOnsiaG9tZUZvb3RlciI6W3sidGl0bGUiOiJBYm91dCIsInVybCI6Ii9hYm91dCJ9LHsidGl0bGUiOiJUZXJtcyBvZiBTZXJ2aWNlIiwidXJsIjoiL2Fib3V0L3RvcyJ9LHsidGl0bGUiOiJQcml2YWN5IFBvbGljeSIsInVybCI6Ii9hYm91dC9wcml2YWN5In0seyJ0aXRsZSI6IkRNQ0EiLCJ1cmwiOiIvYWJvdXQvZG1jYSJ9LHsidGl0bGUiOiJTb3VyY2UgQ29kZSIsInVybCI6Ii9hYm91dCNvcGVuc291cmNlIn1dfSwicHJvbW9QYW5lbCI6eyJpdGVtcyI6W3siaWNvbiI6ImNvbW1lbnQtbyIsInRleHQiOiJHbGVhc29uYXRvciB0aGVtZSBzb25nIiwidXJsIjoiaHR0cHM6Ly9tZWRpYS5nbGVhc29uYXRvci5jb20vY3VzdG9tLzI2MTkwNV9nbGVhc29uYXRvcl9zb25nLm1wMyJ9XX0sInZlcmlmaWVkQ2FuRWRpdE5hbWUiOnRydWV9fQ==",
|
||||
"/api/v1/instance": "eyJhcHByb3ZhbF9yZXF1aXJlZCI6dHJ1ZSwiYXZhdGFyX3VwbG9hZF9saW1pdCI6MjAwMDAwMCwiYmFja2dyb3VuZF9pbWFnZSI6Imh0dHBzOi8vZ2xlYXNvbmF0b3IuY29tL2ltYWdlcy9jaXR5LmpwZyIsImJhY2tncm91bmRfdXBsb2FkX2xpbWl0Ijo0MDAwMDAwLCJiYW5uZXJfdXBsb2FkX2xpbWl0Ijo0MDAwMDAwLCJjaGF0X2xpbWl0Ijo1MDAwLCJkZXNjcmlwdGlvbiI6IkJ1aWxkaW5nIHRoZSBuZXh0IGdlbmVyYXRpb24gb2YgdGhlIEZlZGl2ZXJzZS4gU3BlYWsgZnJlZWx5LiIsImRlc2NyaXB0aW9uX2xpbWl0Ijo1MDAwLCJlbWFpbCI6ImFsZXhAYWxleGdsZWFzb24ubWUiLCJsYW5ndWFnZXMiOlsiZW4iXSwibWF4X3Rvb3RfY2hhcnMiOjUwMDAsInBsZXJvbWEiOnsibWV0YWRhdGEiOnsiYWNjb3VudF9hY3RpdmF0aW9uX3JlcXVpcmVkIjpmYWxzZSwiZmVhdHVyZXMiOlsicGxlcm9tYV9hcGkiLCJtYXN0b2Rvbl9hcGkiLCJtYXN0b2Rvbl9hcGlfc3RyZWFtaW5nIiwicG9sbHMiLCJwbGVyb21hX2V4cGxpY2l0X2FkZHJlc3NpbmciLCJzaGFyZWFibGVfZW1vamlfcGFja3MiLCJtdWx0aWZldGNoIiwicGxlcm9tYTphcGkvdjEvbm90aWZpY2F0aW9uczppbmNsdWRlX3R5cGVzX2ZpbHRlciIsIm1lZGlhX3Byb3h5IiwicmVsYXkiLCJwbGVyb21hX2Vtb2ppX3JlYWN0aW9ucyIsInBsZXJvbWFfY2hhdF9tZXNzYWdlcyIsImVtYWlsX2xpc3QiXSwiZmVkZXJhdGlvbiI6eyJlbmFibGVkIjp0cnVlLCJleGNsdXNpb25zIjpmYWxzZSwibXJmX3BvbGljaWVzIjpbIlRhZ1BvbGljeSIsIlNpbXBsZVBvbGljeSJdLCJtcmZfc2ltcGxlIjp7ImFjY2VwdCI6W10sImF2YXRhcl9yZW1vdmFsIjpbInBhd29vLm5ldCIsInNpbmJsci5jb20iLCJkYWppYXdlaWJvLmNvbSJdLCJiYW5uZXJfcmVtb3ZhbCI6WyJwYXdvby5uZXQiLCJzaW5ibHIuY29tIiwiZGFqaWF3ZWliby5jb20iXSwiZmVkZXJhdGVkX3RpbWVsaW5lX3JlbW92YWwiOltdLCJmb2xsb3dlcnNfb25seSI6W10sIm1lZGlhX25zZnciOltdLCJtZWRpYV9yZW1vdmFsIjpbInBhd29vLm5ldCIsInNpbmJsci5jb20iLCJkYWppYXdlaWJvLmNvbSJdLCJyZWplY3QiOltdLCJyZWplY3RfZGVsZXRlcyI6W10sInJlcG9ydF9yZW1vdmFsIjpbXX0sInF1YXJhbnRpbmVkX2luc3RhbmNlcyI6W119LCJmaWVsZHNfbGltaXRzIjp7Im1heF9maWVsZHMiOjE1LCJtYXhfcmVtb3RlX2ZpZWxkcyI6MjAsIm5hbWVfbGVuZ3RoIjo1MTIsInZhbHVlX2xlbmd0aCI6MjA0OH0sInBvc3RfZm9ybWF0cyI6WyJ0ZXh0L3BsYWluIiwidGV4dC9odG1sIiwidGV4dC9tYXJrZG93biIsInRleHQvYmJjb2RlIl19LCJzdGF0cyI6eyJtYXUiOjU0fSwidmFwaWRfcHVibGljX2tleSI6IkJMRWxMUVZKVm1ZX2U0RjVKb1l4STVqWGlWT1lOc0o5cC1hbWt5a2M5TmNJLWp3YTlUMVkyR0liRHFiWS1IcUM2YXlQa2ZXNEs0bzl2Z0JGS1lta3VTNCJ9LCJwb2xsX2xpbWl0cyI6eyJtYXhfZXhwaXJhdGlvbiI6MzE1MzYwMDAsIm1heF9vcHRpb25fY2hhcnMiOjIwMCwibWF4X29wdGlvbnMiOjIwLCJtaW5fZXhwaXJhdGlvbiI6MH0sInJlZ2lzdHJhdGlvbnMiOnRydWUsInNvYXBib3giOnsidmVyc2lvbiI6IjEuMS4xIn0sInN0YXRzIjp7ImRvbWFpbl9jb3VudCI6NzIwMCwic3RhdHVzX2NvdW50Ijo3ODkwNiwidXNlcl9jb3VudCI6MzU3fSwidGh1bWJuYWlsIjoiaHR0cHM6Ly9nbGVhc29uYXRvci5jb21odHRwczovL21lZGlhLmdsZWFzb25hdG9yLmNvbS9jMGQzOGJkZTZlZjBiM2JhYTQ4M2Y1NzQ3OTc2NjJlYmQ4M2VmOWUxYTExNjJlOGU0ZmNkOTMwYmI0YjNjMDY4LnBuZyIsInRpdGxlIjoiR2xlYXNvbmF0b3IiLCJ1cGxvYWRfbGltaXQiOjEwMDAwMDAwMCwidXJpIjoiaHR0cHM6Ly9nbGVhc29uYXRvci5jb20iLCJ1cmxzIjp7InN0cmVhbWluZ19hcGkiOiJ3c3M6Ly9nbGVhc29uYXRvci5jb20ifSwidmVyc2lvbiI6IjIuNy4yIChjb21wYXRpYmxlOyBQbGVyb21hIDIuMy4wLTExMS1nYjQ3OGE4N2UtZGV2ZWxvcCkifQ==",
|
||||
"/instance/panel.html": "IjxkaXYgc3R5bGU9XCJtYXJnaW4tbGVmdDoxMnB4OyBtYXJnaW4tcmlnaHQ6MTJweFwiPlxuPHA+V2VsY29tZSB0byA8YSBocmVmPVwiaHR0cHM6Ly9wbGVyb21hLnNvY2lhbFwiIHRhcmdldD1cIl9ibGFua1wiPlBsZXJvbWEhPC9hPjwvcD4gICAgXG48cD48YSBocmVmPVwiL21haW4vYWxsXCI+UGxlcm9tYSBGRTwvYT4gfCA8YSBocmVmPVwiL3dlYlwiPk1hc3RvZG9uIEZFPC9hPjwvcD5cbjwvZGl2PlxuXG4i",
|
||||
"/nodeinfo/2.0.json": "eyJtZXRhZGF0YSI6eyJhY2NvdW50QWN0aXZhdGlvblJlcXVpcmVkIjpmYWxzZSwiZmVhdHVyZXMiOlsicGxlcm9tYV9hcGkiLCJtYXN0b2Rvbl9hcGkiLCJtYXN0b2Rvbl9hcGlfc3RyZWFtaW5nIiwicG9sbHMiLCJwbGVyb21hX2V4cGxpY2l0X2FkZHJlc3NpbmciLCJzaGFyZWFibGVfZW1vamlfcGFja3MiLCJtdWx0aWZldGNoIiwicGxlcm9tYTphcGkvdjEvbm90aWZpY2F0aW9uczppbmNsdWRlX3R5cGVzX2ZpbHRlciIsIm1lZGlhX3Byb3h5IiwicmVsYXkiLCJwbGVyb21hX2Vtb2ppX3JlYWN0aW9ucyIsInBsZXJvbWFfY2hhdF9tZXNzYWdlcyIsImVtYWlsX2xpc3QiXSwiZmVkZXJhdGlvbiI6eyJlbmFibGVkIjp0cnVlLCJleGNsdXNpb25zIjpmYWxzZSwibXJmX3BvbGljaWVzIjpbIlRhZ1BvbGljeSIsIlNpbXBsZVBvbGljeSJdLCJtcmZfc2ltcGxlIjp7ImFjY2VwdCI6W10sImF2YXRhcl9yZW1vdmFsIjpbInBhd29vLm5ldCIsInNpbmJsci5jb20iLCJkYWppYXdlaWJvLmNvbSJdLCJiYW5uZXJfcmVtb3ZhbCI6WyJwYXdvby5uZXQiLCJzaW5ibHIuY29tIiwiZGFqaWF3ZWliby5jb20iXSwiZmVkZXJhdGVkX3RpbWVsaW5lX3JlbW92YWwiOltdLCJmb2xsb3dlcnNfb25seSI6W10sIm1lZGlhX25zZnciOltdLCJtZWRpYV9yZW1vdmFsIjpbInBhd29vLm5ldCIsInNpbmJsci5jb20iLCJkYWppYXdlaWJvLmNvbSJdLCJyZWplY3QiOltdLCJyZWplY3RfZGVsZXRlcyI6W10sInJlcG9ydF9yZW1vdmFsIjpbXX0sInF1YXJhbnRpbmVkX2luc3RhbmNlcyI6W119LCJmaWVsZHNMaW1pdHMiOnsibWF4RmllbGRzIjoxNSwibWF4UmVtb3RlRmllbGRzIjoyMCwibmFtZUxlbmd0aCI6NTEyLCJ2YWx1ZUxlbmd0aCI6MjA0OH0sImludml0ZXNFbmFibGVkIjpmYWxzZSwibWFpbGVyRW5hYmxlZCI6dHJ1ZSwibm9kZURlc2NyaXB0aW9uIjoiQnVpbGRpbmcgdGhlIG5leHQgZ2VuZXJhdGlvbiBvZiB0aGUgRmVkaXZlcnNlLiBTcGVhayBmcmVlbHkuIiwibm9kZU5hbWUiOiJHbGVhc29uYXRvciIsInBvbGxMaW1pdHMiOnsibWF4X2V4cGlyYXRpb24iOjMxNTM2MDAwLCJtYXhfb3B0aW9uX2NoYXJzIjoyMDAsIm1heF9vcHRpb25zIjoyMCwibWluX2V4cGlyYXRpb24iOjB9LCJwb3N0Rm9ybWF0cyI6WyJ0ZXh0L3BsYWluIiwidGV4dC9odG1sIiwidGV4dC9tYXJrZG93biIsInRleHQvYmJjb2RlIl0sInByaXZhdGUiOmZhbHNlLCJyZXN0cmljdGVkTmlja25hbWVzIjpbIi53ZWxsLWtub3duIiwifiIsImFib3V0IiwiYWN0aXZpdGllcyIsImFwaSIsImF1dGgiLCJjaGVja19wYXNzd29yZCIsImRldiIsImZyaWVuZC1yZXF1ZXN0cyIsImluYm94IiwiaW50ZXJuYWwiLCJtYWluIiwibWVkaWEiLCJub2RlaW5mbyIsIm5vdGljZSIsIm9hdXRoIiwib2JqZWN0cyIsIm9zdGF0dXNfc3Vic2NyaWJlIiwicGxlcm9tYSIsInByb3h5IiwicHVzaCIsInJlZ2lzdHJhdGlvbiIsInJlbGF5Iiwic2V0dGluZ3MiLCJzdGF0dXMiLCJ0YWciLCJ1c2VyLXNlYXJjaCIsInVzZXJfZXhpc3RzIiwidXNlcnMiLCJ3ZWIiLCJ2ZXJpZnlfY3JlZGVudGlhbHMiLCJ1cGRhdGVfY3JlZGVudGlhbHMiLCJyZWxhdGlvbnNoaXBzIiwic2VhcmNoIiwiY29uZmlybWF0aW9uX3Jlc2VuZCIsIm1mYSJdLCJza2lwVGhyZWFkQ29udGFpbm1lbnQiOnRydWUsInN0YWZmQWNjb3VudHMiOlsiaHR0cHM6Ly9nbGVhc29uYXRvci5jb20vdXNlcnMvYWxleCJdLCJzdWdnZXN0aW9ucyI6eyJlbmFibGVkIjpmYWxzZX0sInVwbG9hZExpbWl0cyI6eyJhdmF0YXIiOjIwMDAwMDAsImJhY2tncm91bmQiOjQwMDAwMDAsImJhbm5lciI6NDAwMDAwMCwiZ2VuZXJhbCI6MTAwMDAwMDAwfX0sIm9wZW5SZWdpc3RyYXRpb25zIjp0cnVlLCJwcm90b2NvbHMiOlsiYWN0aXZpdHlwdWIiXSwic2VydmljZXMiOnsiaW5ib3VuZCI6W10sIm91dGJvdW5kIjpbXX0sInNvZnR3YXJlIjp7Im5hbWUiOiJwbGVyb21hIiwidmVyc2lvbiI6IjIuMy4wLTExMS1nYjQ3OGE4N2UtZGV2ZWxvcCJ9LCJ1c2FnZSI6eyJsb2NhbFBvc3RzIjo3ODkwNiwidXNlcnMiOnsidG90YWwiOjM1N319LCJ2ZXJzaW9uIjoiMi4wIn0="
|
||||
}
|
|
@ -17,6 +17,7 @@ export function normalizeAccount(account) {
|
|||
|
||||
account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap);
|
||||
account.note_emojified = emojify(account.note, emojiMap);
|
||||
account.note_plain = unescapeHTML(account.note);
|
||||
|
||||
if (account.fields) {
|
||||
account.fields = account.fields.map(pair => ({
|
||||
|
|
|
@ -1,25 +1,57 @@
|
|||
import { mapValues } from 'lodash';
|
||||
|
||||
export const PRELOAD_IMPORT = 'PRELOAD_IMPORT';
|
||||
export const PLEROMA_PRELOAD_IMPORT = 'PLEROMA_PRELOAD_IMPORT';
|
||||
export const MASTODON_PRELOAD_IMPORT = 'MASTODON_PRELOAD_IMPORT';
|
||||
|
||||
// https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1176/diffs
|
||||
const decodeUTF8Base64 = (data) => {
|
||||
const decodeUTF8Base64 = data => {
|
||||
const rawData = atob(data);
|
||||
const array = Uint8Array.from(rawData.split('').map((char) => char.charCodeAt(0)));
|
||||
const text = new TextDecoder().decode(array);
|
||||
return text;
|
||||
};
|
||||
|
||||
const decodeData = data =>
|
||||
mapValues(data, base64string =>
|
||||
JSON.parse(decodeUTF8Base64(base64string)));
|
||||
const decodePleromaData = data => {
|
||||
return mapValues(data, base64string => JSON.parse(decodeUTF8Base64(base64string)));
|
||||
};
|
||||
|
||||
export function preload() {
|
||||
const element = document.getElementById('initial-results');
|
||||
const data = element ? JSON.parse(element.textContent) : {};
|
||||
const pleromaDecoder = json => decodePleromaData(JSON.parse(json));
|
||||
|
||||
return {
|
||||
type: PRELOAD_IMPORT,
|
||||
data: decodeData(data),
|
||||
// This will throw if it fails.
|
||||
// Should be called inside a try-catch.
|
||||
const decodeFromMarkup = (elementId, decoder) => {
|
||||
const { textContent } = document.getElementById(elementId);
|
||||
return decoder(textContent);
|
||||
};
|
||||
|
||||
function preloadFromMarkup(elementId, decoder, action) {
|
||||
return (dispatch, getState) => {
|
||||
try {
|
||||
const data = decodeFromMarkup(elementId, decoder);
|
||||
dispatch(action(data));
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function preload() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(preloadFromMarkup('initial-results', pleromaDecoder, preloadPleroma));
|
||||
dispatch(preloadFromMarkup('initial-state', JSON.parse, preloadMastodon));
|
||||
};
|
||||
}
|
||||
|
||||
export function preloadPleroma(data) {
|
||||
return {
|
||||
type: PLEROMA_PRELOAD_IMPORT,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
export function preloadMastodon(data) {
|
||||
return {
|
||||
type: MASTODON_PRELOAD_IMPORT,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
|
18
app/soapbox/actions/suggestions_v2.js
Normal file
18
app/soapbox/actions/suggestions_v2.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import api from '../api';
|
||||
import { importFetchedAccount } from './importer';
|
||||
|
||||
export const SUGGESTIONS_V2_FETCH_REQUEST = 'SUGGESTIONS_V2_FETCH_REQUEST';
|
||||
export const SUGGESTIONS_V2_FETCH_SUCCESS = 'SUGGESTIONS_V2_FETCH_SUCCESS';
|
||||
export const SUGGESTIONS_V2_FETCH_FAIL = 'SUGGESTIONS_V2_FETCH_FAIL';
|
||||
|
||||
export function fetchSuggestions() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: SUGGESTIONS_V2_FETCH_REQUEST, skipLoading: true });
|
||||
api(getState).get('/api/v2/suggestions').then(({ data: suggestions }) => {
|
||||
suggestions.forEach(({ account }) => dispatch(importFetchedAccount(account)));
|
||||
dispatch({ type: SUGGESTIONS_V2_FETCH_SUCCESS, suggestions, skipLoading: true });
|
||||
}).catch(error => {
|
||||
dispatch({ type: SUGGESTIONS_V2_FETCH_FAIL, error, skipLoading: true, skipAlert: true });
|
||||
});
|
||||
};
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
import Avatar from 'soapbox/components/avatar';
|
||||
import DisplayName from 'soapbox/components/display_name';
|
||||
import Permalink from 'soapbox/components/permalink';
|
||||
import IconButton from 'soapbox/components/icon_button';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import { followAccount, unfollowAccount } from 'soapbox/actions/accounts';
|
||||
|
||||
const messages = defineMessages({
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
account: getAccount(state, props.id),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const getFirstSentence = str => {
|
||||
const arr = str.split(/(([.?!]+\s)|[.。?!\n•])/);
|
||||
|
||||
return arr[0];
|
||||
};
|
||||
|
||||
export default @connect(makeMapStateToProps)
|
||||
@injectIntl
|
||||
class Account extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
handleFollow = () => {
|
||||
const { account, dispatch } = this.props;
|
||||
|
||||
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
||||
dispatch(unfollowAccount(account.get('id')));
|
||||
} else {
|
||||
dispatch(followAccount(account.get('id')));
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { account, intl } = this.props;
|
||||
|
||||
let button;
|
||||
|
||||
if (account.getIn(['relationship', 'following'])) {
|
||||
button = <IconButton icon='check' title={intl.formatMessage(messages.unfollow)} active onClick={this.handleFollow} />;
|
||||
} else {
|
||||
button = <IconButton icon='plus' title={intl.formatMessage(messages.follow)} onClick={this.handleFollow} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='account follow-recommendations-account'>
|
||||
<div className='account__wrapper'>
|
||||
<Permalink className='account__display-name account__display-name--with-note' title={account.get('acct')} href={account.get('url')} to={`/accounts/${account.get('id')}`}>
|
||||
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
|
||||
|
||||
<DisplayName account={account} />
|
||||
|
||||
<div className='account__note'>{getFirstSentence(account.get('note_plain'))}</div>
|
||||
</Permalink>
|
||||
|
||||
<div className='account__relationship'>
|
||||
{button}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
79
app/soapbox/features/follow_recommendations/index.js
Normal file
79
app/soapbox/features/follow_recommendations/index.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { fetchSuggestions } from 'soapbox/actions/suggestions_v2';
|
||||
import Column from 'soapbox/features/ui/components/column';
|
||||
import Account from './components/account';
|
||||
import Button from 'soapbox/components/button';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
suggestions: state.getIn(['suggestions_v2', 'items']),
|
||||
isLoading: state.getIn(['suggestions_v2', 'isLoading']),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
class FollowRecommendations extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
suggestions: ImmutablePropTypes.list,
|
||||
isLoading: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { dispatch, suggestions } = this.props;
|
||||
|
||||
// Don't re-fetch if we're e.g. navigating backwards to this page,
|
||||
// since we don't want followed accounts to disappear from the list
|
||||
if (suggestions.size === 0) {
|
||||
dispatch(fetchSuggestions(true));
|
||||
}
|
||||
}
|
||||
|
||||
handleDone = () => {
|
||||
const { router } = this.context;
|
||||
|
||||
router.history.push('/');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { suggestions, isLoading } = this.props;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<div className='scrollable follow-recommendations-container'>
|
||||
<div className='column-title'>
|
||||
<h3><FormattedMessage id='follow_recommendations.heading' defaultMessage="Follow people you'd like to see posts from! Here are some suggestions." /></h3>
|
||||
<p><FormattedMessage id='follow_recommendations.lead' defaultMessage="Posts from people you follow will show up in chronological order on your home feed. Don't be afraid to make mistakes, you can unfollow people just as easily any time!" /></p>
|
||||
</div>
|
||||
|
||||
{!isLoading && (
|
||||
<>
|
||||
<div className='column-list'>
|
||||
{suggestions.size > 0 ? suggestions.map(suggestion => (
|
||||
<Account key={suggestion.get('account')} id={suggestion.get('account')} />
|
||||
)) : (
|
||||
<div className='column-list__empty-message'>
|
||||
<FormattedMessage id='empty_column.follow_recommendations' defaultMessage='Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='column-actions'>
|
||||
<Button onClick={this.handleDone}><FormattedMessage id='follow_recommendations.done' defaultMessage='Done' /></Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -109,6 +109,7 @@ import {
|
|||
UserIndex,
|
||||
FederationRestrictions,
|
||||
Aliases,
|
||||
FollowRecommendations,
|
||||
} from './util/async-components';
|
||||
|
||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||
|
@ -253,6 +254,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
|||
<WrappedRoute path='/notifications' page={DefaultPage} component={Notifications} content={children} />
|
||||
|
||||
<WrappedRoute path='/search' publicRoute page={DefaultPage} component={Search} content={children} />
|
||||
<WrappedRoute path='/suggestions' publicRoute page={DefaultPage} component={FollowRecommendations} content={children} />
|
||||
|
||||
<WrappedRoute path='/chats' exact page={DefaultPage} component={ChatIndex} content={children} />
|
||||
<WrappedRoute path='/chats/:chatId' page={DefaultPage} component={ChatRoom} content={children} />
|
||||
|
|
|
@ -281,3 +281,7 @@ export function Aliases() {
|
|||
export function ScheduleForm() {
|
||||
return import(/* webpackChunkName: "features/compose" */'../../compose/components/schedule_form');
|
||||
}
|
||||
|
||||
export function FollowRecommendations() {
|
||||
return import(/* webpackChunkName: "features/follow_recommendations" */'../../follow_recommendations');
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
SWITCH_ACCOUNT,
|
||||
} from 'soapbox/actions/auth';
|
||||
import { ME_FETCH_SKIP } from 'soapbox/actions/me';
|
||||
import { MASTODON_PRELOAD_IMPORT } from 'soapbox/actions/preload';
|
||||
|
||||
describe('auth reducer', () => {
|
||||
it('should return the initial state', () => {
|
||||
|
@ -311,4 +312,37 @@ describe('auth reducer', () => {
|
|||
expect(result.get('me')).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('MASTODON_PRELOAD_IMPORT', () => {
|
||||
it('imports the user and token', () => {
|
||||
const action = {
|
||||
type: MASTODON_PRELOAD_IMPORT,
|
||||
data: require('soapbox/__fixtures__/mastodon_initial_state.json'),
|
||||
};
|
||||
|
||||
const expected = fromJS({
|
||||
me: 'https://mastodon.social/@benis911',
|
||||
app: {},
|
||||
users: {
|
||||
'https://mastodon.social/@benis911': {
|
||||
id: '106801667066418367',
|
||||
access_token: 'Nh15V9JWyY5Fshf2OJ_feNvOIkTV7YGVfEJFr0Y0D6Q',
|
||||
url: 'https://mastodon.social/@benis911',
|
||||
},
|
||||
},
|
||||
tokens: {
|
||||
'Nh15V9JWyY5Fshf2OJ_feNvOIkTV7YGVfEJFr0Y0D6Q': {
|
||||
access_token: 'Nh15V9JWyY5Fshf2OJ_feNvOIkTV7YGVfEJFr0Y0D6Q',
|
||||
account: '106801667066418367',
|
||||
me: 'https://mastodon.social/@benis911',
|
||||
scope: 'read write follow push',
|
||||
token_type: 'Bearer',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = reducer(undefined, action);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
VERIFY_CREDENTIALS_FAIL,
|
||||
} from '../actions/auth';
|
||||
import { ME_FETCH_SKIP } from '../actions/me';
|
||||
import { MASTODON_PRELOAD_IMPORT } from 'soapbox/actions/preload';
|
||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
import { validId, isURL } from 'soapbox/utils/auth';
|
||||
import { trim } from 'lodash';
|
||||
|
@ -227,6 +228,32 @@ const deleteUser = (state, account) => {
|
|||
});
|
||||
};
|
||||
|
||||
const importMastodonPreload = (state, data) => {
|
||||
return state.withMutations(state => {
|
||||
const accountId = data.getIn(['meta', 'me']);
|
||||
const accountUrl = data.getIn(['accounts', accountId, 'url']);
|
||||
const accessToken = data.getIn(['meta', 'access_token']);
|
||||
|
||||
if (validId(accessToken) && validId(accountId) && isURL(accountUrl)) {
|
||||
state.setIn(['tokens', accessToken], fromJS({
|
||||
access_token: accessToken,
|
||||
account: accountId,
|
||||
me: accountUrl,
|
||||
scope: 'read write follow push',
|
||||
token_type: 'Bearer',
|
||||
}));
|
||||
|
||||
state.setIn(['users', accountUrl], fromJS({
|
||||
id: accountId,
|
||||
access_token: accessToken,
|
||||
url: accountUrl,
|
||||
}));
|
||||
}
|
||||
|
||||
maybeShiftMe(state);
|
||||
});
|
||||
};
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch(action.type) {
|
||||
case AUTH_APP_CREATED:
|
||||
|
@ -245,6 +272,8 @@ const reducer = (state, action) => {
|
|||
return state.set('me', action.account.get('url'));
|
||||
case ME_FETCH_SKIP:
|
||||
return state.set('me', null);
|
||||
case MASTODON_PRELOAD_IMPORT:
|
||||
return importMastodonPreload(state, fromJS(action.data));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import listAdder from './list_adder';
|
|||
import filters from './filters';
|
||||
import conversations from './conversations';
|
||||
import suggestions from './suggestions';
|
||||
import suggestions_v2 from './suggestions_v2';
|
||||
import polls from './polls';
|
||||
import identity_proofs from './identity_proofs';
|
||||
import trends from './trends';
|
||||
|
@ -88,6 +89,7 @@ const appReducer = combineReducers({
|
|||
filters,
|
||||
conversations,
|
||||
suggestions,
|
||||
suggestions_v2,
|
||||
polls,
|
||||
trends,
|
||||
groups,
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
INSTANCE_FETCH_FAIL,
|
||||
NODEINFO_FETCH_SUCCESS,
|
||||
} from '../actions/instance';
|
||||
import { PRELOAD_IMPORT } from 'soapbox/actions/preload';
|
||||
import { PLEROMA_PRELOAD_IMPORT } from 'soapbox/actions/preload';
|
||||
import { ADMIN_CONFIG_UPDATE_REQUEST, ADMIN_CONFIG_UPDATE_SUCCESS } from 'soapbox/actions/admin';
|
||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
import { ConfigDB } from 'soapbox/utils/config_db';
|
||||
|
@ -82,7 +82,7 @@ const handleAuthFetch = state => {
|
|||
|
||||
export default function instance(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case PRELOAD_IMPORT:
|
||||
case PLEROMA_PRELOAD_IMPORT:
|
||||
return preloadImport(state, action, '/api/v1/instance');
|
||||
case INSTANCE_FETCH_SUCCESS:
|
||||
return initialState.mergeDeep(fromJS(action.instance));
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
SOAPBOX_CONFIG_REQUEST_SUCCESS,
|
||||
SOAPBOX_CONFIG_REQUEST_FAIL,
|
||||
} from '../actions/soapbox';
|
||||
import { PRELOAD_IMPORT } from 'soapbox/actions/preload';
|
||||
import { PLEROMA_PRELOAD_IMPORT } from 'soapbox/actions/preload';
|
||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
import { ConfigDB } from 'soapbox/utils/config_db';
|
||||
|
||||
|
@ -38,7 +38,7 @@ const preloadImport = (state, action) => {
|
|||
|
||||
export default function soapbox(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case PRELOAD_IMPORT:
|
||||
case PLEROMA_PRELOAD_IMPORT:
|
||||
return preloadImport(state, action);
|
||||
case SOAPBOX_CONFIG_REQUEST_SUCCESS:
|
||||
return fromJS(action.soapboxConfig);
|
||||
|
|
37
app/soapbox/reducers/suggestions_v2.js
Normal file
37
app/soapbox/reducers/suggestions_v2.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import {
|
||||
SUGGESTIONS_V2_FETCH_REQUEST,
|
||||
SUGGESTIONS_V2_FETCH_SUCCESS,
|
||||
SUGGESTIONS_V2_FETCH_FAIL,
|
||||
} from '../actions/suggestions_v2';
|
||||
import { SUGGESTIONS_DISMISS } from '../actions/suggestions';
|
||||
import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS } from 'soapbox/actions/accounts';
|
||||
import { DOMAIN_BLOCK_SUCCESS } from 'soapbox/actions/domain_blocks';
|
||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
export default function suggestionsReducer(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case SUGGESTIONS_V2_FETCH_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
case SUGGESTIONS_V2_FETCH_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('items', fromJS(action.suggestions.map(x => ({ ...x, account: x.account.id }))));
|
||||
map.set('isLoading', false);
|
||||
});
|
||||
case SUGGESTIONS_V2_FETCH_FAIL:
|
||||
return state.set('isLoading', false);
|
||||
case SUGGESTIONS_DISMISS:
|
||||
return state.update('items', list => list.filterNot(x => x.account === action.id));
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
case ACCOUNT_MUTE_SUCCESS:
|
||||
return state.update('items', list => list.filterNot(x => x.account === action.relationship.id));
|
||||
case DOMAIN_BLOCK_SUCCESS:
|
||||
return state.update('items', list => list.filterNot(x => action.accounts.includes(x.account)));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
import WebSocketClient from 'websocket.js';
|
||||
import WebSocketClient from '@gamestdio/websocket';
|
||||
import { getAccessToken } from 'soapbox/utils/auth';
|
||||
|
||||
const randomIntUpTo = max => Math.floor(Math.random() * Math.floor(max));
|
||||
|
|
|
@ -10,6 +10,7 @@ export const getFeatures = createSelector([
|
|||
], (v, features, federation) => {
|
||||
return {
|
||||
suggestions: v.software === 'Mastodon' && gte(v.compatVersion, '2.4.3'),
|
||||
suggestionsV2: v.software === 'Mastodon' && gte(v.compatVersion, '3.4.0'),
|
||||
trends: v.software === 'Mastodon' && gte(v.compatVersion, '3.0.0'),
|
||||
emojiReacts: v.software === 'Pleroma' && gte(v.version, '2.0.0'),
|
||||
emojiReactsRGI: v.software === 'Pleroma' && gte(v.version, '2.2.49'),
|
||||
|
|
|
@ -206,6 +206,13 @@
|
|||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&__note {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--primary-text-color--faint);
|
||||
}
|
||||
}
|
||||
|
||||
.account__wrapper {
|
||||
|
|
|
@ -43,6 +43,7 @@ body {
|
|||
&.app-body {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
overflow-y: scroll;
|
||||
|
|
|
@ -810,3 +810,45 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-title {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
|
||||
.logo {
|
||||
fill: var(--primary-text-color);
|
||||
width: 50px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
line-height: 1.5;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
color: var(--primary-text-color--faint);
|
||||
}
|
||||
}
|
||||
|
||||
.column-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
padding-top: 40px;
|
||||
|
||||
&__background {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 220px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@use 'sass:math';
|
||||
|
||||
// OpenDyslexic
|
||||
@font-face {
|
||||
font-family: 'OpenDyslexic';
|
||||
|
@ -46,14 +48,14 @@
|
|||
// html and body declaration allows developer to pass px value as argument
|
||||
// Rendered css will default to "rem" and fall back to "px" for unsupported browsers
|
||||
@mixin font-size($size) {
|
||||
$rem: ($size / 10);
|
||||
$rem: math.div($size, 10);
|
||||
$px: $size;
|
||||
font-size: #{$px + "px"};
|
||||
font-size: #{$rem + "rem"};
|
||||
}
|
||||
|
||||
@mixin line-height($size) {
|
||||
$rem: ($size / 10);
|
||||
$rem: math.div($size, 10);
|
||||
$px: $size;
|
||||
line-height: #{$px + "px"};
|
||||
line-height: #{$rem + "rem"};
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
height: 42px;
|
||||
box-sizing: border-box;
|
||||
background-color: transparent;
|
||||
border: 0 solid hsla(var(--brand-color_hsl), 0.5);
|
||||
border: 0 solid;
|
||||
border-color: hsla(var(--brand-color_hsl), 0.5);
|
||||
border-width: 6px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
@ -40,6 +41,10 @@
|
|||
animation: loader-figure 1.15s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
|
||||
.app-body--loading .loading-indicator__figure {
|
||||
border-color: #ddd;
|
||||
}
|
||||
|
||||
@keyframes loader-figure {
|
||||
0% {
|
||||
width: 0;
|
||||
|
|
36
package.json
36
package.json
|
@ -16,9 +16,9 @@
|
|||
"url": "https://gitlab.com/soapbox-pub/soapbox-fe/-/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "npx webpack-dev-server --config webpack",
|
||||
"start": "npx webpack-dev-server",
|
||||
"dev": "${npm_execpath} run start",
|
||||
"build": "npx webpack --config webpack",
|
||||
"build": "npx webpack",
|
||||
"jsdoc": "npx jsdoc -c jsdoc.conf.js",
|
||||
"manage:translations": "node ./webpack/translationRunner.js",
|
||||
"test": "${npm_execpath} run test:lint && ${npm_execpath} run test:jest",
|
||||
|
@ -36,20 +36,21 @@
|
|||
"not dead"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/core": "^7.15.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-proposal-decorators": "^7.14.5",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.14.7",
|
||||
"@babel/plugin-proposal-decorators": "^7.15.4",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.15.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-react-inline-elements": "^7.14.5",
|
||||
"@babel/plugin-transform-react-jsx-self": "^7.14.5",
|
||||
"@babel/plugin-transform-react-jsx-self": "^7.14.9",
|
||||
"@babel/plugin-transform-react-jsx-source": "^7.14.5",
|
||||
"@babel/plugin-transform-runtime": "^7.14.5",
|
||||
"@babel/preset-env": "^7.14.7",
|
||||
"@babel/plugin-transform-runtime": "^7.15.0",
|
||||
"@babel/preset-env": "^7.15.6",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@babel/runtime": "^7.14.6",
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@fontsource/montserrat": "^4.5.1",
|
||||
"@fontsource/roboto": "^4.5.0",
|
||||
"@gamestdio/websocket": "^0.3.2",
|
||||
"@lcdp/offline-plugin": "^5.1.0",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@sentry/browser": "^6.12.0",
|
||||
|
@ -57,13 +58,12 @@
|
|||
"@sentry/tracing": "^6.12.0",
|
||||
"array-includes": "^3.0.3",
|
||||
"autoprefixer": "^10.0.0",
|
||||
"axios": "^0.21.0",
|
||||
"babel-loader": "^8.0.5",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"babel-plugin-preval": "^5.0.0",
|
||||
"babel-plugin-react-intl": "^7.5.20",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"blurhash": "^1.0.0",
|
||||
"bowser": "^2.11.0",
|
||||
"browserslist": "^4.16.6",
|
||||
|
@ -82,12 +82,11 @@
|
|||
"es6-symbol": "^3.1.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"exif-js": "^2.3.0",
|
||||
"file-loader": "^6.0.0",
|
||||
"fork-awesome": "https://github.com/alexgleason/Fork-Awesome#c23fd34246a9f33c4bf24ea095a4cf26e7abe265",
|
||||
"html-webpack-harddisk-plugin": "^2.0.0",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"http-link-header": "^1.0.2",
|
||||
"immutable": "^4.0.0-rc.12",
|
||||
"immutable": "^4.0.0-rc.14",
|
||||
"imports-loader": "^1.0.0",
|
||||
"intersection-observer": "^0.11.0",
|
||||
"intl": "^1.2.5",
|
||||
|
@ -126,15 +125,15 @@
|
|||
"react-notification": "^6.8.4",
|
||||
"react-overlays": "^0.9.0",
|
||||
"react-popper": "^2.2.3",
|
||||
"react-redux": "^7.2.1",
|
||||
"react-redux": "^7.2.5",
|
||||
"react-redux-loading-bar": "^5.0.0",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-router-scroll-4": "^1.0.0-beta.1",
|
||||
"react-sparklines": "^1.7.0",
|
||||
"react-swipeable-views": "^0.13.0",
|
||||
"react-swipeable-views": "^0.14.0",
|
||||
"react-textarea-autosize": "^8.3.3",
|
||||
"react-toggle": "^4.0.1",
|
||||
"redux": "^4.0.5",
|
||||
"redux": "^4.1.1",
|
||||
"redux-immutable": "^4.0.0",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"requestidlecallback": "^0.3.0",
|
||||
|
@ -157,7 +156,6 @@
|
|||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"webpack-cli": "^4.8.0",
|
||||
"webpack-merge": "^5.8.0",
|
||||
"websocket.js": "^0.1.12",
|
||||
"wicg-inert": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -180,7 +178,7 @@
|
|||
"react-test-renderer": "^16.13.1",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
"stylelint": "^13.7.2",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
"stylelint-config-standard": "^22.0.0",
|
||||
"stylelint-scss": "^3.18.0",
|
||||
"webpack-dev-server": "^4.1.0",
|
||||
"yargs": "^16.0.3"
|
||||
|
|
|
@ -6,7 +6,7 @@ switch(NODE_ENV) {
|
|||
case 'development':
|
||||
case 'production':
|
||||
case 'test':
|
||||
module.exports = require(`./${NODE_ENV}`); break;
|
||||
module.exports = require(`./webpack/${NODE_ENV}`); break;
|
||||
default:
|
||||
console.error('ERROR: NODE_ENV must be set to either `development`, `test`, or `production`.');
|
||||
process.exit(1);
|
Loading…
Reference in a new issue