Merge remote-tracking branch 'origin/develop' into profile-avatar-switcher
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
commit
3d2e4ab368
30
.eslintrc.js
30
.eslintrc.js
|
@ -67,8 +67,12 @@ module.exports = {
|
||||||
'consistent-return': 'error',
|
'consistent-return': 'error',
|
||||||
'dot-notation': 'error',
|
'dot-notation': 'error',
|
||||||
eqeqeq: 'error',
|
eqeqeq: 'error',
|
||||||
indent: ['warn', 2],
|
indent: ['error', 2],
|
||||||
'jsx-quotes': ['error', 'prefer-single'],
|
'jsx-quotes': ['error', 'prefer-single'],
|
||||||
|
'key-spacing': [
|
||||||
|
'error',
|
||||||
|
{ mode: 'minimum' },
|
||||||
|
],
|
||||||
'no-catch-shadow': 'error',
|
'no-catch-shadow': 'error',
|
||||||
'no-cond-assign': 'error',
|
'no-cond-assign': 'error',
|
||||||
'no-console': [
|
'no-console': [
|
||||||
|
@ -111,6 +115,13 @@ module.exports = {
|
||||||
'prefer-const': 'error',
|
'prefer-const': 'error',
|
||||||
quotes: ['error', 'single'],
|
quotes: ['error', 'single'],
|
||||||
semi: 'error',
|
semi: 'error',
|
||||||
|
'space-unary-ops': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
words: true,
|
||||||
|
nonwords: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
strict: 'off',
|
strict: 'off',
|
||||||
'valid-typeof': 'error',
|
'valid-typeof': 'error',
|
||||||
|
|
||||||
|
@ -212,6 +223,23 @@ module.exports = {
|
||||||
],
|
],
|
||||||
'import/no-unresolved': 'error',
|
'import/no-unresolved': 'error',
|
||||||
'import/no-webpack-loader-syntax': 'error',
|
'import/no-webpack-loader-syntax': 'error',
|
||||||
|
'import/order': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
groups: [
|
||||||
|
'builtin',
|
||||||
|
'external',
|
||||||
|
'internal',
|
||||||
|
'parent',
|
||||||
|
'sibling',
|
||||||
|
'index',
|
||||||
|
'object',
|
||||||
|
'type',
|
||||||
|
],
|
||||||
|
'newlines-between': 'always',
|
||||||
|
alphabetize: { order: 'asc' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
'promise/catch-or-return': 'error',
|
'promise/catch-or-return': 'error',
|
||||||
|
|
||||||
|
|
|
@ -12,3 +12,12 @@ yarn-error.log*
|
||||||
/static-test/
|
/static-test/
|
||||||
/public/
|
/public/
|
||||||
/dist/
|
/dist/
|
||||||
|
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# surge.sh
|
||||||
|
CNAME
|
||||||
|
AUTH
|
||||||
|
CORS
|
||||||
|
ROUTER
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 811 B |
|
@ -5,6 +5,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no">
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<link href="/manifest.json" rel="manifest">
|
||||||
<!--server-generated-meta-->
|
<!--server-generated-meta-->
|
||||||
<link rel="icon" type="image/png" href="/favicon.png">
|
<link rel="icon" type="image/png" href="/favicon.png">
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
{
|
||||||
|
"id": "107673570598783346",
|
||||||
|
"created_at": "2022-01-23T20:05:01.372Z",
|
||||||
|
"in_reply_to_id": null,
|
||||||
|
"in_reply_to_account_id": null,
|
||||||
|
"sensitive": false,
|
||||||
|
"spoiler_text": "",
|
||||||
|
"visibility": "public",
|
||||||
|
"language": "en",
|
||||||
|
"uri": "https://fedibird.com/users/alex/statuses/107673570598783346",
|
||||||
|
"url": "https://fedibird.com/@alex/107673570598783346",
|
||||||
|
"replies_count": 0,
|
||||||
|
"reblogs_count": 0,
|
||||||
|
"favourites_count": 0,
|
||||||
|
"emoji_reactions_count": 0,
|
||||||
|
"emoji_reactions": [],
|
||||||
|
"content": "<p>test quote of a quote<span class=\"quote-inline\"><br/>QT: <a class=\"status-url-link\" data-status-account-acct=\"alex\" data-status-id=\"107673570082615319\" href=\"https://fedibird.com/@alex/107673570082615319\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">fedibird.com/@alex/10767357008</span><span class=\"invisible\">2615319</span></a></span></p>",
|
||||||
|
"quote_id": "107673570082615319",
|
||||||
|
"reblog": null,
|
||||||
|
"application": {
|
||||||
|
"name": "Web",
|
||||||
|
"website": null
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"id": "66768",
|
||||||
|
"username": "alex",
|
||||||
|
"acct": "alex",
|
||||||
|
"display_name": "",
|
||||||
|
"locked": false,
|
||||||
|
"bot": false,
|
||||||
|
"discoverable": null,
|
||||||
|
"group": false,
|
||||||
|
"created_at": "2020-01-27T00:00:00.000Z",
|
||||||
|
"note": "<p></p>",
|
||||||
|
"url": "https://fedibird.com/@alex",
|
||||||
|
"avatar": "https://fedibird.com/avatars/original/missing.png",
|
||||||
|
"avatar_static": "https://fedibird.com/avatars/original/missing.png",
|
||||||
|
"header": "https://fedibird.com/headers/original/missing.png",
|
||||||
|
"header_static": "https://fedibird.com/headers/original/missing.png",
|
||||||
|
"followers_count": 0,
|
||||||
|
"following_count": 1,
|
||||||
|
"subscribing_count": 0,
|
||||||
|
"statuses_count": 3,
|
||||||
|
"last_status_at": "2022-01-23",
|
||||||
|
"emojis": [],
|
||||||
|
"fields": []
|
||||||
|
},
|
||||||
|
"media_attachments": [],
|
||||||
|
"mentions": [],
|
||||||
|
"tags": [],
|
||||||
|
"emojis": [],
|
||||||
|
"card": null,
|
||||||
|
"poll": null,
|
||||||
|
"quote": {
|
||||||
|
"id": "107673570082615319",
|
||||||
|
"created_at": "2022-01-23T20:04:53.494Z",
|
||||||
|
"in_reply_to_id": null,
|
||||||
|
"in_reply_to_account_id": null,
|
||||||
|
"sensitive": false,
|
||||||
|
"spoiler_text": "",
|
||||||
|
"visibility": "public",
|
||||||
|
"language": "en",
|
||||||
|
"uri": "https://fedibird.com/users/alex/statuses/107673570082615319",
|
||||||
|
"url": "https://fedibird.com/@alex/107673570082615319",
|
||||||
|
"replies_count": 0,
|
||||||
|
"reblogs_count": 0,
|
||||||
|
"favourites_count": 0,
|
||||||
|
"emoji_reactions_count": 0,
|
||||||
|
"emoji_reactions": [],
|
||||||
|
"content": "<p>test quote<span class=\"quote-inline\"><br/>QT: <a class=\"status-url-link\" data-status-account-acct=\"alex\" data-status-id=\"107673569214329435\" href=\"https://fedibird.com/@alex/107673569214329435\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">fedibird.com/@alex/10767356921</span><span class=\"invisible\">4329435</span></a></span></p>",
|
||||||
|
"quote_id": "107673569214329435",
|
||||||
|
"quote": null,
|
||||||
|
"reblog": null,
|
||||||
|
"application": {
|
||||||
|
"name": "Web",
|
||||||
|
"website": null
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"id": "66768",
|
||||||
|
"username": "alex",
|
||||||
|
"acct": "alex",
|
||||||
|
"display_name": "",
|
||||||
|
"locked": false,
|
||||||
|
"bot": false,
|
||||||
|
"discoverable": null,
|
||||||
|
"group": false,
|
||||||
|
"created_at": "2020-01-27T00:00:00.000Z",
|
||||||
|
"note": "<p></p>",
|
||||||
|
"url": "https://fedibird.com/@alex",
|
||||||
|
"avatar": "https://fedibird.com/avatars/original/missing.png",
|
||||||
|
"avatar_static": "https://fedibird.com/avatars/original/missing.png",
|
||||||
|
"header": "https://fedibird.com/headers/original/missing.png",
|
||||||
|
"header_static": "https://fedibird.com/headers/original/missing.png",
|
||||||
|
"followers_count": 0,
|
||||||
|
"following_count": 1,
|
||||||
|
"subscribing_count": 0,
|
||||||
|
"statuses_count": 3,
|
||||||
|
"last_status_at": "2022-01-23",
|
||||||
|
"emojis": [],
|
||||||
|
"fields": []
|
||||||
|
},
|
||||||
|
"media_attachments": [],
|
||||||
|
"mentions": [],
|
||||||
|
"tags": [],
|
||||||
|
"emojis": [],
|
||||||
|
"card": null,
|
||||||
|
"poll": null
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
{
|
||||||
|
"id": "107673570082615319",
|
||||||
|
"created_at": "2022-01-23T20:04:53.494Z",
|
||||||
|
"in_reply_to_id": null,
|
||||||
|
"in_reply_to_account_id": null,
|
||||||
|
"sensitive": false,
|
||||||
|
"spoiler_text": "",
|
||||||
|
"visibility": "public",
|
||||||
|
"language": "en",
|
||||||
|
"uri": "https://fedibird.com/users/alex/statuses/107673570082615319",
|
||||||
|
"url": "https://fedibird.com/@alex/107673570082615319",
|
||||||
|
"replies_count": 0,
|
||||||
|
"reblogs_count": 0,
|
||||||
|
"favourites_count": 0,
|
||||||
|
"emoji_reactions_count": 0,
|
||||||
|
"emoji_reactions": [],
|
||||||
|
"content": "<p>test quote<span class=\"quote-inline\"><br/>QT: <a class=\"status-url-link\" data-status-account-acct=\"alex\" data-status-id=\"107673569214329435\" href=\"https://fedibird.com/@alex/107673569214329435\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">fedibird.com/@alex/10767356921</span><span class=\"invisible\">4329435</span></a></span></p>",
|
||||||
|
"quote_id": "107673569214329435",
|
||||||
|
"reblog": null,
|
||||||
|
"application": {
|
||||||
|
"name": "Web",
|
||||||
|
"website": null
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"id": "66768",
|
||||||
|
"username": "alex",
|
||||||
|
"acct": "alex",
|
||||||
|
"display_name": "",
|
||||||
|
"locked": false,
|
||||||
|
"bot": false,
|
||||||
|
"discoverable": null,
|
||||||
|
"group": false,
|
||||||
|
"created_at": "2020-01-27T00:00:00.000Z",
|
||||||
|
"note": "<p></p>",
|
||||||
|
"url": "https://fedibird.com/@alex",
|
||||||
|
"avatar": "https://fedibird.com/avatars/original/missing.png",
|
||||||
|
"avatar_static": "https://fedibird.com/avatars/original/missing.png",
|
||||||
|
"header": "https://fedibird.com/headers/original/missing.png",
|
||||||
|
"header_static": "https://fedibird.com/headers/original/missing.png",
|
||||||
|
"followers_count": 0,
|
||||||
|
"following_count": 1,
|
||||||
|
"subscribing_count": 0,
|
||||||
|
"statuses_count": 3,
|
||||||
|
"last_status_at": "2022-01-23",
|
||||||
|
"emojis": [],
|
||||||
|
"fields": []
|
||||||
|
},
|
||||||
|
"media_attachments": [],
|
||||||
|
"mentions": [],
|
||||||
|
"tags": [],
|
||||||
|
"emojis": [],
|
||||||
|
"card": null,
|
||||||
|
"poll": null,
|
||||||
|
"quote": {
|
||||||
|
"id": "107673569214329435",
|
||||||
|
"created_at": "2022-01-23T20:04:40.249Z",
|
||||||
|
"in_reply_to_id": null,
|
||||||
|
"in_reply_to_account_id": null,
|
||||||
|
"sensitive": false,
|
||||||
|
"spoiler_text": "",
|
||||||
|
"visibility": "public",
|
||||||
|
"language": "en",
|
||||||
|
"uri": "https://fedibird.com/users/alex/statuses/107673569214329435",
|
||||||
|
"url": "https://fedibird.com/@alex/107673569214329435",
|
||||||
|
"replies_count": 0,
|
||||||
|
"reblogs_count": 0,
|
||||||
|
"favourites_count": 0,
|
||||||
|
"emoji_reactions_count": 0,
|
||||||
|
"emoji_reactions": [],
|
||||||
|
"content": "<p>test post</p>",
|
||||||
|
"quote": null,
|
||||||
|
"reblog": null,
|
||||||
|
"application": {
|
||||||
|
"name": "Web",
|
||||||
|
"website": null
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"id": "66768",
|
||||||
|
"username": "alex",
|
||||||
|
"acct": "alex",
|
||||||
|
"display_name": "",
|
||||||
|
"locked": false,
|
||||||
|
"bot": false,
|
||||||
|
"discoverable": null,
|
||||||
|
"group": false,
|
||||||
|
"created_at": "2020-01-27T00:00:00.000Z",
|
||||||
|
"note": "<p></p>",
|
||||||
|
"url": "https://fedibird.com/@alex",
|
||||||
|
"avatar": "https://fedibird.com/avatars/original/missing.png",
|
||||||
|
"avatar_static": "https://fedibird.com/avatars/original/missing.png",
|
||||||
|
"header": "https://fedibird.com/headers/original/missing.png",
|
||||||
|
"header_static": "https://fedibird.com/headers/original/missing.png",
|
||||||
|
"followers_count": 0,
|
||||||
|
"following_count": 1,
|
||||||
|
"subscribing_count": 0,
|
||||||
|
"statuses_count": 3,
|
||||||
|
"last_status_at": "2022-01-23",
|
||||||
|
"emojis": [],
|
||||||
|
"fields": []
|
||||||
|
},
|
||||||
|
"media_attachments": [],
|
||||||
|
"mentions": [],
|
||||||
|
"tags": [],
|
||||||
|
"emojis": [],
|
||||||
|
"card": null,
|
||||||
|
"poll": null
|
||||||
|
}
|
||||||
|
}
|
|
@ -106,7 +106,7 @@
|
||||||
"confirmations.delete_list.confirm": "Delete",
|
"confirmations.delete_list.confirm": "Delete",
|
||||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications.",
|
||||||
"confirmations.mute.confirm": "Mute",
|
"confirmations.mute.confirm": "Mute",
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||||
"confirmations.redraft.confirm": "Delete & redraft",
|
"confirmations.redraft.confirm": "Delete & redraft",
|
||||||
|
@ -584,7 +584,7 @@
|
||||||
"confirmations.delete_list.confirm": "Delete",
|
"confirmations.delete_list.confirm": "Delete",
|
||||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications.",
|
||||||
"confirmations.mute.confirm": "Mute",
|
"confirmations.mute.confirm": "Mute",
|
||||||
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
"confirmations.mute.message": "Are you sure you want to mute {name}?",
|
||||||
"confirmations.redraft.confirm": "Delete & redraft",
|
"confirmations.redraft.confirm": "Delete & redraft",
|
||||||
|
|
|
@ -0,0 +1,371 @@
|
||||||
|
{
|
||||||
|
"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": "<a href=\"https://alexgleason.me\" rel=\"ugc\">https://alexgleason.me</a>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pleroma+Soapbox",
|
||||||
|
"value": "<a href=\"https://soapbox.pub\" rel=\"ugc\">https://soapbox.pub</a>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Email",
|
||||||
|
"value": "alex@alexgleason.me"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gender identity",
|
||||||
|
"value": "Soyboy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Donate (PayPal)",
|
||||||
|
"value": "<a href=\"https://paypal.me/gleasonator\" rel=\"ugc\">https://paypal.me/gleasonator</a>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$BTC",
|
||||||
|
"value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$ETH",
|
||||||
|
"value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$DOGE",
|
||||||
|
"value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XMR",
|
||||||
|
"value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"followers_count": 2220,
|
||||||
|
"following_count": 1544,
|
||||||
|
"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-01-24T21:02:44",
|
||||||
|
"locked": false,
|
||||||
|
"note": "I create Fediverse software that empowers people online.<br/><br/>I'm vegan btw<br/><br/>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,
|
||||||
|
"also_known_as": [],
|
||||||
|
"ap_id": "https://gleasonator.com/users/alex",
|
||||||
|
"background_image": null,
|
||||||
|
"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,
|
||||||
|
"relationship": {},
|
||||||
|
"skip_thread_containment": false,
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "Website",
|
||||||
|
"value": "https://alexgleason.me"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pleroma+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
|
||||||
|
},
|
||||||
|
"sensitive": false
|
||||||
|
},
|
||||||
|
"statuses_count": 23004,
|
||||||
|
"url": "https://gleasonator.com/users/alex",
|
||||||
|
"username": "alex"
|
||||||
|
},
|
||||||
|
"application": {
|
||||||
|
"name": "Soapbox FE",
|
||||||
|
"website": "https://soapbox.pub/"
|
||||||
|
},
|
||||||
|
"bookmarked": false,
|
||||||
|
"card": null,
|
||||||
|
"content": "<p>Quote of quote post</p>",
|
||||||
|
"created_at": "2022-01-24T21:02:43.000Z",
|
||||||
|
"emojis": [],
|
||||||
|
"favourited": false,
|
||||||
|
"favourites_count": 0,
|
||||||
|
"id": "AFmFNKmfrR9CxtV01g",
|
||||||
|
"in_reply_to_account_id": null,
|
||||||
|
"in_reply_to_id": null,
|
||||||
|
"language": null,
|
||||||
|
"media_attachments": [],
|
||||||
|
"mentions": [
|
||||||
|
{
|
||||||
|
"acct": "alex",
|
||||||
|
"id": "9v5bmRalQvjOy0ECcC",
|
||||||
|
"url": "https://gleasonator.com/users/alex",
|
||||||
|
"username": "alex"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muted": false,
|
||||||
|
"pinned": false,
|
||||||
|
"pleroma": {
|
||||||
|
"content": {
|
||||||
|
"text/plain": "Quote of quote post"
|
||||||
|
},
|
||||||
|
"conversation_id": "AFmFNKkXzLRirIVIi8",
|
||||||
|
"direct_conversation_id": null,
|
||||||
|
"emoji_reactions": [],
|
||||||
|
"expires_at": null,
|
||||||
|
"in_reply_to_account_acct": null,
|
||||||
|
"local": true,
|
||||||
|
"parent_visible": false,
|
||||||
|
"pinned_at": null,
|
||||||
|
"quote": {
|
||||||
|
"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": "<a href=\"https://alexgleason.me\" rel=\"ugc\">https://alexgleason.me</a>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pleroma+Soapbox",
|
||||||
|
"value": "<a href=\"https://soapbox.pub\" rel=\"ugc\">https://soapbox.pub</a>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Email",
|
||||||
|
"value": "alex@alexgleason.me"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gender identity",
|
||||||
|
"value": "Soyboy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Donate (PayPal)",
|
||||||
|
"value": "<a href=\"https://paypal.me/gleasonator\" rel=\"ugc\">https://paypal.me/gleasonator</a>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$BTC",
|
||||||
|
"value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$ETH",
|
||||||
|
"value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$DOGE",
|
||||||
|
"value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XMR",
|
||||||
|
"value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"followers_count": 2220,
|
||||||
|
"following_count": 1544,
|
||||||
|
"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-01-24T21:02:44",
|
||||||
|
"locked": false,
|
||||||
|
"note": "I create Fediverse software that empowers people online.<br/><br/>I'm vegan btw<br/><br/>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,
|
||||||
|
"also_known_as": [],
|
||||||
|
"ap_id": "https://gleasonator.com/users/alex",
|
||||||
|
"background_image": null,
|
||||||
|
"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,
|
||||||
|
"relationship": {},
|
||||||
|
"skip_thread_containment": false,
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "Website",
|
||||||
|
"value": "https://alexgleason.me"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pleroma+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
|
||||||
|
},
|
||||||
|
"sensitive": false
|
||||||
|
},
|
||||||
|
"statuses_count": 23004,
|
||||||
|
"url": "https://gleasonator.com/users/alex",
|
||||||
|
"username": "alex"
|
||||||
|
},
|
||||||
|
"application": {
|
||||||
|
"name": "Soapbox FE",
|
||||||
|
"website": "https://soapbox.pub/"
|
||||||
|
},
|
||||||
|
"bookmarked": false,
|
||||||
|
"card": null,
|
||||||
|
"content": "<p>Quote post</p>",
|
||||||
|
"created_at": "2022-01-24T21:02:34.000Z",
|
||||||
|
"emojis": [],
|
||||||
|
"favourited": false,
|
||||||
|
"favourites_count": 0,
|
||||||
|
"id": "AFmFMSpITT9xcOJKcK",
|
||||||
|
"in_reply_to_account_id": null,
|
||||||
|
"in_reply_to_id": null,
|
||||||
|
"language": null,
|
||||||
|
"media_attachments": [],
|
||||||
|
"mentions": [
|
||||||
|
{
|
||||||
|
"acct": "alex",
|
||||||
|
"id": "9v5bmRalQvjOy0ECcC",
|
||||||
|
"url": "https://gleasonator.com/users/alex",
|
||||||
|
"username": "alex"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muted": false,
|
||||||
|
"pinned": false,
|
||||||
|
"pleroma": {
|
||||||
|
"content": {
|
||||||
|
"text/plain": "Quote post"
|
||||||
|
},
|
||||||
|
"conversation_id": "AFmFMSnWa3k3WtTur2",
|
||||||
|
"direct_conversation_id": null,
|
||||||
|
"emoji_reactions": [
|
||||||
|
{
|
||||||
|
"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": "https://gleasonator.com/objects/4f35159c-3794-4037-9269-a7c84f7137c7",
|
||||||
|
"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://gleasonator.com/objects/54d93075-7d04-4016-a128-81f3843bca79",
|
||||||
|
"url": "https://gleasonator.com/notice/AFmFMSpITT9xcOJKcK",
|
||||||
|
"visibility": "public"
|
||||||
|
},
|
||||||
|
"quote_url": "https://gleasonator.com/objects/54d93075-7d04-4016-a128-81f3843bca79",
|
||||||
|
"spoiler_text": {
|
||||||
|
"text/plain": ""
|
||||||
|
},
|
||||||
|
"thread_muted": false
|
||||||
|
},
|
||||||
|
"poll": null,
|
||||||
|
"reblog": null,
|
||||||
|
"reblogged": false,
|
||||||
|
"reblogs_count": 0,
|
||||||
|
"replies_count": 1,
|
||||||
|
"sensitive": false,
|
||||||
|
"spoiler_text": "",
|
||||||
|
"tags": [],
|
||||||
|
"text": null,
|
||||||
|
"uri": "https://gleasonator.com/objects/1e2cfb5a-ece5-42df-9ec1-13e5de6d9f5b",
|
||||||
|
"url": "https://gleasonator.com/notice/AFmFNKmfrR9CxtV01g",
|
||||||
|
"visibility": "public"
|
||||||
|
}
|
|
@ -0,0 +1,364 @@
|
||||||
|
{
|
||||||
|
"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": "<a href=\"https://alexgleason.me\" rel=\"ugc\">https://alexgleason.me</a>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pleroma+Soapbox",
|
||||||
|
"value": "<a href=\"https://soapbox.pub\" rel=\"ugc\">https://soapbox.pub</a>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Email",
|
||||||
|
"value": "alex@alexgleason.me"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gender identity",
|
||||||
|
"value": "Soyboy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Donate (PayPal)",
|
||||||
|
"value": "<a href=\"https://paypal.me/gleasonator\" rel=\"ugc\">https://paypal.me/gleasonator</a>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$BTC",
|
||||||
|
"value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$ETH",
|
||||||
|
"value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$DOGE",
|
||||||
|
"value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XMR",
|
||||||
|
"value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"followers_count": 2220,
|
||||||
|
"following_count": 1544,
|
||||||
|
"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-01-24T21:02:44",
|
||||||
|
"locked": false,
|
||||||
|
"note": "I create Fediverse software that empowers people online.<br/><br/>I'm vegan btw<br/><br/>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,
|
||||||
|
"also_known_as": [],
|
||||||
|
"ap_id": "https://gleasonator.com/users/alex",
|
||||||
|
"background_image": null,
|
||||||
|
"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,
|
||||||
|
"relationship": {},
|
||||||
|
"skip_thread_containment": false,
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "Website",
|
||||||
|
"value": "https://alexgleason.me"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pleroma+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
|
||||||
|
},
|
||||||
|
"sensitive": false
|
||||||
|
},
|
||||||
|
"statuses_count": 23004,
|
||||||
|
"url": "https://gleasonator.com/users/alex",
|
||||||
|
"username": "alex"
|
||||||
|
},
|
||||||
|
"application": {
|
||||||
|
"name": "Soapbox FE",
|
||||||
|
"website": "https://soapbox.pub/"
|
||||||
|
},
|
||||||
|
"bookmarked": false,
|
||||||
|
"card": null,
|
||||||
|
"content": "<p>Quote post</p>",
|
||||||
|
"created_at": "2022-01-24T21:02:34.000Z",
|
||||||
|
"emojis": [],
|
||||||
|
"favourited": false,
|
||||||
|
"favourites_count": 0,
|
||||||
|
"id": "AFmFMSpITT9xcOJKcK",
|
||||||
|
"in_reply_to_account_id": null,
|
||||||
|
"in_reply_to_id": null,
|
||||||
|
"language": null,
|
||||||
|
"media_attachments": [],
|
||||||
|
"mentions": [
|
||||||
|
{
|
||||||
|
"acct": "alex",
|
||||||
|
"id": "9v5bmRalQvjOy0ECcC",
|
||||||
|
"url": "https://gleasonator.com/users/alex",
|
||||||
|
"username": "alex"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muted": false,
|
||||||
|
"pinned": false,
|
||||||
|
"pleroma": {
|
||||||
|
"content": {
|
||||||
|
"text/plain": "Quote post"
|
||||||
|
},
|
||||||
|
"conversation_id": "AFmFMSnWa3k3WtTur2",
|
||||||
|
"direct_conversation_id": null,
|
||||||
|
"emoji_reactions": [
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"me": false,
|
||||||
|
"name": "👍"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expires_at": null,
|
||||||
|
"in_reply_to_account_acct": null,
|
||||||
|
"local": true,
|
||||||
|
"parent_visible": false,
|
||||||
|
"pinned_at": null,
|
||||||
|
"quote": {
|
||||||
|
"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": "<a href=\"https://alexgleason.me\" rel=\"ugc\">https://alexgleason.me</a>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pleroma+Soapbox",
|
||||||
|
"value": "<a href=\"https://soapbox.pub\" rel=\"ugc\">https://soapbox.pub</a>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Email",
|
||||||
|
"value": "alex@alexgleason.me"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gender identity",
|
||||||
|
"value": "Soyboy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Donate (PayPal)",
|
||||||
|
"value": "<a href=\"https://paypal.me/gleasonator\" rel=\"ugc\">https://paypal.me/gleasonator</a>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$BTC",
|
||||||
|
"value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$ETH",
|
||||||
|
"value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$DOGE",
|
||||||
|
"value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "$XMR",
|
||||||
|
"value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"followers_count": 2220,
|
||||||
|
"following_count": 1544,
|
||||||
|
"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-01-24T21:02:44",
|
||||||
|
"locked": false,
|
||||||
|
"note": "I create Fediverse software that empowers people online.<br/><br/>I'm vegan btw<br/><br/>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,
|
||||||
|
"also_known_as": [],
|
||||||
|
"ap_id": "https://gleasonator.com/users/alex",
|
||||||
|
"background_image": null,
|
||||||
|
"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,
|
||||||
|
"relationship": {},
|
||||||
|
"skip_thread_containment": false,
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "Website",
|
||||||
|
"value": "https://alexgleason.me"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pleroma+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
|
||||||
|
},
|
||||||
|
"sensitive": false
|
||||||
|
},
|
||||||
|
"statuses_count": 23004,
|
||||||
|
"url": "https://gleasonator.com/users/alex",
|
||||||
|
"username": "alex"
|
||||||
|
},
|
||||||
|
"application": {
|
||||||
|
"name": "Soapbox FE",
|
||||||
|
"website": "https://soapbox.pub/"
|
||||||
|
},
|
||||||
|
"bookmarked": false,
|
||||||
|
"card": null,
|
||||||
|
"content": "<p>Test post</p>",
|
||||||
|
"created_at": "2022-01-24T21:02:25.000Z",
|
||||||
|
"emojis": [],
|
||||||
|
"favourited": false,
|
||||||
|
"favourites_count": 0,
|
||||||
|
"id": "AFmFLcd6XYVdjWCrOS",
|
||||||
|
"in_reply_to_account_id": null,
|
||||||
|
"in_reply_to_id": null,
|
||||||
|
"language": null,
|
||||||
|
"media_attachments": [],
|
||||||
|
"mentions": [],
|
||||||
|
"muted": false,
|
||||||
|
"pinned": false,
|
||||||
|
"pleroma": {
|
||||||
|
"content": {
|
||||||
|
"text/plain": "Test post"
|
||||||
|
},
|
||||||
|
"conversation_id": "AFmFLcaGi6EzaisayO",
|
||||||
|
"direct_conversation_id": null,
|
||||||
|
"emoji_reactions": [],
|
||||||
|
"expires_at": null,
|
||||||
|
"in_reply_to_account_acct": null,
|
||||||
|
"local": true,
|
||||||
|
"parent_visible": false,
|
||||||
|
"pinned_at": null,
|
||||||
|
"quote": null,
|
||||||
|
"quote_url": null,
|
||||||
|
"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://gleasonator.com/objects/4f35159c-3794-4037-9269-a7c84f7137c7",
|
||||||
|
"url": "https://gleasonator.com/notice/AFmFLcd6XYVdjWCrOS",
|
||||||
|
"visibility": "public"
|
||||||
|
},
|
||||||
|
"quote_url": "https://gleasonator.com/objects/4f35159c-3794-4037-9269-a7c84f7137c7",
|
||||||
|
"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://gleasonator.com/objects/54d93075-7d04-4016-a128-81f3843bca79",
|
||||||
|
"url": "https://gleasonator.com/notice/AFmFMSpITT9xcOJKcK",
|
||||||
|
"visibility": "public"
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
{
|
||||||
|
"account": {
|
||||||
|
"acct": "apropos@freespeechextremist.com",
|
||||||
|
"avatar": "https://gleasonator.com/proxy/WVdkCbG7AOZ_eqMzskzXQoyjq8o/aHR0cHM6Ly9mcmVlc3BlZWNoZXh0cmVtaXN0LmNvbS9tZWRpYS8zN2I4MDMzZC03OGQ1LTQ0YmMtYmY5NC0xYTI2NzY5NTQwM2YvYmxvYi5wbmc_bmFtZT1ibG9iLnBuZw/blob.png",
|
||||||
|
"avatar_static": "https://gleasonator.com/proxy/WVdkCbG7AOZ_eqMzskzXQoyjq8o/aHR0cHM6Ly9mcmVlc3BlZWNoZXh0cmVtaXN0LmNvbS9tZWRpYS8zN2I4MDMzZC03OGQ1LTQ0YmMtYmY5NC0xYTI2NzY5NTQwM2YvYmxvYi5wbmc_bmFtZT1ibG9iLnBuZw/blob.png",
|
||||||
|
"bot": false,
|
||||||
|
"created_at": "2020-05-21T07:20:46.000Z",
|
||||||
|
"display_name": "of nothing",
|
||||||
|
"emojis": [],
|
||||||
|
"fields": [],
|
||||||
|
"followers_count": 87,
|
||||||
|
"following_count": 85,
|
||||||
|
"fqn": "apropos@freespeechextremist.com",
|
||||||
|
"header": "https://gleasonator.com/proxy/pIracLGWm_skCfOOgdwcCNqES5s/aHR0cHM6Ly9mcmVlc3BlZWNoZXh0cmVtaXN0LmNvbS9tZWRpYS8yZDEwYmRjZC01NDUwLTRjZjYtYWFhZS1hNTJjMzYwYjk2YjYvdHJhY2tzb25tYXJzLmpwZz9uYW1lPXRyYWNrc29ubWFycy5qcGc/tracksonmars.jpg",
|
||||||
|
"header_static": "https://gleasonator.com/proxy/pIracLGWm_skCfOOgdwcCNqES5s/aHR0cHM6Ly9mcmVlc3BlZWNoZXh0cmVtaXN0LmNvbS9tZWRpYS8yZDEwYmRjZC01NDUwLTRjZjYtYWFhZS1hNTJjMzYwYjk2YjYvdHJhY2tzb25tYXJzLmpwZz9uYW1lPXRyYWNrc29ubWFycy5qcGc/tracksonmars.jpg",
|
||||||
|
"id": "9vGR3IWmWVYRkKUZ4i",
|
||||||
|
"last_status_at": "2022-01-07T21:47:39",
|
||||||
|
"locked": false,
|
||||||
|
"note": "If you wait by the river long enough, the bodies of your enemies will float by.<br/><br/>Deo Vindice",
|
||||||
|
"pleroma": {
|
||||||
|
"accepts_chat_messages": true,
|
||||||
|
"also_known_as": [],
|
||||||
|
"ap_id": "https://freespeechextremist.com/users/apropos",
|
||||||
|
"background_image": null,
|
||||||
|
"favicon": "https://gleasonator.com/proxy/EN7BSaEEYTRpmRj4lITIjgWp2sg/aHR0cHM6Ly9mcmVlc3BlZWNoZXh0cmVtaXN0LmNvbS9mYXZpY29uLnBuZw/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,
|
||||||
|
"relationship": {},
|
||||||
|
"skip_thread_containment": false,
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"fields": [],
|
||||||
|
"note": "",
|
||||||
|
"pleroma": {
|
||||||
|
"actor_type": "Person",
|
||||||
|
"discoverable": false
|
||||||
|
},
|
||||||
|
"sensitive": false
|
||||||
|
},
|
||||||
|
"statuses_count": 7087,
|
||||||
|
"url": "https://freespeechextremist.com/users/apropos",
|
||||||
|
"username": "apropos"
|
||||||
|
},
|
||||||
|
"application": null,
|
||||||
|
"bookmarked": false,
|
||||||
|
"card": null,
|
||||||
|
"content": "<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"9r7CIa1GGezUtzTFyq\" href=\"https://iddqd.social/users/NEETzsche\" rel=\"ugc\">@<span>NEETzsche</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"9qoN7ZXtt7bllprZtw\" href=\"https://gleasonator.com/users/alex\" rel=\"ugc\">@<span>alex</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"A3dBnkLBdLgHFdxV2G\" href=\"https://pleroma.skyshanty.xyz/users/Lumeinshin\" rel=\"ugc\">@<span>Lumeinshin</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"ACrsGCVvaMiNdATg7E\" href=\"https://social.silkky.cloud/users/sneeden\" rel=\"ugc\">@<span>sneeden</span></a></span> <br/>>seething<br/>'posting', just like you.",
|
||||||
|
"created_at": "2022-01-07T17:29:58.000Z",
|
||||||
|
"emojis": [],
|
||||||
|
"favourited": false,
|
||||||
|
"favourites_count": 1,
|
||||||
|
"id": "AFChectaqZjmOVkXZ2",
|
||||||
|
"in_reply_to_account_id": "9v5bw7hEGBPc9nrpzc",
|
||||||
|
"in_reply_to_id": "AFChbnWqrAZ2VIlPJw",
|
||||||
|
"language": null,
|
||||||
|
"media_attachments": [],
|
||||||
|
"mentions": [
|
||||||
|
{
|
||||||
|
"acct": "alex",
|
||||||
|
"id": "9v5bmRalQvjOy0ECcC",
|
||||||
|
"url": "https://gleasonator.com/users/alex",
|
||||||
|
"username": "alex"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"acct": "NEETzsche@iddqd.social",
|
||||||
|
"id": "9v5bw7hEGBPc9nrpzc",
|
||||||
|
"url": "https://iddqd.social/users/NEETzsche",
|
||||||
|
"username": "NEETzsche"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"acct": "Lumeinshin@pleroma.skyshanty.xyz",
|
||||||
|
"id": "A3dFSwTkwgRfd998iG",
|
||||||
|
"url": "https://pleroma.skyshanty.xyz/users/Lumeinshin",
|
||||||
|
"username": "Lumeinshin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"acct": "sneeden@social.silkky.cloud",
|
||||||
|
"id": "ACrsPAbAOPh3GbKZhQ",
|
||||||
|
"url": "https://social.silkky.cloud/users/sneeden",
|
||||||
|
"username": "sneeden"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muted": false,
|
||||||
|
"pinned": false,
|
||||||
|
"pleroma": {
|
||||||
|
"content": {
|
||||||
|
"text/plain": "@NEETzsche @alex @Lumeinshin @sneeden >seething'posting', just like you."
|
||||||
|
},
|
||||||
|
"conversation_id": "AFCYCBFN9SgOwoIWTg",
|
||||||
|
"direct_conversation_id": null,
|
||||||
|
"emoji_reactions": [],
|
||||||
|
"expires_at": null,
|
||||||
|
"in_reply_to_account_acct": "NEETzsche@iddqd.social",
|
||||||
|
"local": false,
|
||||||
|
"parent_visible": true,
|
||||||
|
"pinned_at": null,
|
||||||
|
"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://freespeechextremist.com/objects/714b0e04-bec4-4a2a-9514-312814380064",
|
||||||
|
"url": "https://freespeechextremist.com/objects/714b0e04-bec4-4a2a-9514-312814380064",
|
||||||
|
"visibility": "public"
|
||||||
|
}
|
|
@ -1,13 +1,15 @@
|
||||||
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
|
import { Map as ImmutableMap } from 'immutable';
|
||||||
|
|
||||||
|
import { staticClient } from 'soapbox/api';
|
||||||
|
import { mockStore } from 'soapbox/test_helpers';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FETCH_ABOUT_PAGE_REQUEST,
|
FETCH_ABOUT_PAGE_REQUEST,
|
||||||
FETCH_ABOUT_PAGE_SUCCESS,
|
FETCH_ABOUT_PAGE_SUCCESS,
|
||||||
FETCH_ABOUT_PAGE_FAIL,
|
FETCH_ABOUT_PAGE_FAIL,
|
||||||
fetchAboutPage,
|
fetchAboutPage,
|
||||||
} from '../about';
|
} from '../about';
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
|
||||||
import { staticClient } from 'soapbox/api';
|
|
||||||
import { mockStore } from 'soapbox/test_helpers';
|
|
||||||
|
|
||||||
describe('fetchAboutPage()', () => {
|
describe('fetchAboutPage()', () => {
|
||||||
it('creates the expected actions on success', () => {
|
it('creates the expected actions on success', () => {
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
|
import { Map as ImmutableMap } from 'immutable';
|
||||||
|
|
||||||
|
import { __stub } from 'soapbox/api';
|
||||||
|
import { mockStore } from 'soapbox/test_helpers';
|
||||||
|
|
||||||
|
import { VERIFY_CREDENTIALS_REQUEST } from '../auth';
|
||||||
|
import { ACCOUNTS_IMPORT } from '../importer';
|
||||||
import {
|
import {
|
||||||
MASTODON_PRELOAD_IMPORT,
|
MASTODON_PRELOAD_IMPORT,
|
||||||
preloadMastodon,
|
preloadMastodon,
|
||||||
} from '../preload';
|
} from '../preload';
|
||||||
import { VERIFY_CREDENTIALS_REQUEST } from '../auth';
|
|
||||||
import { ACCOUNTS_IMPORT } from '../importer';
|
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
|
||||||
import { __stub } from 'soapbox/api';
|
|
||||||
import { mockStore } from 'soapbox/test_helpers';
|
|
||||||
|
|
||||||
describe('preloadMastodon()', () => {
|
describe('preloadMastodon()', () => {
|
||||||
it('creates the expected actions', () => {
|
it('creates the expected actions', () => {
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
|
||||||
import api, { getLinks } from '../api';
|
import api, { getLinks } from '../api';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
importFetchedAccount,
|
importFetchedAccount,
|
||||||
importFetchedAccounts,
|
importFetchedAccounts,
|
||||||
importErrorWhileFetchingAccountByUsername,
|
importErrorWhileFetchingAccountByUsername,
|
||||||
} from './importer';
|
} from './importer';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
|
||||||
|
|
||||||
export const ACCOUNT_CREATE_REQUEST = 'ACCOUNT_CREATE_REQUEST';
|
export const ACCOUNT_CREATE_REQUEST = 'ACCOUNT_CREATE_REQUEST';
|
||||||
export const ACCOUNT_CREATE_SUCCESS = 'ACCOUNT_CREATE_SUCCESS';
|
export const ACCOUNT_CREATE_SUCCESS = 'ACCOUNT_CREATE_SUCCESS';
|
||||||
|
@ -55,10 +57,18 @@ export const ACCOUNT_UNPIN_REQUEST = 'ACCOUNT_UNPIN_REQUEST';
|
||||||
export const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS';
|
export const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS';
|
||||||
export const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL';
|
export const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL';
|
||||||
|
|
||||||
|
export const PINNED_ACCOUNTS_FETCH_REQUEST = 'PINNED_ACCOUNTS_FETCH_REQUEST';
|
||||||
|
export const PINNED_ACCOUNTS_FETCH_SUCCESS = 'PINNED_ACCOUNTS_FETCH_SUCCESS';
|
||||||
|
export const PINNED_ACCOUNTS_FETCH_FAIL = 'PINNED_ACCOUNTS_FETCH_FAIL';
|
||||||
|
|
||||||
export const ACCOUNT_SEARCH_REQUEST = 'ACCOUNT_SEARCH_REQUEST';
|
export const ACCOUNT_SEARCH_REQUEST = 'ACCOUNT_SEARCH_REQUEST';
|
||||||
export const ACCOUNT_SEARCH_SUCCESS = 'ACCOUNT_SEARCH_SUCCESS';
|
export const ACCOUNT_SEARCH_SUCCESS = 'ACCOUNT_SEARCH_SUCCESS';
|
||||||
export const ACCOUNT_SEARCH_FAIL = 'ACCOUNT_SEARCH_FAIL';
|
export const ACCOUNT_SEARCH_FAIL = 'ACCOUNT_SEARCH_FAIL';
|
||||||
|
|
||||||
|
export const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST';
|
||||||
|
export const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS';
|
||||||
|
export const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL';
|
||||||
|
|
||||||
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
|
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
|
||||||
export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
|
export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
|
||||||
export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL';
|
export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL';
|
||||||
|
@ -144,8 +154,16 @@ export function fetchAccountByUsername(username) {
|
||||||
|
|
||||||
const instance = state.get('instance');
|
const instance = state.get('instance');
|
||||||
const features = getFeatures(instance);
|
const features = getFeatures(instance);
|
||||||
|
const me = state.get('me');
|
||||||
|
|
||||||
if (features.accountByUsername) {
|
if (!me && features.accountLookup) {
|
||||||
|
dispatch(accountLookup(username)).then(account => {
|
||||||
|
dispatch(fetchAccountSuccess(account));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(fetchAccountFail(null, error));
|
||||||
|
dispatch(importErrorWhileFetchingAccountByUsername(username));
|
||||||
|
});
|
||||||
|
} else if (features.accountByUsername) {
|
||||||
api(getState).get(`/api/v1/accounts/${username}`).then(response => {
|
api(getState).get(`/api/v1/accounts/${username}`).then(response => {
|
||||||
dispatch(fetchRelationships([response.data.id]));
|
dispatch(fetchRelationships([response.data.id]));
|
||||||
dispatch(importFetchedAccount(response.data));
|
dispatch(importFetchedAccount(response.data));
|
||||||
|
@ -948,6 +966,43 @@ export function unpinAccountFail(error) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchPinnedAccounts(id) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch(fetchPinnedAccountsRequest(id));
|
||||||
|
|
||||||
|
api(getState).get(`/api/v1/pleroma/accounts/${id}/endorsements`).then(response => {
|
||||||
|
dispatch(importFetchedAccounts(response.data));
|
||||||
|
dispatch(fetchPinnedAccountsSuccess(id, response.data, null));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(fetchPinnedAccountsFail(id, error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchPinnedAccountsRequest(id) {
|
||||||
|
return {
|
||||||
|
type: PINNED_ACCOUNTS_FETCH_REQUEST,
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchPinnedAccountsSuccess(id, accounts, next) {
|
||||||
|
return {
|
||||||
|
type: PINNED_ACCOUNTS_FETCH_SUCCESS,
|
||||||
|
id,
|
||||||
|
accounts,
|
||||||
|
next,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchPinnedAccountsFail(id, error) {
|
||||||
|
return {
|
||||||
|
type: PINNED_ACCOUNTS_FETCH_FAIL,
|
||||||
|
id,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function accountSearch(params, cancelToken) {
|
export function accountSearch(params, cancelToken) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: ACCOUNT_SEARCH_REQUEST, params });
|
dispatch({ type: ACCOUNT_SEARCH_REQUEST, params });
|
||||||
|
@ -961,3 +1016,17 @@ export function accountSearch(params, cancelToken) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function accountLookup(acct, cancelToken) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch({ type: ACCOUNT_LOOKUP_REQUEST, acct });
|
||||||
|
return api(getState).get('/api/v1/accounts/lookup', { params: { acct }, cancelToken }).then(({ data: account }) => {
|
||||||
|
if (account && account.id) dispatch(importFetchedAccount(account));
|
||||||
|
dispatch({ type: ACCOUNT_LOOKUP_SUCCESS, account });
|
||||||
|
return account;
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch({ type: ACCOUNT_LOOKUP_FAIL });
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import api from '../api';
|
|
||||||
import { importFetchedAccount, importFetchedStatuses } from 'soapbox/actions/importer';
|
|
||||||
import { fetchRelationships } from 'soapbox/actions/accounts';
|
import { fetchRelationships } from 'soapbox/actions/accounts';
|
||||||
|
import { importFetchedAccount, importFetchedStatuses } from 'soapbox/actions/importer';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
export const ADMIN_CONFIG_FETCH_REQUEST = 'ADMIN_CONFIG_FETCH_REQUEST';
|
export const ADMIN_CONFIG_FETCH_REQUEST = 'ADMIN_CONFIG_FETCH_REQUEST';
|
||||||
export const ADMIN_CONFIG_FETCH_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS';
|
export const ADMIN_CONFIG_FETCH_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS';
|
||||||
|
@ -62,6 +63,14 @@ export const ADMIN_REMOVE_PERMISSION_GROUP_REQUEST = 'ADMIN_REMOVE_PERMISSION_GR
|
||||||
export const ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS = 'ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS';
|
export const ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS = 'ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS';
|
||||||
export const ADMIN_REMOVE_PERMISSION_GROUP_FAIL = 'ADMIN_REMOVE_PERMISSION_GROUP_FAIL';
|
export const ADMIN_REMOVE_PERMISSION_GROUP_FAIL = 'ADMIN_REMOVE_PERMISSION_GROUP_FAIL';
|
||||||
|
|
||||||
|
export const ADMIN_USERS_SUGGEST_REQUEST = 'ADMIN_USERS_SUGGEST_REQUEST';
|
||||||
|
export const ADMIN_USERS_SUGGEST_SUCCESS = 'ADMIN_USERS_SUGGEST_SUCCESS';
|
||||||
|
export const ADMIN_USERS_SUGGEST_FAIL = 'ADMIN_USERS_SUGGEST_FAIL';
|
||||||
|
|
||||||
|
export const ADMIN_USERS_UNSUGGEST_REQUEST = 'ADMIN_USERS_UNSUGGEST_REQUEST';
|
||||||
|
export const ADMIN_USERS_UNSUGGEST_SUCCESS = 'ADMIN_USERS_UNSUGGEST_SUCCESS';
|
||||||
|
export const ADMIN_USERS_UNSUGGEST_FAIL = 'ADMIN_USERS_UNSUGGEST_FAIL';
|
||||||
|
|
||||||
const nicknamesFromIds = (getState, ids) => ids.map(id => getState().getIn(['accounts', id, 'acct']));
|
const nicknamesFromIds = (getState, ids) => ids.map(id => getState().getIn(['accounts', id, 'acct']));
|
||||||
|
|
||||||
export function fetchConfig() {
|
export function fetchConfig() {
|
||||||
|
@ -319,3 +328,31 @@ export function demoteToUser(accountId) {
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function suggestUsers(accountIds) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||||
|
dispatch({ type: ADMIN_USERS_SUGGEST_REQUEST, accountIds });
|
||||||
|
return api(getState)
|
||||||
|
.patch('/api/pleroma/admin/users/suggest', { nicknames })
|
||||||
|
.then(({ data: { users } }) => {
|
||||||
|
dispatch({ type: ADMIN_USERS_SUGGEST_SUCCESS, users, accountIds });
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch({ type: ADMIN_USERS_SUGGEST_FAIL, error, accountIds });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unsuggestUsers(accountIds) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||||
|
dispatch({ type: ADMIN_USERS_UNSUGGEST_REQUEST, accountIds });
|
||||||
|
return api(getState)
|
||||||
|
.patch('/api/pleroma/admin/users/unsuggest', { nicknames })
|
||||||
|
.then(({ data: { users } }) => {
|
||||||
|
dispatch({ type: ADMIN_USERS_UNSUGGEST_SUCCESS, users, accountIds });
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch({ type: ADMIN_USERS_UNSUGGEST_FAIL, error, accountIds });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { defineMessages } from 'react-intl';
|
import { defineMessages } from 'react-intl';
|
||||||
import api from '../api';
|
|
||||||
import { importFetchedAccount, importFetchedAccounts } from './importer';
|
|
||||||
import { showAlertForError } from './alerts';
|
|
||||||
import snackbar from './snackbar';
|
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
|
import { showAlertForError } from './alerts';
|
||||||
|
import { importFetchedAccount, importFetchedAccounts } from './importer';
|
||||||
import { ME_PATCH_SUCCESS } from './me';
|
import { ME_PATCH_SUCCESS } from './me';
|
||||||
|
import snackbar from './snackbar';
|
||||||
|
|
||||||
export const ALIASES_SUGGESTIONS_CHANGE = 'ALIASES_SUGGESTIONS_CHANGE';
|
export const ALIASES_SUGGESTIONS_CHANGE = 'ALIASES_SUGGESTIONS_CHANGE';
|
||||||
export const ALIASES_SUGGESTIONS_READY = 'ALIASES_SUGGESTIONS_READY';
|
export const ALIASES_SUGGESTIONS_READY = 'ALIASES_SUGGESTIONS_READY';
|
||||||
|
|
|
@ -8,18 +8,22 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { defineMessages } from 'react-intl';
|
import { defineMessages } from 'react-intl';
|
||||||
import api, { baseClient } from '../api';
|
|
||||||
import { importFetchedAccount } from './importer';
|
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
|
||||||
import { createAccount } from 'soapbox/actions/accounts';
|
import { createAccount } from 'soapbox/actions/accounts';
|
||||||
import { fetchMeSuccess, fetchMeFail } from 'soapbox/actions/me';
|
|
||||||
import { getLoggedInAccount, parseBaseURL } from 'soapbox/utils/auth';
|
|
||||||
import { createApp } from 'soapbox/actions/apps';
|
import { createApp } from 'soapbox/actions/apps';
|
||||||
|
import { fetchMeSuccess, fetchMeFail } from 'soapbox/actions/me';
|
||||||
import { obtainOAuthToken, revokeOAuthToken } from 'soapbox/actions/oauth';
|
import { obtainOAuthToken, revokeOAuthToken } from 'soapbox/actions/oauth';
|
||||||
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
|
import KVStore from 'soapbox/storage/kv_store';
|
||||||
|
import { getLoggedInAccount, parseBaseURL } from 'soapbox/utils/auth';
|
||||||
import sourceCode from 'soapbox/utils/code';
|
import sourceCode from 'soapbox/utils/code';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
import { isStandalone } from 'soapbox/utils/state';
|
import { isStandalone } from 'soapbox/utils/state';
|
||||||
|
|
||||||
|
import api, { baseClient } from '../api';
|
||||||
|
|
||||||
|
import { importFetchedAccount } from './importer';
|
||||||
|
|
||||||
export const SWITCH_ACCOUNT = 'SWITCH_ACCOUNT';
|
export const SWITCH_ACCOUNT = 'SWITCH_ACCOUNT';
|
||||||
|
|
||||||
export const AUTH_APP_CREATED = 'AUTH_APP_CREATED';
|
export const AUTH_APP_CREATED = 'AUTH_APP_CREATED';
|
||||||
|
@ -31,6 +35,10 @@ export const VERIFY_CREDENTIALS_REQUEST = 'VERIFY_CREDENTIALS_REQUEST';
|
||||||
export const VERIFY_CREDENTIALS_SUCCESS = 'VERIFY_CREDENTIALS_SUCCESS';
|
export const VERIFY_CREDENTIALS_SUCCESS = 'VERIFY_CREDENTIALS_SUCCESS';
|
||||||
export const VERIFY_CREDENTIALS_FAIL = 'VERIFY_CREDENTIALS_FAIL';
|
export const VERIFY_CREDENTIALS_FAIL = 'VERIFY_CREDENTIALS_FAIL';
|
||||||
|
|
||||||
|
export const AUTH_ACCOUNT_REMEMBER_REQUEST = 'AUTH_ACCOUNT_REMEMBER_REQUEST';
|
||||||
|
export const AUTH_ACCOUNT_REMEMBER_SUCCESS = 'AUTH_ACCOUNT_REMEMBER_SUCCESS';
|
||||||
|
export const AUTH_ACCOUNT_REMEMBER_FAIL = 'AUTH_ACCOUNT_REMEMBER_FAIL';
|
||||||
|
|
||||||
export const messages = defineMessages({
|
export const messages = defineMessages({
|
||||||
loggedOut: { id: 'auth.logged_out', defaultMessage: 'Logged out.' },
|
loggedOut: { id: 'auth.logged_out', defaultMessage: 'Logged out.' },
|
||||||
invalidCredentials: { id: 'auth.invalid_credentials', defaultMessage: 'Wrong username or password' },
|
invalidCredentials: { id: 'auth.invalid_credentials', defaultMessage: 'Wrong username or password' },
|
||||||
|
@ -135,6 +143,7 @@ export function otpVerify(code, mfa_token) {
|
||||||
code: code,
|
code: code,
|
||||||
challenge_type: 'totp',
|
challenge_type: 'totp',
|
||||||
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
||||||
|
scope: getScopes(getState()),
|
||||||
}).then(({ data: token }) => dispatch(authLoggedIn(token)));
|
}).then(({ data: token }) => dispatch(authLoggedIn(token)));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -157,9 +166,31 @@ export function verifyCredentials(token, accountUrl) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function rememberAuthAccount(accountUrl) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch({ type: AUTH_ACCOUNT_REMEMBER_REQUEST, accountUrl });
|
||||||
|
return KVStore.getItemOrError(`authAccount:${accountUrl}`).then(account => {
|
||||||
|
dispatch(importFetchedAccount(account));
|
||||||
|
dispatch({ type: AUTH_ACCOUNT_REMEMBER_SUCCESS, account, accountUrl });
|
||||||
|
if (account.id === getState().get('me')) dispatch(fetchMeSuccess(account));
|
||||||
|
return account;
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch({ type: AUTH_ACCOUNT_REMEMBER_FAIL, error, accountUrl, skipAlert: true });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadCredentials(token, accountUrl) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
return dispatch(rememberAuthAccount(accountUrl)).finally(() => {
|
||||||
|
return dispatch(verifyCredentials(token, accountUrl));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function logIn(intl, username, password) {
|
export function logIn(intl, username, password) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
return dispatch(createAppAndToken()).then(() => {
|
return dispatch(createAuthApp()).then(() => {
|
||||||
return dispatch(createUserToken(username, password));
|
return dispatch(createUserToken(username, password));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (error.response.data.error === 'mfa_required') {
|
if (error.response.data.error === 'mfa_required') {
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import api, { getLinks } from '../api';
|
|
||||||
import { fetchRelationships } from './accounts';
|
|
||||||
import { importFetchedAccounts } from './importer';
|
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
import { getNextLinkName } from 'soapbox/utils/quirks';
|
import { getNextLinkName } from 'soapbox/utils/quirks';
|
||||||
|
|
||||||
|
import api, { getLinks } from '../api';
|
||||||
|
|
||||||
|
import { fetchRelationships } from './accounts';
|
||||||
|
import { importFetchedAccounts } from './importer';
|
||||||
|
|
||||||
export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST';
|
export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST';
|
||||||
export const BLOCKS_FETCH_SUCCESS = 'BLOCKS_FETCH_SUCCESS';
|
export const BLOCKS_FETCH_SUCCESS = 'BLOCKS_FETCH_SUCCESS';
|
||||||
export const BLOCKS_FETCH_FAIL = 'BLOCKS_FETCH_FAIL';
|
export const BLOCKS_FETCH_FAIL = 'BLOCKS_FETCH_FAIL';
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import api, { getLinks } from '../api';
|
import api, { getLinks } from '../api';
|
||||||
|
|
||||||
import { importFetchedStatuses } from './importer';
|
import { importFetchedStatuses } from './importer';
|
||||||
|
|
||||||
export const BOOKMARKED_STATUSES_FETCH_REQUEST = 'BOOKMARKED_STATUSES_FETCH_REQUEST';
|
export const BOOKMARKED_STATUSES_FETCH_REQUEST = 'BOOKMARKED_STATUSES_FETCH_REQUEST';
|
||||||
|
@ -9,15 +10,17 @@ export const BOOKMARKED_STATUSES_EXPAND_REQUEST = 'BOOKMARKED_STATUSES_EXPAND_RE
|
||||||
export const BOOKMARKED_STATUSES_EXPAND_SUCCESS = 'BOOKMARKED_STATUSES_EXPAND_SUCCESS';
|
export const BOOKMARKED_STATUSES_EXPAND_SUCCESS = 'BOOKMARKED_STATUSES_EXPAND_SUCCESS';
|
||||||
export const BOOKMARKED_STATUSES_EXPAND_FAIL = 'BOOKMARKED_STATUSES_EXPAND_FAIL';
|
export const BOOKMARKED_STATUSES_EXPAND_FAIL = 'BOOKMARKED_STATUSES_EXPAND_FAIL';
|
||||||
|
|
||||||
|
const noOp = () => new Promise(f => f());
|
||||||
|
|
||||||
export function fetchBookmarkedStatuses() {
|
export function fetchBookmarkedStatuses() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
if (getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) {
|
if (getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) {
|
||||||
return;
|
return dispatch(noOp);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(fetchBookmarkedStatusesRequest());
|
dispatch(fetchBookmarkedStatusesRequest());
|
||||||
|
|
||||||
api(getState).get('/api/v1/bookmarks').then(response => {
|
return api(getState).get('/api/v1/bookmarks').then(response => {
|
||||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
dispatch(importFetchedStatuses(response.data));
|
dispatch(importFetchedStatuses(response.data));
|
||||||
dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
|
dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
|
||||||
|
@ -53,12 +56,12 @@ export function expandBookmarkedStatuses() {
|
||||||
const url = getState().getIn(['status_lists', 'bookmarks', 'next'], null);
|
const url = getState().getIn(['status_lists', 'bookmarks', 'next'], null);
|
||||||
|
|
||||||
if (url === null || getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) {
|
if (url === null || getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) {
|
||||||
return;
|
return dispatch(noOp);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(expandBookmarkedStatusesRequest());
|
dispatch(expandBookmarkedStatusesRequest());
|
||||||
|
|
||||||
api(getState).get(url).then(response => {
|
return api(getState).get(url).then(response => {
|
||||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
dispatch(importFetchedStatuses(response.data));
|
dispatch(importFetchedStatuses(response.data));
|
||||||
dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
|
dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
import api from '../api';
|
|
||||||
import { getSettings, changeSetting } from 'soapbox/actions/settings';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
import { Map as ImmutableMap } from 'immutable';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
import { getSettings, changeSetting } from 'soapbox/actions/settings';
|
||||||
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
|
||||||
|
import api, { getLinks } from '../api';
|
||||||
|
|
||||||
export const CHATS_FETCH_REQUEST = 'CHATS_FETCH_REQUEST';
|
export const CHATS_FETCH_REQUEST = 'CHATS_FETCH_REQUEST';
|
||||||
export const CHATS_FETCH_SUCCESS = 'CHATS_FETCH_SUCCESS';
|
export const CHATS_FETCH_SUCCESS = 'CHATS_FETCH_SUCCESS';
|
||||||
export const CHATS_FETCH_FAIL = 'CHATS_FETCH_FAIL';
|
export const CHATS_FETCH_FAIL = 'CHATS_FETCH_FAIL';
|
||||||
|
|
||||||
|
export const CHATS_EXPAND_REQUEST = 'CHATS_EXPAND_REQUEST';
|
||||||
|
export const CHATS_EXPAND_SUCCESS = 'CHATS_EXPAND_SUCCESS';
|
||||||
|
export const CHATS_EXPAND_FAIL = 'CHATS_EXPAND_FAIL';
|
||||||
|
|
||||||
export const CHAT_MESSAGES_FETCH_REQUEST = 'CHAT_MESSAGES_FETCH_REQUEST';
|
export const CHAT_MESSAGES_FETCH_REQUEST = 'CHAT_MESSAGES_FETCH_REQUEST';
|
||||||
export const CHAT_MESSAGES_FETCH_SUCCESS = 'CHAT_MESSAGES_FETCH_SUCCESS';
|
export const CHAT_MESSAGES_FETCH_SUCCESS = 'CHAT_MESSAGES_FETCH_SUCCESS';
|
||||||
export const CHAT_MESSAGES_FETCH_FAIL = 'CHAT_MESSAGES_FETCH_FAIL';
|
export const CHAT_MESSAGES_FETCH_FAIL = 'CHAT_MESSAGES_FETCH_FAIL';
|
||||||
|
@ -27,14 +34,61 @@ export const CHAT_MESSAGE_DELETE_REQUEST = 'CHAT_MESSAGE_DELETE_REQUEST';
|
||||||
export const CHAT_MESSAGE_DELETE_SUCCESS = 'CHAT_MESSAGE_DELETE_SUCCESS';
|
export const CHAT_MESSAGE_DELETE_SUCCESS = 'CHAT_MESSAGE_DELETE_SUCCESS';
|
||||||
export const CHAT_MESSAGE_DELETE_FAIL = 'CHAT_MESSAGE_DELETE_FAIL';
|
export const CHAT_MESSAGE_DELETE_FAIL = 'CHAT_MESSAGE_DELETE_FAIL';
|
||||||
|
|
||||||
export function fetchChats() {
|
export function fetchChatsV1() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) =>
|
||||||
dispatch({ type: CHATS_FETCH_REQUEST });
|
api(getState).get('/api/v1/pleroma/chats').then((response) => {
|
||||||
return api(getState).get('/api/v1/pleroma/chats').then(({ data }) => {
|
dispatch({ type: CHATS_FETCH_SUCCESS, chats: response.data });
|
||||||
dispatch({ type: CHATS_FETCH_SUCCESS, chats: data });
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: CHATS_FETCH_FAIL, error });
|
dispatch({ type: CHATS_FETCH_FAIL, error });
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchChatsV2() {
|
||||||
|
return (dispatch, getState) =>
|
||||||
|
api(getState).get('/api/v2/pleroma/chats').then((response) => {
|
||||||
|
let next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
|
|
||||||
|
if (!next && response.data.length) {
|
||||||
|
next = { uri: `/api/v2/pleroma/chats?max_id=${response.data[response.data.length - 1].id}&offset=0` };
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({ type: CHATS_FETCH_SUCCESS, chats: response.data, next: next ? next.uri : null });
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch({ type: CHATS_FETCH_FAIL, error });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchChats() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const instance = state.get('instance');
|
||||||
|
const features = getFeatures(instance);
|
||||||
|
|
||||||
|
dispatch({ type: CHATS_FETCH_REQUEST });
|
||||||
|
if (features.chatsV2) {
|
||||||
|
dispatch(fetchChatsV2());
|
||||||
|
} else {
|
||||||
|
dispatch(fetchChatsV1());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function expandChats() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const url = getState().getIn(['chats', 'next']);
|
||||||
|
|
||||||
|
if (url === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({ type: CHATS_EXPAND_REQUEST });
|
||||||
|
api(getState).get(url).then(response => {
|
||||||
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
|
|
||||||
|
dispatch({ type: CHATS_EXPAND_SUCCESS, chats: response.data, next: next ? next.uri : null });
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch({ type: CHATS_EXPAND_FAIL, error });
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +194,7 @@ export function startChat(accountId) {
|
||||||
|
|
||||||
export function markChatRead(chatId, lastReadId) {
|
export function markChatRead(chatId, lastReadId) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const chat = getState().getIn(['chats', chatId]);
|
const chat = getState().getIn(['chats', 'items', chatId]);
|
||||||
if (!lastReadId) lastReadId = chat.get('last_message');
|
if (!lastReadId) lastReadId = chat.get('last_message');
|
||||||
|
|
||||||
if (chat.get('unread') < 1) return;
|
if (chat.get('unread') < 1) return;
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
import api from '../api';
|
|
||||||
import { CancelToken, isCancel } from 'axios';
|
import { CancelToken, isCancel } from 'axios';
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
|
import { defineMessages } from 'react-intl';
|
||||||
|
|
||||||
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
|
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
|
||||||
import { tagHistory } from '../settings';
|
import { tagHistory } from '../settings';
|
||||||
import { useEmoji } from './emojis';
|
|
||||||
import resizeImage from '../utils/resize_image';
|
import resizeImage from '../utils/resize_image';
|
||||||
import { importFetchedAccounts } from './importer';
|
|
||||||
import { showAlert, showAlertForError } from './alerts';
|
import { showAlert, showAlertForError } from './alerts';
|
||||||
import { defineMessages } from 'react-intl';
|
import { useEmoji } from './emojis';
|
||||||
|
import { importFetchedAccounts } from './importer';
|
||||||
|
import { uploadMedia, fetchMedia, updateMedia } from './media';
|
||||||
import { openModal, closeModal } from './modal';
|
import { openModal, closeModal } from './modal';
|
||||||
import { getSettings } from './settings';
|
import { getSettings } from './settings';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
|
||||||
import { uploadMedia, fetchMedia, updateMedia } from './media';
|
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
|
||||||
import { createStatus } from './statuses';
|
import { createStatus } from './statuses';
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
|
||||||
|
|
||||||
let cancelFetchComposeSuggestionsAccounts;
|
let cancelFetchComposeSuggestionsAccounts;
|
||||||
|
|
||||||
|
@ -24,6 +27,8 @@ export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
|
||||||
export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL';
|
export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL';
|
||||||
export const COMPOSE_REPLY = 'COMPOSE_REPLY';
|
export const COMPOSE_REPLY = 'COMPOSE_REPLY';
|
||||||
export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL';
|
export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL';
|
||||||
|
export const COMPOSE_QUOTE = 'COMPOSE_QUOTE';
|
||||||
|
export const COMPOSE_QUOTE_CANCEL = 'COMPOSE_QUOTE_CANCEL';
|
||||||
export const COMPOSE_DIRECT = 'COMPOSE_DIRECT';
|
export const COMPOSE_DIRECT = 'COMPOSE_DIRECT';
|
||||||
export const COMPOSE_MENTION = 'COMPOSE_MENTION';
|
export const COMPOSE_MENTION = 'COMPOSE_MENTION';
|
||||||
export const COMPOSE_RESET = 'COMPOSE_RESET';
|
export const COMPOSE_RESET = 'COMPOSE_RESET';
|
||||||
|
@ -68,6 +73,9 @@ export const COMPOSE_SCHEDULE_ADD = 'COMPOSE_SCHEDULE_ADD';
|
||||||
export const COMPOSE_SCHEDULE_SET = 'COMPOSE_SCHEDULE_SET';
|
export const COMPOSE_SCHEDULE_SET = 'COMPOSE_SCHEDULE_SET';
|
||||||
export const COMPOSE_SCHEDULE_REMOVE = 'COMPOSE_SCHEDULE_REMOVE';
|
export const COMPOSE_SCHEDULE_REMOVE = 'COMPOSE_SCHEDULE_REMOVE';
|
||||||
|
|
||||||
|
export const COMPOSE_ADD_TO_MENTIONS = 'COMPOSE_ADD_TO_MENTIONS';
|
||||||
|
export const COMPOSE_REMOVE_FROM_MENTIONS = 'COMPOSE_REMOVE_FROM_MENTIONS';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
||||||
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
|
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
|
||||||
|
@ -93,10 +101,14 @@ export function changeCompose(text) {
|
||||||
export function replyCompose(status, routerHistory) {
|
export function replyCompose(status, routerHistory) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
const instance = state.get('instance');
|
||||||
|
const { explicitAddressing } = getFeatures(instance);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: COMPOSE_REPLY,
|
type: COMPOSE_REPLY,
|
||||||
status: status,
|
status: status,
|
||||||
account: state.getIn(['accounts', state.get('me')]),
|
account: state.getIn(['accounts', state.get('me')]),
|
||||||
|
explicitAddressing,
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(openModal('COMPOSE'));
|
dispatch(openModal('COMPOSE'));
|
||||||
|
@ -109,6 +121,29 @@ export function cancelReplyCompose() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function quoteCompose(status, routerHistory) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const instance = state.get('instance');
|
||||||
|
const { explicitAddressing } = getFeatures(instance);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: COMPOSE_QUOTE,
|
||||||
|
status: status,
|
||||||
|
account: state.getIn(['accounts', state.get('me')]),
|
||||||
|
explicitAddressing,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(openModal('COMPOSE'));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cancelQuoteCompose() {
|
||||||
|
return {
|
||||||
|
type: COMPOSE_QUOTE_CANCEL,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function resetCompose() {
|
export function resetCompose() {
|
||||||
return {
|
return {
|
||||||
type: COMPOSE_RESET,
|
type: COMPOSE_RESET,
|
||||||
|
@ -155,7 +190,7 @@ export function handleComposeSubmit(dispatch, getState, data, status) {
|
||||||
|
|
||||||
dispatch(insertIntoTagHistory(data.tags || [], status));
|
dispatch(insertIntoTagHistory(data.tags || [], status));
|
||||||
dispatch(submitComposeSuccess({ ...data }));
|
dispatch(submitComposeSuccess({ ...data }));
|
||||||
dispatch(snackbar.show('post', messages.success));
|
dispatch(snackbar.success(messages.success));
|
||||||
}
|
}
|
||||||
|
|
||||||
const needsDescriptions = state => {
|
const needsDescriptions = state => {
|
||||||
|
@ -183,6 +218,7 @@ export function submitCompose(routerHistory, force = false) {
|
||||||
|
|
||||||
const status = state.getIn(['compose', 'text'], '');
|
const status = state.getIn(['compose', 'text'], '');
|
||||||
const media = state.getIn(['compose', 'media_attachments']);
|
const media = state.getIn(['compose', 'media_attachments']);
|
||||||
|
let to = state.getIn(['compose', 'to'], null);
|
||||||
|
|
||||||
if (!validateSchedule(state)) {
|
if (!validateSchedule(state)) {
|
||||||
dispatch(snackbar.error(messages.scheduleError));
|
dispatch(snackbar.error(messages.scheduleError));
|
||||||
|
@ -200,6 +236,13 @@ export function submitCompose(routerHistory, force = false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (to && status) {
|
||||||
|
const mentions = status.match(/(?:^|\s|\.)@([a-z0-9_]+(?:@[a-z0-9\.\-]+)?)/gi); // not a perfect regex
|
||||||
|
|
||||||
|
if (mentions)
|
||||||
|
to = to.union(mentions.map(mention => mention.trim().slice(1)));
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(submitComposeRequest());
|
dispatch(submitComposeRequest());
|
||||||
dispatch(closeModal());
|
dispatch(closeModal());
|
||||||
|
|
||||||
|
@ -208,6 +251,7 @@ export function submitCompose(routerHistory, force = false) {
|
||||||
const params = {
|
const params = {
|
||||||
status,
|
status,
|
||||||
in_reply_to_id: state.getIn(['compose', 'in_reply_to'], null),
|
in_reply_to_id: state.getIn(['compose', 'in_reply_to'], null),
|
||||||
|
quote_id: state.getIn(['compose', 'quote'], null),
|
||||||
media_ids: media.map(item => item.get('id')),
|
media_ids: media.map(item => item.get('id')),
|
||||||
sensitive: state.getIn(['compose', 'sensitive']),
|
sensitive: state.getIn(['compose', 'sensitive']),
|
||||||
spoiler_text: state.getIn(['compose', 'spoiler_text'], ''),
|
spoiler_text: state.getIn(['compose', 'spoiler_text'], ''),
|
||||||
|
@ -215,6 +259,7 @@ export function submitCompose(routerHistory, force = false) {
|
||||||
content_type: state.getIn(['compose', 'content_type']),
|
content_type: state.getIn(['compose', 'content_type']),
|
||||||
poll: state.getIn(['compose', 'poll'], null),
|
poll: state.getIn(['compose', 'poll'], null),
|
||||||
scheduled_at: state.getIn(['compose', 'schedule'], null),
|
scheduled_at: state.getIn(['compose', 'schedule'], null),
|
||||||
|
to,
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(createStatus(params, idempotencyKey)).then(function(data) {
|
dispatch(createStatus(params, idempotencyKey)).then(function(data) {
|
||||||
|
@ -643,3 +688,27 @@ export function openComposeWithText(text = '') {
|
||||||
dispatch(changeCompose(text));
|
dispatch(changeCompose(text));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function addToMentions(accountId) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const acct = state.getIn(['accounts', accountId, 'acct']);
|
||||||
|
|
||||||
|
return dispatch({
|
||||||
|
type: COMPOSE_ADD_TO_MENTIONS,
|
||||||
|
account: acct,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeFromMentions(accountId) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const acct = state.getIn(['accounts', accountId, 'acct']);
|
||||||
|
|
||||||
|
return dispatch({
|
||||||
|
type: COMPOSE_REMOVE_FROM_MENTIONS,
|
||||||
|
account: acct,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
import api, { getLinks } from '../api';
|
import api, { getLinks } from '../api';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
importFetchedAccounts,
|
importFetchedAccounts,
|
||||||
importFetchedStatuses,
|
importFetchedStatuses,
|
||||||
importFetchedStatus,
|
importFetchedStatus,
|
||||||
} from './importer';
|
} from './importer';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
|
||||||
|
|
||||||
export const CONVERSATIONS_MOUNT = 'CONVERSATIONS_MOUNT';
|
export const CONVERSATIONS_MOUNT = 'CONVERSATIONS_MOUNT';
|
||||||
export const CONVERSATIONS_UNMOUNT = 'CONVERSATIONS_UNMOUNT';
|
export const CONVERSATIONS_UNMOUNT = 'CONVERSATIONS_UNMOUNT';
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
|
import { fetchRelationships } from './accounts';
|
||||||
|
import { importFetchedAccounts } from './importer';
|
||||||
|
|
||||||
|
export const DIRECTORY_FETCH_REQUEST = 'DIRECTORY_FETCH_REQUEST';
|
||||||
|
export const DIRECTORY_FETCH_SUCCESS = 'DIRECTORY_FETCH_SUCCESS';
|
||||||
|
export const DIRECTORY_FETCH_FAIL = 'DIRECTORY_FETCH_FAIL';
|
||||||
|
|
||||||
|
export const DIRECTORY_EXPAND_REQUEST = 'DIRECTORY_EXPAND_REQUEST';
|
||||||
|
export const DIRECTORY_EXPAND_SUCCESS = 'DIRECTORY_EXPAND_SUCCESS';
|
||||||
|
export const DIRECTORY_EXPAND_FAIL = 'DIRECTORY_EXPAND_FAIL';
|
||||||
|
|
||||||
|
export const fetchDirectory = params => (dispatch, getState) => {
|
||||||
|
dispatch(fetchDirectoryRequest());
|
||||||
|
|
||||||
|
api(getState).get('/api/v1/directory', { params: { ...params, limit: 20 } }).then(({ data }) => {
|
||||||
|
dispatch(importFetchedAccounts(data));
|
||||||
|
dispatch(fetchDirectorySuccess(data));
|
||||||
|
dispatch(fetchRelationships(data.map(x => x.id)));
|
||||||
|
}).catch(error => dispatch(fetchDirectoryFail(error)));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchDirectoryRequest = () => ({
|
||||||
|
type: DIRECTORY_FETCH_REQUEST,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchDirectorySuccess = accounts => ({
|
||||||
|
type: DIRECTORY_FETCH_SUCCESS,
|
||||||
|
accounts,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchDirectoryFail = error => ({
|
||||||
|
type: DIRECTORY_FETCH_FAIL,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const expandDirectory = params => (dispatch, getState) => {
|
||||||
|
dispatch(expandDirectoryRequest());
|
||||||
|
|
||||||
|
const loadedItems = getState().getIn(['user_lists', 'directory', 'items']).size;
|
||||||
|
|
||||||
|
api(getState).get('/api/v1/directory', { params: { ...params, offset: loadedItems, limit: 20 } }).then(({ data }) => {
|
||||||
|
dispatch(importFetchedAccounts(data));
|
||||||
|
dispatch(expandDirectorySuccess(data));
|
||||||
|
dispatch(fetchRelationships(data.map(x => x.id)));
|
||||||
|
}).catch(error => dispatch(expandDirectoryFail(error)));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const expandDirectoryRequest = () => ({
|
||||||
|
type: DIRECTORY_EXPAND_REQUEST,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const expandDirectorySuccess = accounts => ({
|
||||||
|
type: DIRECTORY_EXPAND_SUCCESS,
|
||||||
|
accounts,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const expandDirectoryFail = error => ({
|
||||||
|
type: DIRECTORY_EXPAND_FAIL,
|
||||||
|
error,
|
||||||
|
});
|
|
@ -1,6 +1,7 @@
|
||||||
import api, { getLinks } from '../api';
|
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
import api, { getLinks } from '../api';
|
||||||
|
|
||||||
export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST';
|
export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST';
|
||||||
export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS';
|
export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS';
|
||||||
export const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL';
|
export const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL';
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
import { List as ImmutableList } from 'immutable';
|
||||||
|
|
||||||
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
||||||
import { favourite, unfavourite } from './interactions';
|
import { favourite, unfavourite } from './interactions';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
|
||||||
import { List as ImmutableList } from 'immutable';
|
|
||||||
|
|
||||||
export const EMOJI_REACT_REQUEST = 'EMOJI_REACT_REQUEST';
|
export const EMOJI_REACT_REQUEST = 'EMOJI_REACT_REQUEST';
|
||||||
export const EMOJI_REACT_SUCCESS = 'EMOJI_REACT_SUCCESS';
|
export const EMOJI_REACT_SUCCESS = 'EMOJI_REACT_SUCCESS';
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { defineMessages } from 'react-intl';
|
import { defineMessages } from 'react-intl';
|
||||||
|
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
|
|
||||||
import api, { getLinks } from '../api';
|
import api, { getLinks } from '../api';
|
||||||
|
|
||||||
export const EXPORT_FOLLOWS_REQUEST = 'EXPORT_FOLLOWS_REQUEST';
|
export const EXPORT_FOLLOWS_REQUEST = 'EXPORT_FOLLOWS_REQUEST';
|
||||||
|
|
|
@ -6,21 +6,23 @@
|
||||||
* @see module:soapbox/actions/oauth
|
* @see module:soapbox/actions/oauth
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { baseClient } from '../api';
|
|
||||||
import { createApp } from 'soapbox/actions/apps';
|
|
||||||
import { obtainOAuthToken } from 'soapbox/actions/oauth';
|
|
||||||
import { authLoggedIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
|
||||||
import { parseBaseURL } from 'soapbox/utils/auth';
|
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
|
||||||
import sourceCode from 'soapbox/utils/code';
|
|
||||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||||
|
|
||||||
|
import { createApp } from 'soapbox/actions/apps';
|
||||||
|
import { authLoggedIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth';
|
||||||
|
import { obtainOAuthToken } from 'soapbox/actions/oauth';
|
||||||
|
import { parseBaseURL } from 'soapbox/utils/auth';
|
||||||
|
import sourceCode from 'soapbox/utils/code';
|
||||||
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
|
||||||
|
import { baseClient } from '../api';
|
||||||
|
|
||||||
const fetchExternalInstance = baseURL => {
|
const fetchExternalInstance = baseURL => {
|
||||||
return baseClient(null, baseURL)
|
return baseClient(null, baseURL)
|
||||||
.get('/api/v1/instance')
|
.get('/api/v1/instance')
|
||||||
.then(({ data: instance }) => fromJS(instance))
|
.then(({ data: instance }) => fromJS(instance))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.response.status === 401) {
|
if (error.response && error.response.status === 401) {
|
||||||
// Authenticated fetch is enabled.
|
// Authenticated fetch is enabled.
|
||||||
// Continue with a limited featureset.
|
// Continue with a limited featureset.
|
||||||
return ImmutableMap({ version: '0.0.0' });
|
return ImmutableMap({ version: '0.0.0' });
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import api, { getLinks } from '../api';
|
|
||||||
import { importFetchedStatuses } from './importer';
|
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
import api, { getLinks } from '../api';
|
||||||
|
|
||||||
|
import { importFetchedStatuses } from './importer';
|
||||||
|
|
||||||
export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST';
|
export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST';
|
||||||
export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS';
|
export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS';
|
||||||
export const FAVOURITED_STATUSES_FETCH_FAIL = 'FAVOURITED_STATUSES_FETCH_FAIL';
|
export const FAVOURITED_STATUSES_FETCH_FAIL = 'FAVOURITED_STATUSES_FETCH_FAIL';
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { defineMessages } from 'react-intl';
|
import { defineMessages } from 'react-intl';
|
||||||
import api from '../api';
|
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
export const FILTERS_FETCH_REQUEST = 'FILTERS_FETCH_REQUEST';
|
export const FILTERS_FETCH_REQUEST = 'FILTERS_FETCH_REQUEST';
|
||||||
export const FILTERS_FETCH_SUCCESS = 'FILTERS_FETCH_SUCCESS';
|
export const FILTERS_FETCH_SUCCESS = 'FILTERS_FETCH_SUCCESS';
|
||||||
export const FILTERS_FETCH_FAIL = 'FILTERS_FETCH_FAIL';
|
export const FILTERS_FETCH_FAIL = 'FILTERS_FETCH_FAIL';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import api from '../api';
|
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
export const GROUP_CREATE_REQUEST = 'GROUP_CREATE_REQUEST';
|
export const GROUP_CREATE_REQUEST = 'GROUP_CREATE_REQUEST';
|
||||||
export const GROUP_CREATE_SUCCESS = 'GROUP_CREATE_SUCCESS';
|
export const GROUP_CREATE_SUCCESS = 'GROUP_CREATE_SUCCESS';
|
||||||
export const GROUP_CREATE_FAIL = 'GROUP_CREATE_FAIL';
|
export const GROUP_CREATE_FAIL = 'GROUP_CREATE_FAIL';
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import api, { getLinks } from '../api';
|
|
||||||
import { importFetchedAccounts } from './importer';
|
|
||||||
import { fetchRelationships } from './accounts';
|
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
import api, { getLinks } from '../api';
|
||||||
|
|
||||||
|
import { fetchRelationships } from './accounts';
|
||||||
|
import { importFetchedAccounts } from './importer';
|
||||||
|
|
||||||
export const GROUP_FETCH_REQUEST = 'GROUP_FETCH_REQUEST';
|
export const GROUP_FETCH_REQUEST = 'GROUP_FETCH_REQUEST';
|
||||||
export const GROUP_FETCH_SUCCESS = 'GROUP_FETCH_SUCCESS';
|
export const GROUP_FETCH_SUCCESS = 'GROUP_FETCH_SUCCESS';
|
||||||
export const GROUP_FETCH_FAIL = 'GROUP_FETCH_FAIL';
|
export const GROUP_FETCH_FAIL = 'GROUP_FETCH_FAIL';
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { defineMessages } from 'react-intl';
|
import { defineMessages } from 'react-intl';
|
||||||
import api from '../api';
|
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
export const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST';
|
export const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST';
|
||||||
export const IMPORT_FOLLOWS_SUCCESS = 'IMPORT_FOLLOWS_SUCCESS';
|
export const IMPORT_FOLLOWS_SUCCESS = 'IMPORT_FOLLOWS_SUCCESS';
|
||||||
export const IMPORT_FOLLOWS_FAIL = 'IMPORT_FOLLOWS_FAIL';
|
export const IMPORT_FOLLOWS_FAIL = 'IMPORT_FOLLOWS_FAIL';
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { getSettings } from '../settings';
|
import { getSettings } from '../settings';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
normalizeAccount,
|
normalizeAccount,
|
||||||
normalizeStatus,
|
normalizeStatus,
|
||||||
|
@ -12,12 +13,6 @@ export const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
||||||
export const POLLS_IMPORT = 'POLLS_IMPORT';
|
export const POLLS_IMPORT = 'POLLS_IMPORT';
|
||||||
export const ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP = 'ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP';
|
export const ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP = 'ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP';
|
||||||
|
|
||||||
function pushUnique(array, object) {
|
|
||||||
if (array.every(element => element.id !== object.id)) {
|
|
||||||
array.push(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function importAccount(account) {
|
export function importAccount(account) {
|
||||||
return { type: ACCOUNT_IMPORT, account };
|
return { type: ACCOUNT_IMPORT, account };
|
||||||
}
|
}
|
||||||
|
@ -48,7 +43,7 @@ export function importFetchedAccounts(accounts) {
|
||||||
function processAccount(account) {
|
function processAccount(account) {
|
||||||
if (!account.id) return;
|
if (!account.id) return;
|
||||||
|
|
||||||
pushUnique(normalAccounts, normalizeAccount(account));
|
normalAccounts.push(normalizeAccount(account));
|
||||||
|
|
||||||
if (account.moved) {
|
if (account.moved) {
|
||||||
processAccount(account.moved);
|
processAccount(account.moved);
|
||||||
|
@ -74,6 +69,15 @@ export function importFetchedStatus(status, idempotencyKey) {
|
||||||
dispatch(importFetchedStatus(status.reblog));
|
dispatch(importFetchedStatus(status.reblog));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fedibird quotes
|
||||||
|
if (status.quote && status.quote.id) {
|
||||||
|
dispatch(importFetchedStatus(status.quote));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.pleroma && status.pleroma.quote && status.pleroma.quote.id) {
|
||||||
|
dispatch(importFetchedStatus(status.pleroma.quote));
|
||||||
|
}
|
||||||
|
|
||||||
if (status.poll && status.poll.id) {
|
if (status.poll && status.poll.id) {
|
||||||
dispatch(importFetchedPoll(status.poll));
|
dispatch(importFetchedPoll(status.poll));
|
||||||
}
|
}
|
||||||
|
@ -112,15 +116,24 @@ export function importFetchedStatuses(statuses) {
|
||||||
const normalOldStatus = getState().getIn(['statuses', status.id]);
|
const normalOldStatus = getState().getIn(['statuses', status.id]);
|
||||||
const expandSpoilers = getSettings(getState()).get('expandSpoilers');
|
const expandSpoilers = getSettings(getState()).get('expandSpoilers');
|
||||||
|
|
||||||
pushUnique(normalStatuses, normalizeStatus(status, normalOldStatus, expandSpoilers));
|
normalStatuses.push(normalizeStatus(status, normalOldStatus, expandSpoilers));
|
||||||
pushUnique(accounts, status.account);
|
accounts.push(status.account);
|
||||||
|
|
||||||
if (status.reblog && status.reblog.id) {
|
if (status.reblog && status.reblog.id) {
|
||||||
processStatus(status.reblog);
|
processStatus(status.reblog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fedibird quotes
|
||||||
|
if (status.quote && status.quote.id) {
|
||||||
|
processStatus(status.quote);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.pleroma && status.pleroma.quote && status.pleroma.quote.id) {
|
||||||
|
processStatus(status.pleroma.quote);
|
||||||
|
}
|
||||||
|
|
||||||
if (status.poll && status.poll.id) {
|
if (status.poll && status.poll.id) {
|
||||||
pushUnique(polls, normalizePoll(status.poll));
|
polls.push(normalizePoll(status.poll));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import escapeTextContentForBrowser from 'escape-html';
|
import escapeTextContentForBrowser from 'escape-html';
|
||||||
|
|
||||||
import emojify from '../../features/emoji/emoji';
|
import emojify from '../../features/emoji/emoji';
|
||||||
import { unescapeHTML } from '../../utils/html';
|
import { unescapeHTML } from '../../utils/html';
|
||||||
|
|
||||||
|
@ -36,7 +37,12 @@ export function normalizeAccount(account) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeStatus(status, normalOldStatus, expandSpoilers) {
|
export function normalizeStatus(status, normalOldStatus, expandSpoilers) {
|
||||||
const normalStatus = { ...status };
|
const normalStatus = { ...status };
|
||||||
|
|
||||||
|
// Copy the pleroma object too, so we can modify our copy
|
||||||
|
if (status.pleroma) {
|
||||||
|
normalStatus.pleroma = { ...status.pleroma };
|
||||||
|
}
|
||||||
|
|
||||||
normalStatus.account = status.account.id;
|
normalStatus.account = status.account.id;
|
||||||
|
|
||||||
|
@ -48,6 +54,18 @@ export function normalizeStatus(status, normalOldStatus, expandSpoilers) {
|
||||||
normalStatus.poll = status.poll.id;
|
normalStatus.poll = status.poll.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status.pleroma && status.pleroma.quote && status.pleroma.quote.id) {
|
||||||
|
// Normalize quote to the top-level, so delete the original for performance
|
||||||
|
normalStatus.quote = status.pleroma.quote.id;
|
||||||
|
delete normalStatus.pleroma.quote;
|
||||||
|
} else if (status.quote && status.quote.id) {
|
||||||
|
// Fedibird compatibility, because why not
|
||||||
|
normalStatus.quote = status.quote.id;
|
||||||
|
} else if (status.quote_id) {
|
||||||
|
// Fedibird: fall back to quote_id
|
||||||
|
normalStatus.quote = status.quote_id;
|
||||||
|
}
|
||||||
|
|
||||||
// Only calculate these values when status first encountered
|
// Only calculate these values when status first encountered
|
||||||
// Otherwise keep the ones already in the reducer
|
// Otherwise keep the ones already in the reducer
|
||||||
if (normalOldStatus) {
|
if (normalOldStatus) {
|
||||||
|
@ -74,8 +92,9 @@ export function normalizePoll(poll) {
|
||||||
|
|
||||||
const emojiMap = makeEmojiMap(normalPoll);
|
const emojiMap = makeEmojiMap(normalPoll);
|
||||||
|
|
||||||
normalPoll.options = poll.options.map(option => ({
|
normalPoll.options = poll.options.map((option, index) => ({
|
||||||
...option,
|
...option,
|
||||||
|
voted: poll.own_votes && poll.own_votes.includes(index),
|
||||||
title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
|
title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -1,62 +1,89 @@
|
||||||
import api from '../api';
|
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
|
||||||
|
import KVStore from 'soapbox/storage/kv_store';
|
||||||
|
import { getAuthUserUrl } from 'soapbox/utils/auth';
|
||||||
import { parseVersion } from 'soapbox/utils/features';
|
import { parseVersion } from 'soapbox/utils/features';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
|
export const INSTANCE_FETCH_REQUEST = 'INSTANCE_FETCH_REQUEST';
|
||||||
export const INSTANCE_FETCH_SUCCESS = 'INSTANCE_FETCH_SUCCESS';
|
export const INSTANCE_FETCH_SUCCESS = 'INSTANCE_FETCH_SUCCESS';
|
||||||
export const INSTANCE_FETCH_FAIL = 'INSTANCE_FETCH_FAIL';
|
export const INSTANCE_FETCH_FAIL = 'INSTANCE_FETCH_FAIL';
|
||||||
|
|
||||||
|
export const INSTANCE_REMEMBER_REQUEST = 'INSTANCE_REMEMBER_REQUEST';
|
||||||
|
export const INSTANCE_REMEMBER_SUCCESS = 'INSTANCE_REMEMBER_SUCCESS';
|
||||||
|
export const INSTANCE_REMEMBER_FAIL = 'INSTANCE_REMEMBER_FAIL';
|
||||||
|
|
||||||
|
export const NODEINFO_FETCH_REQUEST = 'NODEINFO_FETCH_REQUEST';
|
||||||
export const NODEINFO_FETCH_SUCCESS = 'NODEINFO_FETCH_SUCCESS';
|
export const NODEINFO_FETCH_SUCCESS = 'NODEINFO_FETCH_SUCCESS';
|
||||||
export const NODEINFO_FETCH_FAIL = 'NODEINFO_FETCH_FAIL';
|
export const NODEINFO_FETCH_FAIL = 'NODEINFO_FETCH_FAIL';
|
||||||
|
|
||||||
|
const getMeUrl = state => {
|
||||||
|
const me = state.get('me');
|
||||||
|
return state.getIn(['accounts', me, 'url']);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Figure out the appropriate instance to fetch depending on the state
|
||||||
|
export const getHost = state => {
|
||||||
|
const accountUrl = getMeUrl(state) || getAuthUserUrl(state);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new URL(accountUrl).host;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function rememberInstance(host) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch({ type: INSTANCE_REMEMBER_REQUEST, host });
|
||||||
|
return KVStore.getItemOrError(`instance:${host}`).then(instance => {
|
||||||
|
dispatch({ type: INSTANCE_REMEMBER_SUCCESS, host, instance });
|
||||||
|
return instance;
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch({ type: INSTANCE_REMEMBER_FAIL, host, error, skipAlert: true });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// We may need to fetch nodeinfo on Pleroma < 2.1
|
||||||
|
const needsNodeinfo = instance => {
|
||||||
|
const v = parseVersion(get(instance, 'version'));
|
||||||
|
return v.software === 'Pleroma' && !get(instance, ['pleroma', 'metadata']);
|
||||||
|
};
|
||||||
|
|
||||||
export function fetchInstance() {
|
export function fetchInstance() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
return api(getState).get('/api/v1/instance').then(response => {
|
dispatch({ type: INSTANCE_FETCH_REQUEST });
|
||||||
dispatch(importInstance(response.data));
|
return api(getState).get('/api/v1/instance').then(({ data: instance }) => {
|
||||||
const v = parseVersion(get(response.data, 'version'));
|
dispatch({ type: INSTANCE_FETCH_SUCCESS, instance });
|
||||||
if (v.software === 'Pleroma' && !get(response.data, ['pleroma', 'metadata'])) {
|
if (needsNodeinfo(instance)) {
|
||||||
dispatch(fetchNodeinfo()); // Pleroma < 2.1 backwards compatibility
|
dispatch(fetchNodeinfo()); // Pleroma < 2.1 backwards compatibility
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(instanceFail(error));
|
dispatch({ type: INSTANCE_FETCH_FAIL, error, skipAlert: true });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tries to remember the instance from browser storage before fetching it
|
||||||
|
export function loadInstance() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const host = getHost(getState());
|
||||||
|
|
||||||
|
return dispatch(rememberInstance(host)).finally(() => {
|
||||||
|
return dispatch(fetchInstance());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchNodeinfo() {
|
export function fetchNodeinfo() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
api(getState).get('/nodeinfo/2.1.json').then(response => {
|
dispatch({ type: NODEINFO_FETCH_REQUEST });
|
||||||
dispatch(importNodeinfo(response.data));
|
api(getState).get('/nodeinfo/2.1.json').then(({ data: nodeinfo }) => {
|
||||||
|
dispatch({ type: NODEINFO_FETCH_SUCCESS, nodeinfo });
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(nodeinfoFail(error));
|
dispatch({ type: NODEINFO_FETCH_FAIL, error, skipAlert: true });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function importInstance(instance) {
|
|
||||||
return {
|
|
||||||
type: INSTANCE_FETCH_SUCCESS,
|
|
||||||
instance,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function instanceFail(error) {
|
|
||||||
return {
|
|
||||||
type: INSTANCE_FETCH_FAIL,
|
|
||||||
error,
|
|
||||||
skipAlert: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function importNodeinfo(nodeinfo) {
|
|
||||||
return {
|
|
||||||
type: NODEINFO_FETCH_SUCCESS,
|
|
||||||
nodeinfo,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function nodeinfoFail(error) {
|
|
||||||
return {
|
|
||||||
type: NODEINFO_FETCH_FAIL,
|
|
||||||
error,
|
|
||||||
skipAlert: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { defineMessages } from 'react-intl';
|
import { defineMessages } from 'react-intl';
|
||||||
import api from '../api';
|
|
||||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
|
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
||||||
|
|
||||||
export const REBLOG_REQUEST = 'REBLOG_REQUEST';
|
export const REBLOG_REQUEST = 'REBLOG_REQUEST';
|
||||||
export const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
|
export const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
|
||||||
export const REBLOG_FAIL = 'REBLOG_FAIL';
|
export const REBLOG_FAIL = 'REBLOG_FAIL';
|
||||||
|
@ -48,6 +51,10 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST';
|
||||||
export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS';
|
export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS';
|
||||||
export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL';
|
export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL';
|
||||||
|
|
||||||
|
export const REMOTE_INTERACTION_REQUEST = 'REMOTE_INTERACTION_REQUEST';
|
||||||
|
export const REMOTE_INTERACTION_SUCCESS = 'REMOTE_INTERACTION_SUCCESS';
|
||||||
|
export const REMOTE_INTERACTION_FAIL = 'REMOTE_INTERACTION_FAIL';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
bookmarkAdded: { id: 'status.bookmarked', defaultMessage: 'Bookmark added.' },
|
bookmarkAdded: { id: 'status.bookmarked', defaultMessage: 'Bookmark added.' },
|
||||||
bookmarkRemoved: { id: 'status.unbookmarked', defaultMessage: 'Bookmark removed.' },
|
bookmarkRemoved: { id: 'status.unbookmarked', defaultMessage: 'Bookmark removed.' },
|
||||||
|
@ -77,7 +84,6 @@ export function unreblog(status) {
|
||||||
dispatch(unreblogRequest(status));
|
dispatch(unreblogRequest(status));
|
||||||
|
|
||||||
api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => {
|
api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => {
|
||||||
dispatch(importFetchedStatus(response.data));
|
|
||||||
dispatch(unreblogSuccess(status));
|
dispatch(unreblogSuccess(status));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(unreblogFail(status, error));
|
dispatch(unreblogFail(status, error));
|
||||||
|
@ -157,7 +163,6 @@ export function unfavourite(status) {
|
||||||
dispatch(unfavouriteRequest(status));
|
dispatch(unfavouriteRequest(status));
|
||||||
|
|
||||||
api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => {
|
api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => {
|
||||||
dispatch(importFetchedStatus(response.data));
|
|
||||||
dispatch(unfavouriteSuccess(status));
|
dispatch(unfavouriteSuccess(status));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(unfavouriteFail(status, error));
|
dispatch(unfavouriteFail(status, error));
|
||||||
|
@ -477,3 +482,46 @@ export function unpinFail(status, error) {
|
||||||
skipLoading: true,
|
skipLoading: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function remoteInteraction(ap_id, profile) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch(remoteInteractionRequest(ap_id, profile));
|
||||||
|
|
||||||
|
return api(getState).post('/api/v1/pleroma/remote_interaction', { ap_id, profile }).then(({ data }) => {
|
||||||
|
if (data.error) throw new Error(data.error);
|
||||||
|
|
||||||
|
dispatch(remoteInteractionSuccess(ap_id, profile, data.url));
|
||||||
|
|
||||||
|
return data.url;
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(remoteInteractionFail(ap_id, profile, error));
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remoteInteractionRequest(ap_id, profile) {
|
||||||
|
return {
|
||||||
|
type: REMOTE_INTERACTION_REQUEST,
|
||||||
|
ap_id,
|
||||||
|
profile,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remoteInteractionSuccess(ap_id, profile, url) {
|
||||||
|
return {
|
||||||
|
type: REMOTE_INTERACTION_SUCCESS,
|
||||||
|
ap_id,
|
||||||
|
profile,
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remoteInteractionFail(ap_id, profile, error) {
|
||||||
|
return {
|
||||||
|
type: REMOTE_INTERACTION_FAIL,
|
||||||
|
ap_id,
|
||||||
|
profile,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import api from '../api';
|
|
||||||
import { importFetchedAccounts } from './importer';
|
|
||||||
import { showAlertForError } from './alerts';
|
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
|
import { showAlertForError } from './alerts';
|
||||||
|
import { importFetchedAccounts } from './importer';
|
||||||
|
|
||||||
export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST';
|
export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST';
|
||||||
export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS';
|
export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS';
|
||||||
export const LIST_FETCH_FAIL = 'LIST_FETCH_FAIL';
|
export const LIST_FETCH_FAIL = 'LIST_FETCH_FAIL';
|
||||||
|
@ -367,7 +369,7 @@ export const fetchAccountLists = accountId => (dispatch, getState) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchAccountListsRequest = id => ({
|
export const fetchAccountListsRequest = id => ({
|
||||||
type:LIST_ADDER_LISTS_FETCH_REQUEST,
|
type: LIST_ADDER_LISTS_FETCH_REQUEST,
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import api from '../api';
|
|
||||||
import { importFetchedAccount } from './importer';
|
|
||||||
import { verifyCredentials } from './auth';
|
|
||||||
import { getAuthUserId, getAuthUserUrl } from 'soapbox/utils/auth';
|
import { getAuthUserId, getAuthUserUrl } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
|
import { loadCredentials } from './auth';
|
||||||
|
import { importFetchedAccount } from './importer';
|
||||||
|
|
||||||
export const ME_FETCH_REQUEST = 'ME_FETCH_REQUEST';
|
export const ME_FETCH_REQUEST = 'ME_FETCH_REQUEST';
|
||||||
export const ME_FETCH_SUCCESS = 'ME_FETCH_SUCCESS';
|
export const ME_FETCH_SUCCESS = 'ME_FETCH_SUCCESS';
|
||||||
export const ME_FETCH_FAIL = 'ME_FETCH_FAIL';
|
export const ME_FETCH_FAIL = 'ME_FETCH_FAIL';
|
||||||
|
@ -38,7 +40,7 @@ export function fetchMe() {
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(fetchMeRequest());
|
dispatch(fetchMeRequest());
|
||||||
return dispatch(verifyCredentials(token, accountUrl)).catch(error => {
|
return dispatch(loadCredentials(token, accountUrl)).catch(error => {
|
||||||
dispatch(fetchMeFail(error));
|
dispatch(fetchMeFail(error));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -66,7 +68,6 @@ export function fetchMeRequest() {
|
||||||
|
|
||||||
export function fetchMeSuccess(me) {
|
export function fetchMeSuccess(me) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(importFetchedAccount(me));
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ME_FETCH_SUCCESS,
|
type: ME_FETCH_SUCCESS,
|
||||||
me,
|
me,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import api from '../api';
|
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
const noOp = () => {};
|
const noOp = () => {};
|
||||||
|
|
||||||
export function fetchMedia(mediaId) {
|
export function fetchMedia(mediaId) {
|
||||||
|
|
|
@ -1,180 +1,84 @@
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
export const TOTP_SETTINGS_FETCH_REQUEST = 'TOTP_SETTINGS_FETCH_REQUEST';
|
export const MFA_FETCH_REQUEST = 'MFA_FETCH_REQUEST';
|
||||||
export const TOTP_SETTINGS_FETCH_SUCCESS = 'TOTP_SETTINGS_FETCH_SUCCESS';
|
export const MFA_FETCH_SUCCESS = 'MFA_FETCH_SUCCESS';
|
||||||
export const TOTP_SETTINGS_FETCH_FAIL = 'TOTP_SETTINGS_FETCH_FAIL';
|
export const MFA_FETCH_FAIL = 'MFA_FETCH_FAIL';
|
||||||
|
|
||||||
export const BACKUP_CODES_FETCH_REQUEST = 'BACKUP_CODES_FETCH_REQUEST';
|
export const MFA_BACKUP_CODES_FETCH_REQUEST = 'MFA_BACKUP_CODES_FETCH_REQUEST';
|
||||||
export const BACKUP_CODES_FETCH_SUCCESS = 'BACKUP_CODES_FETCH_SUCCESS';
|
export const MFA_BACKUP_CODES_FETCH_SUCCESS = 'MFA_BACKUP_CODES_FETCH_SUCCESS';
|
||||||
export const BACKUP_CODES_FETCH_FAIL = 'BACKUP_CODES_FETCH_FAIL';
|
export const MFA_BACKUP_CODES_FETCH_FAIL = 'MFA_BACKUP_CODES_FETCH_FAIL';
|
||||||
|
|
||||||
export const TOTP_SETUP_FETCH_REQUEST = 'TOTP_SETUP_FETCH_REQUEST';
|
export const MFA_SETUP_REQUEST = 'MFA_SETUP_REQUEST';
|
||||||
export const TOTP_SETUP_FETCH_SUCCESS = 'TOTP_SETUP_FETCH_SUCCESS';
|
export const MFA_SETUP_SUCCESS = 'MFA_SETUP_SUCCESS';
|
||||||
export const TOTP_SETUP_FETCH_FAIL = 'TOTP_SETUP_FETCH_FAIL';
|
export const MFA_SETUP_FAIL = 'MFA_SETUP_FAIL';
|
||||||
|
|
||||||
export const CONFIRM_TOTP_REQUEST = 'CONFIRM_TOTP_REQUEST';
|
export const MFA_CONFIRM_REQUEST = 'MFA_CONFIRM_REQUEST';
|
||||||
export const CONFIRM_TOTP_SUCCESS = 'CONFIRM_TOTP_SUCCESS';
|
export const MFA_CONFIRM_SUCCESS = 'MFA_CONFIRM_SUCCESS';
|
||||||
export const CONFIRM_TOTP_FAIL = 'CONFIRM_TOTP_FAIL';
|
export const MFA_CONFIRM_FAIL = 'MFA_CONFIRM_FAIL';
|
||||||
|
|
||||||
export const DISABLE_TOTP_REQUEST = 'DISABLE_TOTP_REQUEST';
|
export const MFA_DISABLE_REQUEST = 'MFA_DISABLE_REQUEST';
|
||||||
export const DISABLE_TOTP_SUCCESS = 'DISABLE_TOTP_SUCCESS';
|
export const MFA_DISABLE_SUCCESS = 'MFA_DISABLE_SUCCESS';
|
||||||
export const DISABLE_TOTP_FAIL = 'DISABLE_TOTP_FAIL';
|
export const MFA_DISABLE_FAIL = 'MFA_DISABLE_FAIL';
|
||||||
|
|
||||||
export function fetchUserMfaSettings() {
|
export function fetchMfa() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: TOTP_SETTINGS_FETCH_REQUEST });
|
dispatch({ type: MFA_FETCH_REQUEST });
|
||||||
return api(getState).get('/api/pleroma/accounts/mfa').then(response => {
|
return api(getState).get('/api/pleroma/accounts/mfa').then(({ data }) => {
|
||||||
dispatch({ type: TOTP_SETTINGS_FETCH_SUCCESS, totpEnabled: response.data.totp });
|
dispatch({ type: MFA_FETCH_SUCCESS, data });
|
||||||
return response;
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: TOTP_SETTINGS_FETCH_FAIL });
|
dispatch({ type: MFA_FETCH_FAIL });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchUserMfaSettingsRequest() {
|
|
||||||
return {
|
|
||||||
type: TOTP_SETTINGS_FETCH_REQUEST,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchUserMfaSettingsSuccess() {
|
|
||||||
return {
|
|
||||||
type: TOTP_SETTINGS_FETCH_SUCCESS,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchUserMfaSettingsFail() {
|
|
||||||
return {
|
|
||||||
type: TOTP_SETTINGS_FETCH_FAIL,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchBackupCodes() {
|
export function fetchBackupCodes() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: BACKUP_CODES_FETCH_REQUEST });
|
dispatch({ type: MFA_BACKUP_CODES_FETCH_REQUEST });
|
||||||
return api(getState).get('/api/pleroma/accounts/mfa/backup_codes').then(response => {
|
return api(getState).get('/api/pleroma/accounts/mfa/backup_codes').then(({ data }) => {
|
||||||
dispatch({ type: BACKUP_CODES_FETCH_SUCCESS, backup_codes: response.data });
|
dispatch({ type: MFA_BACKUP_CODES_FETCH_SUCCESS, data });
|
||||||
return response;
|
return data;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: BACKUP_CODES_FETCH_FAIL });
|
dispatch({ type: MFA_BACKUP_CODES_FETCH_FAIL });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchBackupCodesRequest() {
|
export function setupMfa(method) {
|
||||||
return {
|
|
||||||
type: BACKUP_CODES_FETCH_REQUEST,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchBackupCodesSuccess(backup_codes, response) {
|
|
||||||
return {
|
|
||||||
type: BACKUP_CODES_FETCH_SUCCESS,
|
|
||||||
backup_codes: response.data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchBackupCodesFail(error) {
|
|
||||||
return {
|
|
||||||
type: BACKUP_CODES_FETCH_FAIL,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchToptSetup() {
|
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: TOTP_SETUP_FETCH_REQUEST });
|
dispatch({ type: MFA_SETUP_REQUEST, method });
|
||||||
return api(getState).get('/api/pleroma/accounts/mfa/setup/totp').then(response => {
|
return api(getState).get(`/api/pleroma/accounts/mfa/setup/${method}`).then(({ data }) => {
|
||||||
dispatch({ type: TOTP_SETUP_FETCH_SUCCESS, totp_setup: response.data });
|
dispatch({ type: MFA_SETUP_SUCCESS, data });
|
||||||
return response;
|
return data;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: TOTP_SETUP_FETCH_FAIL });
|
dispatch({ type: MFA_SETUP_FAIL });
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchToptSetupRequest() {
|
export function confirmMfa(method, code, password) {
|
||||||
return {
|
|
||||||
type: TOTP_SETUP_FETCH_REQUEST,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchToptSetupSuccess(totp_setup, response) {
|
|
||||||
return {
|
|
||||||
type: TOTP_SETUP_FETCH_SUCCESS,
|
|
||||||
totp_setup: response.data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchToptSetupFail(error) {
|
|
||||||
return {
|
|
||||||
type: TOTP_SETUP_FETCH_FAIL,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function confirmToptSetup(code, password) {
|
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: CONFIRM_TOTP_REQUEST, code });
|
const params = { code, password };
|
||||||
return api(getState).post('/api/pleroma/accounts/mfa/confirm/totp', {
|
dispatch({ type: MFA_CONFIRM_REQUEST, method, code });
|
||||||
code,
|
return api(getState).post(`/api/pleroma/accounts/mfa/confirm/${method}`, params).then(({ data }) => {
|
||||||
password,
|
dispatch({ type: MFA_CONFIRM_SUCCESS, method, code });
|
||||||
}).then(response => {
|
return data;
|
||||||
dispatch({ type: CONFIRM_TOTP_SUCCESS });
|
|
||||||
return response;
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: CONFIRM_TOTP_FAIL });
|
dispatch({ type: MFA_CONFIRM_FAIL, method, code, error, skipAlert: true });
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function confirmToptRequest() {
|
export function disableMfa(method, password) {
|
||||||
return {
|
|
||||||
type: CONFIRM_TOTP_REQUEST,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function confirmToptSuccess(backup_codes, response) {
|
|
||||||
return {
|
|
||||||
type: CONFIRM_TOTP_SUCCESS,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function confirmToptFail(error) {
|
|
||||||
return {
|
|
||||||
type: CONFIRM_TOTP_FAIL,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function disableToptSetup(password) {
|
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: DISABLE_TOTP_REQUEST });
|
dispatch({ type: MFA_DISABLE_REQUEST, method });
|
||||||
return api(getState).delete('/api/pleroma/accounts/mfa/totp', { data: { password } }).then(response => {
|
return api(getState).delete(`/api/pleroma/accounts/mfa/${method}`, { data: { password } }).then(({ data }) => {
|
||||||
dispatch({ type: DISABLE_TOTP_SUCCESS });
|
dispatch({ type: MFA_DISABLE_SUCCESS, method });
|
||||||
return response;
|
return data;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: DISABLE_TOTP_FAIL });
|
dispatch({ type: MFA_DISABLE_FAIL, method, skipAlert: true });
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function disableToptRequest() {
|
|
||||||
return {
|
|
||||||
type: DISABLE_TOTP_REQUEST,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function disableToptSuccess(backup_codes, response) {
|
|
||||||
return {
|
|
||||||
type: DISABLE_TOTP_SUCCESS,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function disableToptFail(error) {
|
|
||||||
return {
|
|
||||||
type: DISABLE_TOTP_FAIL,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,9 +9,10 @@ export function openModal(type, props) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeModal(type) {
|
export function closeModal(type, noPop) {
|
||||||
return {
|
return {
|
||||||
type: MODAL_CLOSE,
|
type: MODAL_CLOSE,
|
||||||
modalType: type,
|
modalType: type,
|
||||||
|
noPop,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,32 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defineMessages } from 'react-intl';
|
import { defineMessages } from 'react-intl';
|
||||||
import { openModal } from 'soapbox/actions/modal';
|
|
||||||
import { deactivateUsers, deleteUsers, deleteStatus, toggleStatusSensitivity } from 'soapbox/actions/admin';
|
|
||||||
import { fetchAccountByUsername } from 'soapbox/actions/accounts';
|
import { fetchAccountByUsername } from 'soapbox/actions/accounts';
|
||||||
|
import { deactivateUsers, deleteUsers, deleteStatus, toggleStatusSensitivity } from 'soapbox/actions/admin';
|
||||||
|
import { openModal } from 'soapbox/actions/modal';
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import AccountContainer from 'soapbox/containers/account_container';
|
import AccountContainer from 'soapbox/containers/account_container';
|
||||||
import { isLocal } from 'soapbox/utils/accounts';
|
import { isLocal } from 'soapbox/utils/accounts';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
deactivateUserHeading: { id: 'confirmations.admin.deactivate_user.heading', defaultMessage: 'Deactivate @{acct}' },
|
||||||
deactivateUserPrompt: { id: 'confirmations.admin.deactivate_user.message', defaultMessage: 'You are about to deactivate @{acct}. Deactivating a user is a reversible action.' },
|
deactivateUserPrompt: { id: 'confirmations.admin.deactivate_user.message', defaultMessage: 'You are about to deactivate @{acct}. Deactivating a user is a reversible action.' },
|
||||||
deactivateUserConfirm: { id: 'confirmations.admin.deactivate_user.confirm', defaultMessage: 'Deactivate @{name}' },
|
deactivateUserConfirm: { id: 'confirmations.admin.deactivate_user.confirm', defaultMessage: 'Deactivate @{name}' },
|
||||||
userDeactivated: { id: 'admin.users.user_deactivated_message', defaultMessage: '@{acct} was deactivated' },
|
userDeactivated: { id: 'admin.users.user_deactivated_message', defaultMessage: '@{acct} was deactivated' },
|
||||||
|
deleteUserHeading: { id: 'confirmations.admin.delete_user.heading', defaultMessage: 'Delete @{acct}' },
|
||||||
deleteUserPrompt: { id: 'confirmations.admin.delete_user.message', defaultMessage: 'You are about to delete @{acct}. THIS IS A DESTRUCTIVE ACTION THAT CANNOT BE UNDONE.' },
|
deleteUserPrompt: { id: 'confirmations.admin.delete_user.message', defaultMessage: 'You are about to delete @{acct}. THIS IS A DESTRUCTIVE ACTION THAT CANNOT BE UNDONE.' },
|
||||||
deleteUserConfirm: { id: 'confirmations.admin.delete_user.confirm', defaultMessage: 'Delete @{name}' },
|
deleteUserConfirm: { id: 'confirmations.admin.delete_user.confirm', defaultMessage: 'Delete @{name}' },
|
||||||
deleteLocalUserCheckbox: { id: 'confirmations.admin.delete_local_user.checkbox', defaultMessage: 'I understand that I am about to delete a local user.' },
|
deleteLocalUserCheckbox: { id: 'confirmations.admin.delete_local_user.checkbox', defaultMessage: 'I understand that I am about to delete a local user.' },
|
||||||
userDeleted: { id: 'admin.users.user_deleted_message', defaultMessage: '@{acct} was deleted' },
|
userDeleted: { id: 'admin.users.user_deleted_message', defaultMessage: '@{acct} was deleted' },
|
||||||
|
deleteStatusHeading: { id: 'confirmations.admin.delete_status.heading', defaultMessage: 'Delete post' },
|
||||||
deleteStatusPrompt: { id: 'confirmations.admin.delete_status.message', defaultMessage: 'You are about to delete a post by @{acct}. This action cannot be undone.' },
|
deleteStatusPrompt: { id: 'confirmations.admin.delete_status.message', defaultMessage: 'You are about to delete a post by @{acct}. This action cannot be undone.' },
|
||||||
deleteStatusConfirm: { id: 'confirmations.admin.delete_status.confirm', defaultMessage: 'Delete post' },
|
deleteStatusConfirm: { id: 'confirmations.admin.delete_status.confirm', defaultMessage: 'Delete post' },
|
||||||
|
rejectUserHeading: { id: 'confirmations.admin.reject_user.heading', defaultMessage: 'Reject @{acct}' },
|
||||||
|
rejectUserPrompt: { id: 'confirmations.admin.reject_user.message', defaultMessage: 'You are about to reject @{acct} registration request. This action cannot be undone.' },
|
||||||
|
rejectUserConfirm: { id: 'confirmations.admin.reject_user.confirm', defaultMessage: 'Reject @{name}' },
|
||||||
statusDeleted: { id: 'admin.statuses.status_deleted_message', defaultMessage: 'Post by @{acct} was deleted' },
|
statusDeleted: { id: 'admin.statuses.status_deleted_message', defaultMessage: 'Post by @{acct} was deleted' },
|
||||||
|
markStatusSensitiveHeading: { id: 'confirmations.admin.mark_status_sensitive.heading', defaultMessage: 'Mark post sensitive' },
|
||||||
|
markStatusNotSensitiveHeading: { id: 'confirmations.admin.mark_status_not_sensitive.heading', defaultMessage: 'Mark post not sensitive.' },
|
||||||
markStatusSensitivePrompt: { id: 'confirmations.admin.mark_status_sensitive.message', defaultMessage: 'You are about to mark a post by @{acct} sensitive.' },
|
markStatusSensitivePrompt: { id: 'confirmations.admin.mark_status_sensitive.message', defaultMessage: 'You are about to mark a post by @{acct} sensitive.' },
|
||||||
markStatusNotSensitivePrompt: { id: 'confirmations.admin.mark_status_not_sensitive.message', defaultMessage: 'You are about to mark a post by @{acct} not sensitive.' },
|
markStatusNotSensitivePrompt: { id: 'confirmations.admin.mark_status_not_sensitive.message', defaultMessage: 'You are about to mark a post by @{acct} not sensitive.' },
|
||||||
markStatusSensitiveConfirm: { id: 'confirmations.admin.mark_status_sensitive.confirm', defaultMessage: 'Mark post sensitive' },
|
markStatusSensitiveConfirm: { id: 'confirmations.admin.mark_status_sensitive.confirm', defaultMessage: 'Mark post sensitive' },
|
||||||
|
@ -33,6 +42,8 @@ export function deactivateUserModal(intl, accountId, afterConfirm = () => {}) {
|
||||||
const name = state.getIn(['accounts', accountId, 'username']);
|
const name = state.getIn(['accounts', accountId, 'username']);
|
||||||
|
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
|
icon: require('@tabler/icons/icons/user-off.svg'),
|
||||||
|
heading: intl.formatMessage(messages.deactivateUserHeading, { acct }),
|
||||||
message: intl.formatMessage(messages.deactivateUserPrompt, { acct }),
|
message: intl.formatMessage(messages.deactivateUserPrompt, { acct }),
|
||||||
confirm: intl.formatMessage(messages.deactivateUserConfirm, { name }),
|
confirm: intl.formatMessage(messages.deactivateUserConfirm, { name }),
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
|
@ -70,6 +81,8 @@ export function deleteUserModal(intl, accountId, afterConfirm = () => {}) {
|
||||||
const checkbox = local ? intl.formatMessage(messages.deleteLocalUserCheckbox) : false;
|
const checkbox = local ? intl.formatMessage(messages.deleteLocalUserCheckbox) : false;
|
||||||
|
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
|
icon: require('@tabler/icons/icons/user-minus.svg'),
|
||||||
|
heading: intl.formatMessage(messages.deleteUserHeading, { acct }),
|
||||||
message,
|
message,
|
||||||
confirm,
|
confirm,
|
||||||
checkbox,
|
checkbox,
|
||||||
|
@ -85,6 +98,28 @@ export function deleteUserModal(intl, accountId, afterConfirm = () => {}) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function rejectUserModal(intl, accountId, afterConfirm = () => {}) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
const state = getState();
|
||||||
|
const acct = state.getIn(['accounts', accountId, 'acct']);
|
||||||
|
const name = state.getIn(['accounts', accountId, 'username']);
|
||||||
|
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
icon: require('@tabler/icons/icons/user-off.svg'),
|
||||||
|
heading: intl.formatMessage(messages.rejectUserHeading, { acct }),
|
||||||
|
message: intl.formatMessage(messages.rejectUserPrompt, { acct }),
|
||||||
|
confirm: intl.formatMessage(messages.rejectUserConfirm, { name }),
|
||||||
|
onConfirm: () => {
|
||||||
|
dispatch(deleteUsers([accountId]))
|
||||||
|
.then(() => {
|
||||||
|
afterConfirm();
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function toggleStatusSensitivityModal(intl, statusId, sensitive, afterConfirm = () => {}) {
|
export function toggleStatusSensitivityModal(intl, statusId, sensitive, afterConfirm = () => {}) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
@ -92,6 +127,8 @@ export function toggleStatusSensitivityModal(intl, statusId, sensitive, afterCon
|
||||||
const acct = state.getIn(['accounts', accountId, 'acct']);
|
const acct = state.getIn(['accounts', accountId, 'acct']);
|
||||||
|
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
|
icon: require('@tabler/icons/icons/alert-triangle.svg'),
|
||||||
|
heading: intl.formatMessage(sensitive === false ? messages.markStatusSensitiveHeading : messages.markStatusNotSensitiveHeading),
|
||||||
message: intl.formatMessage(sensitive === false ? messages.markStatusSensitivePrompt : messages.markStatusNotSensitivePrompt, { acct }),
|
message: intl.formatMessage(sensitive === false ? messages.markStatusSensitivePrompt : messages.markStatusNotSensitivePrompt, { acct }),
|
||||||
confirm: intl.formatMessage(sensitive === false ? messages.markStatusSensitiveConfirm : messages.markStatusNotSensitiveConfirm),
|
confirm: intl.formatMessage(sensitive === false ? messages.markStatusSensitiveConfirm : messages.markStatusNotSensitiveConfirm),
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
|
@ -112,6 +149,8 @@ export function deleteStatusModal(intl, statusId, afterConfirm = () => {}) {
|
||||||
const acct = state.getIn(['accounts', accountId, 'acct']);
|
const acct = state.getIn(['accounts', accountId, 'acct']);
|
||||||
|
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
|
icon: require('@tabler/icons/icons/trash.svg'),
|
||||||
|
heading: intl.formatMessage(messages.deleteStatusHeading),
|
||||||
message: intl.formatMessage(messages.deleteStatusPrompt, { acct }),
|
message: intl.formatMessage(messages.deleteStatusPrompt, { acct }),
|
||||||
confirm: intl.formatMessage(messages.deleteStatusConfirm),
|
confirm: intl.formatMessage(messages.deleteStatusConfirm),
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { fetchConfig, updateConfig } from './admin';
|
|
||||||
import { Set as ImmutableSet } from 'immutable';
|
import { Set as ImmutableSet } from 'immutable';
|
||||||
|
|
||||||
import ConfigDB from 'soapbox/utils/config_db';
|
import ConfigDB from 'soapbox/utils/config_db';
|
||||||
|
|
||||||
|
import { fetchConfig, updateConfig } from './admin';
|
||||||
|
|
||||||
const simplePolicyMerge = (simplePolicy, host, restrictions) => {
|
const simplePolicyMerge = (simplePolicy, host, restrictions) => {
|
||||||
return simplePolicy.map((hosts, key) => {
|
return simplePolicy.map((hosts, key) => {
|
||||||
const isRestricted = restrictions.get(key);
|
const isRestricted = restrictions.get(key);
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
import { getNextLinkName } from 'soapbox/utils/quirks';
|
||||||
|
|
||||||
import api, { getLinks } from '../api';
|
import api, { getLinks } from '../api';
|
||||||
|
|
||||||
import { fetchRelationships } from './accounts';
|
import { fetchRelationships } from './accounts';
|
||||||
import { importFetchedAccounts } from './importer';
|
import { importFetchedAccounts } from './importer';
|
||||||
import { openModal } from './modal';
|
import { openModal } from './modal';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
|
||||||
import { getNextLinkName } from 'soapbox/utils/quirks';
|
|
||||||
|
|
||||||
export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST';
|
export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST';
|
||||||
export const MUTES_FETCH_SUCCESS = 'MUTES_FETCH_SUCCESS';
|
export const MUTES_FETCH_SUCCESS = 'MUTES_FETCH_SUCCESS';
|
||||||
|
|
|
@ -1,6 +1,20 @@
|
||||||
import api, { getLinks } from '../api';
|
import {
|
||||||
|
List as ImmutableList,
|
||||||
|
Map as ImmutableMap,
|
||||||
|
OrderedMap as ImmutableOrderedMap,
|
||||||
|
} from 'immutable';
|
||||||
import IntlMessageFormat from 'intl-messageformat';
|
import IntlMessageFormat from 'intl-messageformat';
|
||||||
import 'intl-pluralrules';
|
import 'intl-pluralrules';
|
||||||
|
import { defineMessages } from 'react-intl';
|
||||||
|
|
||||||
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
import { parseVersion, PLEROMA } from 'soapbox/utils/features';
|
||||||
|
import { joinPublicPath } from 'soapbox/utils/static';
|
||||||
|
|
||||||
|
import api, { getLinks } from '../api';
|
||||||
|
import { getFilters, regexFromFilters } from '../selectors';
|
||||||
|
import { unescapeHTML } from '../utils/html';
|
||||||
|
|
||||||
import { fetchRelationships } from './accounts';
|
import { fetchRelationships } from './accounts';
|
||||||
import {
|
import {
|
||||||
importFetchedAccount,
|
importFetchedAccount,
|
||||||
|
@ -10,16 +24,6 @@ import {
|
||||||
} from './importer';
|
} from './importer';
|
||||||
import { saveMarker } from './markers';
|
import { saveMarker } from './markers';
|
||||||
import { getSettings, saveSettings } from './settings';
|
import { getSettings, saveSettings } from './settings';
|
||||||
import { defineMessages } from 'react-intl';
|
|
||||||
import {
|
|
||||||
List as ImmutableList,
|
|
||||||
Map as ImmutableMap,
|
|
||||||
OrderedMap as ImmutableOrderedMap,
|
|
||||||
} from 'immutable';
|
|
||||||
import { unescapeHTML } from '../utils/html';
|
|
||||||
import { getFilters, regexFromFilters } from '../selectors';
|
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
|
||||||
import { parseVersion, PLEROMA } from 'soapbox/utils/features';
|
|
||||||
|
|
||||||
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
|
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
|
||||||
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
|
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
|
||||||
|
@ -102,16 +106,20 @@ export function updateNotificationsQueue(notification, intlMessages, intlLocale,
|
||||||
|
|
||||||
// Desktop notifications
|
// Desktop notifications
|
||||||
try {
|
try {
|
||||||
if (typeof window.Notification !== 'undefined' && showAlert && !filtered) {
|
if (showAlert && !filtered) {
|
||||||
const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username });
|
const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username });
|
||||||
const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : '');
|
const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : '');
|
||||||
|
|
||||||
const notify = new Notification(title, { body, icon: notification.account.avatar, tag: notification.id });
|
navigator.serviceWorker.ready.then(serviceWorkerRegistration => {
|
||||||
|
serviceWorkerRegistration.showNotification(title, {
|
||||||
notify.addEventListener('click', () => {
|
body,
|
||||||
window.focus();
|
icon: notification.account.avatar,
|
||||||
notify.close();
|
tag: notification.id,
|
||||||
});
|
data: {
|
||||||
|
url: joinPublicPath('/notifications'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}).catch(console.error);
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import api from '../api';
|
|
||||||
import { importFetchedStatuses } from './importer';
|
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
|
import { importFetchedStatuses } from './importer';
|
||||||
|
|
||||||
export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST';
|
export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST';
|
||||||
export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS';
|
export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS';
|
||||||
export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL';
|
export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL';
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
import { importFetchedPoll } from './importer';
|
import { importFetchedPoll } from './importer';
|
||||||
|
|
||||||
export const POLL_VOTE_REQUEST = 'POLL_VOTE_REQUEST';
|
export const POLL_VOTE_REQUEST = 'POLL_VOTE_REQUEST';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { mapValues } from 'lodash';
|
import { mapValues } from 'lodash';
|
||||||
import { importFetchedAccounts } from './importer';
|
|
||||||
import { verifyCredentials } from './auth';
|
import { verifyCredentials } from './auth';
|
||||||
|
import { importFetchedAccounts } from './importer';
|
||||||
|
|
||||||
export const PLEROMA_PRELOAD_IMPORT = 'PLEROMA_PRELOAD_IMPORT';
|
export const PLEROMA_PRELOAD_IMPORT = 'PLEROMA_PRELOAD_IMPORT';
|
||||||
export const MASTODON_PRELOAD_IMPORT = 'MASTODON_PRELOAD_IMPORT';
|
export const MASTODON_PRELOAD_IMPORT = 'MASTODON_PRELOAD_IMPORT';
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { register, saveSettings } from './registerer';
|
||||||
import {
|
import {
|
||||||
SET_BROWSER_SUPPORT,
|
SET_BROWSER_SUPPORT,
|
||||||
SET_SUBSCRIPTION,
|
SET_SUBSCRIPTION,
|
||||||
|
@ -5,7 +6,6 @@ import {
|
||||||
SET_ALERTS,
|
SET_ALERTS,
|
||||||
setAlerts,
|
setAlerts,
|
||||||
} from './setter';
|
} from './setter';
|
||||||
import { register, saveSettings } from './registerer';
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SET_BROWSER_SUPPORT,
|
SET_BROWSER_SUPPORT,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import api from '../../api';
|
import { createPushSubsription, updatePushSubscription } from 'soapbox/actions/push_subscriptions';
|
||||||
import { decode as decodeBase64 } from '../../utils/base64';
|
import { getVapidKey } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
import { pushNotificationsSetting } from '../../settings';
|
import { pushNotificationsSetting } from '../../settings';
|
||||||
|
import { decode as decodeBase64 } from '../../utils/base64';
|
||||||
|
|
||||||
import { setBrowserSupport, setSubscription, clearSubscription } from './setter';
|
import { setBrowserSupport, setSubscription, clearSubscription } from './setter';
|
||||||
|
|
||||||
// Taken from https://www.npmjs.com/package/web-push
|
// Taken from https://www.npmjs.com/package/web-push
|
||||||
|
@ -13,12 +16,6 @@ const urlBase64ToUint8Array = (base64String) => {
|
||||||
return decodeBase64(base64);
|
return decodeBase64(base64);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getApplicationServerKey = getState => {
|
|
||||||
const key = getState().getIn(['auth', 'app', 'vapid_key']);
|
|
||||||
if (!key) console.error('Could not get vapid key. Push notifications will not work.');
|
|
||||||
return key;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRegistration = () => navigator.serviceWorker.ready;
|
const getRegistration = () => navigator.serviceWorker.ready;
|
||||||
|
|
||||||
const getPushSubscription = (registration) =>
|
const getPushSubscription = (registration) =>
|
||||||
|
@ -28,23 +25,26 @@ const getPushSubscription = (registration) =>
|
||||||
const subscribe = (registration, getState) =>
|
const subscribe = (registration, getState) =>
|
||||||
registration.pushManager.subscribe({
|
registration.pushManager.subscribe({
|
||||||
userVisibleOnly: true,
|
userVisibleOnly: true,
|
||||||
applicationServerKey: urlBase64ToUint8Array(getApplicationServerKey(getState)),
|
applicationServerKey: urlBase64ToUint8Array(getVapidKey(getState())),
|
||||||
});
|
});
|
||||||
|
|
||||||
const unsubscribe = ({ registration, subscription }) =>
|
const unsubscribe = ({ registration, subscription }) =>
|
||||||
subscription ? subscription.unsubscribe().then(() => registration) : registration;
|
subscription ? subscription.unsubscribe().then(() => registration) : registration;
|
||||||
|
|
||||||
const sendSubscriptionToBackend = (subscription, me) => {
|
const sendSubscriptionToBackend = (subscription, me) => {
|
||||||
const params = { subscription };
|
return (dispatch, getState) => {
|
||||||
|
const alerts = getState().getIn(['push_notifications', 'alerts']).toJS();
|
||||||
|
const params = { subscription, data: { alerts } };
|
||||||
|
|
||||||
if (me) {
|
if (me) {
|
||||||
const data = pushNotificationsSetting.get(me);
|
const data = pushNotificationsSetting.get(me);
|
||||||
if (data) {
|
if (data) {
|
||||||
params.data = data;
|
params.data = data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return api().post('/api/web/push_subscriptions', params).then(response => response.data);
|
return dispatch(createPushSubsription(params));
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload
|
// Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload
|
||||||
|
@ -53,85 +53,87 @@ const supportsPushNotifications = ('serviceWorker' in navigator && 'PushManager'
|
||||||
export function register() {
|
export function register() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const me = getState().get('me');
|
const me = getState().get('me');
|
||||||
|
const vapidKey = getVapidKey(getState());
|
||||||
|
|
||||||
dispatch(setBrowserSupport(supportsPushNotifications));
|
dispatch(setBrowserSupport(supportsPushNotifications));
|
||||||
|
|
||||||
if (supportsPushNotifications) {
|
if (!supportsPushNotifications) {
|
||||||
if (!getApplicationServerKey(getState)) {
|
|
||||||
console.error('The VAPID public key is not set. You will not be able to receive Web Push Notifications.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
getRegistration()
|
|
||||||
.then(getPushSubscription)
|
|
||||||
.then(({ registration, subscription }) => {
|
|
||||||
if (subscription !== null) {
|
|
||||||
// We have a subscription, check if it is still valid
|
|
||||||
const currentServerKey = (new Uint8Array(subscription.options.applicationServerKey)).toString();
|
|
||||||
const subscriptionServerKey = urlBase64ToUint8Array(getApplicationServerKey(getState)).toString();
|
|
||||||
const serverEndpoint = getState().getIn(['push_notifications', 'subscription', 'endpoint']);
|
|
||||||
|
|
||||||
// If the VAPID public key did not change and the endpoint corresponds
|
|
||||||
// to the endpoint saved in the backend, the subscription is valid
|
|
||||||
if (subscriptionServerKey === currentServerKey && subscription.endpoint === serverEndpoint) {
|
|
||||||
return subscription;
|
|
||||||
} else {
|
|
||||||
// Something went wrong, try to subscribe again
|
|
||||||
return unsubscribe({ registration, subscription }).then(registration => {
|
|
||||||
return subscribe(registration, getState);
|
|
||||||
}).then(
|
|
||||||
subscription => sendSubscriptionToBackend(subscription, me));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No subscription, try to subscribe
|
|
||||||
return subscribe(registration, getState).then(
|
|
||||||
subscription => sendSubscriptionToBackend(subscription, me));
|
|
||||||
})
|
|
||||||
.then(subscription => {
|
|
||||||
// If we got a PushSubscription (and not a subscription object from the backend)
|
|
||||||
// it means that the backend subscription is valid (and was set during hydration)
|
|
||||||
if (!(subscription instanceof PushSubscription)) {
|
|
||||||
dispatch(setSubscription(subscription));
|
|
||||||
if (me) {
|
|
||||||
pushNotificationsSetting.set(me, { alerts: subscription.alerts });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
if (error.code === 20 && error.name === 'AbortError') {
|
|
||||||
console.warn('Your browser supports Web Push Notifications, but does not seem to implement the VAPID protocol.');
|
|
||||||
} else if (error.code === 5 && error.name === 'InvalidCharacterError') {
|
|
||||||
console.error('The VAPID public key seems to be invalid:', getApplicationServerKey(getState));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear alerts and hide UI settings
|
|
||||||
dispatch(clearSubscription());
|
|
||||||
if (me) {
|
|
||||||
pushNotificationsSetting.remove(me);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getRegistration()
|
|
||||||
.then(getPushSubscription)
|
|
||||||
.then(unsubscribe);
|
|
||||||
})
|
|
||||||
.catch(console.warn);
|
|
||||||
} else {
|
|
||||||
console.warn('Your browser does not support Web Push Notifications.');
|
console.warn('Your browser does not support Web Push Notifications.');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!vapidKey) {
|
||||||
|
console.error('The VAPID public key is not set. You will not be able to receive Web Push Notifications.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRegistration()
|
||||||
|
.then(getPushSubscription)
|
||||||
|
.then(({ registration, subscription }) => {
|
||||||
|
if (subscription !== null) {
|
||||||
|
// We have a subscription, check if it is still valid
|
||||||
|
const currentServerKey = (new Uint8Array(subscription.options.applicationServerKey)).toString();
|
||||||
|
const subscriptionServerKey = urlBase64ToUint8Array(vapidKey).toString();
|
||||||
|
const serverEndpoint = getState().getIn(['push_notifications', 'subscription', 'endpoint']);
|
||||||
|
|
||||||
|
// If the VAPID public key did not change and the endpoint corresponds
|
||||||
|
// to the endpoint saved in the backend, the subscription is valid
|
||||||
|
if (subscriptionServerKey === currentServerKey && subscription.endpoint === serverEndpoint) {
|
||||||
|
return subscription;
|
||||||
|
} else {
|
||||||
|
// Something went wrong, try to subscribe again
|
||||||
|
return unsubscribe({ registration, subscription }).then(registration => {
|
||||||
|
return subscribe(registration, getState);
|
||||||
|
}).then(
|
||||||
|
subscription => dispatch(sendSubscriptionToBackend(subscription, me)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No subscription, try to subscribe
|
||||||
|
return subscribe(registration, getState)
|
||||||
|
.then(subscription => dispatch(sendSubscriptionToBackend(subscription, me)));
|
||||||
|
})
|
||||||
|
.then(subscription => {
|
||||||
|
// If we got a PushSubscription (and not a subscription object from the backend)
|
||||||
|
// it means that the backend subscription is valid (and was set during hydration)
|
||||||
|
if (!(subscription instanceof PushSubscription)) {
|
||||||
|
dispatch(setSubscription(subscription));
|
||||||
|
if (me) {
|
||||||
|
pushNotificationsSetting.set(me, { alerts: subscription.alerts });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
if (error.code === 20 && error.name === 'AbortError') {
|
||||||
|
console.warn('Your browser supports Web Push Notifications, but does not seem to implement the VAPID protocol.');
|
||||||
|
} else if (error.code === 5 && error.name === 'InvalidCharacterError') {
|
||||||
|
console.error('The VAPID public key seems to be invalid:', vapidKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear alerts and hide UI settings
|
||||||
|
dispatch(clearSubscription());
|
||||||
|
|
||||||
|
if (me) {
|
||||||
|
pushNotificationsSetting.remove(me);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getRegistration()
|
||||||
|
.then(getPushSubscription)
|
||||||
|
.then(unsubscribe);
|
||||||
|
})
|
||||||
|
.catch(console.warn);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveSettings() {
|
export function saveSettings() {
|
||||||
return (_, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState().get('push_notifications');
|
const state = getState().get('push_notifications');
|
||||||
const subscription = state.get('subscription');
|
|
||||||
const alerts = state.get('alerts');
|
const alerts = state.get('alerts');
|
||||||
const data = { alerts };
|
const data = { alerts };
|
||||||
const me = getState().get('me');
|
const me = getState().get('me');
|
||||||
|
|
||||||
api().put(`/api/web/push_subscriptions/${subscription.get('id')}`, {
|
return dispatch(updatePushSubscription({ data })).then(() => {
|
||||||
data,
|
|
||||||
}).then(() => {
|
|
||||||
if (me) {
|
if (me) {
|
||||||
pushNotificationsSetting.set(me, data);
|
pushNotificationsSetting.set(me, data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ export function createPushSubsription(params) {
|
||||||
dispatch({ type: PUSH_SUBSCRIPTION_CREATE_REQUEST, params });
|
dispatch({ type: PUSH_SUBSCRIPTION_CREATE_REQUEST, params });
|
||||||
return api(getState).post('/api/v1/push/subscription', params).then(({ data: subscription }) => {
|
return api(getState).post('/api/v1/push/subscription', params).then(({ data: subscription }) => {
|
||||||
dispatch({ type: PUSH_SUBSCRIPTION_CREATE_SUCCESS, params, subscription });
|
dispatch({ type: PUSH_SUBSCRIPTION_CREATE_SUCCESS, params, subscription });
|
||||||
|
return subscription;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: PUSH_SUBSCRIPTION_CREATE_FAIL, params, error });
|
dispatch({ type: PUSH_SUBSCRIPTION_CREATE_FAIL, params, error });
|
||||||
});
|
});
|
||||||
|
@ -38,7 +39,7 @@ export function fetchPushSubsription() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updatePushSubsription(params) {
|
export function updatePushSubscription(params) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: PUSH_SUBSCRIPTION_UPDATE_REQUEST, params });
|
dispatch({ type: PUSH_SUBSCRIPTION_UPDATE_REQUEST, params });
|
||||||
return api(getState).put('/api/v1/push/subscription', params).then(({ data: subscription }) => {
|
return api(getState).put('/api/v1/push/subscription', params).then(({ data: subscription }) => {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
import { openModal, closeModal } from './modal';
|
import { openModal, closeModal } from './modal';
|
||||||
|
|
||||||
export const REPORT_INIT = 'REPORT_INIT';
|
export const REPORT_INIT = 'REPORT_INIT';
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
import { fetchRelationships } from './accounts';
|
import { fetchRelationships } from './accounts';
|
||||||
import { importFetchedAccounts, importFetchedStatuses } from './importer';
|
import { importFetchedAccounts, importFetchedStatuses } from './importer';
|
||||||
|
|
||||||
|
@ -17,9 +18,16 @@ export const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS';
|
||||||
export const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL';
|
export const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL';
|
||||||
|
|
||||||
export function changeSearch(value) {
|
export function changeSearch(value) {
|
||||||
return {
|
return (dispatch, getState) => {
|
||||||
type: SEARCH_CHANGE,
|
// If backspaced all the way, clear the search
|
||||||
value,
|
if (value.length === 0) {
|
||||||
|
return dispatch(clearSearch());
|
||||||
|
} else {
|
||||||
|
return dispatch({
|
||||||
|
type: SEARCH_CHANGE,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,10 +37,12 @@ export function clearSearch() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function submitSearch() {
|
export function submitSearch(filter) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const value = getState().getIn(['search', 'value']);
|
const value = getState().getIn(['search', 'value']);
|
||||||
|
const type = filter || getState().getIn(['search', 'filter'], 'accounts');
|
||||||
|
|
||||||
|
// An empty search doesn't return any results
|
||||||
if (value.length === 0) {
|
if (value.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -44,6 +54,7 @@ export function submitSearch() {
|
||||||
q: value,
|
q: value,
|
||||||
resolve: true,
|
resolve: true,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
|
type,
|
||||||
},
|
},
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
if (response.data.accounts) {
|
if (response.data.accounts) {
|
||||||
|
@ -54,7 +65,7 @@ export function submitSearch() {
|
||||||
dispatch(importFetchedStatuses(response.data.statuses));
|
dispatch(importFetchedStatuses(response.data.statuses));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(fetchSearchSuccess(response.data));
|
dispatch(fetchSearchSuccess(response.data, value, type));
|
||||||
dispatch(fetchRelationships(response.data.accounts.map(item => item.id)));
|
dispatch(fetchRelationships(response.data.accounts.map(item => item.id)));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(fetchSearchFail(error));
|
dispatch(fetchSearchFail(error));
|
||||||
|
@ -69,10 +80,12 @@ export function fetchSearchRequest(value) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchSearchSuccess(results) {
|
export function fetchSearchSuccess(results, searchTerm, searchType) {
|
||||||
return {
|
return {
|
||||||
type: SEARCH_FETCH_SUCCESS,
|
type: SEARCH_FETCH_SUCCESS,
|
||||||
results,
|
results,
|
||||||
|
searchTerm,
|
||||||
|
searchType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,19 +96,23 @@ export function fetchSearchFail(error) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setFilter = filterType => dispatch => {
|
export function setFilter(filterType) {
|
||||||
dispatch({
|
return (dispatch) => {
|
||||||
type: SEARCH_FILTER_SET,
|
dispatch(submitSearch(filterType));
|
||||||
path: ['search', 'filter'],
|
|
||||||
value: filterType,
|
dispatch({
|
||||||
});
|
type: SEARCH_FILTER_SET,
|
||||||
};
|
path: ['search', 'filter'],
|
||||||
|
value: filterType,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const expandSearch = type => (dispatch, getState) => {
|
export const expandSearch = type => (dispatch, getState) => {
|
||||||
const value = getState().getIn(['search', 'value']);
|
const value = getState().getIn(['search', 'value']);
|
||||||
const offset = getState().getIn(['search', 'results', type]).size;
|
const offset = getState().getIn(['search', 'results', type]).size;
|
||||||
|
|
||||||
dispatch(expandSearchRequest());
|
dispatch(expandSearchRequest(type));
|
||||||
|
|
||||||
api(getState).get('/api/v2/search', {
|
api(getState).get('/api/v2/search', {
|
||||||
params: {
|
params: {
|
||||||
|
@ -119,8 +136,9 @@ export const expandSearch = type => (dispatch, getState) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const expandSearchRequest = () => ({
|
export const expandSearchRequest = (searchType) => ({
|
||||||
type: SEARCH_EXPAND_REQUEST,
|
type: SEARCH_EXPAND_REQUEST,
|
||||||
|
searchType,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const expandSearchSuccess = (results, searchTerm, searchType) => ({
|
export const expandSearchSuccess = (results, searchTerm, searchType) => ({
|
||||||
|
|
|
@ -4,9 +4,11 @@
|
||||||
* @see module:soapbox/actions/auth
|
* @see module:soapbox/actions/auth
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import api from '../api';
|
|
||||||
import { getLoggedInAccount } from 'soapbox/utils/auth';
|
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
|
import { getLoggedInAccount } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
import { AUTH_LOGGED_OUT, messages } from './auth';
|
import { AUTH_LOGGED_OUT, messages } from './auth';
|
||||||
|
|
||||||
export const FETCH_TOKENS_REQUEST = 'FETCH_TOKENS_REQUEST';
|
export const FETCH_TOKENS_REQUEST = 'FETCH_TOKENS_REQUEST';
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
import { debounce } from 'lodash';
|
|
||||||
import { showAlertForError } from './alerts';
|
|
||||||
import { patchMe } from 'soapbox/actions/me';
|
|
||||||
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { debounce } from 'lodash';
|
||||||
import uuid from '../uuid';
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
import { patchMe } from 'soapbox/actions/me';
|
||||||
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
import uuid from '../uuid';
|
||||||
|
|
||||||
|
import { showAlertForError } from './alerts';
|
||||||
|
|
||||||
export const SETTING_CHANGE = 'SETTING_CHANGE';
|
export const SETTING_CHANGE = 'SETTING_CHANGE';
|
||||||
export const SETTING_SAVE = 'SETTING_SAVE';
|
export const SETTING_SAVE = 'SETTING_SAVE';
|
||||||
|
export const SETTINGS_UPDATE = 'SETTINGS_UPDATE';
|
||||||
|
|
||||||
export const FE_NAME = 'soapbox_fe';
|
export const FE_NAME = 'soapbox_fe';
|
||||||
|
|
||||||
|
@ -30,13 +34,15 @@ export const defaultSettings = ImmutableMap({
|
||||||
locale: navigator.language.split(/[-_]/)[0] || 'en',
|
locale: navigator.language.split(/[-_]/)[0] || 'en',
|
||||||
showExplanationBox: true,
|
showExplanationBox: true,
|
||||||
explanationBox: true,
|
explanationBox: true,
|
||||||
otpEnabled: false,
|
|
||||||
autoloadTimelines: true,
|
autoloadTimelines: true,
|
||||||
|
autoloadMore: true,
|
||||||
|
|
||||||
systemFont: false,
|
systemFont: false,
|
||||||
dyslexicFont: false,
|
dyslexicFont: false,
|
||||||
demetricator: false,
|
demetricator: false,
|
||||||
|
|
||||||
|
isDeveloper: false,
|
||||||
|
|
||||||
chats: ImmutableMap({
|
chats: ImmutableMap({
|
||||||
panes: ImmutableList(),
|
panes: ImmutableList(),
|
||||||
mainWindow: 'minimized',
|
mainWindow: 'minimized',
|
||||||
|
@ -100,6 +106,7 @@ export const defaultSettings = ImmutableMap({
|
||||||
shows: ImmutableMap({
|
shows: ImmutableMap({
|
||||||
reblog: false,
|
reblog: false,
|
||||||
reply: true,
|
reply: true,
|
||||||
|
direct: false,
|
||||||
}),
|
}),
|
||||||
other: ImmutableMap({
|
other: ImmutableMap({
|
||||||
onlyMedia: false,
|
onlyMedia: false,
|
||||||
|
@ -113,6 +120,7 @@ export const defaultSettings = ImmutableMap({
|
||||||
shows: ImmutableMap({
|
shows: ImmutableMap({
|
||||||
reblog: true,
|
reblog: true,
|
||||||
reply: true,
|
reply: true,
|
||||||
|
direct: false,
|
||||||
}),
|
}),
|
||||||
other: ImmutableMap({
|
other: ImmutableMap({
|
||||||
onlyMedia: false,
|
onlyMedia: false,
|
||||||
|
@ -132,6 +140,7 @@ export const defaultSettings = ImmutableMap({
|
||||||
shows: ImmutableMap({
|
shows: ImmutableMap({
|
||||||
reblog: true,
|
reblog: true,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
|
direct: false,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -159,6 +168,18 @@ export const getSettings = createSelector([
|
||||||
.mergeDeep(settings);
|
.mergeDeep(settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function changeSettingImmediate(path, value) {
|
||||||
|
return dispatch => {
|
||||||
|
dispatch({
|
||||||
|
type: SETTING_CHANGE,
|
||||||
|
path,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(saveSettingsImmediate());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function changeSetting(path, value) {
|
export function changeSetting(path, value) {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
@ -171,23 +192,29 @@ export function changeSetting(path, value) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function saveSettingsImmediate() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
if (!isLoggedIn(getState)) return;
|
||||||
|
|
||||||
|
const state = getState();
|
||||||
|
if (getSettings(state).getIn(['saved'])) return;
|
||||||
|
|
||||||
|
const data = state.get('settings').delete('saved').toJS();
|
||||||
|
|
||||||
|
dispatch(patchMe({
|
||||||
|
pleroma_settings_store: {
|
||||||
|
[FE_NAME]: data,
|
||||||
|
},
|
||||||
|
})).then(response => {
|
||||||
|
dispatch({ type: SETTING_SAVE });
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(showAlertForError(error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const debouncedSave = debounce((dispatch, getState) => {
|
const debouncedSave = debounce((dispatch, getState) => {
|
||||||
if (!isLoggedIn(getState)) return;
|
dispatch(saveSettingsImmediate());
|
||||||
|
|
||||||
const state = getState();
|
|
||||||
if (getSettings(state).getIn(['saved'])) return;
|
|
||||||
|
|
||||||
const data = state.get('settings').delete('saved').toJS();
|
|
||||||
|
|
||||||
dispatch(patchMe({
|
|
||||||
pleroma_settings_store: {
|
|
||||||
[FE_NAME]: data,
|
|
||||||
},
|
|
||||||
})).then(response => {
|
|
||||||
dispatch({ type: SETTING_SAVE });
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(showAlertForError(error));
|
|
||||||
});
|
|
||||||
}, 5000, { trailing: true });
|
}, 5000, { trailing: true });
|
||||||
|
|
||||||
export function saveSettings() {
|
export function saveSettings() {
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
import api, { staticClient } from '../api';
|
|
||||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
import { getHost } from 'soapbox/actions/instance';
|
||||||
|
import KVStore from 'soapbox/storage/kv_store';
|
||||||
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
|
||||||
|
import api, { staticClient } from '../api';
|
||||||
|
|
||||||
export const SOAPBOX_CONFIG_REQUEST_SUCCESS = 'SOAPBOX_CONFIG_REQUEST_SUCCESS';
|
export const SOAPBOX_CONFIG_REQUEST_SUCCESS = 'SOAPBOX_CONFIG_REQUEST_SUCCESS';
|
||||||
export const SOAPBOX_CONFIG_REQUEST_FAIL = 'SOAPBOX_CONFIG_REQUEST_FAIL';
|
export const SOAPBOX_CONFIG_REQUEST_FAIL = 'SOAPBOX_CONFIG_REQUEST_FAIL';
|
||||||
|
|
||||||
|
export const SOAPBOX_CONFIG_REMEMBER_REQUEST = 'SOAPBOX_CONFIG_REMEMBER_REQUEST';
|
||||||
|
export const SOAPBOX_CONFIG_REMEMBER_SUCCESS = 'SOAPBOX_CONFIG_REMEMBER_SUCCESS';
|
||||||
|
export const SOAPBOX_CONFIG_REMEMBER_FAIL = 'SOAPBOX_CONFIG_REMEMBER_FAIL';
|
||||||
|
|
||||||
const allowedEmoji = ImmutableList([
|
const allowedEmoji = ImmutableList([
|
||||||
'👍',
|
'👍',
|
||||||
'❤',
|
'❤',
|
||||||
|
@ -61,46 +69,71 @@ export const getSoapboxConfig = createSelector([
|
||||||
return makeDefaultConfig(features).merge(soapbox);
|
return makeDefaultConfig(features).merge(soapbox);
|
||||||
});
|
});
|
||||||
|
|
||||||
export function fetchSoapboxConfig() {
|
export function rememberSoapboxConfig(host) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch({ type: SOAPBOX_CONFIG_REMEMBER_REQUEST, host });
|
||||||
|
return KVStore.getItemOrError(`soapbox_config:${host}`).then(soapboxConfig => {
|
||||||
|
dispatch({ type: SOAPBOX_CONFIG_REMEMBER_SUCCESS, host, soapboxConfig });
|
||||||
|
return soapboxConfig;
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch({ type: SOAPBOX_CONFIG_REMEMBER_FAIL, host, error, skipAlert: true });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchSoapboxConfig(host) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
api(getState).get('/api/pleroma/frontend_configurations').then(response => {
|
api(getState).get('/api/pleroma/frontend_configurations').then(response => {
|
||||||
if (response.data.soapbox_fe) {
|
if (response.data.soapbox_fe) {
|
||||||
dispatch(importSoapboxConfig(response.data.soapbox_fe));
|
dispatch(importSoapboxConfig(response.data.soapbox_fe, host));
|
||||||
} else {
|
} else {
|
||||||
dispatch(fetchSoapboxJson());
|
dispatch(fetchSoapboxJson(host));
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(fetchSoapboxJson());
|
dispatch(fetchSoapboxJson(host));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchSoapboxJson() {
|
// Tries to remember the config from browser storage before fetching it
|
||||||
|
export function loadSoapboxConfig() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const host = getHost(getState());
|
||||||
|
|
||||||
|
return dispatch(rememberSoapboxConfig(host)).finally(() => {
|
||||||
|
return dispatch(fetchSoapboxConfig(host));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchSoapboxJson(host) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
staticClient.get('/instance/soapbox.json').then(({ data }) => {
|
staticClient.get('/instance/soapbox.json').then(({ data }) => {
|
||||||
if (!isObject(data)) throw 'soapbox.json failed';
|
if (!isObject(data)) throw 'soapbox.json failed';
|
||||||
dispatch(importSoapboxConfig(data));
|
dispatch(importSoapboxConfig(data, host));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(soapboxConfigFail(error));
|
dispatch(soapboxConfigFail(error, host));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function importSoapboxConfig(soapboxConfig) {
|
export function importSoapboxConfig(soapboxConfig, host) {
|
||||||
if (!soapboxConfig.brandColor) {
|
if (!soapboxConfig.brandColor) {
|
||||||
soapboxConfig.brandColor = '#0482d8';
|
soapboxConfig.brandColor = '#0482d8';
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: SOAPBOX_CONFIG_REQUEST_SUCCESS,
|
type: SOAPBOX_CONFIG_REQUEST_SUCCESS,
|
||||||
soapboxConfig,
|
soapboxConfig,
|
||||||
|
host,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function soapboxConfigFail(error) {
|
export function soapboxConfigFail(error, host) {
|
||||||
return {
|
return {
|
||||||
type: SOAPBOX_CONFIG_REQUEST_FAIL,
|
type: SOAPBOX_CONFIG_REQUEST_FAIL,
|
||||||
error,
|
error,
|
||||||
skipAlert: true,
|
skipAlert: true,
|
||||||
|
host,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
import { shouldHaveCard } from 'soapbox/utils/status';
|
||||||
|
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
import { deleteFromTimelines } from './timelines';
|
|
||||||
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
||||||
import { openModal } from './modal';
|
import { openModal } from './modal';
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { deleteFromTimelines } from './timelines';
|
||||||
|
|
||||||
export const STATUS_CREATE_REQUEST = 'STATUS_CREATE_REQUEST';
|
export const STATUS_CREATE_REQUEST = 'STATUS_CREATE_REQUEST';
|
||||||
export const STATUS_CREATE_SUCCESS = 'STATUS_CREATE_SUCCESS';
|
export const STATUS_CREATE_SUCCESS = 'STATUS_CREATE_SUCCESS';
|
||||||
|
@ -33,13 +37,9 @@ export const STATUS_HIDE = 'STATUS_HIDE';
|
||||||
|
|
||||||
export const REDRAFT = 'REDRAFT';
|
export const REDRAFT = 'REDRAFT';
|
||||||
|
|
||||||
export function fetchStatusRequest(id, skipLoading) {
|
const statusExists = (getState, statusId) => {
|
||||||
return {
|
return getState().getIn(['statuses', statusId], null) !== null;
|
||||||
type: STATUS_FETCH_REQUEST,
|
};
|
||||||
id,
|
|
||||||
skipLoading,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createStatus(params, idempotencyKey) {
|
export function createStatus(params, idempotencyKey) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
|
@ -48,8 +48,31 @@ export function createStatus(params, idempotencyKey) {
|
||||||
return api(getState).post('/api/v1/statuses', params, {
|
return api(getState).post('/api/v1/statuses', params, {
|
||||||
headers: { 'Idempotency-Key': idempotencyKey },
|
headers: { 'Idempotency-Key': idempotencyKey },
|
||||||
}).then(({ data: status }) => {
|
}).then(({ data: status }) => {
|
||||||
|
// The backend might still be processing the rich media attachment
|
||||||
|
if (!status.card && shouldHaveCard(status)) {
|
||||||
|
status.expectsCard = true;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(importFetchedStatus(status, idempotencyKey));
|
dispatch(importFetchedStatus(status, idempotencyKey));
|
||||||
dispatch({ type: STATUS_CREATE_SUCCESS, status, params, idempotencyKey });
|
dispatch({ type: STATUS_CREATE_SUCCESS, status, params, idempotencyKey });
|
||||||
|
|
||||||
|
// Poll the backend for the updated card
|
||||||
|
if (status.expectsCard) {
|
||||||
|
const delay = 1000;
|
||||||
|
|
||||||
|
const poll = (retries = 5) => {
|
||||||
|
api(getState).get(`/api/v1/statuses/${status.id}`).then(response => {
|
||||||
|
if (response.data && response.data.card) {
|
||||||
|
dispatch(importFetchedStatus(response.data));
|
||||||
|
} else if (retries > 0 && response.status === 200) {
|
||||||
|
setTimeout(() => poll(retries - 1), delay);
|
||||||
|
}
|
||||||
|
}).catch(console.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(() => poll(), delay);
|
||||||
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: STATUS_CREATE_FAIL, error, params, idempotencyKey });
|
dispatch({ type: STATUS_CREATE_FAIL, error, params, idempotencyKey });
|
||||||
|
@ -60,48 +83,32 @@ export function createStatus(params, idempotencyKey) {
|
||||||
|
|
||||||
export function fetchStatus(id) {
|
export function fetchStatus(id) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const skipLoading = getState().getIn(['statuses', id], null) !== null;
|
const skipLoading = statusExists(getState, id);
|
||||||
|
|
||||||
dispatch(fetchContext(id));
|
dispatch({ type: STATUS_FETCH_REQUEST, id, skipLoading });
|
||||||
|
|
||||||
if (skipLoading) {
|
return api(getState).get(`/api/v1/statuses/${id}`).then(({ data: status }) => {
|
||||||
return;
|
dispatch(importFetchedStatus(status));
|
||||||
}
|
dispatch({ type: STATUS_FETCH_SUCCESS, status, skipLoading });
|
||||||
|
return status;
|
||||||
dispatch(fetchStatusRequest(id, skipLoading));
|
|
||||||
|
|
||||||
api(getState).get(`/api/v1/statuses/${id}`).then(response => {
|
|
||||||
dispatch(importFetchedStatus(response.data));
|
|
||||||
dispatch(fetchStatusSuccess(response.data, skipLoading));
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(fetchStatusFail(id, error, skipLoading));
|
dispatch({ type: STATUS_FETCH_FAIL, id, error, skipLoading, skipAlert: true });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchStatusSuccess(status, skipLoading) {
|
|
||||||
return {
|
|
||||||
type: STATUS_FETCH_SUCCESS,
|
|
||||||
status,
|
|
||||||
skipLoading,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchStatusFail(id, error, skipLoading) {
|
|
||||||
return {
|
|
||||||
type: STATUS_FETCH_FAIL,
|
|
||||||
id,
|
|
||||||
error,
|
|
||||||
skipLoading,
|
|
||||||
skipAlert: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function redraft(status, raw_text) {
|
export function redraft(status, raw_text) {
|
||||||
return {
|
return (dispatch, getState) => {
|
||||||
type: REDRAFT,
|
const state = getState();
|
||||||
status,
|
const instance = state.get('instance');
|
||||||
raw_text,
|
const { explicitAddressing } = getFeatures(instance);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: REDRAFT,
|
||||||
|
status,
|
||||||
|
raw_text,
|
||||||
|
explicitAddressing,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,10 +122,10 @@ export function deleteStatus(id, routerHistory, withRedraft = false) {
|
||||||
status = status.set('poll', getState().getIn(['polls', status.get('poll')]));
|
status = status.set('poll', getState().getIn(['polls', status.get('poll')]));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(deleteStatusRequest(id));
|
dispatch({ type: STATUS_DELETE_REQUEST, id });
|
||||||
|
|
||||||
api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
|
api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
|
||||||
dispatch(deleteStatusSuccess(id));
|
dispatch({ type: STATUS_DELETE_SUCCESS, id });
|
||||||
dispatch(deleteFromTimelines(id));
|
dispatch(deleteFromTimelines(id));
|
||||||
|
|
||||||
if (withRedraft) {
|
if (withRedraft) {
|
||||||
|
@ -126,73 +133,37 @@ export function deleteStatus(id, routerHistory, withRedraft = false) {
|
||||||
dispatch(openModal('COMPOSE'));
|
dispatch(openModal('COMPOSE'));
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(deleteStatusFail(id, error));
|
dispatch({ type: STATUS_DELETE_FAIL, id, error });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteStatusRequest(id) {
|
|
||||||
return {
|
|
||||||
type: STATUS_DELETE_REQUEST,
|
|
||||||
id: id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteStatusSuccess(id) {
|
|
||||||
return {
|
|
||||||
type: STATUS_DELETE_SUCCESS,
|
|
||||||
id: id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteStatusFail(id, error) {
|
|
||||||
return {
|
|
||||||
type: STATUS_DELETE_FAIL,
|
|
||||||
id: id,
|
|
||||||
error: error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchContext(id) {
|
export function fetchContext(id) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(fetchContextRequest(id));
|
dispatch({ type: CONTEXT_FETCH_REQUEST, id });
|
||||||
|
|
||||||
api(getState).get(`/api/v1/statuses/${id}/context`).then(response => {
|
|
||||||
dispatch(importFetchedStatuses(response.data.ancestors.concat(response.data.descendants)));
|
|
||||||
dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants));
|
|
||||||
|
|
||||||
|
return api(getState).get(`/api/v1/statuses/${id}/context`).then(({ data: context }) => {
|
||||||
|
const { ancestors, descendants } = context;
|
||||||
|
const statuses = ancestors.concat(descendants);
|
||||||
|
dispatch(importFetchedStatuses(statuses));
|
||||||
|
dispatch({ type: CONTEXT_FETCH_SUCCESS, id, ancestors, descendants });
|
||||||
|
return context;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (error.response && error.response.status === 404) {
|
if (error.response && error.response.status === 404) {
|
||||||
dispatch(deleteFromTimelines(id));
|
dispatch(deleteFromTimelines(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(fetchContextFail(id, error));
|
dispatch({ type: CONTEXT_FETCH_FAIL, id, error, skipAlert: true });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchContextRequest(id) {
|
export function fetchStatusWithContext(id) {
|
||||||
return {
|
return (dispatch, getState) => {
|
||||||
type: CONTEXT_FETCH_REQUEST,
|
return Promise.all([
|
||||||
id,
|
dispatch(fetchContext(id)),
|
||||||
};
|
dispatch(fetchStatus(id)),
|
||||||
}
|
]);
|
||||||
|
|
||||||
export function fetchContextSuccess(id, ancestors, descendants) {
|
|
||||||
return {
|
|
||||||
type: CONTEXT_FETCH_SUCCESS,
|
|
||||||
id,
|
|
||||||
ancestors,
|
|
||||||
descendants,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchContextFail(id, error) {
|
|
||||||
return {
|
|
||||||
type: CONTEXT_FETCH_FAIL,
|
|
||||||
id,
|
|
||||||
error,
|
|
||||||
skipAlert: true,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,74 +171,28 @@ export function muteStatus(id) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
if (!isLoggedIn(getState)) return;
|
if (!isLoggedIn(getState)) return;
|
||||||
|
|
||||||
dispatch(muteStatusRequest(id));
|
dispatch({ type: STATUS_MUTE_REQUEST, id });
|
||||||
|
|
||||||
api(getState).post(`/api/v1/statuses/${id}/mute`).then(() => {
|
api(getState).post(`/api/v1/statuses/${id}/mute`).then(() => {
|
||||||
dispatch(muteStatusSuccess(id));
|
dispatch({ type: STATUS_MUTE_SUCCESS, id });
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(muteStatusFail(id, error));
|
dispatch({ type: STATUS_MUTE_FAIL, id, error });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function muteStatusRequest(id) {
|
|
||||||
return {
|
|
||||||
type: STATUS_MUTE_REQUEST,
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function muteStatusSuccess(id) {
|
|
||||||
return {
|
|
||||||
type: STATUS_MUTE_SUCCESS,
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function muteStatusFail(id, error) {
|
|
||||||
return {
|
|
||||||
type: STATUS_MUTE_FAIL,
|
|
||||||
id,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unmuteStatus(id) {
|
export function unmuteStatus(id) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
if (!isLoggedIn(getState)) return;
|
if (!isLoggedIn(getState)) return;
|
||||||
|
|
||||||
dispatch(unmuteStatusRequest(id));
|
dispatch({ type: STATUS_UNMUTE_REQUEST, id });
|
||||||
|
|
||||||
api(getState).post(`/api/v1/statuses/${id}/unmute`).then(() => {
|
api(getState).post(`/api/v1/statuses/${id}/unmute`).then(() => {
|
||||||
dispatch(unmuteStatusSuccess(id));
|
dispatch({ type: STATUS_UNMUTE_SUCCESS, id });
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(unmuteStatusFail(id, error));
|
dispatch({ type: STATUS_UNMUTE_FAIL, id, error });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unmuteStatusRequest(id) {
|
|
||||||
return {
|
|
||||||
type: STATUS_UNMUTE_REQUEST,
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unmuteStatusSuccess(id) {
|
|
||||||
return {
|
|
||||||
type: STATUS_UNMUTE_SUCCESS,
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unmuteStatusFail(id, error) {
|
|
||||||
return {
|
|
||||||
type: STATUS_UNMUTE_FAIL,
|
|
||||||
id,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hideStatus(ids) {
|
export function hideStatus(ids) {
|
||||||
if (!Array.isArray(ids)) {
|
if (!Array.isArray(ids)) {
|
||||||
ids = [ids];
|
ids = [ids];
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
|
import messages from 'soapbox/locales/messages';
|
||||||
|
|
||||||
import { connectStream } from '../stream';
|
import { connectStream } from '../stream';
|
||||||
|
|
||||||
|
import { updateConversations } from './conversations';
|
||||||
|
import { fetchFilters } from './filters';
|
||||||
|
import { updateNotificationsQueue, expandNotifications } from './notifications';
|
||||||
import {
|
import {
|
||||||
deleteFromTimelines,
|
deleteFromTimelines,
|
||||||
expandHomeTimeline,
|
expandHomeTimeline,
|
||||||
|
@ -6,11 +13,6 @@ import {
|
||||||
disconnectTimeline,
|
disconnectTimeline,
|
||||||
processTimelineUpdate,
|
processTimelineUpdate,
|
||||||
} from './timelines';
|
} from './timelines';
|
||||||
import { updateNotificationsQueue, expandNotifications } from './notifications';
|
|
||||||
import { updateConversations } from './conversations';
|
|
||||||
import { fetchFilters } from './filters';
|
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
|
||||||
import messages from 'soapbox/locales/messages';
|
|
||||||
|
|
||||||
export const STREAMING_CHAT_UPDATE = 'STREAMING_CHAT_UPDATE';
|
export const STREAMING_CHAT_UPDATE = 'STREAMING_CHAT_UPDATE';
|
||||||
export const STREAMING_FOLLOW_RELATIONSHIPS_UPDATE = 'STREAMING_FOLLOW_RELATIONSHIPS_UPDATE';
|
export const STREAMING_FOLLOW_RELATIONSHIPS_UPDATE = 'STREAMING_FOLLOW_RELATIONSHIPS_UPDATE';
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import api from '../api';
|
|
||||||
import { importFetchedAccounts } from './importer';
|
|
||||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
|
|
||||||
|
import api from '../api';
|
||||||
|
|
||||||
import { fetchRelationships } from './accounts';
|
import { fetchRelationships } from './accounts';
|
||||||
|
import { importFetchedAccounts } from './importer';
|
||||||
|
|
||||||
export const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST';
|
export const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST';
|
||||||
export const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS';
|
export const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS';
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
|
||||||
import api, { getLinks } from '../api';
|
|
||||||
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
||||||
|
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
import { shouldFilter } from 'soapbox/utils/timelines';
|
import { shouldFilter } from 'soapbox/utils/timelines';
|
||||||
|
|
||||||
|
import api, { getLinks } from '../api';
|
||||||
|
|
||||||
|
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
||||||
|
|
||||||
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
|
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
|
||||||
export const TIMELINE_DELETE = 'TIMELINE_DELETE';
|
export const TIMELINE_DELETE = 'TIMELINE_DELETE';
|
||||||
export const TIMELINE_CLEAR = 'TIMELINE_CLEAR';
|
export const TIMELINE_CLEAR = 'TIMELINE_CLEAR';
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import LinkHeader from 'http-link-header';
|
import LinkHeader from 'http-link-header';
|
||||||
import { getAccessToken, getAppToken, parseBaseURL } from 'soapbox/utils/auth';
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import { BACKEND_URL, FE_SUBDIRECTORY } from 'soapbox/build_config';
|
import { BACKEND_URL, FE_SUBDIRECTORY } from 'soapbox/build_config';
|
||||||
|
import { getAccessToken, getAppToken, parseBaseURL } from 'soapbox/utils/auth';
|
||||||
import { isURL } from 'soapbox/utils/auth';
|
import { isURL } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,9 +4,10 @@ import 'intl';
|
||||||
import 'intl/locale-data/jsonp/en';
|
import 'intl/locale-data/jsonp/en';
|
||||||
import 'es6-symbol/implement';
|
import 'es6-symbol/implement';
|
||||||
import includes from 'array-includes';
|
import includes from 'array-includes';
|
||||||
|
import isNaN from 'is-nan';
|
||||||
import assign from 'object-assign';
|
import assign from 'object-assign';
|
||||||
import values from 'object.values';
|
import values from 'object.values';
|
||||||
import isNaN from 'is-nan';
|
|
||||||
import { decode as decodeBase64 } from './utils/base64';
|
import { decode as decodeBase64 } from './utils/base64';
|
||||||
|
|
||||||
if (!Array.prototype.includes) {
|
if (!Array.prototype.includes) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
export default function InlineSVG({ src }) {
|
export default function InlineSVG({ src }) {
|
||||||
return <svg id={src} />;
|
return <svg id={src} />;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
import AutosuggestEmoji from '../autosuggest_emoji';
|
import AutosuggestEmoji from '../autosuggest_emoji';
|
||||||
|
|
||||||
describe('<AutosuggestEmoji />', () => {
|
describe('<AutosuggestEmoji />', () => {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
|
||||||
import { fromJS } from 'immutable';
|
import { fromJS } from 'immutable';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { createComponent } from 'soapbox/test_helpers';
|
import { createComponent } from 'soapbox/test_helpers';
|
||||||
|
|
||||||
import Avatar from '../avatar';
|
import Avatar from '../avatar';
|
||||||
|
|
||||||
describe('<Avatar />', () => {
|
describe('<Avatar />', () => {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React from 'react';
|
|
||||||
import { fromJS } from 'immutable';
|
import { fromJS } from 'immutable';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { createComponent } from 'soapbox/test_helpers';
|
import { createComponent } from 'soapbox/test_helpers';
|
||||||
|
|
||||||
import AvatarOverlay from '../avatar_overlay';
|
import AvatarOverlay from '../avatar_overlay';
|
||||||
|
|
||||||
describe('<AvatarOverlay', () => {
|
describe('<AvatarOverlay', () => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
import Badge from '../badge';
|
import Badge from '../badge';
|
||||||
|
|
||||||
describe('<Badge />', () => {
|
describe('<Badge />', () => {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
import Button from '../button';
|
import Button from '../button';
|
||||||
|
|
||||||
describe('<Button />', () => {
|
describe('<Button />', () => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
import Column from '../column';
|
import Column from '../column';
|
||||||
|
|
||||||
describe('<Column />', () => {
|
describe('<Column />', () => {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ColumnBackButton from '../column_back_button';
|
|
||||||
import { createComponent } from 'soapbox/test_helpers';
|
import { createComponent } from 'soapbox/test_helpers';
|
||||||
|
|
||||||
|
import ColumnBackButton from '../column_back_button';
|
||||||
|
|
||||||
describe('<ColumnBackButton />', () => {
|
describe('<ColumnBackButton />', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
const component = createComponent(<ColumnBackButton />);
|
const component = createComponent(<ColumnBackButton />);
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import React from 'react';
|
|
||||||
import { fromJS } from 'immutable';
|
import { fromJS } from 'immutable';
|
||||||
import DisplayName from '../display_name';
|
import React from 'react';
|
||||||
|
|
||||||
import { createComponent } from 'soapbox/test_helpers';
|
import { createComponent } from 'soapbox/test_helpers';
|
||||||
|
|
||||||
|
import DisplayName from '../display_name';
|
||||||
|
|
||||||
describe('<DisplayName />', () => {
|
describe('<DisplayName />', () => {
|
||||||
it('renders display name + account name', () => {
|
it('renders display name + account name', () => {
|
||||||
const account = fromJS({
|
const account = fromJS({
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { createComponent } from 'soapbox/test_helpers';
|
import { createComponent } from 'soapbox/test_helpers';
|
||||||
|
|
||||||
import EmojiSelector from '../emoji_selector';
|
import EmojiSelector from '../emoji_selector';
|
||||||
|
|
||||||
describe('<EmojiSelector />', () => {
|
describe('<EmojiSelector />', () => {
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import TimelineQueueButtonHeader from '../timeline_queue_button_header';
|
|
||||||
import { createComponent } from 'soapbox/test_helpers';
|
|
||||||
import { defineMessages } from 'react-intl';
|
import { defineMessages } from 'react-intl';
|
||||||
|
|
||||||
|
import { createComponent } from 'soapbox/test_helpers';
|
||||||
|
|
||||||
|
import TimelineQueueButtonHeader from '../timeline_queue_button_header';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
queue: { id: 'status_list.queue_label', defaultMessage: 'Click to see {count} new {count, plural, one {post} other {posts}}' },
|
queue: { id: 'status_list.queue_label', defaultMessage: 'Click to see {count} new {count, plural, one {post} other {posts}}' },
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import React, { Fragment } from 'react';
|
import classNames from 'classnames';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import emojify from 'soapbox/features/emoji/emoji';
|
||||||
|
import ActionButton from 'soapbox/features/ui/components/action_button';
|
||||||
|
|
||||||
import Avatar from './avatar';
|
import Avatar from './avatar';
|
||||||
import DisplayName from './display_name';
|
import DisplayName from './display_name';
|
||||||
import Permalink from './permalink';
|
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import IconButton from './icon_button';
|
import IconButton from './icon_button';
|
||||||
import ActionButton from 'soapbox/features/ui/components/action_button';
|
import Permalink from './permalink';
|
||||||
import RelativeTimestamp from './relative_timestamp';
|
import RelativeTimestamp from './relative_timestamp';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import emojify from 'soapbox/features/emoji/emoji';
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
import Icon from 'soapbox/components/icon';
|
|
||||||
import AutosuggestAccountInput from 'soapbox/components/autosuggest_account_input';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import AutosuggestAccountInput from 'soapbox/components/autosuggest_account_input';
|
||||||
|
import Icon from 'soapbox/components/icon';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
placeholder: { id: 'account_search.placeholder', defaultMessage: 'Search for an account' },
|
placeholder: { id: 'account_search.placeholder', defaultMessage: 'Search for an account' },
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
import Icon from 'soapbox/components/icon';
|
import Icon from 'soapbox/components/icon';
|
||||||
|
|
||||||
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
|
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { MediaGallery } from 'soapbox/features/ui/util/async-components';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { openModal } from 'soapbox/actions/modal';
|
import { openModal } from 'soapbox/actions/modal';
|
||||||
import Bundle from 'soapbox/features/ui/components/bundle';
|
import Bundle from 'soapbox/features/ui/components/bundle';
|
||||||
|
import { MediaGallery } from 'soapbox/features/ui/util/async-components';
|
||||||
|
|
||||||
export default @connect()
|
export default @connect()
|
||||||
class AttachmentThumbs extends ImmutablePureComponent {
|
class AttachmentThumbs extends ImmutablePureComponent {
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import React from 'react';
|
|
||||||
import AutosuggestInput from './autosuggest_input';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { CancelToken } from 'axios';
|
import { CancelToken } from 'axios';
|
||||||
|
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
|
||||||
import { accountSearch } from 'soapbox/actions/accounts';
|
import { accountSearch } from 'soapbox/actions/accounts';
|
||||||
import { throttle } from 'lodash';
|
|
||||||
|
import AutosuggestInput from './autosuggest_input';
|
||||||
|
|
||||||
const noOp = () => {};
|
const noOp = () => {};
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light';
|
import React from 'react';
|
||||||
|
|
||||||
import { joinPublicPath } from 'soapbox/utils/static';
|
import { joinPublicPath } from 'soapbox/utils/static';
|
||||||
|
|
||||||
|
import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light';
|
||||||
|
|
||||||
export default class AutosuggestEmoji extends React.PureComponent {
|
export default class AutosuggestEmoji extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import React from 'react';
|
|
||||||
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
|
|
||||||
import AutosuggestEmoji from './autosuggest_emoji';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { isRtl } from '../rtl';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { List as ImmutableList } from 'immutable';
|
import { List as ImmutableList } from 'immutable';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
|
import Icon from 'soapbox/components/icon';
|
||||||
|
|
||||||
|
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
|
||||||
|
import { isRtl } from '../rtl';
|
||||||
|
|
||||||
|
import AutosuggestEmoji from './autosuggest_emoji';
|
||||||
|
|
||||||
const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
|
const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
|
||||||
let word;
|
let word;
|
||||||
|
@ -47,21 +51,28 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
||||||
onKeyUp: PropTypes.func,
|
onKeyUp: PropTypes.func,
|
||||||
onKeyDown: PropTypes.func,
|
onKeyDown: PropTypes.func,
|
||||||
autoFocus: PropTypes.bool,
|
autoFocus: PropTypes.bool,
|
||||||
|
autoSelect: PropTypes.bool,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
searchTokens: PropTypes.arrayOf(PropTypes.string),
|
searchTokens: PropTypes.arrayOf(PropTypes.string),
|
||||||
maxLength: PropTypes.number,
|
maxLength: PropTypes.number,
|
||||||
|
menu: PropTypes.arrayOf(PropTypes.object),
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
|
autoSelect: true,
|
||||||
searchTokens: ImmutableList(['@', ':', '#']),
|
searchTokens: ImmutableList(['@', ':', '#']),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getFirstIndex = () => {
|
||||||
|
return this.props.autoSelect ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
suggestionsHidden: true,
|
suggestionsHidden: true,
|
||||||
focused: false,
|
focused: false,
|
||||||
selectedSuggestion: 0,
|
selectedSuggestion: this.getFirstIndex(),
|
||||||
lastToken: null,
|
lastToken: null,
|
||||||
tokenStart: 0,
|
tokenStart: 0,
|
||||||
};
|
};
|
||||||
|
@ -81,8 +92,10 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown = (e) => {
|
onKeyDown = (e) => {
|
||||||
const { suggestions, disabled } = this.props;
|
const { suggestions, menu, disabled } = this.props;
|
||||||
const { selectedSuggestion, suggestionsHidden } = this.state;
|
const { selectedSuggestion, suggestionsHidden } = this.state;
|
||||||
|
const firstIndex = this.getFirstIndex();
|
||||||
|
const lastIndex = suggestions.size + (menu || []).length - 1;
|
||||||
|
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -106,26 +119,33 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
if (!suggestionsHidden && (suggestions.size > 0 || menu)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
|
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, lastIndex) });
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'ArrowUp':
|
case 'ArrowUp':
|
||||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
if (!suggestionsHidden && (suggestions.size > 0 || menu)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
|
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, firstIndex) });
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
case 'Tab':
|
case 'Tab':
|
||||||
// Select suggestion
|
// Select suggestion
|
||||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
if (!suggestionsHidden && selectedSuggestion > -1 && (suggestions.size > 0 || menu)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
|
this.setState({ selectedSuggestion: firstIndex });
|
||||||
|
|
||||||
|
if (selectedSuggestion < suggestions.size) {
|
||||||
|
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
|
||||||
|
} else {
|
||||||
|
const item = menu[selectedSuggestion - suggestions.size];
|
||||||
|
this.handleMenuItemAction(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -186,11 +206,51 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMenuItemAction = item => {
|
||||||
|
this.onBlur();
|
||||||
|
item.action();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMenuItemClick = item => {
|
||||||
|
return e => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.handleMenuItemAction(item);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMenu = () => {
|
||||||
|
const { menu, suggestions } = this.props;
|
||||||
|
const { selectedSuggestion } = this.state;
|
||||||
|
|
||||||
|
if (!menu) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return menu.map((item, i) => (
|
||||||
|
<a
|
||||||
|
className={classNames('autosuggest-input__action', { selected: suggestions.size - selectedSuggestion === i })}
|
||||||
|
href='#'
|
||||||
|
role='button'
|
||||||
|
tabIndex='0'
|
||||||
|
onMouseDown={this.handleMenuItemClick(item)}
|
||||||
|
key={i}
|
||||||
|
>
|
||||||
|
{item.icon && (
|
||||||
|
<Icon src={item.icon} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span>{item.text}</span>
|
||||||
|
</a>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength } = this.props;
|
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength, menu } = this.props;
|
||||||
const { suggestionsHidden } = this.state;
|
const { suggestionsHidden } = this.state;
|
||||||
const style = { direction: 'ltr' };
|
const style = { direction: 'ltr' };
|
||||||
|
|
||||||
|
const visible = !suggestionsHidden && (!suggestions.isEmpty() || (menu && value));
|
||||||
|
|
||||||
if (isRtl(value)) {
|
if (isRtl(value)) {
|
||||||
style.direction = 'rtl';
|
style.direction = 'rtl';
|
||||||
}
|
}
|
||||||
|
@ -220,8 +280,9 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
|
<div className={classNames('autosuggest-textarea__suggestions', { 'autosuggest-textarea__suggestions--visible': visible })}>
|
||||||
{suggestions.map(this.renderSuggestion)}
|
{suggestions.map(this.renderSuggestion)}
|
||||||
|
{this.renderMenu()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import React from 'react';
|
import classNames from 'classnames';
|
||||||
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
|
|
||||||
import AutosuggestEmoji from './autosuggest_emoji';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { isRtl } from '../rtl';
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import Textarea from 'react-textarea-autosize';
|
import Textarea from 'react-textarea-autosize';
|
||||||
import classNames from 'classnames';
|
|
||||||
|
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
|
||||||
|
import { isRtl } from '../rtl';
|
||||||
|
|
||||||
|
import AutosuggestEmoji from './autosuggest_emoji';
|
||||||
|
|
||||||
const textAtCursorMatchesToken = (str, caretPosition) => {
|
const textAtCursorMatchesToken = (str, caretPosition) => {
|
||||||
let word;
|
let word;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
import StillImage from 'soapbox/components/still_image';
|
import StillImage from 'soapbox/components/still_image';
|
||||||
|
|
||||||
export default class Avatar extends React.PureComponent {
|
export default class Avatar extends React.PureComponent {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
import StillImage from 'soapbox/components/still_image';
|
import StillImage from 'soapbox/components/still_image';
|
||||||
|
|
||||||
export default class AvatarComposite extends React.PureComponent {
|
export default class AvatarComposite extends React.PureComponent {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
import StillImage from 'soapbox/components/still_image';
|
import StillImage from 'soapbox/components/still_image';
|
||||||
|
|
||||||
export default class AvatarOverlay extends React.PureComponent {
|
export default class AvatarOverlay extends React.PureComponent {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
const Badge = (props) => (
|
const Badge = (props) => (
|
||||||
<span className={'badge badge--' + props.slug}>{props.title}</span>
|
<span className={'badge badge--' + props.slug}>{props.title}</span>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
import { decode } from 'blurhash';
|
import { decode } from 'blurhash';
|
||||||
import React, { useRef, useEffect } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { useRef, useEffect } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef BlurhashPropsBase
|
* @typedef BlurhashPropsBase
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
|
|
||||||
export default class Button extends React.PureComponent {
|
export default class Button extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
type: PropTypes.string,
|
||||||
text: PropTypes.node,
|
text: PropTypes.node,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
to: PropTypes.string,
|
to: PropTypes.string,
|
||||||
|
@ -53,6 +55,7 @@ export default class Button extends React.PureComponent {
|
||||||
|
|
||||||
const btn = (
|
const btn = (
|
||||||
<button
|
<button
|
||||||
|
type={this.props.type}
|
||||||
className={className}
|
className={className}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
export default class Column extends React.PureComponent {
|
export default class Column extends React.PureComponent {
|
||||||
|
|
||||||
|
@ -11,6 +11,10 @@ export default class Column extends React.PureComponent {
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.node = c;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, label, children, transparent, ...rest } = this.props;
|
const { className, label, children, transparent, ...rest } = this.props;
|
||||||
|
|
||||||
|
@ -20,6 +24,7 @@ export default class Column extends React.PureComponent {
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
className={classNames('column', className, { 'column--transparent': transparent })}
|
className={classNames('column', className, { 'column--transparent': transparent })}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
ref={this.setRef}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Icon from 'soapbox/components/icon';
|
import Icon from 'soapbox/components/icon';
|
||||||
|
|
||||||
export default class ColumnBackButton extends React.PureComponent {
|
export default class ColumnBackButton extends React.PureComponent {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
// import classNames from 'classnames';
|
// import classNames from 'classnames';
|
||||||
// import { injectIntl, defineMessages } from 'react-intl';
|
// import { injectIntl, defineMessages } from 'react-intl';
|
||||||
// import Icon from 'soapbox/components/icon';
|
// import Icon from 'soapbox/components/icon';
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import VerificationBadge from './verification_badge';
|
import { connect } from 'react-redux';
|
||||||
import { getAcct } from '../utils/accounts';
|
|
||||||
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
|
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
|
||||||
|
import { isVerified } from 'soapbox/utils/accounts';
|
||||||
|
import { displayFqn } from 'soapbox/utils/state';
|
||||||
|
|
||||||
|
import { getAcct } from '../utils/accounts';
|
||||||
|
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import RelativeTimestamp from './relative_timestamp';
|
import RelativeTimestamp from './relative_timestamp';
|
||||||
import { displayFqn } from 'soapbox/utils/state';
|
import VerificationBadge from './verification_badge';
|
||||||
import { isVerified } from 'soapbox/utils/accounts';
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import IconButton from './icon_button';
|
import React from 'react';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import IconButton from './icon_button';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
||||||
|
@ -32,7 +33,7 @@ class Account extends ImmutablePureComponent {
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className='domain__buttons'>
|
<div className='domain__buttons'>
|
||||||
<IconButton active icon='unlock' title={intl.formatMessage(messages.unblockDomain, { domain })} onClick={this.handleDomainUnblock} />
|
<IconButton active src={require('@tabler/icons/icons/lock-open.svg')} title={intl.formatMessage(messages.unblockDomain, { domain })} onClick={this.handleDomainUnblock} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import React from 'react';
|
import classNames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import IconButton from './icon_button';
|
|
||||||
import Overlay from 'react-overlays/lib/Overlay';
|
|
||||||
import Motion from '../features/ui/util/optional_motion';
|
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import spring from 'react-motion/lib/spring';
|
||||||
|
import Overlay from 'react-overlays/lib/Overlay';
|
||||||
|
|
||||||
|
import Icon from 'soapbox/components/icon';
|
||||||
|
|
||||||
|
import Motion from '../features/ui/util/optional_motion';
|
||||||
|
|
||||||
|
import IconButton from './icon_button';
|
||||||
|
|
||||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||||
let id = 0;
|
let id = 0;
|
||||||
|
@ -146,10 +151,10 @@ class DropdownMenu extends React.PureComponent {
|
||||||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { text, href, to, newTab, isLogout } = option;
|
const { text, href, to, newTab, isLogout, icon, destructive } = option;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className='dropdown-menu__item' key={`${text}-${i}`}>
|
<li className={classNames('dropdown-menu__item', { destructive })} key={`${text}-${i}`}>
|
||||||
<a
|
<a
|
||||||
href={href || to || '#'}
|
href={href || to || '#'}
|
||||||
role='button'
|
role='button'
|
||||||
|
@ -162,6 +167,7 @@ class DropdownMenu extends React.PureComponent {
|
||||||
target={newTab ? '_blank' : null}
|
target={newTab ? '_blank' : null}
|
||||||
data-method={isLogout ? 'delete' : null}
|
data-method={isLogout ? 'delete' : null}
|
||||||
>
|
>
|
||||||
|
{icon && <Icon src={icon} />}
|
||||||
{text}
|
{text}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -201,6 +207,8 @@ export default class Dropdown extends React.PureComponent {
|
||||||
src: PropTypes.string,
|
src: PropTypes.string,
|
||||||
items: PropTypes.array.isRequired,
|
items: PropTypes.array.isRequired,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
|
active: PropTypes.bool,
|
||||||
|
pressed: PropTypes.bool,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
|
@ -211,6 +219,7 @@ export default class Dropdown extends React.PureComponent {
|
||||||
dropdownPlacement: PropTypes.string,
|
dropdownPlacement: PropTypes.string,
|
||||||
openDropdownId: PropTypes.number,
|
openDropdownId: PropTypes.number,
|
||||||
openedViaKeyboard: PropTypes.bool,
|
openedViaKeyboard: PropTypes.bool,
|
||||||
|
text: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -296,7 +305,7 @@ export default class Dropdown extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { icon, src, items, size, title, disabled, dropdownPlacement, openDropdownId, openedViaKeyboard } = this.props;
|
const { icon, src, items, size, title, disabled, dropdownPlacement, openDropdownId, openedViaKeyboard, active, pressed, text } = this.props;
|
||||||
const open = this.state.id === openDropdownId;
|
const open = this.state.id === openDropdownId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -305,9 +314,11 @@ export default class Dropdown extends React.PureComponent {
|
||||||
icon={icon}
|
icon={icon}
|
||||||
src={src}
|
src={src}
|
||||||
title={title}
|
title={title}
|
||||||
active={open}
|
active={open || active}
|
||||||
|
pressed={pressed}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
size={size}
|
size={size}
|
||||||
|
text={text}
|
||||||
ref={this.setTargetRef}
|
ref={this.setTargetRef}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
onMouseDown={this.handleMouseDown}
|
onMouseDown={this.handleMouseDown}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import React from 'react';
|
import classNames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
import { HotKeys } from 'react-hotkeys';
|
import { HotKeys } from 'react-hotkeys';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import emojify from 'soapbox/features/emoji/emoji';
|
|
||||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
import classNames from 'classnames';
|
import emojify from 'soapbox/features/emoji/emoji';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
allowedEmoji: getSoapboxConfig(state).get('allowedEmoji'),
|
allowedEmoji: getSoapboxConfig(state).get('allowedEmoji'),
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { captureException } from 'soapbox/monitoring';
|
|
||||||
import Icon from 'soapbox/components/icon';
|
import Icon from 'soapbox/components/icon';
|
||||||
|
import { captureException } from 'soapbox/monitoring';
|
||||||
|
|
||||||
export default class ErrorBoundary extends React.PureComponent {
|
export default class ErrorBoundary extends React.PureComponent {
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
import { isIOS } from 'soapbox/is_mobile';
|
import { isIOS } from 'soapbox/is_mobile';
|
||||||
|
|
||||||
export default class ExtendedVideoPlayer extends React.PureComponent {
|
export default class ExtendedVideoPlayer extends React.PureComponent {
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default class FilterBar extends React.PureComponent {
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
router: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
items: PropTypes.array.isRequired,
|
||||||
|
active: PropTypes.string,
|
||||||
|
className: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
mounted: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.node.addEventListener('keydown', this.handleKeyDown, false);
|
||||||
|
window.addEventListener('resize', this.handleResize, { passive: true });
|
||||||
|
|
||||||
|
const { left, width } = this.getActiveTabIndicationSize();
|
||||||
|
this.setState({ mounted: true, left, width });
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.node.removeEventListener('keydown', this.handleKeyDown, false);
|
||||||
|
document.removeEventListener('resize', this.handleResize, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResize = debounce(() => {
|
||||||
|
this.setState(this.getActiveTabIndicationSize());
|
||||||
|
}, 300, {
|
||||||
|
trailing: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.active !== prevProps.active) {
|
||||||
|
this.setState(this.getActiveTabIndicationSize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.node = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFocusRef = c => {
|
||||||
|
this.focusedItem = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown = e => {
|
||||||
|
const items = Array.from(this.node.getElementsByTagName('a'));
|
||||||
|
const index = items.indexOf(document.activeElement);
|
||||||
|
let element = null;
|
||||||
|
|
||||||
|
switch(e.key) {
|
||||||
|
case 'ArrowRight':
|
||||||
|
element = items[index+1] || items[0];
|
||||||
|
break;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
element = items[index-1] || items[items.length-1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
element.focus();
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleItemKeyPress = e => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
this.handleClick(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick = e => {
|
||||||
|
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||||
|
const { action, to } = this.props.items[i];
|
||||||
|
|
||||||
|
if (typeof action === 'function') {
|
||||||
|
e.preventDefault();
|
||||||
|
action(e);
|
||||||
|
} else if (to) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.context.router.history.push(to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveTabIndicationSize() {
|
||||||
|
const { active, items } = this.props;
|
||||||
|
|
||||||
|
if (!active || !this.node) return { width: null };
|
||||||
|
|
||||||
|
const index = items.findIndex(({ name }) => name === active);
|
||||||
|
const elements = Array.from(this.node.getElementsByTagName('a'));
|
||||||
|
const element = elements[index];
|
||||||
|
|
||||||
|
if (!element) return { width: null };
|
||||||
|
|
||||||
|
const left = element.offsetLeft;
|
||||||
|
const { width } = element.getBoundingClientRect();
|
||||||
|
|
||||||
|
return { left, width };
|
||||||
|
}
|
||||||
|
|
||||||
|
renderActiveTabIndicator() {
|
||||||
|
const { left, width } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='filter-bar__active' style={{ left, width }} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderItem(option, i) {
|
||||||
|
if (option === null) {
|
||||||
|
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, text, href, to, title } = option;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
key={name}
|
||||||
|
href={href || to || '#'}
|
||||||
|
role='button'
|
||||||
|
tabIndex='0'
|
||||||
|
ref={i === 0 ? this.setFocusRef : null}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
onKeyPress={this.handleItemKeyPress}
|
||||||
|
data-index={i}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { className, items } = this.props;
|
||||||
|
const { mounted } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames('filter-bar', className)} ref={this.setRef}>
|
||||||
|
{mounted && this.renderActiveTabIndicator()}
|
||||||
|
{items.map((option, i) => this.renderItem(option, i))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,9 +5,9 @@
|
||||||
* @see soapbox/components/icon
|
* @see soapbox/components/icon
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
export default class ForkAwesomeIcon extends React.PureComponent {
|
export default class ForkAwesomeIcon extends React.PureComponent {
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,46 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
// import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import Permalink from './permalink';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
||||||
|
|
||||||
import { shortNumberFormat } from '../utils/numbers';
|
import { shortNumberFormat } from '../utils/numbers';
|
||||||
|
|
||||||
const Hashtag = ({ hashtag }) => (
|
import Permalink from './permalink';
|
||||||
<div className='trends__item'>
|
|
||||||
<div className='trends__item__name'>
|
|
||||||
<Permalink href={hashtag.get('url')} to={`/tags/${hashtag.get('name')}`}>
|
|
||||||
#<span>{hashtag.get('name')}</span>
|
|
||||||
</Permalink>
|
|
||||||
|
|
||||||
{hashtag.get('history') && <div className='trends__item__count'>
|
const Hashtag = ({ hashtag }) => {
|
||||||
<FormattedMessage id='trends.count_by_accounts' defaultMessage='{count} {rawCount, plural, one {person} other {people}} talking' values={{ rawCount: hashtag.getIn(['history', 0, 'accounts']), count: <strong>{shortNumberFormat(hashtag.getIn(['history', 0, 'accounts']))}</strong> }} />
|
const count = Number(hashtag.getIn(['history', 0, 'accounts']));
|
||||||
</div>}
|
|
||||||
|
return (
|
||||||
|
<div className='trends__item'>
|
||||||
|
<div className='trends__item__name'>
|
||||||
|
<Permalink href={hashtag.get('url')} to={`/tags/${hashtag.get('name')}`}>
|
||||||
|
#<span>{hashtag.get('name')}</span>
|
||||||
|
</Permalink>
|
||||||
|
|
||||||
|
{hashtag.get('history') && (
|
||||||
|
<div className='trends__item__count'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='trends.count_by_accounts'
|
||||||
|
defaultMessage='{count} {rawCount, plural, one {person} other {people}} talking'
|
||||||
|
values={{
|
||||||
|
rawCount: count,
|
||||||
|
count: <strong>{shortNumberFormat(count)}</strong>,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hashtag.get('history') && (
|
||||||
|
<div className='trends__item__sparkline'>
|
||||||
|
<Sparklines width={50} height={28} data={hashtag.get('history').reverse().map(day => day.get('uses')).toArray()}>
|
||||||
|
<SparklinesCurve style={{ fill: 'none' }} />
|
||||||
|
</Sparklines>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
{/* Pleroma doesn't support tag history yet */}
|
};
|
||||||
{/* hashtag.get('history') && <div className='trends__item__sparkline'>
|
|
||||||
<Sparklines width={50} height={28} data={hashtag.get('history').reverse().map(day => day.get('uses')).toArray()}>
|
|
||||||
<SparklinesCurve style={{ fill: 'none' }} />
|
|
||||||
</Sparklines>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
Hashtag.propTypes = {
|
Hashtag.propTypes = {
|
||||||
hashtag: ImmutablePropTypes.map.isRequired,
|
hashtag: ImmutablePropTypes.map.isRequired,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue