Merge remote-tracking branch 'origin/develop' into custom-imports
|
@ -4,4 +4,5 @@
|
|||
/public/**
|
||||
/tmp/**
|
||||
/coverage/**
|
||||
/custom/**
|
||||
!.eslintrc.js
|
||||
|
|
33
.eslintrc.js
|
@ -4,6 +4,7 @@ module.exports = {
|
|||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:import/typescript',
|
||||
'plugin:compat/recommended',
|
||||
],
|
||||
|
||||
env: {
|
||||
|
@ -21,6 +22,7 @@ module.exports = {
|
|||
|
||||
plugins: [
|
||||
'react',
|
||||
'jsdoc',
|
||||
'jsx-a11y',
|
||||
'import',
|
||||
'promise',
|
||||
|
@ -51,6 +53,14 @@ module.exports = {
|
|||
paths: ['app'],
|
||||
},
|
||||
},
|
||||
polyfills: [
|
||||
'es:all',
|
||||
'fetch',
|
||||
'IntersectionObserver',
|
||||
'Promise',
|
||||
'URL',
|
||||
'URLSearchParams',
|
||||
],
|
||||
},
|
||||
|
||||
rules: {
|
||||
|
@ -65,11 +75,13 @@ module.exports = {
|
|||
],
|
||||
'comma-style': ['warn', 'last'],
|
||||
'space-before-function-paren': ['error', 'never'],
|
||||
'space-infix-ops': 'error',
|
||||
'space-in-parens': ['error', 'never'],
|
||||
'consistent-return': 'error',
|
||||
'keyword-spacing': 'error',
|
||||
'dot-notation': 'error',
|
||||
eqeqeq: 'error',
|
||||
indent: ['error', 2, {
|
||||
SwitchCase: 1, // https://stackoverflow.com/a/53055584/8811886
|
||||
ignoredNodes: ['TemplateLiteral'],
|
||||
}],
|
||||
'jsx-quotes': ['error', 'prefer-single'],
|
||||
|
@ -254,6 +266,7 @@ module.exports = {
|
|||
alphabetize: { order: 'asc' },
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-duplicate-imports': 'error',
|
||||
|
||||
'promise/catch-or-return': 'error',
|
||||
|
||||
|
@ -267,5 +280,23 @@ module.exports = {
|
|||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
},
|
||||
{
|
||||
// Only enforce JSDoc comments on UI components for now.
|
||||
// https://www.npmjs.com/package/eslint-plugin-jsdoc
|
||||
files: ['app/soapbox/components/ui/**/*'],
|
||||
rules: {
|
||||
'jsdoc/require-jsdoc': ['error', {
|
||||
publicOnly: true,
|
||||
require: {
|
||||
ArrowFunctionExpression: true,
|
||||
ClassDeclaration: true,
|
||||
ClassExpression: true,
|
||||
FunctionDeclaration: true,
|
||||
FunctionExpression: true,
|
||||
MethodDefinition: true,
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
1
.gitignore
vendored
|
@ -8,6 +8,7 @@
|
|||
/deploy.sh
|
||||
/.vs/
|
||||
yarn-error.log*
|
||||
/junit.xml
|
||||
|
||||
/static/
|
||||
/static-test/
|
||||
|
|
|
@ -1,27 +1,41 @@
|
|||
image: node:16
|
||||
image: node:18
|
||||
|
||||
variables:
|
||||
NODE_ENV: test
|
||||
|
||||
cache:
|
||||
cache: &cache
|
||||
key:
|
||||
files:
|
||||
- yarn.lock
|
||||
paths:
|
||||
- node_modules
|
||||
- node_modules/
|
||||
policy: pull
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- deps
|
||||
- test
|
||||
- build
|
||||
- deploy
|
||||
|
||||
before_script:
|
||||
- env
|
||||
- yarn
|
||||
deps:
|
||||
stage: deps
|
||||
script: yarn install --ignore-scripts
|
||||
only:
|
||||
changes:
|
||||
- yarn.lock
|
||||
cache:
|
||||
<<: *cache
|
||||
policy: push
|
||||
|
||||
danger:
|
||||
stage: test
|
||||
script:
|
||||
# https://github.com/danger/danger-js/issues/1029#issuecomment-998915436
|
||||
- export CI_MERGE_REQUEST_IID=${CI_OPEN_MERGE_REQUESTS#*!}
|
||||
- npx danger ci
|
||||
allow_failure: true
|
||||
|
||||
lint-js:
|
||||
stage: lint
|
||||
stage: test
|
||||
script: yarn lint:js
|
||||
only:
|
||||
changes:
|
||||
|
@ -33,7 +47,7 @@ lint-js:
|
|||
- ".eslintrc.js"
|
||||
|
||||
lint-sass:
|
||||
stage: lint
|
||||
stage: test
|
||||
script: yarn lint:sass
|
||||
only:
|
||||
changes:
|
||||
|
@ -43,25 +57,43 @@ lint-sass:
|
|||
|
||||
jest:
|
||||
stage: test
|
||||
script: yarn test:coverage
|
||||
script: yarn test:coverage --runInBand
|
||||
only:
|
||||
changes:
|
||||
- "**/*.js"
|
||||
- "**/*.json"
|
||||
- "app/soapbox/**/*"
|
||||
- "webpack/**/*"
|
||||
- "custom/**/*"
|
||||
- "jest.config.js"
|
||||
- "package.json"
|
||||
- "yarn.lock"
|
||||
- ".gitlab-ci.yml"
|
||||
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
|
||||
artifacts:
|
||||
reports:
|
||||
junit: junit.xml
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: .coverage/cobertura-coverage.xml
|
||||
|
||||
nginx-test:
|
||||
stage: test
|
||||
image: nginx:latest
|
||||
before_script: cp installation/mastodon.conf /etc/nginx/conf.d/default.conf
|
||||
script: nginx -t
|
||||
only:
|
||||
changes:
|
||||
- "installation/mastodon.conf"
|
||||
|
||||
build-production:
|
||||
stage: build
|
||||
stage: test
|
||||
script: yarn build
|
||||
variables:
|
||||
NODE_ENV: production
|
||||
artifacts:
|
||||
paths:
|
||||
- static
|
||||
- static
|
||||
|
||||
docs-deploy:
|
||||
stage: deploy
|
||||
|
@ -87,6 +119,15 @@ docs-deploy:
|
|||
# - yarn
|
||||
# - yarn build
|
||||
|
||||
review:
|
||||
stage: deploy
|
||||
environment:
|
||||
name: review/$CI_COMMIT_REF_NAME
|
||||
url: https://$CI_COMMIT_REF_SLUG.git.soapbox.pub
|
||||
script:
|
||||
- npx -y surge static $CI_COMMIT_REF_SLUG.git.soapbox.pub
|
||||
allow_failure: true
|
||||
|
||||
pages:
|
||||
stage: deploy
|
||||
before_script: []
|
||||
|
|
7
.gitlab/issue_templates/Bug.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
### Environment
|
||||
|
||||
* Soapbox version:
|
||||
* Backend (Mastodon, Pleroma, etc):
|
||||
* Browser/OS:
|
||||
|
||||
### Bug description
|
5
.gitlab/merge_request_templates/Default.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
## Summary
|
||||
<!-- Describe your changes in detail -->
|
||||
|
||||
|
||||
## Screenshots (if appropriate):
|
|
@ -1 +1 @@
|
|||
nodejs 16.14.2
|
||||
nodejs 18.2.0
|
||||
|
|
9
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"editorconfig.editorconfig",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"stylelint.vscode-stylelint",
|
||||
"wix.vscode-import-cost"
|
||||
]
|
||||
}
|
58
.vscode/soapbox.code-snippets
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
// Place your soapbox-fe workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
"React component": {
|
||||
"scope": "typescriptreact",
|
||||
"prefix": ["component", "react component"],
|
||||
"body": [
|
||||
"import React from 'react';",
|
||||
"",
|
||||
"interface I${1:Component} {",
|
||||
"}",
|
||||
"",
|
||||
"/** ${1:Component} component. */",
|
||||
"const ${1:Component}: React.FC<I${1:Component}> = () => {",
|
||||
" return (",
|
||||
" <></>",
|
||||
" );",
|
||||
"};",
|
||||
"",
|
||||
"export default ${1:Component};"
|
||||
],
|
||||
"description": "React component"
|
||||
},
|
||||
"React component test": {
|
||||
"scope": "typescriptreact",
|
||||
"prefix": ["test", "component test", "react component test"],
|
||||
"body": [
|
||||
"import React from 'react';",
|
||||
"",
|
||||
"import { render, screen } from 'soapbox/jest/test-helpers';",
|
||||
"",
|
||||
"import ${1:Component} from '${2:..}';",
|
||||
"",
|
||||
"describe('<${1:Component} />', () => {",
|
||||
" it('renders', () => {",
|
||||
" render(<${1:Component} />);",
|
||||
"",
|
||||
" expect(screen.getByTestId('${3:test}')).toBeInTheDocument();",
|
||||
" });",
|
||||
"});"
|
||||
],
|
||||
"description": "React component test"
|
||||
}
|
||||
}
|
83
README.md
|
@ -15,7 +15,7 @@ Installing Soapbox FE on an existing Pleroma server is extremely easy.
|
|||
Just ssh into the server and download a .zip of the latest build:
|
||||
|
||||
```sh
|
||||
curl -L https://gitlab.com/soapbox-pub/soapbox-fe/-/jobs/artifacts/v1.3.0/download?job=build-production -o soapbox-fe.zip
|
||||
curl -L https://gitlab.com/soapbox-pub/soapbox-fe/-/jobs/artifacts/v2.0.0/download?job=build-production -o soapbox-fe.zip
|
||||
```
|
||||
|
||||
Then unpack it into Pleroma's `instance` directory:
|
||||
|
@ -31,6 +31,10 @@ It's not necessary to restart the Pleroma service.
|
|||
|
||||
To remove Soapbox FE and revert to the default pleroma-fe, simply `rm /opt/pleroma/instance/static/index.html` (you can delete other stuff in there too, but be careful not to delete your own HTML files).
|
||||
|
||||
## :elephant: Deploy on Mastodon
|
||||
|
||||
See [Installing Soapbox over Mastodon](https://docs.soapbox.pub/frontend/administration/mastodon/).
|
||||
|
||||
## How does it work?
|
||||
|
||||
Soapbox FE is a [single-page application (SPA)](https://en.wikipedia.org/wiki/Single-page_application) that runs entirely in the browser with JavaScript.
|
||||
|
@ -38,7 +42,23 @@ Soapbox FE is a [single-page application (SPA)](https://en.wikipedia.org/wiki/Si
|
|||
It has a single HTML file, `index.html`, responsible only for loading the required JavaScript and CSS.
|
||||
It interacts with the backend through [XMLHttpRequest (XHR)](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest).
|
||||
|
||||
It incorporates much of the [Mastodon API](https://docs.joinmastodon.org/methods/) used by Pleroma and Mastodon, but requires many [Pleroma-specific features](https://docs.pleroma.social/backend/development/API/differences_in_mastoapi_responses/) in order to function.
|
||||
Here is a simplified example with Nginx:
|
||||
|
||||
```nginx
|
||||
location /api {
|
||||
proxy_pass http://backend;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /opt/soapbox;
|
||||
try_files $uri index.html;
|
||||
}
|
||||
```
|
||||
|
||||
(See [`mastodon.conf`](https://gitlab.com/soapbox-pub/soapbox-fe/-/blob/develop/installation/mastodon.conf) for a full example.)
|
||||
|
||||
Soapbox incorporates much of the [Mastodon API](https://docs.joinmastodon.org/methods/), [Pleroma API](https://api.pleroma.social/), and more.
|
||||
It detects features supported by the backend to provide the right experience for the backend.
|
||||
|
||||
# Running locally
|
||||
|
||||
|
@ -65,8 +85,9 @@ yarn dev
|
|||
|
||||
It will serve at `http://localhost:3036` by default.
|
||||
|
||||
It will proxy requests to the backend for you.
|
||||
For Pleroma running on `localhost:4000` (the default) no other changes are required, just start a local Pleroma server and it should begin working.
|
||||
You should see an input box - just enter the domain name of your instance to log in.
|
||||
|
||||
Tip: you can even enter a local instance like `http://localhost:3000`!
|
||||
|
||||
### Troubleshooting: `ERROR: NODE_ENV must be set`
|
||||
|
||||
|
@ -79,26 +100,10 @@ cp .env.example .env
|
|||
And ensure that it contains `NODE_ENV=development`.
|
||||
Try again.
|
||||
|
||||
## Developing against a live backend
|
||||
### Troubleshooting: it's not working!
|
||||
|
||||
You can also run Soapbox FE locally with a live production server as the backend.
|
||||
|
||||
> **Note:** Whether or not this works depends on your production server. It does not seem to work with Cloudflare or VanwaNet.
|
||||
|
||||
To do so, just copy the env file:
|
||||
|
||||
```sh
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
And edit `.env`, setting the configuration like this:
|
||||
|
||||
```sh
|
||||
BACKEND_URL="https://pleroma.example.com"
|
||||
PROXY_HTTPS_INSECURE=true
|
||||
```
|
||||
|
||||
You will need to restart the local development server for the changes to take effect.
|
||||
Run `node -V` and compare your Node.js version with the version in [`.tool-versions`](https://gitlab.com/soapbox-pub/soapbox-fe/-/blob/develop/.tool-versions).
|
||||
If they don't match, try installing [asdf](https://asdf-vm.com/).
|
||||
|
||||
## Local Dev Configuration
|
||||
|
||||
|
@ -165,28 +170,26 @@ NODE_ENV=development
|
|||
|
||||
# Contributing
|
||||
|
||||
We welcome contributions to this project. To contribute, first review the [Contributing doc](docs/contributing.md)
|
||||
|
||||
Additional supporting documents include:
|
||||
* [Soapbox History](docs/history.md)
|
||||
* [Redux Store Map](docs/history.md)
|
||||
We welcome contributions to this project.
|
||||
To contribute, see [Contributing to Soapbox](docs/contributing.md).
|
||||
|
||||
# Customization
|
||||
|
||||
Soapbox supports customization of the user interface, to allow per instance branding and other features. Current customization features include:
|
||||
Soapbox supports customization of the user interface, to allow per-instance branding and other features.
|
||||
Some examples include:
|
||||
|
||||
* Instance name
|
||||
* Site logo
|
||||
* Favicon
|
||||
* About page
|
||||
* Terms of Service page
|
||||
* Privacy Policy page
|
||||
* Copyright Policy (DMCA) page
|
||||
* Promo panel list items, e.g. blog site link
|
||||
* Soapbox extensions, e.g. Patron module
|
||||
* Default settings, e.g. default theme
|
||||
- Instance name
|
||||
- Site logo
|
||||
- Favicon
|
||||
- About page
|
||||
- Terms of Service page
|
||||
- Privacy Policy page
|
||||
- Copyright Policy (DMCA) page
|
||||
- Promo panel list items, e.g. blog site link
|
||||
- Soapbox extensions, e.g. Patron module
|
||||
- Default settings, e.g. default theme
|
||||
|
||||
Customization details can be found in the [Customization doc](docs/customization.md)
|
||||
More details can be found in [Customizing Soapbox](docs/customization.md).
|
||||
|
||||
# License & Credits
|
||||
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
# Custom icons
|
||||
|
||||
- dashboard-filled.svg - Modified from Tabler icons, MIT
|
||||
- fediverse.svg - Modified from Wikipedia, CC0
|
||||
- home-squared.svg - Modified from Tabler icons, MIT
|
||||
- pen-plus.svg - Modified from Tabler icons, MIT
|
||||
- verified.svg - Created by Alex Gleason. CC0
|
||||
|
||||
Tabler: https://tabler-icons.io/
|
||||
Feather: https://feathericons.com/
|
||||
Fediverse logo: https://en.wikipedia.org/wiki/Fediverse#/media/File:Fediverse_logo_proposal.svg
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<svg viewBox="0 0 24 24" stroke="currentColor" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 14v6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h6M19 9a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
Before Width: | Height: | Size: 267 B |
|
@ -1 +0,0 @@
|
|||
<svg fill="white" stroke="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="M8.21 4.175V5.86h1.685a.842.842 0 0 1 0 1.684H8.21v1.684a.842.842 0 1 1-1.685 0V7.544H4.842a.842.842 0 1 1 0-1.684h1.684V4.175a.842.842 0 1 1 1.685 0Zm12.87 3.523a.814.814 0 0 1 0 1.18l-1.43 1.6-3.2-3.2 1.515-1.517a.814.814 0 0 1 1.179 0l1.937 1.937ZM6.573 18.364a5 5 0 0 1 1.392-2.686l7.559-7.559 3.116 3.2-7.47 7.544a5 5 0 0 1-2.704 1.409l-2.29.395.397-2.303Z"/></svg>
|
Before Width: | Height: | Size: 487 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler" width="24" height="24" stroke-width="2" stroke="currentColor" fill="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M11.969 3.955A9 9 0 0 0 6.4 20H17.6a9 9 0 0 0-5.631-16.045zM15.529 8.5a1 1 0 0 1 .678 1.707l-1.51 1.51c.189.391.303.823.303 1.283 0 1.645-1.355 3-3 3s-3-1.355-3-3 1.355-3 3-3c.46 0 .892.114 1.283.303l1.51-1.51a1 1 0 0 1 .736-.293zM12 12c-.564 0-1 .436-1 1s.436 1 1 1 1-.436 1-1-.436-1-1-1z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 506 B |
|
@ -1 +0,0 @@
|
|||
<svg viewBox="0 0 24 24" stroke="currentColor" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M22 3H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h20a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1ZM1 15h22M1 21h22" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
Before Width: | Height: | Size: 264 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler" viewBox="0 0 24 24" width="24" height="24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m 12,3 -9,7 v 11 c 1.9540513,0 3.8465823,0 6,0 v -6 c 0,-1.104569 0.8954305,-2 2,-2 h 2 c 1.104569,0 2,0.895431 2,2 v 6 c 2,0 4,0 6,0 V 10 Z" />
|
||||
</svg>
|
Before Width: | Height: | Size: 370 B |
|
@ -1,4 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M6 20h4L20.5 9.5a2.83 2.83 0 0 0 0-4 2.83 2.83 0 0 0-4 0L6 16v4M5.5 3v7"/>
|
||||
<path d="M2 6.5h7m6.5 0l4 4"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 333 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0zm-4 7a7 7 0 0 0-7 7h14a7 7 0 0 0-7-7z"/></svg>
|
Before Width: | Height: | Size: 247 B |
1
app/images/soapbox-logo-white.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 95 95" width="100" height="100"><path d="M94.909 19.374C94.909 8.674 86.235 0 75.534 0c-10.647 0-19.28 8.591-19.365 19.217l-15.631 2.09c-1.961-6.007-7.598-10.35-14.258-10.35-8.284 0-15.002 6.716-15.002 15.002 0 6.642 4.321 12.267 10.303 14.24l-2.205 16.056c-10.66.049-19.285 8.7-19.285 19.37C.091 86.325 8.765 95 19.466 95c10.677 0 19.332-8.638 19.37-19.304l18.093-2.501c1.979 5.972 7.598 10.285 14.234 10.285 8.284 0 15.002-6.716 15.002-15.002 0-6.891-4.652-12.682-10.983-14.441l1.365-15.339c10.229-.53 18.363-8.966 18.363-19.324zM56.194 67.8l-18.116 2.505a19.39 19.39 0 0 0-13.312-13.3l2.205-16.077a14.98 14.98 0 0 0 14.27-14.222l15.655-2.094c1.894 6.757 7.351 12.009 14.225 13.612l-1.365 15.322c-7.4.688-13.224 6.753-13.562 14.254z" fill="#ffffff"/></svg>
|
After Width: | Height: | Size: 812 B |
44
app/soapbox/__fixtures__/announcements.json
Normal file
|
@ -0,0 +1,44 @@
|
|||
[
|
||||
{
|
||||
"id": "1",
|
||||
"content": "<p>Updated to Soapbox v3.</p>",
|
||||
"starts_at": null,
|
||||
"ends_at": null,
|
||||
"all_day": false,
|
||||
"published_at": "2022-06-15T18:47:14.190Z",
|
||||
"updated_at": "2022-06-15T18:47:18.339Z",
|
||||
"read": true,
|
||||
"mentions": [],
|
||||
"statuses": [],
|
||||
"tags": [],
|
||||
"emojis": [],
|
||||
"reactions": [
|
||||
{
|
||||
"name": "📈",
|
||||
"count": 476,
|
||||
"me": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"content": "<p>Rolled back to Soapbox v2 for now.</p>",
|
||||
"starts_at": null,
|
||||
"ends_at": null,
|
||||
"all_day": false,
|
||||
"published_at": "2022-07-13T11:11:50.628Z",
|
||||
"updated_at": "2022-07-13T11:11:50.628Z",
|
||||
"read": true,
|
||||
"mentions": [],
|
||||
"statuses": [],
|
||||
"tags": [],
|
||||
"emojis": [],
|
||||
"reactions": [
|
||||
{
|
||||
"name": "📉",
|
||||
"count": 420,
|
||||
"me": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
8
app/soapbox/__fixtures__/blocks.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"id": "22",
|
||||
"username": "twoods",
|
||||
"acct": "twoods",
|
||||
"display_name": "Tiger Woods"
|
||||
}
|
||||
]
|
|
@ -1,739 +0,0 @@
|
|||
{
|
||||
"ancestors": [
|
||||
{
|
||||
"account": {
|
||||
"acct": "alex",
|
||||
"avatar": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"avatar_static": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"follow_requests_count": 0,
|
||||
"followers_count": 725,
|
||||
"following_count": 1211,
|
||||
"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",
|
||||
"locked": false,
|
||||
"note": "Fediverse developer. I come in peace.<br/><br/><a class=\"hashtag\" data-tag=\"vegan\" href=\"https://gleasonator.com/tag/vegan\">#vegan</a> <a class=\"hashtag\" data-tag=\"freeculture\" href=\"https://gleasonator.com/tag/freeculture\">#freeculture</a> <a class=\"hashtag\" data-tag=\"atheist\" href=\"https://gleasonator.com/tag/atheist\">#atheist</a> <a class=\"hashtag\" data-tag=\"antiporn\" href=\"https://gleasonator.com/tag/antiporn\">#antiporn</a> <a class=\"hashtag\" data-tag=\"gendercritical\" href=\"https://gleasonator.com/tag/gendercritical\">#gendercritical</a>.<br/><br/>Boosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"accepts_chat_messages": true,
|
||||
"allow_following_move": true,
|
||||
"ap_id": "https://gleasonator.com/users/alex",
|
||||
"background_image": null,
|
||||
"confirmation_pending": false,
|
||||
"deactivated": false,
|
||||
"favicon": "https://gleasonator.com/favicon.png",
|
||||
"hide_favorites": true,
|
||||
"hide_followers": false,
|
||||
"hide_followers_count": false,
|
||||
"hide_follows": false,
|
||||
"hide_follows_count": false,
|
||||
"is_admin": true,
|
||||
"is_moderator": false,
|
||||
"notification_settings": {
|
||||
"block_from_strangers": false,
|
||||
"hide_notification_contents": false
|
||||
},
|
||||
"relationship": {},
|
||||
"skip_thread_containment": false,
|
||||
"tags": [],
|
||||
"unread_conversation_count": 95,
|
||||
"unread_notifications_count": 0
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"note": "Fediverse developer. I come in peace.\r\n\r\n#vegan #freeculture #atheist #antiporn #gendercritical.\r\n\r\nBoosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"actor_type": "Person",
|
||||
"discoverable": false,
|
||||
"no_rich_text": false,
|
||||
"show_role": true
|
||||
},
|
||||
"privacy": "public",
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 9157,
|
||||
"url": "https://gleasonator.com/users/alex",
|
||||
"username": "alex"
|
||||
},
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "<p>A</p>",
|
||||
"created_at": "2020-09-18T20:07:10.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9zIH6kDXA10YqhMKqO",
|
||||
"in_reply_to_account_id": null,
|
||||
"in_reply_to_id": null,
|
||||
"language": null,
|
||||
"media_attachments": [],
|
||||
"mentions": [],
|
||||
"muted": false,
|
||||
"pinned": false,
|
||||
"pleroma": {
|
||||
"content": {
|
||||
"text/plain": "A"
|
||||
},
|
||||
"conversation_id": 5089485,
|
||||
"direct_conversation_id": null,
|
||||
"emoji_reactions": [],
|
||||
"expires_at": null,
|
||||
"in_reply_to_account_acct": null,
|
||||
"local": true,
|
||||
"parent_visible": false,
|
||||
"spoiler_text": {
|
||||
"text/plain": ""
|
||||
},
|
||||
"thread_muted": false
|
||||
},
|
||||
"poll": null,
|
||||
"reblog": null,
|
||||
"reblogged": false,
|
||||
"reblogs_count": 0,
|
||||
"replies_count": 0,
|
||||
"sensitive": false,
|
||||
"spoiler_text": "",
|
||||
"tags": [],
|
||||
"text": null,
|
||||
"uri": "https://gleasonator.com/objects/9995c074-2ff6-4a01-b596-7ef6971ed5d2",
|
||||
"url": "https://gleasonator.com/notice/9zIH6kDXA10YqhMKqO",
|
||||
"visibility": "direct"
|
||||
},
|
||||
{
|
||||
"account": {
|
||||
"acct": "alex",
|
||||
"avatar": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"avatar_static": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"follow_requests_count": 0,
|
||||
"followers_count": 725,
|
||||
"following_count": 1211,
|
||||
"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",
|
||||
"locked": false,
|
||||
"note": "Fediverse developer. I come in peace.<br/><br/><a class=\"hashtag\" data-tag=\"vegan\" href=\"https://gleasonator.com/tag/vegan\">#vegan</a> <a class=\"hashtag\" data-tag=\"freeculture\" href=\"https://gleasonator.com/tag/freeculture\">#freeculture</a> <a class=\"hashtag\" data-tag=\"atheist\" href=\"https://gleasonator.com/tag/atheist\">#atheist</a> <a class=\"hashtag\" data-tag=\"antiporn\" href=\"https://gleasonator.com/tag/antiporn\">#antiporn</a> <a class=\"hashtag\" data-tag=\"gendercritical\" href=\"https://gleasonator.com/tag/gendercritical\">#gendercritical</a>.<br/><br/>Boosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"accepts_chat_messages": true,
|
||||
"allow_following_move": true,
|
||||
"ap_id": "https://gleasonator.com/users/alex",
|
||||
"background_image": null,
|
||||
"confirmation_pending": false,
|
||||
"deactivated": false,
|
||||
"favicon": "https://gleasonator.com/favicon.png",
|
||||
"hide_favorites": true,
|
||||
"hide_followers": false,
|
||||
"hide_followers_count": false,
|
||||
"hide_follows": false,
|
||||
"hide_follows_count": false,
|
||||
"is_admin": true,
|
||||
"is_moderator": false,
|
||||
"notification_settings": {
|
||||
"block_from_strangers": false,
|
||||
"hide_notification_contents": false
|
||||
},
|
||||
"relationship": {},
|
||||
"skip_thread_containment": false,
|
||||
"tags": [],
|
||||
"unread_conversation_count": 95,
|
||||
"unread_notifications_count": 0
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"note": "Fediverse developer. I come in peace.\r\n\r\n#vegan #freeculture #atheist #antiporn #gendercritical.\r\n\r\nBoosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"actor_type": "Person",
|
||||
"discoverable": false,
|
||||
"no_rich_text": false,
|
||||
"show_role": true
|
||||
},
|
||||
"privacy": "public",
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 9157,
|
||||
"url": "https://gleasonator.com/users/alex",
|
||||
"username": "alex"
|
||||
},
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "<p>B</p>",
|
||||
"created_at": "2020-09-18T20:07:18.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9zIH7PUdhK3Ircg4hM",
|
||||
"in_reply_to_account_id": "9v5bmRalQvjOy0ECcC",
|
||||
"in_reply_to_id": "9zIH6kDXA10YqhMKqO",
|
||||
"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": "B"
|
||||
},
|
||||
"conversation_id": 5089485,
|
||||
"direct_conversation_id": null,
|
||||
"emoji_reactions": [],
|
||||
"expires_at": null,
|
||||
"in_reply_to_account_acct": "alex",
|
||||
"local": true,
|
||||
"parent_visible": true,
|
||||
"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/992ca99a-425d-46eb-b094-60412e9fb141",
|
||||
"url": "https://gleasonator.com/notice/9zIH7PUdhK3Ircg4hM",
|
||||
"visibility": "direct"
|
||||
},
|
||||
{
|
||||
"account": {
|
||||
"acct": "alex",
|
||||
"avatar": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"avatar_static": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"follow_requests_count": 0,
|
||||
"followers_count": 725,
|
||||
"following_count": 1211,
|
||||
"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",
|
||||
"locked": false,
|
||||
"note": "Fediverse developer. I come in peace.<br/><br/><a class=\"hashtag\" data-tag=\"vegan\" href=\"https://gleasonator.com/tag/vegan\">#vegan</a> <a class=\"hashtag\" data-tag=\"freeculture\" href=\"https://gleasonator.com/tag/freeculture\">#freeculture</a> <a class=\"hashtag\" data-tag=\"atheist\" href=\"https://gleasonator.com/tag/atheist\">#atheist</a> <a class=\"hashtag\" data-tag=\"antiporn\" href=\"https://gleasonator.com/tag/antiporn\">#antiporn</a> <a class=\"hashtag\" data-tag=\"gendercritical\" href=\"https://gleasonator.com/tag/gendercritical\">#gendercritical</a>.<br/><br/>Boosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"accepts_chat_messages": true,
|
||||
"allow_following_move": true,
|
||||
"ap_id": "https://gleasonator.com/users/alex",
|
||||
"background_image": null,
|
||||
"confirmation_pending": false,
|
||||
"deactivated": false,
|
||||
"favicon": "https://gleasonator.com/favicon.png",
|
||||
"hide_favorites": true,
|
||||
"hide_followers": false,
|
||||
"hide_followers_count": false,
|
||||
"hide_follows": false,
|
||||
"hide_follows_count": false,
|
||||
"is_admin": true,
|
||||
"is_moderator": false,
|
||||
"notification_settings": {
|
||||
"block_from_strangers": false,
|
||||
"hide_notification_contents": false
|
||||
},
|
||||
"relationship": {},
|
||||
"skip_thread_containment": false,
|
||||
"tags": [],
|
||||
"unread_conversation_count": 95,
|
||||
"unread_notifications_count": 0
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"note": "Fediverse developer. I come in peace.\r\n\r\n#vegan #freeculture #atheist #antiporn #gendercritical.\r\n\r\nBoosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"actor_type": "Person",
|
||||
"discoverable": false,
|
||||
"no_rich_text": false,
|
||||
"show_role": true
|
||||
},
|
||||
"privacy": "public",
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 9157,
|
||||
"url": "https://gleasonator.com/users/alex",
|
||||
"username": "alex"
|
||||
},
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "<p>C</p>",
|
||||
"created_at": "2020-09-18T20:07:22.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9zIH7mMGgc1RmJwDLM",
|
||||
"in_reply_to_account_id": "9v5bmRalQvjOy0ECcC",
|
||||
"in_reply_to_id": "9zIH6kDXA10YqhMKqO",
|
||||
"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": "C"
|
||||
},
|
||||
"conversation_id": 5089485,
|
||||
"direct_conversation_id": null,
|
||||
"emoji_reactions": [],
|
||||
"expires_at": null,
|
||||
"in_reply_to_account_acct": "alex",
|
||||
"local": true,
|
||||
"parent_visible": true,
|
||||
"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/a2c25ef5-a40e-4098-b07e-b468989ef749",
|
||||
"url": "https://gleasonator.com/notice/9zIH7mMGgc1RmJwDLM",
|
||||
"visibility": "direct"
|
||||
}
|
||||
],
|
||||
"descendants": [
|
||||
{
|
||||
"account": {
|
||||
"acct": "alex",
|
||||
"avatar": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"avatar_static": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"follow_requests_count": 0,
|
||||
"followers_count": 725,
|
||||
"following_count": 1211,
|
||||
"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",
|
||||
"locked": false,
|
||||
"note": "Fediverse developer. I come in peace.<br/><br/><a class=\"hashtag\" data-tag=\"vegan\" href=\"https://gleasonator.com/tag/vegan\">#vegan</a> <a class=\"hashtag\" data-tag=\"freeculture\" href=\"https://gleasonator.com/tag/freeculture\">#freeculture</a> <a class=\"hashtag\" data-tag=\"atheist\" href=\"https://gleasonator.com/tag/atheist\">#atheist</a> <a class=\"hashtag\" data-tag=\"antiporn\" href=\"https://gleasonator.com/tag/antiporn\">#antiporn</a> <a class=\"hashtag\" data-tag=\"gendercritical\" href=\"https://gleasonator.com/tag/gendercritical\">#gendercritical</a>.<br/><br/>Boosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"accepts_chat_messages": true,
|
||||
"allow_following_move": true,
|
||||
"ap_id": "https://gleasonator.com/users/alex",
|
||||
"background_image": null,
|
||||
"confirmation_pending": false,
|
||||
"deactivated": false,
|
||||
"favicon": "https://gleasonator.com/favicon.png",
|
||||
"hide_favorites": true,
|
||||
"hide_followers": false,
|
||||
"hide_followers_count": false,
|
||||
"hide_follows": false,
|
||||
"hide_follows_count": false,
|
||||
"is_admin": true,
|
||||
"is_moderator": false,
|
||||
"notification_settings": {
|
||||
"block_from_strangers": false,
|
||||
"hide_notification_contents": false
|
||||
},
|
||||
"relationship": {},
|
||||
"skip_thread_containment": false,
|
||||
"tags": [],
|
||||
"unread_conversation_count": 95,
|
||||
"unread_notifications_count": 0
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"note": "Fediverse developer. I come in peace.\r\n\r\n#vegan #freeculture #atheist #antiporn #gendercritical.\r\n\r\nBoosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"actor_type": "Person",
|
||||
"discoverable": false,
|
||||
"no_rich_text": false,
|
||||
"show_role": true
|
||||
},
|
||||
"privacy": "public",
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 9157,
|
||||
"url": "https://gleasonator.com/users/alex",
|
||||
"username": "alex"
|
||||
},
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "<p>E</p>",
|
||||
"created_at": "2020-09-18T20:07:38.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9zIH9GTCDWEFSRt2um",
|
||||
"in_reply_to_account_id": "9v5bmRalQvjOy0ECcC",
|
||||
"in_reply_to_id": "9zIH7PUdhK3Ircg4hM",
|
||||
"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": "E"
|
||||
},
|
||||
"conversation_id": 5089485,
|
||||
"direct_conversation_id": null,
|
||||
"emoji_reactions": [],
|
||||
"expires_at": null,
|
||||
"in_reply_to_account_acct": "alex",
|
||||
"local": true,
|
||||
"parent_visible": true,
|
||||
"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/a1e45493-2158-4f11-88ca-ba621429dbe5",
|
||||
"url": "https://gleasonator.com/notice/9zIH9GTCDWEFSRt2um",
|
||||
"visibility": "direct"
|
||||
},
|
||||
{
|
||||
"account": {
|
||||
"acct": "alex",
|
||||
"avatar": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"avatar_static": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"follow_requests_count": 0,
|
||||
"followers_count": 725,
|
||||
"following_count": 1211,
|
||||
"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",
|
||||
"locked": false,
|
||||
"note": "Fediverse developer. I come in peace.<br/><br/><a class=\"hashtag\" data-tag=\"vegan\" href=\"https://gleasonator.com/tag/vegan\">#vegan</a> <a class=\"hashtag\" data-tag=\"freeculture\" href=\"https://gleasonator.com/tag/freeculture\">#freeculture</a> <a class=\"hashtag\" data-tag=\"atheist\" href=\"https://gleasonator.com/tag/atheist\">#atheist</a> <a class=\"hashtag\" data-tag=\"antiporn\" href=\"https://gleasonator.com/tag/antiporn\">#antiporn</a> <a class=\"hashtag\" data-tag=\"gendercritical\" href=\"https://gleasonator.com/tag/gendercritical\">#gendercritical</a>.<br/><br/>Boosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"accepts_chat_messages": true,
|
||||
"allow_following_move": true,
|
||||
"ap_id": "https://gleasonator.com/users/alex",
|
||||
"background_image": null,
|
||||
"confirmation_pending": false,
|
||||
"deactivated": false,
|
||||
"favicon": "https://gleasonator.com/favicon.png",
|
||||
"hide_favorites": true,
|
||||
"hide_followers": false,
|
||||
"hide_followers_count": false,
|
||||
"hide_follows": false,
|
||||
"hide_follows_count": false,
|
||||
"is_admin": true,
|
||||
"is_moderator": false,
|
||||
"notification_settings": {
|
||||
"block_from_strangers": false,
|
||||
"hide_notification_contents": false
|
||||
},
|
||||
"relationship": {},
|
||||
"skip_thread_containment": false,
|
||||
"tags": [],
|
||||
"unread_conversation_count": 95,
|
||||
"unread_notifications_count": 0
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"note": "Fediverse developer. I come in peace.\r\n\r\n#vegan #freeculture #atheist #antiporn #gendercritical.\r\n\r\nBoosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"actor_type": "Person",
|
||||
"discoverable": false,
|
||||
"no_rich_text": false,
|
||||
"show_role": true
|
||||
},
|
||||
"privacy": "public",
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 9157,
|
||||
"url": "https://gleasonator.com/users/alex",
|
||||
"username": "alex"
|
||||
},
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "<p>F</p>",
|
||||
"created_at": "2020-09-18T20:07:42.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9zIH9fhaP9atiJoOJc",
|
||||
"in_reply_to_account_id": "9v5bmRalQvjOy0ECcC",
|
||||
"in_reply_to_id": "9zIH8WYwtnUx4yDzUm",
|
||||
"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": "F"
|
||||
},
|
||||
"conversation_id": 5089485,
|
||||
"direct_conversation_id": null,
|
||||
"emoji_reactions": [],
|
||||
"expires_at": null,
|
||||
"in_reply_to_account_acct": "alex",
|
||||
"local": true,
|
||||
"parent_visible": true,
|
||||
"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/ee661cf9-35d4-4e84-88ff-13b5950f7556",
|
||||
"url": "https://gleasonator.com/notice/9zIH9fhaP9atiJoOJc",
|
||||
"visibility": "direct"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,739 +0,0 @@
|
|||
{
|
||||
"ancestors": [
|
||||
{
|
||||
"account": {
|
||||
"acct": "alex",
|
||||
"avatar": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"avatar_static": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"follow_requests_count": 0,
|
||||
"followers_count": 725,
|
||||
"following_count": 1211,
|
||||
"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",
|
||||
"locked": false,
|
||||
"note": "Fediverse developer. I come in peace.<br/><br/><a class=\"hashtag\" data-tag=\"vegan\" href=\"https://gleasonator.com/tag/vegan\">#vegan</a> <a class=\"hashtag\" data-tag=\"freeculture\" href=\"https://gleasonator.com/tag/freeculture\">#freeculture</a> <a class=\"hashtag\" data-tag=\"atheist\" href=\"https://gleasonator.com/tag/atheist\">#atheist</a> <a class=\"hashtag\" data-tag=\"antiporn\" href=\"https://gleasonator.com/tag/antiporn\">#antiporn</a> <a class=\"hashtag\" data-tag=\"gendercritical\" href=\"https://gleasonator.com/tag/gendercritical\">#gendercritical</a>.<br/><br/>Boosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"accepts_chat_messages": true,
|
||||
"allow_following_move": true,
|
||||
"ap_id": "https://gleasonator.com/users/alex",
|
||||
"background_image": null,
|
||||
"confirmation_pending": false,
|
||||
"deactivated": false,
|
||||
"favicon": "https://gleasonator.com/favicon.png",
|
||||
"hide_favorites": true,
|
||||
"hide_followers": false,
|
||||
"hide_followers_count": false,
|
||||
"hide_follows": false,
|
||||
"hide_follows_count": false,
|
||||
"is_admin": true,
|
||||
"is_moderator": false,
|
||||
"notification_settings": {
|
||||
"block_from_strangers": false,
|
||||
"hide_notification_contents": false
|
||||
},
|
||||
"relationship": {},
|
||||
"skip_thread_containment": false,
|
||||
"tags": [],
|
||||
"unread_conversation_count": 95,
|
||||
"unread_notifications_count": 0
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"note": "Fediverse developer. I come in peace.\r\n\r\n#vegan #freeculture #atheist #antiporn #gendercritical.\r\n\r\nBoosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"actor_type": "Person",
|
||||
"discoverable": false,
|
||||
"no_rich_text": false,
|
||||
"show_role": true
|
||||
},
|
||||
"privacy": "public",
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 9157,
|
||||
"url": "https://gleasonator.com/users/alex",
|
||||
"username": "alex"
|
||||
},
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "<p>A</p>",
|
||||
"created_at": "2020-09-18T20:07:10.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9zIH6kDXA10YqhMKqO",
|
||||
"in_reply_to_account_id": null,
|
||||
"in_reply_to_id": null,
|
||||
"language": null,
|
||||
"media_attachments": [],
|
||||
"mentions": [],
|
||||
"muted": false,
|
||||
"pinned": false,
|
||||
"pleroma": {
|
||||
"content": {
|
||||
"text/plain": "A"
|
||||
},
|
||||
"conversation_id": 5089485,
|
||||
"direct_conversation_id": null,
|
||||
"emoji_reactions": [],
|
||||
"expires_at": null,
|
||||
"in_reply_to_account_acct": null,
|
||||
"local": true,
|
||||
"parent_visible": false,
|
||||
"spoiler_text": {
|
||||
"text/plain": ""
|
||||
},
|
||||
"thread_muted": false
|
||||
},
|
||||
"poll": null,
|
||||
"reblog": null,
|
||||
"reblogged": false,
|
||||
"reblogs_count": 0,
|
||||
"replies_count": 0,
|
||||
"sensitive": false,
|
||||
"spoiler_text": "",
|
||||
"tags": [],
|
||||
"text": null,
|
||||
"uri": "https://gleasonator.com/objects/9995c074-2ff6-4a01-b596-7ef6971ed5d2",
|
||||
"url": "https://gleasonator.com/notice/9zIH6kDXA10YqhMKqO",
|
||||
"visibility": "direct"
|
||||
}
|
||||
],
|
||||
"descendants": [
|
||||
{
|
||||
"account": {
|
||||
"acct": "alex",
|
||||
"avatar": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"avatar_static": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"follow_requests_count": 0,
|
||||
"followers_count": 725,
|
||||
"following_count": 1211,
|
||||
"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",
|
||||
"locked": false,
|
||||
"note": "Fediverse developer. I come in peace.<br/><br/><a class=\"hashtag\" data-tag=\"vegan\" href=\"https://gleasonator.com/tag/vegan\">#vegan</a> <a class=\"hashtag\" data-tag=\"freeculture\" href=\"https://gleasonator.com/tag/freeculture\">#freeculture</a> <a class=\"hashtag\" data-tag=\"atheist\" href=\"https://gleasonator.com/tag/atheist\">#atheist</a> <a class=\"hashtag\" data-tag=\"antiporn\" href=\"https://gleasonator.com/tag/antiporn\">#antiporn</a> <a class=\"hashtag\" data-tag=\"gendercritical\" href=\"https://gleasonator.com/tag/gendercritical\">#gendercritical</a>.<br/><br/>Boosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"accepts_chat_messages": true,
|
||||
"allow_following_move": true,
|
||||
"ap_id": "https://gleasonator.com/users/alex",
|
||||
"background_image": null,
|
||||
"confirmation_pending": false,
|
||||
"deactivated": false,
|
||||
"favicon": "https://gleasonator.com/favicon.png",
|
||||
"hide_favorites": true,
|
||||
"hide_followers": false,
|
||||
"hide_followers_count": false,
|
||||
"hide_follows": false,
|
||||
"hide_follows_count": false,
|
||||
"is_admin": true,
|
||||
"is_moderator": false,
|
||||
"notification_settings": {
|
||||
"block_from_strangers": false,
|
||||
"hide_notification_contents": false
|
||||
},
|
||||
"relationship": {},
|
||||
"skip_thread_containment": false,
|
||||
"tags": [],
|
||||
"unread_conversation_count": 95,
|
||||
"unread_notifications_count": 0
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"note": "Fediverse developer. I come in peace.\r\n\r\n#vegan #freeculture #atheist #antiporn #gendercritical.\r\n\r\nBoosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"actor_type": "Person",
|
||||
"discoverable": false,
|
||||
"no_rich_text": false,
|
||||
"show_role": true
|
||||
},
|
||||
"privacy": "public",
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 9157,
|
||||
"url": "https://gleasonator.com/users/alex",
|
||||
"username": "alex"
|
||||
},
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "<p>C</p>",
|
||||
"created_at": "2020-09-18T20:07:22.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9zIH7mMGgc1RmJwDLM",
|
||||
"in_reply_to_account_id": "9v5bmRalQvjOy0ECcC",
|
||||
"in_reply_to_id": "9zIH6kDXA10YqhMKqO",
|
||||
"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": "C"
|
||||
},
|
||||
"conversation_id": 5089485,
|
||||
"direct_conversation_id": null,
|
||||
"emoji_reactions": [],
|
||||
"expires_at": null,
|
||||
"in_reply_to_account_acct": "alex",
|
||||
"local": true,
|
||||
"parent_visible": true,
|
||||
"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/a2c25ef5-a40e-4098-b07e-b468989ef749",
|
||||
"url": "https://gleasonator.com/notice/9zIH7mMGgc1RmJwDLM",
|
||||
"visibility": "direct"
|
||||
},
|
||||
{
|
||||
"account": {
|
||||
"acct": "alex",
|
||||
"avatar": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"avatar_static": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"follow_requests_count": 0,
|
||||
"followers_count": 725,
|
||||
"following_count": 1211,
|
||||
"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",
|
||||
"locked": false,
|
||||
"note": "Fediverse developer. I come in peace.<br/><br/><a class=\"hashtag\" data-tag=\"vegan\" href=\"https://gleasonator.com/tag/vegan\">#vegan</a> <a class=\"hashtag\" data-tag=\"freeculture\" href=\"https://gleasonator.com/tag/freeculture\">#freeculture</a> <a class=\"hashtag\" data-tag=\"atheist\" href=\"https://gleasonator.com/tag/atheist\">#atheist</a> <a class=\"hashtag\" data-tag=\"antiporn\" href=\"https://gleasonator.com/tag/antiporn\">#antiporn</a> <a class=\"hashtag\" data-tag=\"gendercritical\" href=\"https://gleasonator.com/tag/gendercritical\">#gendercritical</a>.<br/><br/>Boosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"accepts_chat_messages": true,
|
||||
"allow_following_move": true,
|
||||
"ap_id": "https://gleasonator.com/users/alex",
|
||||
"background_image": null,
|
||||
"confirmation_pending": false,
|
||||
"deactivated": false,
|
||||
"favicon": "https://gleasonator.com/favicon.png",
|
||||
"hide_favorites": true,
|
||||
"hide_followers": false,
|
||||
"hide_followers_count": false,
|
||||
"hide_follows": false,
|
||||
"hide_follows_count": false,
|
||||
"is_admin": true,
|
||||
"is_moderator": false,
|
||||
"notification_settings": {
|
||||
"block_from_strangers": false,
|
||||
"hide_notification_contents": false
|
||||
},
|
||||
"relationship": {},
|
||||
"skip_thread_containment": false,
|
||||
"tags": [],
|
||||
"unread_conversation_count": 95,
|
||||
"unread_notifications_count": 0
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"note": "Fediverse developer. I come in peace.\r\n\r\n#vegan #freeculture #atheist #antiporn #gendercritical.\r\n\r\nBoosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"actor_type": "Person",
|
||||
"discoverable": false,
|
||||
"no_rich_text": false,
|
||||
"show_role": true
|
||||
},
|
||||
"privacy": "public",
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 9157,
|
||||
"url": "https://gleasonator.com/users/alex",
|
||||
"username": "alex"
|
||||
},
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "<p>D</p>",
|
||||
"created_at": "2020-09-18T20:07:30.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9zIH8WYwtnUx4yDzUm",
|
||||
"in_reply_to_account_id": "9v5bmRalQvjOy0ECcC",
|
||||
"in_reply_to_id": "9zIH7PUdhK3Ircg4hM",
|
||||
"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": "D"
|
||||
},
|
||||
"conversation_id": 5089485,
|
||||
"direct_conversation_id": null,
|
||||
"emoji_reactions": [],
|
||||
"expires_at": null,
|
||||
"in_reply_to_account_acct": "alex",
|
||||
"local": true,
|
||||
"parent_visible": true,
|
||||
"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/bb423adc-ed86-42d8-942e-84efbe7b1acf",
|
||||
"url": "https://gleasonator.com/notice/9zIH8WYwtnUx4yDzUm",
|
||||
"visibility": "direct"
|
||||
},
|
||||
{
|
||||
"account": {
|
||||
"acct": "alex",
|
||||
"avatar": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"avatar_static": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"follow_requests_count": 0,
|
||||
"followers_count": 725,
|
||||
"following_count": 1211,
|
||||
"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",
|
||||
"locked": false,
|
||||
"note": "Fediverse developer. I come in peace.<br/><br/><a class=\"hashtag\" data-tag=\"vegan\" href=\"https://gleasonator.com/tag/vegan\">#vegan</a> <a class=\"hashtag\" data-tag=\"freeculture\" href=\"https://gleasonator.com/tag/freeculture\">#freeculture</a> <a class=\"hashtag\" data-tag=\"atheist\" href=\"https://gleasonator.com/tag/atheist\">#atheist</a> <a class=\"hashtag\" data-tag=\"antiporn\" href=\"https://gleasonator.com/tag/antiporn\">#antiporn</a> <a class=\"hashtag\" data-tag=\"gendercritical\" href=\"https://gleasonator.com/tag/gendercritical\">#gendercritical</a>.<br/><br/>Boosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"accepts_chat_messages": true,
|
||||
"allow_following_move": true,
|
||||
"ap_id": "https://gleasonator.com/users/alex",
|
||||
"background_image": null,
|
||||
"confirmation_pending": false,
|
||||
"deactivated": false,
|
||||
"favicon": "https://gleasonator.com/favicon.png",
|
||||
"hide_favorites": true,
|
||||
"hide_followers": false,
|
||||
"hide_followers_count": false,
|
||||
"hide_follows": false,
|
||||
"hide_follows_count": false,
|
||||
"is_admin": true,
|
||||
"is_moderator": false,
|
||||
"notification_settings": {
|
||||
"block_from_strangers": false,
|
||||
"hide_notification_contents": false
|
||||
},
|
||||
"relationship": {},
|
||||
"skip_thread_containment": false,
|
||||
"tags": [],
|
||||
"unread_conversation_count": 95,
|
||||
"unread_notifications_count": 0
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"note": "Fediverse developer. I come in peace.\r\n\r\n#vegan #freeculture #atheist #antiporn #gendercritical.\r\n\r\nBoosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"actor_type": "Person",
|
||||
"discoverable": false,
|
||||
"no_rich_text": false,
|
||||
"show_role": true
|
||||
},
|
||||
"privacy": "public",
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 9157,
|
||||
"url": "https://gleasonator.com/users/alex",
|
||||
"username": "alex"
|
||||
},
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "<p>E</p>",
|
||||
"created_at": "2020-09-18T20:07:38.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9zIH9GTCDWEFSRt2um",
|
||||
"in_reply_to_account_id": "9v5bmRalQvjOy0ECcC",
|
||||
"in_reply_to_id": "9zIH7PUdhK3Ircg4hM",
|
||||
"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": "E"
|
||||
},
|
||||
"conversation_id": 5089485,
|
||||
"direct_conversation_id": null,
|
||||
"emoji_reactions": [],
|
||||
"expires_at": null,
|
||||
"in_reply_to_account_acct": "alex",
|
||||
"local": true,
|
||||
"parent_visible": true,
|
||||
"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/a1e45493-2158-4f11-88ca-ba621429dbe5",
|
||||
"url": "https://gleasonator.com/notice/9zIH9GTCDWEFSRt2um",
|
||||
"visibility": "direct"
|
||||
},
|
||||
{
|
||||
"account": {
|
||||
"acct": "alex",
|
||||
"avatar": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"avatar_static": "https://media.gleasonator.com/26f0ca4ef51f7047829fdb65a43cb7d0304413ce0a5d00dd1638458994608718.jpg",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"follow_requests_count": 0,
|
||||
"followers_count": 725,
|
||||
"following_count": 1211,
|
||||
"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",
|
||||
"locked": false,
|
||||
"note": "Fediverse developer. I come in peace.<br/><br/><a class=\"hashtag\" data-tag=\"vegan\" href=\"https://gleasonator.com/tag/vegan\">#vegan</a> <a class=\"hashtag\" data-tag=\"freeculture\" href=\"https://gleasonator.com/tag/freeculture\">#freeculture</a> <a class=\"hashtag\" data-tag=\"atheist\" href=\"https://gleasonator.com/tag/atheist\">#atheist</a> <a class=\"hashtag\" data-tag=\"antiporn\" href=\"https://gleasonator.com/tag/antiporn\">#antiporn</a> <a class=\"hashtag\" data-tag=\"gendercritical\" href=\"https://gleasonator.com/tag/gendercritical\">#gendercritical</a>.<br/><br/>Boosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"accepts_chat_messages": true,
|
||||
"allow_following_move": true,
|
||||
"ap_id": "https://gleasonator.com/users/alex",
|
||||
"background_image": null,
|
||||
"confirmation_pending": false,
|
||||
"deactivated": false,
|
||||
"favicon": "https://gleasonator.com/favicon.png",
|
||||
"hide_favorites": true,
|
||||
"hide_followers": false,
|
||||
"hide_followers_count": false,
|
||||
"hide_follows": false,
|
||||
"hide_follows_count": false,
|
||||
"is_admin": true,
|
||||
"is_moderator": false,
|
||||
"notification_settings": {
|
||||
"block_from_strangers": false,
|
||||
"hide_notification_contents": false
|
||||
},
|
||||
"relationship": {},
|
||||
"skip_thread_containment": false,
|
||||
"tags": [],
|
||||
"unread_conversation_count": 95,
|
||||
"unread_notifications_count": 0
|
||||
},
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"note": "Fediverse developer. I come in peace.\r\n\r\n#vegan #freeculture #atheist #antiporn #gendercritical.\r\n\r\nBoosts ≠ endorsements.",
|
||||
"pleroma": {
|
||||
"actor_type": "Person",
|
||||
"discoverable": false,
|
||||
"no_rich_text": false,
|
||||
"show_role": true
|
||||
},
|
||||
"privacy": "public",
|
||||
"sensitive": false
|
||||
},
|
||||
"statuses_count": 9157,
|
||||
"url": "https://gleasonator.com/users/alex",
|
||||
"username": "alex"
|
||||
},
|
||||
"application": {
|
||||
"name": "Web",
|
||||
"website": null
|
||||
},
|
||||
"bookmarked": false,
|
||||
"card": null,
|
||||
"content": "<p>F</p>",
|
||||
"created_at": "2020-09-18T20:07:42.000Z",
|
||||
"emojis": [],
|
||||
"favourited": false,
|
||||
"favourites_count": 0,
|
||||
"id": "9zIH9fhaP9atiJoOJc",
|
||||
"in_reply_to_account_id": "9v5bmRalQvjOy0ECcC",
|
||||
"in_reply_to_id": "9zIH8WYwtnUx4yDzUm",
|
||||
"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": "F"
|
||||
},
|
||||
"conversation_id": 5089485,
|
||||
"direct_conversation_id": null,
|
||||
"emoji_reactions": [],
|
||||
"expires_at": null,
|
||||
"in_reply_to_account_acct": "alex",
|
||||
"local": true,
|
||||
"parent_visible": true,
|
||||
"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/ee661cf9-35d4-4e84-88ff-13b5950f7556",
|
||||
"url": "https://gleasonator.com/notice/9zIH9fhaP9atiJoOJc",
|
||||
"visibility": "direct"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -159,7 +159,7 @@
|
|||
"empty_column.follow_requests": "You don\"t have any follow requests yet. When you receive one, it will show up here.",
|
||||
"empty_column.group": "There is nothing in this group yet. When members of this group make new posts, they will appear here.",
|
||||
"empty_column.hashtag": "There is nothing in this hashtag yet.",
|
||||
"empty_column.home": "Your home timeline is empty! Visit {public} to get started and meet other users.",
|
||||
"empty_column.home": "Or you can visit {public} to get started and meet other users.",
|
||||
"empty_column.home.local_tab": "the {site_title} tab",
|
||||
"empty_column.list": "There is nothing in this list yet. When members of this list create new posts, they will appear here.",
|
||||
"empty_column.lists": "You don\"t have any lists yet. When you create one, it will show up here.",
|
||||
|
@ -243,7 +243,7 @@
|
|||
"lists.edit": "Edit list",
|
||||
"lists.edit.submit": "Change title",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.create_title": "Create",
|
||||
"lists.new.create_title": "Add list",
|
||||
"lists.new.save_title": "Save Title",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among people you follow",
|
||||
|
@ -397,7 +397,7 @@
|
|||
"security.update_email.success": "Email successfully updated.",
|
||||
"security.update_password.fail": "Update password failed.",
|
||||
"security.update_password.success": "Password successfully updated.",
|
||||
"signup_panel.subtitle": "Sign up now to discuss.",
|
||||
"signup_panel.subtitle": "Sign up now to discuss what's happening.",
|
||||
"signup_panel.title": "New to {site_title}?",
|
||||
"status.admin_account": "Open moderation interface for @{name}",
|
||||
"status.admin_status": "Open this post in the moderation interface",
|
||||
|
@ -637,7 +637,7 @@
|
|||
"empty_column.follow_requests": "You don\"t have any follow requests yet. When you receive one, it will show up here.",
|
||||
"empty_column.group": "There is nothing in this group yet. When members of this group make new posts, they will appear here.",
|
||||
"empty_column.hashtag": "There is nothing in this hashtag yet.",
|
||||
"empty_column.home": "Your home timeline is empty! Visit {public} to get started and meet other users.",
|
||||
"empty_column.home": "Or you can visit {public} to get started and meet other users.",
|
||||
"empty_column.home.local_tab": "the {site_title} tab",
|
||||
"empty_column.list": "There is nothing in this list yet. When members of this list create new posts, they will appear here.",
|
||||
"empty_column.lists": "You don\"t have any lists yet. When you create one, it will show up here.",
|
||||
|
@ -721,7 +721,7 @@
|
|||
"lists.edit": "Edit list",
|
||||
"lists.edit.submit": "Change title",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.create_title": "Create",
|
||||
"lists.new.create_title": "Add list",
|
||||
"lists.new.save_title": "Save Title",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.search": "Search among people you follow",
|
||||
|
@ -836,6 +836,8 @@
|
|||
"registration.lead": "With an account on {instance} you\"ll be able to follow people on any server in the fediverse.",
|
||||
"registration.sign_up": "Sign up",
|
||||
"registration.tos": "Terms of Service",
|
||||
"registration.privacy": "Privacy Policy",
|
||||
"registration.acceptance": "By registering, you agree to the {terms} and {privacy}.",
|
||||
"registration.reason": "Reason for Joining",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.hours": "{number}h",
|
||||
|
@ -876,7 +878,7 @@
|
|||
"security.update_email.success": "Email successfully updated.",
|
||||
"security.update_password.fail": "Update password failed.",
|
||||
"security.update_password.success": "Password successfully updated.",
|
||||
"signup_panel.subtitle": "Sign up now to discuss.",
|
||||
"signup_panel.subtitle": "Sign up now to discuss what's happening.",
|
||||
"signup_panel.title": "New to {site_title}?",
|
||||
"status.admin_account": "Open moderation interface for @{name}",
|
||||
"status.admin_status": "Open this post in the moderation interface",
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { jest } from '@jest/globals';
|
||||
import { AxiosInstance } from 'axios';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import LinkHeader from 'http-link-header';
|
||||
|
||||
import type { AxiosInstance, AxiosResponse } from 'axios';
|
||||
|
||||
const api = jest.requireActual('../api') as Record<string, Function>;
|
||||
let mocks: Array<Function> = [];
|
||||
|
||||
export const __stub = (func: Function) => mocks.push(func);
|
||||
export const __stub = (func: (mock: MockAdapter) => void) => mocks.push(func);
|
||||
export const __clear = (): Function[] => mocks = [];
|
||||
|
||||
const setupMock = (axios: AxiosInstance) => {
|
||||
|
@ -15,6 +17,10 @@ const setupMock = (axios: AxiosInstance) => {
|
|||
|
||||
export const staticClient = api.staticClient;
|
||||
|
||||
export const getLinks = (response: AxiosResponse): LinkHeader => {
|
||||
return new LinkHeader(response.headers?.link);
|
||||
};
|
||||
|
||||
export const baseClient = (...params: any[]) => {
|
||||
const axios = api.baseClient(...params);
|
||||
setupMock(axios);
|
||||
|
|
7
app/soapbox/__tests__/compare_id.test.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import compareId from '../compare_id';
|
||||
|
||||
test('compareId', () => {
|
||||
expect(compareId('3', '3')).toBe(0);
|
||||
expect(compareId('10', '1')).toBe(1);
|
||||
expect(compareId('99', '100')).toBe(-1);
|
||||
});
|
110
app/soapbox/actions/__tests__/account-notes.test.ts
Normal file
|
@ -0,0 +1,110 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { __stub } from 'soapbox/api';
|
||||
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
|
||||
import { ReducerRecord, EditRecord } from 'soapbox/reducers/account_notes';
|
||||
|
||||
import { normalizeAccount, normalizeRelationship } from '../../normalizers';
|
||||
import { changeAccountNoteComment, initAccountNoteModal, submitAccountNote } from '../account-notes';
|
||||
|
||||
import type { Account } from 'soapbox/types/entities';
|
||||
|
||||
describe('submitAccountNote()', () => {
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
const state = rootState
|
||||
.set('account_notes', ReducerRecord({ edit: EditRecord({ account: '1', comment: 'hello' }) }));
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
describe('with a successful API request', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onPost('/api/v1/accounts/1/note').reply(200, {});
|
||||
});
|
||||
});
|
||||
|
||||
it('post the note to the API', async() => {
|
||||
const expectedActions = [
|
||||
{ type: 'ACCOUNT_NOTE_SUBMIT_REQUEST' },
|
||||
{ type: 'MODAL_CLOSE', modalType: undefined },
|
||||
{ type: 'ACCOUNT_NOTE_SUBMIT_SUCCESS', relationship: {} },
|
||||
];
|
||||
await store.dispatch(submitAccountNote());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an unsuccessful API request', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onPost('/api/v1/accounts/1/note').networkError();
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch failed action', async() => {
|
||||
const expectedActions = [
|
||||
{ type: 'ACCOUNT_NOTE_SUBMIT_REQUEST' },
|
||||
{
|
||||
type: 'ACCOUNT_NOTE_SUBMIT_FAIL',
|
||||
error: new Error('Network Error'),
|
||||
},
|
||||
];
|
||||
await store.dispatch(submitAccountNote());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('initAccountNoteModal()', () => {
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
const state = rootState
|
||||
.set('relationships', ImmutableMap({ '1': normalizeRelationship({ note: 'hello' }) }));
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
it('dispatches the proper actions', async() => {
|
||||
const account = normalizeAccount({
|
||||
id: '1',
|
||||
acct: 'justin-username',
|
||||
display_name: 'Justin L',
|
||||
avatar: 'test.jpg',
|
||||
verified: true,
|
||||
}) as Account;
|
||||
const expectedActions = [
|
||||
{ type: 'ACCOUNT_NOTE_INIT_MODAL', account, comment: 'hello' },
|
||||
{ type: 'MODAL_OPEN', modalType: 'ACCOUNT_NOTE' },
|
||||
];
|
||||
await store.dispatch(initAccountNoteModal(account));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('changeAccountNoteComment()', () => {
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
const state = rootState;
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
it('dispatches the proper actions', async() => {
|
||||
const comment = 'hello world';
|
||||
const expectedActions = [
|
||||
{ type: 'ACCOUNT_NOTE_CHANGE_COMMENT', comment },
|
||||
];
|
||||
await store.dispatch(changeAccountNoteComment(comment));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
1638
app/soapbox/actions/__tests__/accounts.test.ts
Normal file
146
app/soapbox/actions/__tests__/alerts.test.ts
Normal file
|
@ -0,0 +1,146 @@
|
|||
import { AxiosError } from 'axios';
|
||||
|
||||
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
|
||||
|
||||
import { dismissAlert, showAlert, showAlertForError } from '../alerts';
|
||||
|
||||
const buildError = (message: string, status: number) => new AxiosError<any>(message, String(status), undefined, null, {
|
||||
data: {
|
||||
error: message,
|
||||
},
|
||||
statusText: String(status),
|
||||
status,
|
||||
headers: {},
|
||||
config: {},
|
||||
});
|
||||
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
const state = rootState;
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
describe('dismissAlert()', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const alert = 'hello world';
|
||||
const expectedActions = [
|
||||
{ type: 'ALERT_DISMISS', alert },
|
||||
];
|
||||
await store.dispatch(dismissAlert(alert as any));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('showAlert()', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const title = 'title';
|
||||
const message = 'msg';
|
||||
const severity = 'info';
|
||||
const expectedActions = [
|
||||
{ type: 'ALERT_SHOW', title, message, severity },
|
||||
];
|
||||
await store.dispatch(showAlert(title, message, severity));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('showAlert()', () => {
|
||||
describe('with a 502 status code', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const message = 'The server is down';
|
||||
const error = buildError(message, 502);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'ALERT_SHOW', title: '', message, severity: 'error' },
|
||||
];
|
||||
await store.dispatch(showAlertForError(error));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a 404 status code', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const error = buildError('', 404);
|
||||
|
||||
await store.dispatch(showAlertForError(error));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a 410 status code', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const error = buildError('', 410);
|
||||
|
||||
await store.dispatch(showAlertForError(error));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an accepted status code', () => {
|
||||
describe('with a message from the server', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const message = 'custom message';
|
||||
const error = buildError(message, 200);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'ALERT_SHOW', title: '', message, severity: 'error' },
|
||||
];
|
||||
await store.dispatch(showAlertForError(error));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('without a message from the server', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const message = 'The request has been accepted for processing';
|
||||
const error = buildError(message, 202);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'ALERT_SHOW', title: '', message, severity: 'error' },
|
||||
];
|
||||
await store.dispatch(showAlertForError(error));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('without a response', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const error = new AxiosError();
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: 'ALERT_SHOW',
|
||||
title: {
|
||||
defaultMessage: 'Oops!',
|
||||
id: 'alert.unexpected.title',
|
||||
},
|
||||
message: {
|
||||
defaultMessage: 'An unexpected error occurred.',
|
||||
id: 'alert.unexpected.message',
|
||||
},
|
||||
severity: 'error',
|
||||
},
|
||||
];
|
||||
await store.dispatch(showAlertForError(error));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
113
app/soapbox/actions/__tests__/announcements.test.ts
Normal file
|
@ -0,0 +1,113 @@
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
import { fetchAnnouncements, dismissAnnouncement, addReaction, removeReaction } from 'soapbox/actions/announcements';
|
||||
import { __stub } from 'soapbox/api';
|
||||
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
|
||||
import { normalizeAnnouncement, normalizeInstance } from 'soapbox/normalizers';
|
||||
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
const announcements = require('soapbox/__fixtures__/announcements.json');
|
||||
|
||||
describe('fetchAnnouncements()', () => {
|
||||
describe('with a successful API request', () => {
|
||||
it('should fetch announcements from the API', async() => {
|
||||
const state = rootState
|
||||
.set('instance', normalizeInstance({ version: '3.5.3' }));
|
||||
const store = mockStore(state);
|
||||
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/announcements').reply(200, announcements);
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'ANNOUNCEMENTS_FETCH_REQUEST', skipLoading: true },
|
||||
{ type: 'ANNOUNCEMENTS_FETCH_SUCCESS', announcements, skipLoading: true },
|
||||
{ type: 'POLLS_IMPORT', polls: [] },
|
||||
{ type: 'ACCOUNTS_IMPORT', accounts: [] },
|
||||
{ type: 'STATUSES_IMPORT', statuses: [], expandSpoilers: false },
|
||||
];
|
||||
await store.dispatch(fetchAnnouncements());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dismissAnnouncement', () => {
|
||||
describe('with a successful API request', () => {
|
||||
it('should mark announcement as dismissed', async() => {
|
||||
const store = mockStore(rootState);
|
||||
|
||||
__stub((mock) => {
|
||||
mock.onPost('/api/v1/announcements/1/dismiss').reply(200);
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'ANNOUNCEMENTS_DISMISS_REQUEST', id: '1' },
|
||||
{ type: 'ANNOUNCEMENTS_DISMISS_SUCCESS', id: '1' },
|
||||
];
|
||||
await store.dispatch(dismissAnnouncement('1'));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addReaction', () => {
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
const state = rootState
|
||||
.setIn(['announcements', 'items'], ImmutableList((announcements).map((announcement: APIEntity) => normalizeAnnouncement(announcement))))
|
||||
.setIn(['announcements', 'isLoading'], false);
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
describe('with a successful API request', () => {
|
||||
it('should add reaction to a post', async() => {
|
||||
__stub((mock) => {
|
||||
mock.onPut('/api/v1/announcements/2/reactions/📉').reply(200);
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'ANNOUNCEMENTS_REACTION_ADD_REQUEST', id: '2', name: '📉', skipLoading: true },
|
||||
{ type: 'ANNOUNCEMENTS_REACTION_ADD_SUCCESS', id: '2', name: '📉', skipLoading: true },
|
||||
];
|
||||
await store.dispatch(addReaction('2', '📉'));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeReaction', () => {
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
const state = rootState
|
||||
.setIn(['announcements', 'items'], ImmutableList((announcements).map((announcement: APIEntity) => normalizeAnnouncement(announcement))))
|
||||
.setIn(['announcements', 'isLoading'], false);
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
describe('with a successful API request', () => {
|
||||
it('should remove reaction from a post', async() => {
|
||||
__stub((mock) => {
|
||||
mock.onDelete('/api/v1/announcements/2/reactions/📉').reply(200);
|
||||
});
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'ANNOUNCEMENTS_REACTION_REMOVE_REQUEST', id: '2', name: '📉', skipLoading: true },
|
||||
{ type: 'ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS', id: '2', name: '📉', skipLoading: true },
|
||||
];
|
||||
await store.dispatch(removeReaction('2', '📉'));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
183
app/soapbox/actions/__tests__/blocks.test.ts
Normal file
|
@ -0,0 +1,183 @@
|
|||
import { __stub } from 'soapbox/api';
|
||||
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
|
||||
import { ListRecord, ReducerRecord as UserListsRecord } from 'soapbox/reducers/user_lists';
|
||||
|
||||
import { expandBlocks, fetchBlocks } from '../blocks';
|
||||
|
||||
const account = {
|
||||
acct: 'twoods',
|
||||
display_name: 'Tiger Woods',
|
||||
id: '22',
|
||||
username: 'twoods',
|
||||
};
|
||||
|
||||
describe('fetchBlocks()', () => {
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
describe('if logged out', () => {
|
||||
beforeEach(() => {
|
||||
const state = rootState.set('me', null);
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
it('should do nothing', async() => {
|
||||
await store.dispatch(fetchBlocks());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if logged in', () => {
|
||||
beforeEach(() => {
|
||||
const state = rootState.set('me', '1234');
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
describe('with a successful API request', () => {
|
||||
beforeEach(() => {
|
||||
const blocks = require('soapbox/__fixtures__/blocks.json');
|
||||
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/blocks').reply(200, blocks, {
|
||||
link: '<https://example.com/api/v1/blocks?since_id=1>; rel=\'prev\'',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch blocks from the API', async() => {
|
||||
const expectedActions = [
|
||||
{ type: 'BLOCKS_FETCH_REQUEST' },
|
||||
{ type: 'ACCOUNTS_IMPORT', accounts: [account] },
|
||||
{ type: 'BLOCKS_FETCH_SUCCESS', accounts: [account], next: null },
|
||||
{
|
||||
type: 'RELATIONSHIPS_FETCH_REQUEST',
|
||||
ids: ['22'],
|
||||
skipLoading: true,
|
||||
},
|
||||
];
|
||||
await store.dispatch(fetchBlocks());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an unsuccessful API request', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/blocks').networkError();
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch failed action', async() => {
|
||||
const expectedActions = [
|
||||
{ type: 'BLOCKS_FETCH_REQUEST' },
|
||||
{ type: 'BLOCKS_FETCH_FAIL', error: new Error('Network Error') },
|
||||
];
|
||||
await store.dispatch(fetchBlocks());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('expandBlocks()', () => {
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
describe('if logged out', () => {
|
||||
beforeEach(() => {
|
||||
const state = rootState.set('me', null);
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
it('should do nothing', async() => {
|
||||
await store.dispatch(expandBlocks());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if logged in', () => {
|
||||
beforeEach(() => {
|
||||
const state = rootState.set('me', '1234');
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
describe('without a url', () => {
|
||||
beforeEach(() => {
|
||||
const state = rootState
|
||||
.set('me', '1234')
|
||||
.set('user_lists', UserListsRecord({ blocks: ListRecord({ next: null }) }));
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
it('should do nothing', async() => {
|
||||
await store.dispatch(expandBlocks());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a url', () => {
|
||||
beforeEach(() => {
|
||||
const state = rootState
|
||||
.set('me', '1234')
|
||||
.set('user_lists', UserListsRecord({ blocks: ListRecord({ next: 'example' }) }));
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
describe('with a successful API request', () => {
|
||||
beforeEach(() => {
|
||||
const blocks = require('soapbox/__fixtures__/blocks.json');
|
||||
|
||||
__stub((mock) => {
|
||||
mock.onGet('example').reply(200, blocks, {
|
||||
link: '<https://example.com/api/v1/blocks?since_id=1>; rel=\'prev\'',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch blocks from the url', async() => {
|
||||
const expectedActions = [
|
||||
{ type: 'BLOCKS_EXPAND_REQUEST' },
|
||||
{ type: 'ACCOUNTS_IMPORT', accounts: [account] },
|
||||
{ type: 'BLOCKS_EXPAND_SUCCESS', accounts: [account], next: null },
|
||||
{
|
||||
type: 'RELATIONSHIPS_FETCH_REQUEST',
|
||||
ids: ['22'],
|
||||
skipLoading: true,
|
||||
},
|
||||
];
|
||||
await store.dispatch(expandBlocks());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an unsuccessful API request', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet('example').networkError();
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch failed action', async() => {
|
||||
const expectedActions = [
|
||||
{ type: 'BLOCKS_EXPAND_REQUEST' },
|
||||
{ type: 'BLOCKS_EXPAND_FAIL', error: new Error('Network Error') },
|
||||
];
|
||||
await store.dispatch(expandBlocks());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,26 +1,30 @@
|
|||
import { mockStore } from 'soapbox/jest/test-helpers';
|
||||
import { InstanceRecord } from 'soapbox/normalizers';
|
||||
import rootReducer from 'soapbox/reducers';
|
||||
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
|
||||
import { uploadCompose } from '../compose';
|
||||
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
|
||||
import { InstanceRecord } from 'soapbox/normalizers';
|
||||
|
||||
import { uploadCompose, submitCompose } from '../compose';
|
||||
import { STATUS_CREATE_REQUEST } from '../statuses';
|
||||
|
||||
import type { IntlShape } from 'react-intl';
|
||||
|
||||
describe('uploadCompose()', () => {
|
||||
describe('with images', () => {
|
||||
let files, store;
|
||||
let files: FileList, store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
const instance = InstanceRecord({
|
||||
configuration: {
|
||||
statuses: {
|
||||
configuration: ImmutableMap({
|
||||
statuses: ImmutableMap({
|
||||
max_media_attachments: 4,
|
||||
},
|
||||
media_attachments: {
|
||||
}),
|
||||
media_attachments: ImmutableMap({
|
||||
image_size_limit: 10,
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const state = rootReducer(undefined, {})
|
||||
const state = rootState
|
||||
.set('me', '1234')
|
||||
.set('instance', instance);
|
||||
|
||||
|
@ -30,13 +34,13 @@ describe('uploadCompose()', () => {
|
|||
name: 'Image',
|
||||
size: 15,
|
||||
type: 'image/png',
|
||||
}];
|
||||
}] as unknown as FileList;
|
||||
});
|
||||
|
||||
it('creates an alert if exceeds max size', async() => {
|
||||
const mockIntl = {
|
||||
formatMessage: jest.fn().mockReturnValue('Image exceeds the current file size limit (10 Bytes)'),
|
||||
};
|
||||
} as unknown as IntlShape;
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'COMPOSE_UPLOAD_REQUEST', skipLoading: true },
|
||||
|
@ -58,21 +62,21 @@ describe('uploadCompose()', () => {
|
|||
});
|
||||
|
||||
describe('with videos', () => {
|
||||
let files, store;
|
||||
let files: FileList, store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
const instance = InstanceRecord({
|
||||
configuration: {
|
||||
statuses: {
|
||||
configuration: ImmutableMap({
|
||||
statuses: ImmutableMap({
|
||||
max_media_attachments: 4,
|
||||
},
|
||||
media_attachments: {
|
||||
}),
|
||||
media_attachments: ImmutableMap({
|
||||
video_size_limit: 10,
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
const state = rootReducer(undefined, {})
|
||||
const state = rootState
|
||||
.set('me', '1234')
|
||||
.set('instance', instance);
|
||||
|
||||
|
@ -82,13 +86,13 @@ describe('uploadCompose()', () => {
|
|||
name: 'Video',
|
||||
size: 15,
|
||||
type: 'video/mp4',
|
||||
}];
|
||||
}] as unknown as FileList;
|
||||
});
|
||||
|
||||
it('creates an alert if exceeds max size', async() => {
|
||||
const mockIntl = {
|
||||
formatMessage: jest.fn().mockReturnValue('Video exceeds the current file size limit (10 Bytes)'),
|
||||
};
|
||||
} as unknown as IntlShape;
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'COMPOSE_UPLOAD_REQUEST', skipLoading: true },
|
||||
|
@ -109,3 +113,26 @@ describe('uploadCompose()', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('submitCompose()', () => {
|
||||
it('inserts mentions from text', async() => {
|
||||
const state = rootState
|
||||
.set('me', '123')
|
||||
.setIn(['compose', 'text'], '@alex hello @mkljczk@pl.fediverse.pl @gg@汉语/漢語.com alex@alexgleason.me');
|
||||
|
||||
const store = mockStore(state);
|
||||
await store.dispatch(submitCompose());
|
||||
const actions = store.getActions();
|
||||
|
||||
const statusCreateRequest = actions.find(action => action.type === STATUS_CREATE_REQUEST);
|
||||
const to = statusCreateRequest!.params.to as ImmutableOrderedSet<string>;
|
||||
|
||||
const expected = [
|
||||
'alex',
|
||||
'mkljczk@pl.fediverse.pl',
|
||||
'gg@汉语/漢語.com',
|
||||
];
|
||||
|
||||
expect(to.toJS()).toEqual(expected);
|
||||
});
|
||||
});
|
115
app/soapbox/actions/__tests__/me.test.ts
Normal file
|
@ -0,0 +1,115 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { __stub } from 'soapbox/api';
|
||||
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
|
||||
|
||||
import {
|
||||
fetchMe, patchMe,
|
||||
} from '../me';
|
||||
|
||||
jest.mock('../../storage/kv_store', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
getItemOrError: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
},
|
||||
}));
|
||||
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
describe('fetchMe()', () => {
|
||||
describe('without a token', () => {
|
||||
beforeEach(() => {
|
||||
const state = rootState;
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
it('dispatches the correct actions', async() => {
|
||||
const expectedActions = [{ type: 'ME_FETCH_SKIP' }];
|
||||
await store.dispatch(fetchMe());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a token', () => {
|
||||
const accountUrl = 'accountUrl';
|
||||
const token = '123';
|
||||
|
||||
beforeEach(() => {
|
||||
const state = rootState
|
||||
.set('auth', ImmutableMap({
|
||||
me: accountUrl,
|
||||
users: ImmutableMap({
|
||||
[accountUrl]: ImmutableMap({
|
||||
'access_token': token,
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
.set('accounts', ImmutableMap({
|
||||
[accountUrl]: {
|
||||
url: accountUrl,
|
||||
},
|
||||
}) as any);
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
describe('with a successful API response', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/accounts/verify_credentials').reply(200, {});
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches the correct actions', async() => {
|
||||
const expectedActions = [
|
||||
{ type: 'ME_FETCH_REQUEST' },
|
||||
{ type: 'AUTH_ACCOUNT_REMEMBER_REQUEST', accountUrl },
|
||||
{ type: 'ACCOUNTS_IMPORT', accounts: [] },
|
||||
{
|
||||
type: 'AUTH_ACCOUNT_REMEMBER_SUCCESS',
|
||||
account: {},
|
||||
accountUrl,
|
||||
},
|
||||
{ type: 'VERIFY_CREDENTIALS_REQUEST', token: '123' },
|
||||
{ type: 'ACCOUNTS_IMPORT', accounts: [] },
|
||||
{ type: 'VERIFY_CREDENTIALS_SUCCESS', token: '123', account: {} },
|
||||
];
|
||||
await store.dispatch(fetchMe());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('patchMe()', () => {
|
||||
beforeEach(() => {
|
||||
const state = rootState;
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
describe('with a successful API response', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onPatch('/api/v1/accounts/update_credentials').reply(200, {});
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches the correct actions', async() => {
|
||||
const expectedActions = [
|
||||
{ type: 'ME_PATCH_REQUEST' },
|
||||
{ type: 'ACCOUNTS_IMPORT', accounts: [] },
|
||||
{
|
||||
type: 'ME_PATCH_SUCCESS',
|
||||
me: {},
|
||||
},
|
||||
];
|
||||
await store.dispatch(patchMe({}));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
38
app/soapbox/actions/__tests__/notifications.test.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { OrderedMap as ImmutableOrderedMap } from 'immutable';
|
||||
|
||||
import { __stub } from 'soapbox/api';
|
||||
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
|
||||
import { normalizeNotification } from 'soapbox/normalizers';
|
||||
|
||||
import { markReadNotifications } from '../notifications';
|
||||
|
||||
describe('markReadNotifications()', () => {
|
||||
it('fires off marker when top notification is newer than lastRead', async() => {
|
||||
__stub((mock) => mock.onPost('/api/v1/markers').reply(200, {}));
|
||||
|
||||
const items = ImmutableOrderedMap({
|
||||
'10': normalizeNotification({ id: '10' }),
|
||||
});
|
||||
|
||||
const state = rootState
|
||||
.set('me', '123')
|
||||
.setIn(['notifications', 'lastRead'], '9')
|
||||
.setIn(['notifications', 'items'], items);
|
||||
|
||||
const store = mockStore(state);
|
||||
|
||||
const expectedActions = [{
|
||||
type: 'MARKER_SAVE_REQUEST',
|
||||
marker: {
|
||||
notifications: {
|
||||
last_read_id: '10',
|
||||
},
|
||||
},
|
||||
}];
|
||||
|
||||
store.dispatch(markReadNotifications());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
|
@ -1,5 +1,4 @@
|
|||
import { mockStore, mockWindowProperty } from 'soapbox/jest/test-helpers';
|
||||
import rootReducer from 'soapbox/reducers';
|
||||
import { mockStore, mockWindowProperty, rootState } from 'soapbox/jest/test-helpers';
|
||||
|
||||
import { checkOnboardingStatus, startOnboarding, endOnboarding } from '../onboarding';
|
||||
|
||||
|
@ -17,7 +16,7 @@ describe('checkOnboarding()', () => {
|
|||
it('does nothing if localStorage item is not set', async() => {
|
||||
mockGetItem = jest.fn().mockReturnValue(null);
|
||||
|
||||
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
|
||||
const state = rootState.setIn(['onboarding', 'needsOnboarding'], false);
|
||||
const store = mockStore(state);
|
||||
|
||||
await store.dispatch(checkOnboardingStatus());
|
||||
|
@ -30,7 +29,7 @@ describe('checkOnboarding()', () => {
|
|||
it('does nothing if localStorage item is invalid', async() => {
|
||||
mockGetItem = jest.fn().mockReturnValue('invalid');
|
||||
|
||||
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
|
||||
const state = rootState.setIn(['onboarding', 'needsOnboarding'], false);
|
||||
const store = mockStore(state);
|
||||
|
||||
await store.dispatch(checkOnboardingStatus());
|
||||
|
@ -43,7 +42,7 @@ describe('checkOnboarding()', () => {
|
|||
it('dispatches the correct action', async() => {
|
||||
mockGetItem = jest.fn().mockReturnValue('1');
|
||||
|
||||
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
|
||||
const state = rootState.setIn(['onboarding', 'needsOnboarding'], false);
|
||||
const store = mockStore(state);
|
||||
|
||||
await store.dispatch(checkOnboardingStatus());
|
||||
|
@ -66,7 +65,7 @@ describe('startOnboarding()', () => {
|
|||
});
|
||||
|
||||
it('dispatches the correct action', async() => {
|
||||
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
|
||||
const state = rootState.setIn(['onboarding', 'needsOnboarding'], false);
|
||||
const store = mockStore(state);
|
||||
|
||||
await store.dispatch(startOnboarding());
|
||||
|
@ -89,7 +88,7 @@ describe('endOnboarding()', () => {
|
|||
});
|
||||
|
||||
it('dispatches the correct action', async() => {
|
||||
const state = rootReducer(undefined, { onboarding: { needsOnboarding: false } });
|
||||
const state = rootState.setIn(['onboarding', 'needsOnboarding'], false);
|
||||
const store = mockStore(state);
|
||||
|
||||
await store.dispatch(endOnboarding());
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import { STATUSES_IMPORT } from 'soapbox/actions/importer';
|
||||
import { __stub } from 'soapbox/api';
|
||||
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
|
||||
|
||||
import { fetchContext } from '../statuses';
|
||||
|
||||
describe('fetchContext()', () => {
|
||||
it('handles Mitra context', done => {
|
||||
const statuses = require('soapbox/__fixtures__/mitra-context.json');
|
||||
|
||||
__stub(mock => {
|
||||
mock.onGet('/api/v1/statuses/017ed505-5926-392f-256a-f86d5075df70/context')
|
||||
.reply(200, statuses);
|
||||
});
|
||||
|
||||
const store = mockStore(rootState);
|
||||
|
||||
store.dispatch(fetchContext('017ed505-5926-392f-256a-f86d5075df70')).then(context => {
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions[3].type).toEqual(STATUSES_IMPORT);
|
||||
expect(actions[3].statuses[0].id).toEqual('017ed503-bc96-301a-e871-2c23b30ddd05');
|
||||
|
||||
done();
|
||||
}).catch(console.error);
|
||||
});
|
||||
});
|
160
app/soapbox/actions/__tests__/statuses.test.ts
Normal file
|
@ -0,0 +1,160 @@
|
|||
import { fromJS, Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { STATUSES_IMPORT } from 'soapbox/actions/importer';
|
||||
import { __stub } from 'soapbox/api';
|
||||
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
|
||||
import { normalizeStatus } from 'soapbox/normalizers/status';
|
||||
|
||||
import { deleteStatus, fetchContext } from '../statuses';
|
||||
|
||||
describe('fetchContext()', () => {
|
||||
it('handles Mitra context', done => {
|
||||
const statuses = require('soapbox/__fixtures__/mitra-context.json');
|
||||
|
||||
__stub(mock => {
|
||||
mock.onGet('/api/v1/statuses/017ed505-5926-392f-256a-f86d5075df70/context')
|
||||
.reply(200, statuses);
|
||||
});
|
||||
|
||||
const store = mockStore(rootState);
|
||||
|
||||
store.dispatch(fetchContext('017ed505-5926-392f-256a-f86d5075df70')).then(() => {
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions[3].type).toEqual(STATUSES_IMPORT);
|
||||
expect(actions[3].statuses[0].id).toEqual('017ed503-bc96-301a-e871-2c23b30ddd05');
|
||||
|
||||
done();
|
||||
}).catch(console.error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteStatus()', () => {
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
describe('if logged out', () => {
|
||||
beforeEach(() => {
|
||||
const state = rootState.set('me', null);
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
it('should do nothing', async() => {
|
||||
await store.dispatch(deleteStatus('1'));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if logged in', () => {
|
||||
const statusId = 'AHU2RrX0wdcwzCYjFQ';
|
||||
const cachedStatus = normalizeStatus({
|
||||
id: statusId,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const state = rootState
|
||||
.set('me', '1234')
|
||||
.set('statuses', fromJS({
|
||||
[statusId]: cachedStatus,
|
||||
}) as any);
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
describe('with a successful API request', () => {
|
||||
let status: any;
|
||||
|
||||
beforeEach(() => {
|
||||
status = require('soapbox/__fixtures__/pleroma-status-deleted.json');
|
||||
|
||||
__stub((mock) => {
|
||||
mock.onDelete(`/api/v1/statuses/${statusId}`).reply(200, status);
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete the status from the API', async() => {
|
||||
const expectedActions = [
|
||||
{
|
||||
type: 'STATUS_DELETE_REQUEST',
|
||||
params: cachedStatus,
|
||||
},
|
||||
{ type: 'STATUS_DELETE_SUCCESS', id: statusId },
|
||||
{
|
||||
type: 'TIMELINE_DELETE',
|
||||
id: statusId,
|
||||
accountId: null,
|
||||
references: ImmutableMap({}),
|
||||
reblogOf: null,
|
||||
},
|
||||
];
|
||||
await store.dispatch(deleteStatus(statusId));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
|
||||
it('should handle redraft', async() => {
|
||||
const expectedActions = [
|
||||
{
|
||||
type: 'STATUS_DELETE_REQUEST',
|
||||
params: cachedStatus,
|
||||
},
|
||||
{ type: 'STATUS_DELETE_SUCCESS', id: statusId },
|
||||
{
|
||||
type: 'TIMELINE_DELETE',
|
||||
id: statusId,
|
||||
accountId: null,
|
||||
references: ImmutableMap({}),
|
||||
reblogOf: null,
|
||||
},
|
||||
{
|
||||
type: 'COMPOSE_SET_STATUS',
|
||||
status: cachedStatus,
|
||||
rawText: status.text,
|
||||
explicitAddressing: false,
|
||||
spoilerText: '',
|
||||
contentType: 'text/markdown',
|
||||
v: {
|
||||
build: undefined,
|
||||
compatVersion: '0.0.0',
|
||||
software: 'Mastodon',
|
||||
version: '0.0.0',
|
||||
},
|
||||
withRedraft: true,
|
||||
},
|
||||
{ type: 'MODAL_OPEN', modalType: 'COMPOSE', modalProps: undefined },
|
||||
];
|
||||
await store.dispatch(deleteStatus(statusId, true));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an unsuccessful API request', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onDelete(`/api/v1/statuses/${statusId}`).networkError();
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch failed action', async() => {
|
||||
const expectedActions = [
|
||||
{
|
||||
type: 'STATUS_DELETE_REQUEST',
|
||||
params: cachedStatus,
|
||||
},
|
||||
{
|
||||
type: 'STATUS_DELETE_FAIL',
|
||||
params: cachedStatus,
|
||||
error: new Error('Network Error'),
|
||||
},
|
||||
];
|
||||
await store.dispatch(deleteStatus(statusId, true));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
109
app/soapbox/actions/__tests__/suggestions.test.ts
Normal file
|
@ -0,0 +1,109 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { __stub } from 'soapbox/api';
|
||||
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
|
||||
import { normalizeInstance } from 'soapbox/normalizers';
|
||||
|
||||
import {
|
||||
fetchSuggestions,
|
||||
} from '../suggestions';
|
||||
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
let state;
|
||||
|
||||
describe('fetchSuggestions()', () => {
|
||||
describe('with Truth Social software', () => {
|
||||
beforeEach(() => {
|
||||
state = rootState
|
||||
.set('instance', normalizeInstance({
|
||||
version: '3.4.1 (compatible; TruthSocial 1.0.0)',
|
||||
pleroma: ImmutableMap({
|
||||
metadata: ImmutableMap({
|
||||
features: [],
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
.set('me', '123');
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
describe('with a successful API request', () => {
|
||||
const response = [
|
||||
{
|
||||
account_id: '1',
|
||||
acct: 'jl',
|
||||
account_avatar: 'https://example.com/some.jpg',
|
||||
display_name: 'justin',
|
||||
note: '<p>note</p>',
|
||||
verified: true,
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/truth/carousels/suggestions').reply(200, response, {
|
||||
link: '<https://example.com/api/v1/truth/carousels/suggestions?since_id=1>; rel=\'prev\'',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches the correct actions', async() => {
|
||||
const expectedActions = [
|
||||
{ type: 'SUGGESTIONS_V2_FETCH_REQUEST', skipLoading: true },
|
||||
{
|
||||
type: 'ACCOUNTS_IMPORT', accounts: [{
|
||||
acct: response[0].acct,
|
||||
avatar: response[0].account_avatar,
|
||||
avatar_static: response[0].account_avatar,
|
||||
id: response[0].account_id,
|
||||
note: response[0].note,
|
||||
should_refetch: true,
|
||||
verified: response[0].verified,
|
||||
display_name: response[0].display_name,
|
||||
}],
|
||||
},
|
||||
{
|
||||
type: 'SUGGESTIONS_TRUTH_FETCH_SUCCESS',
|
||||
suggestions: response,
|
||||
next: undefined,
|
||||
skipLoading: true,
|
||||
},
|
||||
{
|
||||
type: 'RELATIONSHIPS_FETCH_REQUEST',
|
||||
skipLoading: true,
|
||||
ids: [response[0].account_id],
|
||||
},
|
||||
];
|
||||
await store.dispatch(fetchSuggestions());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an unsuccessful API request', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/truth/carousels/suggestions').networkError();
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch the correct actions', async() => {
|
||||
const expectedActions = [
|
||||
{ type: 'SUGGESTIONS_V2_FETCH_REQUEST', skipLoading: true },
|
||||
{
|
||||
type: 'SUGGESTIONS_V2_FETCH_FAIL',
|
||||
error: new Error('Network Error'),
|
||||
skipLoading: true,
|
||||
skipAlert: true,
|
||||
},
|
||||
];
|
||||
|
||||
await store.dispatch(fetchSuggestions());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,19 +0,0 @@
|
|||
import { staticClient } from '../api';
|
||||
|
||||
export const FETCH_ABOUT_PAGE_REQUEST = 'FETCH_ABOUT_PAGE_REQUEST';
|
||||
export const FETCH_ABOUT_PAGE_SUCCESS = 'FETCH_ABOUT_PAGE_SUCCESS';
|
||||
export const FETCH_ABOUT_PAGE_FAIL = 'FETCH_ABOUT_PAGE_FAIL';
|
||||
|
||||
export function fetchAboutPage(slug = 'index', locale) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: FETCH_ABOUT_PAGE_REQUEST, slug, locale });
|
||||
const filename = `${slug}${locale ? `.${locale}` : ''}.html`;
|
||||
return staticClient.get(`/instance/about/${filename}`).then(({ data: html }) => {
|
||||
dispatch({ type: FETCH_ABOUT_PAGE_SUCCESS, slug, locale, html });
|
||||
return html;
|
||||
}).catch(error => {
|
||||
dispatch({ type: FETCH_ABOUT_PAGE_FAIL, slug, locale, error });
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
}
|
29
app/soapbox/actions/about.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { staticClient } from '../api';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
|
||||
const FETCH_ABOUT_PAGE_REQUEST = 'FETCH_ABOUT_PAGE_REQUEST';
|
||||
const FETCH_ABOUT_PAGE_SUCCESS = 'FETCH_ABOUT_PAGE_SUCCESS';
|
||||
const FETCH_ABOUT_PAGE_FAIL = 'FETCH_ABOUT_PAGE_FAIL';
|
||||
|
||||
const fetchAboutPage = (slug = 'index', locale?: string) => (dispatch: React.Dispatch<AnyAction>) => {
|
||||
dispatch({ type: FETCH_ABOUT_PAGE_REQUEST, slug, locale });
|
||||
|
||||
const filename = `${slug}${locale ? `.${locale}` : ''}.html`;
|
||||
return staticClient.get(`/instance/about/${filename}`)
|
||||
.then(({ data: html }) => {
|
||||
dispatch({ type: FETCH_ABOUT_PAGE_SUCCESS, slug, locale, html });
|
||||
return html;
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch({ type: FETCH_ABOUT_PAGE_FAIL, slug, locale, error });
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
fetchAboutPage,
|
||||
FETCH_ABOUT_PAGE_REQUEST,
|
||||
FETCH_ABOUT_PAGE_SUCCESS,
|
||||
FETCH_ABOUT_PAGE_FAIL,
|
||||
};
|
82
app/soapbox/actions/account-notes.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
import api from '../api';
|
||||
|
||||
import { openModal, closeModal } from './modals';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { AnyAction } from 'redux';
|
||||
import type { RootState } from 'soapbox/store';
|
||||
import type { Account } from 'soapbox/types/entities';
|
||||
|
||||
const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST';
|
||||
const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS';
|
||||
const ACCOUNT_NOTE_SUBMIT_FAIL = 'ACCOUNT_NOTE_SUBMIT_FAIL';
|
||||
|
||||
const ACCOUNT_NOTE_INIT_MODAL = 'ACCOUNT_NOTE_INIT_MODAL';
|
||||
|
||||
const ACCOUNT_NOTE_CHANGE_COMMENT = 'ACCOUNT_NOTE_CHANGE_COMMENT';
|
||||
|
||||
const submitAccountNote = () => (dispatch: React.Dispatch<AnyAction>, getState: () => RootState) => {
|
||||
dispatch(submitAccountNoteRequest());
|
||||
|
||||
const id = getState().account_notes.edit.account;
|
||||
|
||||
return api(getState)
|
||||
.post(`/api/v1/accounts/${id}/note`, {
|
||||
comment: getState().account_notes.edit.comment,
|
||||
})
|
||||
.then(response => {
|
||||
dispatch(closeModal());
|
||||
dispatch(submitAccountNoteSuccess(response.data));
|
||||
})
|
||||
.catch(error => dispatch(submitAccountNoteFail(error)));
|
||||
};
|
||||
|
||||
function submitAccountNoteRequest() {
|
||||
return {
|
||||
type: ACCOUNT_NOTE_SUBMIT_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
function submitAccountNoteSuccess(relationship: any) {
|
||||
return {
|
||||
type: ACCOUNT_NOTE_SUBMIT_SUCCESS,
|
||||
relationship,
|
||||
};
|
||||
}
|
||||
|
||||
function submitAccountNoteFail(error: AxiosError) {
|
||||
return {
|
||||
type: ACCOUNT_NOTE_SUBMIT_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
const initAccountNoteModal = (account: Account) => (dispatch: React.Dispatch<AnyAction>, getState: () => RootState) => {
|
||||
const comment = getState().relationships.get(account.id)!.note;
|
||||
|
||||
dispatch({
|
||||
type: ACCOUNT_NOTE_INIT_MODAL,
|
||||
account,
|
||||
comment,
|
||||
});
|
||||
|
||||
dispatch(openModal('ACCOUNT_NOTE'));
|
||||
};
|
||||
|
||||
function changeAccountNoteComment(comment: string) {
|
||||
return {
|
||||
type: ACCOUNT_NOTE_CHANGE_COMMENT,
|
||||
comment,
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
submitAccountNote,
|
||||
initAccountNoteModal,
|
||||
changeAccountNoteComment,
|
||||
ACCOUNT_NOTE_SUBMIT_REQUEST,
|
||||
ACCOUNT_NOTE_SUBMIT_SUCCESS,
|
||||
ACCOUNT_NOTE_SUBMIT_FAIL,
|
||||
ACCOUNT_NOTE_INIT_MODAL,
|
||||
ACCOUNT_NOTE_CHANGE_COMMENT,
|
||||
};
|
|
@ -1,67 +0,0 @@
|
|||
import api from '../api';
|
||||
|
||||
import { openModal, closeModal } from './modals';
|
||||
|
||||
export const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST';
|
||||
export const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS';
|
||||
export const ACCOUNT_NOTE_SUBMIT_FAIL = 'ACCOUNT_NOTE_SUBMIT_FAIL';
|
||||
|
||||
export const ACCOUNT_NOTE_INIT_MODAL = 'ACCOUNT_NOTE_INIT_MODAL';
|
||||
|
||||
export const ACCOUNT_NOTE_CHANGE_COMMENT = 'ACCOUNT_NOTE_CHANGE_COMMENT';
|
||||
|
||||
export function submitAccountNote() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(submitAccountNoteRequest());
|
||||
|
||||
const id = getState().getIn(['account_notes', 'edit', 'account_id']);
|
||||
|
||||
api(getState).post(`/api/v1/accounts/${id}/note`, {
|
||||
comment: getState().getIn(['account_notes', 'edit', 'comment']),
|
||||
}).then(response => {
|
||||
dispatch(closeModal());
|
||||
dispatch(submitAccountNoteSuccess(response.data));
|
||||
}).catch(error => dispatch(submitAccountNoteFail(error)));
|
||||
};
|
||||
}
|
||||
|
||||
export function submitAccountNoteRequest() {
|
||||
return {
|
||||
type: ACCOUNT_NOTE_SUBMIT_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
export function submitAccountNoteSuccess(relationship) {
|
||||
return {
|
||||
type: ACCOUNT_NOTE_SUBMIT_SUCCESS,
|
||||
relationship,
|
||||
};
|
||||
}
|
||||
|
||||
export function submitAccountNoteFail(error) {
|
||||
return {
|
||||
type: ACCOUNT_NOTE_SUBMIT_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function initAccountNoteModal(account) {
|
||||
return (dispatch, getState) => {
|
||||
const comment = getState().getIn(['relationships', account.get('id'), 'note']);
|
||||
|
||||
dispatch({
|
||||
type: ACCOUNT_NOTE_INIT_MODAL,
|
||||
account,
|
||||
comment,
|
||||
});
|
||||
|
||||
dispatch(openModal('ACCOUNT_NOTE'));
|
||||
};
|
||||
}
|
||||
|
||||
export function changeAccountNoteComment(comment) {
|
||||
return {
|
||||
type: ACCOUNT_NOTE_CHANGE_COMMENT,
|
||||
comment,
|
||||
};
|
||||
}
|
1144
app/soapbox/actions/accounts.ts
Normal file
|
@ -1,370 +0,0 @@
|
|||
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_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS';
|
||||
export const ADMIN_CONFIG_FETCH_FAIL = 'ADMIN_CONFIG_FETCH_FAIL';
|
||||
|
||||
export const ADMIN_CONFIG_UPDATE_REQUEST = 'ADMIN_CONFIG_UPDATE_REQUEST';
|
||||
export const ADMIN_CONFIG_UPDATE_SUCCESS = 'ADMIN_CONFIG_UPDATE_SUCCESS';
|
||||
export const ADMIN_CONFIG_UPDATE_FAIL = 'ADMIN_CONFIG_UPDATE_FAIL';
|
||||
|
||||
export const ADMIN_REPORTS_FETCH_REQUEST = 'ADMIN_REPORTS_FETCH_REQUEST';
|
||||
export const ADMIN_REPORTS_FETCH_SUCCESS = 'ADMIN_REPORTS_FETCH_SUCCESS';
|
||||
export const ADMIN_REPORTS_FETCH_FAIL = 'ADMIN_REPORTS_FETCH_FAIL';
|
||||
|
||||
export const ADMIN_REPORTS_PATCH_REQUEST = 'ADMIN_REPORTS_PATCH_REQUEST';
|
||||
export const ADMIN_REPORTS_PATCH_SUCCESS = 'ADMIN_REPORTS_PATCH_SUCCESS';
|
||||
export const ADMIN_REPORTS_PATCH_FAIL = 'ADMIN_REPORTS_PATCH_FAIL';
|
||||
|
||||
export const ADMIN_USERS_FETCH_REQUEST = 'ADMIN_USERS_FETCH_REQUEST';
|
||||
export const ADMIN_USERS_FETCH_SUCCESS = 'ADMIN_USERS_FETCH_SUCCESS';
|
||||
export const ADMIN_USERS_FETCH_FAIL = 'ADMIN_USERS_FETCH_FAIL';
|
||||
|
||||
export const ADMIN_USERS_DELETE_REQUEST = 'ADMIN_USERS_DELETE_REQUEST';
|
||||
export const ADMIN_USERS_DELETE_SUCCESS = 'ADMIN_USERS_DELETE_SUCCESS';
|
||||
export const ADMIN_USERS_DELETE_FAIL = 'ADMIN_USERS_DELETE_FAIL';
|
||||
|
||||
export const ADMIN_USERS_APPROVE_REQUEST = 'ADMIN_USERS_APPROVE_REQUEST';
|
||||
export const ADMIN_USERS_APPROVE_SUCCESS = 'ADMIN_USERS_APPROVE_SUCCESS';
|
||||
export const ADMIN_USERS_APPROVE_FAIL = 'ADMIN_USERS_APPROVE_FAIL';
|
||||
|
||||
export const ADMIN_USERS_DEACTIVATE_REQUEST = 'ADMIN_USERS_DEACTIVATE_REQUEST';
|
||||
export const ADMIN_USERS_DEACTIVATE_SUCCESS = 'ADMIN_USERS_DEACTIVATE_SUCCESS';
|
||||
export const ADMIN_USERS_DEACTIVATE_FAIL = 'ADMIN_USERS_DEACTIVATE_FAIL';
|
||||
|
||||
export const ADMIN_STATUS_DELETE_REQUEST = 'ADMIN_STATUS_DELETE_REQUEST';
|
||||
export const ADMIN_STATUS_DELETE_SUCCESS = 'ADMIN_STATUS_DELETE_SUCCESS';
|
||||
export const ADMIN_STATUS_DELETE_FAIL = 'ADMIN_STATUS_DELETE_FAIL';
|
||||
|
||||
export const ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST';
|
||||
export const ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS';
|
||||
export const ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL';
|
||||
|
||||
export const ADMIN_LOG_FETCH_REQUEST = 'ADMIN_LOG_FETCH_REQUEST';
|
||||
export const ADMIN_LOG_FETCH_SUCCESS = 'ADMIN_LOG_FETCH_SUCCESS';
|
||||
export const ADMIN_LOG_FETCH_FAIL = 'ADMIN_LOG_FETCH_FAIL';
|
||||
|
||||
export const ADMIN_USERS_TAG_REQUEST = 'ADMIN_USERS_TAG_REQUEST';
|
||||
export const ADMIN_USERS_TAG_SUCCESS = 'ADMIN_USERS_TAG_SUCCESS';
|
||||
export const ADMIN_USERS_TAG_FAIL = 'ADMIN_USERS_TAG_FAIL';
|
||||
|
||||
export const ADMIN_USERS_UNTAG_REQUEST = 'ADMIN_USERS_UNTAG_REQUEST';
|
||||
export const ADMIN_USERS_UNTAG_SUCCESS = 'ADMIN_USERS_UNTAG_SUCCESS';
|
||||
export const ADMIN_USERS_UNTAG_FAIL = 'ADMIN_USERS_UNTAG_FAIL';
|
||||
|
||||
export const ADMIN_ADD_PERMISSION_GROUP_REQUEST = 'ADMIN_ADD_PERMISSION_GROUP_REQUEST';
|
||||
export const ADMIN_ADD_PERMISSION_GROUP_SUCCESS = 'ADMIN_ADD_PERMISSION_GROUP_SUCCESS';
|
||||
export const ADMIN_ADD_PERMISSION_GROUP_FAIL = 'ADMIN_ADD_PERMISSION_GROUP_FAIL';
|
||||
|
||||
export const ADMIN_REMOVE_PERMISSION_GROUP_REQUEST = 'ADMIN_REMOVE_PERMISSION_GROUP_REQUEST';
|
||||
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_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']));
|
||||
|
||||
export function fetchConfig() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: ADMIN_CONFIG_FETCH_REQUEST });
|
||||
return api(getState)
|
||||
.get('/api/pleroma/admin/config')
|
||||
.then(({ data }) => {
|
||||
dispatch({ type: ADMIN_CONFIG_FETCH_SUCCESS, configs: data.configs, needsReboot: data.need_reboot });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_CONFIG_FETCH_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function updateConfig(configs) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: ADMIN_CONFIG_UPDATE_REQUEST, configs });
|
||||
return api(getState)
|
||||
.post('/api/pleroma/admin/config', { configs })
|
||||
.then(({ data }) => {
|
||||
dispatch({ type: ADMIN_CONFIG_UPDATE_SUCCESS, configs: data.configs, needsReboot: data.need_reboot });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_CONFIG_UPDATE_FAIL, error, configs });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchReports(params) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: ADMIN_REPORTS_FETCH_REQUEST, params });
|
||||
return api(getState)
|
||||
.get('/api/pleroma/admin/reports', { params })
|
||||
.then(({ data: { reports } }) => {
|
||||
reports.forEach(report => {
|
||||
dispatch(importFetchedAccount(report.account));
|
||||
dispatch(importFetchedAccount(report.actor));
|
||||
dispatch(importFetchedStatuses(report.statuses));
|
||||
});
|
||||
dispatch({ type: ADMIN_REPORTS_FETCH_SUCCESS, reports, params });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_REPORTS_FETCH_FAIL, error, params });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function patchReports(ids, state) {
|
||||
const reports = ids.map(id => ({ id, state }));
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: ADMIN_REPORTS_PATCH_REQUEST, reports });
|
||||
return api(getState)
|
||||
.patch('/api/pleroma/admin/reports', { reports })
|
||||
.then(() => {
|
||||
dispatch({ type: ADMIN_REPORTS_PATCH_SUCCESS, reports });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_REPORTS_PATCH_FAIL, error, reports });
|
||||
});
|
||||
};
|
||||
}
|
||||
export function closeReports(ids) {
|
||||
return patchReports(ids, 'closed');
|
||||
}
|
||||
|
||||
export function fetchUsers(filters = [], page = 1, query, pageSize = 50) {
|
||||
return (dispatch, getState) => {
|
||||
const params = { filters: filters.join(), page, page_size: pageSize };
|
||||
if (query) params.query = query;
|
||||
|
||||
dispatch({ type: ADMIN_USERS_FETCH_REQUEST, filters, page, pageSize });
|
||||
return api(getState)
|
||||
.get('/api/pleroma/admin/users', { params })
|
||||
.then(({ data: { users, count, page_size: pageSize } }) => {
|
||||
dispatch(fetchRelationships(users.map(user => user.id)));
|
||||
dispatch({ type: ADMIN_USERS_FETCH_SUCCESS, users, count, pageSize, filters, page });
|
||||
return { users, count, pageSize };
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_USERS_FETCH_FAIL, error, filters, page, pageSize });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function deactivateUsers(accountIds) {
|
||||
return (dispatch, getState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
dispatch({ type: ADMIN_USERS_DEACTIVATE_REQUEST, accountIds });
|
||||
return api(getState)
|
||||
.patch('/api/pleroma/admin/users/deactivate', { nicknames })
|
||||
.then(({ data: { users } }) => {
|
||||
dispatch({ type: ADMIN_USERS_DEACTIVATE_SUCCESS, users, accountIds });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_USERS_DEACTIVATE_FAIL, error, accountIds });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteUsers(accountIds) {
|
||||
return (dispatch, getState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
dispatch({ type: ADMIN_USERS_DELETE_REQUEST, accountIds });
|
||||
return api(getState)
|
||||
.delete('/api/pleroma/admin/users', { data: { nicknames } })
|
||||
.then(({ data: nicknames }) => {
|
||||
dispatch({ type: ADMIN_USERS_DELETE_SUCCESS, nicknames, accountIds });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_USERS_DELETE_FAIL, error, accountIds });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function approveUsers(accountIds) {
|
||||
return (dispatch, getState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
dispatch({ type: ADMIN_USERS_APPROVE_REQUEST, accountIds });
|
||||
return api(getState)
|
||||
.patch('/api/pleroma/admin/users/approve', { nicknames })
|
||||
.then(({ data: { users } }) => {
|
||||
dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, users, accountIds });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_USERS_APPROVE_FAIL, error, accountIds });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteStatus(id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: ADMIN_STATUS_DELETE_REQUEST, id });
|
||||
return api(getState)
|
||||
.delete(`/api/pleroma/admin/statuses/${id}`)
|
||||
.then(() => {
|
||||
dispatch({ type: ADMIN_STATUS_DELETE_SUCCESS, id });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_STATUS_DELETE_FAIL, error, id });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleStatusSensitivity(id, sensitive) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST, id });
|
||||
return api(getState)
|
||||
.put(`/api/pleroma/admin/statuses/${id}`, { sensitive: !sensitive })
|
||||
.then(() => {
|
||||
dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS, id });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL, error, id });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchModerationLog(params) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: ADMIN_LOG_FETCH_REQUEST });
|
||||
return api(getState)
|
||||
.get('/api/pleroma/admin/moderation_log', { params })
|
||||
.then(({ data }) => {
|
||||
dispatch({ type: ADMIN_LOG_FETCH_SUCCESS, items: data.items, total: data.total });
|
||||
return data;
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_LOG_FETCH_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function tagUsers(accountIds, tags) {
|
||||
return (dispatch, getState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
dispatch({ type: ADMIN_USERS_TAG_REQUEST, accountIds, tags });
|
||||
return api(getState)
|
||||
.put('/api/v1/pleroma/admin/users/tag', { nicknames, tags })
|
||||
.then(() => {
|
||||
dispatch({ type: ADMIN_USERS_TAG_SUCCESS, accountIds, tags });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_USERS_TAG_FAIL, error, accountIds, tags });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function untagUsers(accountIds, tags) {
|
||||
return (dispatch, getState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
dispatch({ type: ADMIN_USERS_UNTAG_REQUEST, accountIds, tags });
|
||||
return api(getState)
|
||||
.delete('/api/v1/pleroma/admin/users/tag', { data: { nicknames, tags } })
|
||||
.then(() => {
|
||||
dispatch({ type: ADMIN_USERS_UNTAG_SUCCESS, accountIds, tags });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_USERS_UNTAG_FAIL, error, accountIds, tags });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function verifyUser(accountId) {
|
||||
return (dispatch, getState) => {
|
||||
return dispatch(tagUsers([accountId], ['verified']));
|
||||
};
|
||||
}
|
||||
|
||||
export function unverifyUser(accountId) {
|
||||
return (dispatch, getState) => {
|
||||
return dispatch(untagUsers([accountId], ['verified']));
|
||||
};
|
||||
}
|
||||
|
||||
export function setDonor(accountId) {
|
||||
return (dispatch, getState) => {
|
||||
return dispatch(tagUsers([accountId], ['donor']));
|
||||
};
|
||||
}
|
||||
|
||||
export function removeDonor(accountId) {
|
||||
return (dispatch, getState) => {
|
||||
return dispatch(untagUsers([accountId], ['donor']));
|
||||
};
|
||||
}
|
||||
|
||||
export function addPermission(accountIds, permissionGroup) {
|
||||
return (dispatch, getState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
dispatch({ type: ADMIN_ADD_PERMISSION_GROUP_REQUEST, accountIds, permissionGroup });
|
||||
return api(getState)
|
||||
.post(`/api/v1/pleroma/admin/users/permission_group/${permissionGroup}`, { nicknames })
|
||||
.then(({ data }) => {
|
||||
dispatch({ type: ADMIN_ADD_PERMISSION_GROUP_SUCCESS, accountIds, permissionGroup, data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_ADD_PERMISSION_GROUP_FAIL, error, accountIds, permissionGroup });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function removePermission(accountIds, permissionGroup) {
|
||||
return (dispatch, getState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
dispatch({ type: ADMIN_REMOVE_PERMISSION_GROUP_REQUEST, accountIds, permissionGroup });
|
||||
return api(getState)
|
||||
.delete(`/api/v1/pleroma/admin/users/permission_group/${permissionGroup}`, { data: { nicknames } })
|
||||
.then(({ data }) => {
|
||||
dispatch({ type: ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS, accountIds, permissionGroup, data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_REMOVE_PERMISSION_GROUP_FAIL, error, accountIds, permissionGroup });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function promoteToAdmin(accountId) {
|
||||
return (dispatch, getState) => {
|
||||
return Promise.all([
|
||||
dispatch(addPermission([accountId], 'admin')),
|
||||
dispatch(removePermission([accountId], 'moderator')),
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
export function promoteToModerator(accountId) {
|
||||
return (dispatch, getState) => {
|
||||
return Promise.all([
|
||||
dispatch(removePermission([accountId], 'admin')),
|
||||
dispatch(addPermission([accountId], 'moderator')),
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
export function demoteToUser(accountId) {
|
||||
return (dispatch, getState) => {
|
||||
return Promise.all([
|
||||
dispatch(removePermission([accountId], 'admin')),
|
||||
dispatch(removePermission([accountId], 'moderator')),
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
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 });
|
||||
});
|
||||
};
|
||||
}
|
581
app/soapbox/actions/admin.ts
Normal file
|
@ -0,0 +1,581 @@
|
|||
import { fetchRelationships } from 'soapbox/actions/accounts';
|
||||
import { importFetchedAccount, importFetchedAccounts, importFetchedStatuses } from 'soapbox/actions/importer';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import api, { getLinks } from '../api';
|
||||
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
const ADMIN_CONFIG_FETCH_REQUEST = 'ADMIN_CONFIG_FETCH_REQUEST';
|
||||
const ADMIN_CONFIG_FETCH_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS';
|
||||
const ADMIN_CONFIG_FETCH_FAIL = 'ADMIN_CONFIG_FETCH_FAIL';
|
||||
|
||||
const ADMIN_CONFIG_UPDATE_REQUEST = 'ADMIN_CONFIG_UPDATE_REQUEST';
|
||||
const ADMIN_CONFIG_UPDATE_SUCCESS = 'ADMIN_CONFIG_UPDATE_SUCCESS';
|
||||
const ADMIN_CONFIG_UPDATE_FAIL = 'ADMIN_CONFIG_UPDATE_FAIL';
|
||||
|
||||
const ADMIN_REPORTS_FETCH_REQUEST = 'ADMIN_REPORTS_FETCH_REQUEST';
|
||||
const ADMIN_REPORTS_FETCH_SUCCESS = 'ADMIN_REPORTS_FETCH_SUCCESS';
|
||||
const ADMIN_REPORTS_FETCH_FAIL = 'ADMIN_REPORTS_FETCH_FAIL';
|
||||
|
||||
const ADMIN_REPORTS_PATCH_REQUEST = 'ADMIN_REPORTS_PATCH_REQUEST';
|
||||
const ADMIN_REPORTS_PATCH_SUCCESS = 'ADMIN_REPORTS_PATCH_SUCCESS';
|
||||
const ADMIN_REPORTS_PATCH_FAIL = 'ADMIN_REPORTS_PATCH_FAIL';
|
||||
|
||||
const ADMIN_USERS_FETCH_REQUEST = 'ADMIN_USERS_FETCH_REQUEST';
|
||||
const ADMIN_USERS_FETCH_SUCCESS = 'ADMIN_USERS_FETCH_SUCCESS';
|
||||
const ADMIN_USERS_FETCH_FAIL = 'ADMIN_USERS_FETCH_FAIL';
|
||||
|
||||
const ADMIN_USERS_DELETE_REQUEST = 'ADMIN_USERS_DELETE_REQUEST';
|
||||
const ADMIN_USERS_DELETE_SUCCESS = 'ADMIN_USERS_DELETE_SUCCESS';
|
||||
const ADMIN_USERS_DELETE_FAIL = 'ADMIN_USERS_DELETE_FAIL';
|
||||
|
||||
const ADMIN_USERS_APPROVE_REQUEST = 'ADMIN_USERS_APPROVE_REQUEST';
|
||||
const ADMIN_USERS_APPROVE_SUCCESS = 'ADMIN_USERS_APPROVE_SUCCESS';
|
||||
const ADMIN_USERS_APPROVE_FAIL = 'ADMIN_USERS_APPROVE_FAIL';
|
||||
|
||||
const ADMIN_USERS_DEACTIVATE_REQUEST = 'ADMIN_USERS_DEACTIVATE_REQUEST';
|
||||
const ADMIN_USERS_DEACTIVATE_SUCCESS = 'ADMIN_USERS_DEACTIVATE_SUCCESS';
|
||||
const ADMIN_USERS_DEACTIVATE_FAIL = 'ADMIN_USERS_DEACTIVATE_FAIL';
|
||||
|
||||
const ADMIN_STATUS_DELETE_REQUEST = 'ADMIN_STATUS_DELETE_REQUEST';
|
||||
const ADMIN_STATUS_DELETE_SUCCESS = 'ADMIN_STATUS_DELETE_SUCCESS';
|
||||
const ADMIN_STATUS_DELETE_FAIL = 'ADMIN_STATUS_DELETE_FAIL';
|
||||
|
||||
const ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST';
|
||||
const ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS';
|
||||
const ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL';
|
||||
|
||||
const ADMIN_LOG_FETCH_REQUEST = 'ADMIN_LOG_FETCH_REQUEST';
|
||||
const ADMIN_LOG_FETCH_SUCCESS = 'ADMIN_LOG_FETCH_SUCCESS';
|
||||
const ADMIN_LOG_FETCH_FAIL = 'ADMIN_LOG_FETCH_FAIL';
|
||||
|
||||
const ADMIN_USERS_TAG_REQUEST = 'ADMIN_USERS_TAG_REQUEST';
|
||||
const ADMIN_USERS_TAG_SUCCESS = 'ADMIN_USERS_TAG_SUCCESS';
|
||||
const ADMIN_USERS_TAG_FAIL = 'ADMIN_USERS_TAG_FAIL';
|
||||
|
||||
const ADMIN_USERS_UNTAG_REQUEST = 'ADMIN_USERS_UNTAG_REQUEST';
|
||||
const ADMIN_USERS_UNTAG_SUCCESS = 'ADMIN_USERS_UNTAG_SUCCESS';
|
||||
const ADMIN_USERS_UNTAG_FAIL = 'ADMIN_USERS_UNTAG_FAIL';
|
||||
|
||||
const ADMIN_ADD_PERMISSION_GROUP_REQUEST = 'ADMIN_ADD_PERMISSION_GROUP_REQUEST';
|
||||
const ADMIN_ADD_PERMISSION_GROUP_SUCCESS = 'ADMIN_ADD_PERMISSION_GROUP_SUCCESS';
|
||||
const ADMIN_ADD_PERMISSION_GROUP_FAIL = 'ADMIN_ADD_PERMISSION_GROUP_FAIL';
|
||||
|
||||
const ADMIN_REMOVE_PERMISSION_GROUP_REQUEST = 'ADMIN_REMOVE_PERMISSION_GROUP_REQUEST';
|
||||
const ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS = 'ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS';
|
||||
const ADMIN_REMOVE_PERMISSION_GROUP_FAIL = 'ADMIN_REMOVE_PERMISSION_GROUP_FAIL';
|
||||
|
||||
const ADMIN_USERS_SUGGEST_REQUEST = 'ADMIN_USERS_SUGGEST_REQUEST';
|
||||
const ADMIN_USERS_SUGGEST_SUCCESS = 'ADMIN_USERS_SUGGEST_SUCCESS';
|
||||
const ADMIN_USERS_SUGGEST_FAIL = 'ADMIN_USERS_SUGGEST_FAIL';
|
||||
|
||||
const ADMIN_USERS_UNSUGGEST_REQUEST = 'ADMIN_USERS_UNSUGGEST_REQUEST';
|
||||
const ADMIN_USERS_UNSUGGEST_SUCCESS = 'ADMIN_USERS_UNSUGGEST_SUCCESS';
|
||||
const ADMIN_USERS_UNSUGGEST_FAIL = 'ADMIN_USERS_UNSUGGEST_FAIL';
|
||||
|
||||
const nicknamesFromIds = (getState: () => RootState, ids: string[]) => ids.map(id => getState().accounts.get(id)!.acct);
|
||||
|
||||
const fetchConfig = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: ADMIN_CONFIG_FETCH_REQUEST });
|
||||
return api(getState)
|
||||
.get('/api/pleroma/admin/config')
|
||||
.then(({ data }) => {
|
||||
dispatch({ type: ADMIN_CONFIG_FETCH_SUCCESS, configs: data.configs, needsReboot: data.need_reboot });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_CONFIG_FETCH_FAIL, error });
|
||||
});
|
||||
};
|
||||
|
||||
const updateConfig = (configs: Record<string, any>[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: ADMIN_CONFIG_UPDATE_REQUEST, configs });
|
||||
return api(getState)
|
||||
.post('/api/pleroma/admin/config', { configs })
|
||||
.then(({ data }) => {
|
||||
dispatch({ type: ADMIN_CONFIG_UPDATE_SUCCESS, configs: data.configs, needsReboot: data.need_reboot });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_CONFIG_UPDATE_FAIL, error, configs });
|
||||
});
|
||||
};
|
||||
|
||||
const fetchMastodonReports = (params: Record<string, any>) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
api(getState)
|
||||
.get('/api/v1/admin/reports', { params })
|
||||
.then(({ data: reports }) => {
|
||||
reports.forEach((report: APIEntity) => {
|
||||
dispatch(importFetchedAccount(report.account?.account));
|
||||
dispatch(importFetchedAccount(report.target_account?.account));
|
||||
dispatch(importFetchedStatuses(report.statuses));
|
||||
});
|
||||
dispatch({ type: ADMIN_REPORTS_FETCH_SUCCESS, reports, params });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_REPORTS_FETCH_FAIL, error, params });
|
||||
});
|
||||
|
||||
const fetchPleromaReports = (params: Record<string, any>) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
api(getState)
|
||||
.get('/api/pleroma/admin/reports', { params })
|
||||
.then(({ data: { reports } }) => {
|
||||
reports.forEach((report: APIEntity) => {
|
||||
dispatch(importFetchedAccount(report.account));
|
||||
dispatch(importFetchedAccount(report.actor));
|
||||
dispatch(importFetchedStatuses(report.statuses));
|
||||
});
|
||||
dispatch({ type: ADMIN_REPORTS_FETCH_SUCCESS, reports, params });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_REPORTS_FETCH_FAIL, error, params });
|
||||
});
|
||||
|
||||
const fetchReports = (params: Record<string, any> = {}) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
|
||||
const instance = state.instance;
|
||||
const features = getFeatures(instance);
|
||||
|
||||
dispatch({ type: ADMIN_REPORTS_FETCH_REQUEST, params });
|
||||
|
||||
if (features.mastodonAdmin) {
|
||||
return dispatch(fetchMastodonReports(params));
|
||||
} else {
|
||||
const { resolved } = params;
|
||||
|
||||
return dispatch(fetchPleromaReports({
|
||||
state: resolved === false ? 'open' : (resolved ? 'resolved' : null),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const patchMastodonReports = (reports: { id: string, state: string }[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
Promise.all(reports.map(({ id, state }) => api(getState)
|
||||
.post(`/api/v1/admin/reports/${id}/${state === 'resolved' ? 'reopen' : 'resolve'}`)
|
||||
.then(() => {
|
||||
dispatch({ type: ADMIN_REPORTS_PATCH_SUCCESS, reports });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_REPORTS_PATCH_FAIL, error, reports });
|
||||
}),
|
||||
));
|
||||
|
||||
const patchPleromaReports = (reports: { id: string, state: string }[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
api(getState)
|
||||
.patch('/api/pleroma/admin/reports', { reports })
|
||||
.then(() => {
|
||||
dispatch({ type: ADMIN_REPORTS_PATCH_SUCCESS, reports });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_REPORTS_PATCH_FAIL, error, reports });
|
||||
});
|
||||
|
||||
const patchReports = (ids: string[], reportState: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
|
||||
const instance = state.instance;
|
||||
const features = getFeatures(instance);
|
||||
|
||||
const reports = ids.map(id => ({ id, state: reportState }));
|
||||
|
||||
dispatch({ type: ADMIN_REPORTS_PATCH_REQUEST, reports });
|
||||
|
||||
if (features.mastodonAdmin) {
|
||||
return dispatch(patchMastodonReports(reports));
|
||||
} else {
|
||||
return dispatch(patchPleromaReports(reports));
|
||||
}
|
||||
};
|
||||
|
||||
const closeReports = (ids: string[]) =>
|
||||
patchReports(ids, 'closed');
|
||||
|
||||
const fetchMastodonUsers = (filters: string[], page: number, query: string | null | undefined, pageSize: number, next?: string | null) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const params: Record<string, any> = {
|
||||
username: query,
|
||||
};
|
||||
|
||||
if (filters.includes('local')) params.local = true;
|
||||
if (filters.includes('active')) params.active = true;
|
||||
if (filters.includes('need_approval')) params.pending = true;
|
||||
|
||||
return api(getState)
|
||||
.get(next || '/api/v1/admin/accounts', { params })
|
||||
.then(({ data: accounts, ...response }) => {
|
||||
const next = getLinks(response as AxiosResponse<any, any>).refs.find(link => link.rel === 'next');
|
||||
|
||||
const count = next
|
||||
? page * pageSize + 1
|
||||
: (page - 1) * pageSize + accounts.length;
|
||||
|
||||
dispatch(importFetchedAccounts(accounts.map(({ account }: APIEntity) => account)));
|
||||
dispatch(fetchRelationships(accounts.map((account: APIEntity) => account.id)));
|
||||
dispatch({ type: ADMIN_USERS_FETCH_SUCCESS, users: accounts, count, pageSize, filters, page, next: next?.uri || false });
|
||||
return { users: accounts, count, pageSize, next: next?.uri || false };
|
||||
}).catch(error =>
|
||||
dispatch({ type: ADMIN_USERS_FETCH_FAIL, error, filters, page, pageSize }),
|
||||
);
|
||||
};
|
||||
|
||||
const fetchPleromaUsers = (filters: string[], page: number, query?: string | null, pageSize?: number) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const params: Record<string, any> = { filters: filters.join(), page, page_size: pageSize };
|
||||
if (query) params.query = query;
|
||||
|
||||
return api(getState)
|
||||
.get('/api/pleroma/admin/users', { params })
|
||||
.then(({ data: { users, count, page_size: pageSize } }) => {
|
||||
dispatch(fetchRelationships(users.map((user: APIEntity) => user.id)));
|
||||
dispatch({ type: ADMIN_USERS_FETCH_SUCCESS, users, count, pageSize, filters, page });
|
||||
return { users, count, pageSize };
|
||||
}).catch(error =>
|
||||
dispatch({ type: ADMIN_USERS_FETCH_FAIL, error, filters, page, pageSize }),
|
||||
);
|
||||
};
|
||||
|
||||
const fetchUsers = (filters: string[] = [], page = 1, query?: string | null, pageSize = 50, next?: string | null) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
|
||||
const instance = state.instance;
|
||||
const features = getFeatures(instance);
|
||||
|
||||
dispatch({ type: ADMIN_USERS_FETCH_REQUEST, filters, page, pageSize });
|
||||
|
||||
if (features.mastodonAdmin) {
|
||||
return dispatch(fetchMastodonUsers(filters, page, query, pageSize, next));
|
||||
} else {
|
||||
return dispatch(fetchPleromaUsers(filters, page, query, pageSize));
|
||||
}
|
||||
};
|
||||
|
||||
const deactivateMastodonUsers = (accountIds: string[], reportId?: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
Promise.all(accountIds.map(accountId => {
|
||||
api(getState)
|
||||
.post(`/api/v1/admin/accounts/${accountId}/action`, {
|
||||
type: 'disable',
|
||||
report_id: reportId,
|
||||
})
|
||||
.then(() => {
|
||||
dispatch({ type: ADMIN_USERS_DEACTIVATE_SUCCESS, accountIds: [accountId] });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_USERS_DEACTIVATE_FAIL, error, accountIds: [accountId] });
|
||||
});
|
||||
}));
|
||||
|
||||
const deactivatePleromaUsers = (accountIds: string[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
return api(getState)
|
||||
.patch('/api/pleroma/admin/users/deactivate', { nicknames })
|
||||
.then(({ data: { users } }) => {
|
||||
dispatch({ type: ADMIN_USERS_DEACTIVATE_SUCCESS, users, accountIds });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_USERS_DEACTIVATE_FAIL, error, accountIds });
|
||||
});
|
||||
};
|
||||
|
||||
const deactivateUsers = (accountIds: string[], reportId?: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
|
||||
const instance = state.instance;
|
||||
const features = getFeatures(instance);
|
||||
|
||||
dispatch({ type: ADMIN_USERS_DEACTIVATE_REQUEST, accountIds });
|
||||
|
||||
if (features.mastodonAdmin) {
|
||||
return dispatch(deactivateMastodonUsers(accountIds, reportId));
|
||||
} else {
|
||||
return dispatch(deactivatePleromaUsers(accountIds));
|
||||
}
|
||||
};
|
||||
|
||||
const deleteUsers = (accountIds: string[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
dispatch({ type: ADMIN_USERS_DELETE_REQUEST, accountIds });
|
||||
return api(getState)
|
||||
.delete('/api/pleroma/admin/users', { data: { nicknames } })
|
||||
.then(({ data: nicknames }) => {
|
||||
dispatch({ type: ADMIN_USERS_DELETE_SUCCESS, nicknames, accountIds });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_USERS_DELETE_FAIL, error, accountIds });
|
||||
});
|
||||
};
|
||||
|
||||
const approveMastodonUsers = (accountIds: string[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
Promise.all(accountIds.map(accountId => {
|
||||
api(getState)
|
||||
.post(`/api/v1/admin/accounts/${accountId}/approve`)
|
||||
.then(({ data: user }) => {
|
||||
dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, users: [user], accountIds: [accountId] });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_USERS_APPROVE_FAIL, error, accountIds: [accountId] });
|
||||
});
|
||||
}));
|
||||
|
||||
const approvePleromaUsers = (accountIds: string[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
return api(getState)
|
||||
.patch('/api/pleroma/admin/users/approve', { nicknames })
|
||||
.then(({ data: { users } }) => {
|
||||
dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, users, accountIds });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_USERS_APPROVE_FAIL, error, accountIds });
|
||||
});
|
||||
};
|
||||
|
||||
const approveUsers = (accountIds: string[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
|
||||
const instance = state.instance;
|
||||
const features = getFeatures(instance);
|
||||
|
||||
dispatch({ type: ADMIN_USERS_APPROVE_REQUEST, accountIds });
|
||||
|
||||
if (features.mastodonAdmin) {
|
||||
return dispatch(approveMastodonUsers(accountIds));
|
||||
} else {
|
||||
return dispatch(approvePleromaUsers(accountIds));
|
||||
}
|
||||
};
|
||||
|
||||
const deleteStatus = (id: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: ADMIN_STATUS_DELETE_REQUEST, id });
|
||||
return api(getState)
|
||||
.delete(`/api/pleroma/admin/statuses/${id}`)
|
||||
.then(() => {
|
||||
dispatch({ type: ADMIN_STATUS_DELETE_SUCCESS, id });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_STATUS_DELETE_FAIL, error, id });
|
||||
});
|
||||
};
|
||||
|
||||
const toggleStatusSensitivity = (id: string, sensitive: boolean) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST, id });
|
||||
return api(getState)
|
||||
.put(`/api/pleroma/admin/statuses/${id}`, { sensitive: !sensitive })
|
||||
.then(() => {
|
||||
dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS, id });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL, error, id });
|
||||
});
|
||||
};
|
||||
|
||||
const fetchModerationLog = (params?: Record<string, any>) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: ADMIN_LOG_FETCH_REQUEST });
|
||||
return api(getState)
|
||||
.get('/api/pleroma/admin/moderation_log', { params })
|
||||
.then(({ data }) => {
|
||||
dispatch({ type: ADMIN_LOG_FETCH_SUCCESS, items: data.items, total: data.total });
|
||||
return data;
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_LOG_FETCH_FAIL, error });
|
||||
});
|
||||
};
|
||||
|
||||
const tagUsers = (accountIds: string[], tags: string[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
dispatch({ type: ADMIN_USERS_TAG_REQUEST, accountIds, tags });
|
||||
return api(getState)
|
||||
.put('/api/v1/pleroma/admin/users/tag', { nicknames, tags })
|
||||
.then(() => {
|
||||
dispatch({ type: ADMIN_USERS_TAG_SUCCESS, accountIds, tags });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_USERS_TAG_FAIL, error, accountIds, tags });
|
||||
});
|
||||
};
|
||||
|
||||
const untagUsers = (accountIds: string[], tags: string[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
dispatch({ type: ADMIN_USERS_UNTAG_REQUEST, accountIds, tags });
|
||||
return api(getState)
|
||||
.delete('/api/v1/pleroma/admin/users/tag', { data: { nicknames, tags } })
|
||||
.then(() => {
|
||||
dispatch({ type: ADMIN_USERS_UNTAG_SUCCESS, accountIds, tags });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_USERS_UNTAG_FAIL, error, accountIds, tags });
|
||||
});
|
||||
};
|
||||
|
||||
const verifyUser = (accountId: string) =>
|
||||
(dispatch: AppDispatch) =>
|
||||
dispatch(tagUsers([accountId], ['verified']));
|
||||
|
||||
const unverifyUser = (accountId: string) =>
|
||||
(dispatch: AppDispatch) =>
|
||||
dispatch(untagUsers([accountId], ['verified']));
|
||||
|
||||
const setDonor = (accountId: string) =>
|
||||
(dispatch: AppDispatch) =>
|
||||
dispatch(tagUsers([accountId], ['donor']));
|
||||
|
||||
const removeDonor = (accountId: string) =>
|
||||
(dispatch: AppDispatch) =>
|
||||
dispatch(untagUsers([accountId], ['donor']));
|
||||
|
||||
const addPermission = (accountIds: string[], permissionGroup: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
dispatch({ type: ADMIN_ADD_PERMISSION_GROUP_REQUEST, accountIds, permissionGroup });
|
||||
return api(getState)
|
||||
.post(`/api/v1/pleroma/admin/users/permission_group/${permissionGroup}`, { nicknames })
|
||||
.then(({ data }) => {
|
||||
dispatch({ type: ADMIN_ADD_PERMISSION_GROUP_SUCCESS, accountIds, permissionGroup, data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_ADD_PERMISSION_GROUP_FAIL, error, accountIds, permissionGroup });
|
||||
});
|
||||
};
|
||||
|
||||
const removePermission = (accountIds: string[], permissionGroup: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const nicknames = nicknamesFromIds(getState, accountIds);
|
||||
dispatch({ type: ADMIN_REMOVE_PERMISSION_GROUP_REQUEST, accountIds, permissionGroup });
|
||||
return api(getState)
|
||||
.delete(`/api/v1/pleroma/admin/users/permission_group/${permissionGroup}`, { data: { nicknames } })
|
||||
.then(({ data }) => {
|
||||
dispatch({ type: ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS, accountIds, permissionGroup, data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: ADMIN_REMOVE_PERMISSION_GROUP_FAIL, error, accountIds, permissionGroup });
|
||||
});
|
||||
};
|
||||
|
||||
const promoteToAdmin = (accountId: string) =>
|
||||
(dispatch: AppDispatch) =>
|
||||
Promise.all([
|
||||
dispatch(addPermission([accountId], 'admin')),
|
||||
dispatch(removePermission([accountId], 'moderator')),
|
||||
]);
|
||||
|
||||
const promoteToModerator = (accountId: string) =>
|
||||
(dispatch: AppDispatch) =>
|
||||
Promise.all([
|
||||
dispatch(removePermission([accountId], 'admin')),
|
||||
dispatch(addPermission([accountId], 'moderator')),
|
||||
]);
|
||||
|
||||
const demoteToUser = (accountId: string) =>
|
||||
(dispatch: AppDispatch) =>
|
||||
Promise.all([
|
||||
dispatch(removePermission([accountId], 'admin')),
|
||||
dispatch(removePermission([accountId], 'moderator')),
|
||||
]);
|
||||
|
||||
const suggestUsers = (accountIds: string[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
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 });
|
||||
});
|
||||
};
|
||||
|
||||
const unsuggestUsers = (accountIds: string[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
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 });
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
ADMIN_CONFIG_FETCH_REQUEST,
|
||||
ADMIN_CONFIG_FETCH_SUCCESS,
|
||||
ADMIN_CONFIG_FETCH_FAIL,
|
||||
ADMIN_CONFIG_UPDATE_REQUEST,
|
||||
ADMIN_CONFIG_UPDATE_SUCCESS,
|
||||
ADMIN_CONFIG_UPDATE_FAIL,
|
||||
ADMIN_REPORTS_FETCH_REQUEST,
|
||||
ADMIN_REPORTS_FETCH_SUCCESS,
|
||||
ADMIN_REPORTS_FETCH_FAIL,
|
||||
ADMIN_REPORTS_PATCH_REQUEST,
|
||||
ADMIN_REPORTS_PATCH_SUCCESS,
|
||||
ADMIN_REPORTS_PATCH_FAIL,
|
||||
ADMIN_USERS_FETCH_REQUEST,
|
||||
ADMIN_USERS_FETCH_SUCCESS,
|
||||
ADMIN_USERS_FETCH_FAIL,
|
||||
ADMIN_USERS_DELETE_REQUEST,
|
||||
ADMIN_USERS_DELETE_SUCCESS,
|
||||
ADMIN_USERS_DELETE_FAIL,
|
||||
ADMIN_USERS_APPROVE_REQUEST,
|
||||
ADMIN_USERS_APPROVE_SUCCESS,
|
||||
ADMIN_USERS_APPROVE_FAIL,
|
||||
ADMIN_USERS_DEACTIVATE_REQUEST,
|
||||
ADMIN_USERS_DEACTIVATE_SUCCESS,
|
||||
ADMIN_USERS_DEACTIVATE_FAIL,
|
||||
ADMIN_STATUS_DELETE_REQUEST,
|
||||
ADMIN_STATUS_DELETE_SUCCESS,
|
||||
ADMIN_STATUS_DELETE_FAIL,
|
||||
ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST,
|
||||
ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS,
|
||||
ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL,
|
||||
ADMIN_LOG_FETCH_REQUEST,
|
||||
ADMIN_LOG_FETCH_SUCCESS,
|
||||
ADMIN_LOG_FETCH_FAIL,
|
||||
ADMIN_USERS_TAG_REQUEST,
|
||||
ADMIN_USERS_TAG_SUCCESS,
|
||||
ADMIN_USERS_TAG_FAIL,
|
||||
ADMIN_USERS_UNTAG_REQUEST,
|
||||
ADMIN_USERS_UNTAG_SUCCESS,
|
||||
ADMIN_USERS_UNTAG_FAIL,
|
||||
ADMIN_ADD_PERMISSION_GROUP_REQUEST,
|
||||
ADMIN_ADD_PERMISSION_GROUP_SUCCESS,
|
||||
ADMIN_ADD_PERMISSION_GROUP_FAIL,
|
||||
ADMIN_REMOVE_PERMISSION_GROUP_REQUEST,
|
||||
ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS,
|
||||
ADMIN_REMOVE_PERMISSION_GROUP_FAIL,
|
||||
ADMIN_USERS_SUGGEST_REQUEST,
|
||||
ADMIN_USERS_SUGGEST_SUCCESS,
|
||||
ADMIN_USERS_SUGGEST_FAIL,
|
||||
ADMIN_USERS_UNSUGGEST_REQUEST,
|
||||
ADMIN_USERS_UNSUGGEST_SUCCESS,
|
||||
ADMIN_USERS_UNSUGGEST_FAIL,
|
||||
fetchConfig,
|
||||
updateConfig,
|
||||
fetchReports,
|
||||
closeReports,
|
||||
fetchUsers,
|
||||
deactivateUsers,
|
||||
deleteUsers,
|
||||
approveUsers,
|
||||
deleteStatus,
|
||||
toggleStatusSensitivity,
|
||||
fetchModerationLog,
|
||||
tagUsers,
|
||||
untagUsers,
|
||||
verifyUser,
|
||||
unverifyUser,
|
||||
setDonor,
|
||||
removeDonor,
|
||||
addPermission,
|
||||
removePermission,
|
||||
promoteToAdmin,
|
||||
promoteToModerator,
|
||||
demoteToUser,
|
||||
suggestUsers,
|
||||
unsuggestUsers,
|
||||
};
|
|
@ -1,68 +0,0 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import { httpErrorMessages } from 'soapbox/utils/errors';
|
||||
|
||||
const messages = defineMessages({
|
||||
unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' },
|
||||
unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' },
|
||||
});
|
||||
|
||||
export const ALERT_SHOW = 'ALERT_SHOW';
|
||||
export const ALERT_DISMISS = 'ALERT_DISMISS';
|
||||
export const ALERT_CLEAR = 'ALERT_CLEAR';
|
||||
|
||||
const noOp = () => {};
|
||||
|
||||
export function dismissAlert(alert) {
|
||||
return {
|
||||
type: ALERT_DISMISS,
|
||||
alert,
|
||||
};
|
||||
}
|
||||
|
||||
export function clearAlert() {
|
||||
return {
|
||||
type: ALERT_CLEAR,
|
||||
};
|
||||
}
|
||||
|
||||
export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage, severity = 'info') {
|
||||
return {
|
||||
type: ALERT_SHOW,
|
||||
title,
|
||||
message,
|
||||
severity,
|
||||
};
|
||||
}
|
||||
|
||||
export function showAlertForError(error) {
|
||||
return (dispatch, _getState) => {
|
||||
if (error.response) {
|
||||
const { data, status, statusText } = error.response;
|
||||
|
||||
if (status === 502) {
|
||||
return dispatch(showAlert('', 'The server is down', 'error'));
|
||||
}
|
||||
|
||||
if (status === 404 || status === 410) {
|
||||
// Skip these errors as they are reflected in the UI
|
||||
return dispatch(noOp);
|
||||
}
|
||||
|
||||
let message = statusText;
|
||||
|
||||
if (data.error) {
|
||||
message = data.error;
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
message = httpErrorMessages.find((httpError) => httpError.code === status)?.description;
|
||||
}
|
||||
|
||||
return dispatch(showAlert('', message, 'error'));
|
||||
} else {
|
||||
console.error(error);
|
||||
return dispatch(showAlert(undefined, undefined, 'error'));
|
||||
}
|
||||
};
|
||||
}
|
75
app/soapbox/actions/alerts.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
import { defineMessages, MessageDescriptor } from 'react-intl';
|
||||
|
||||
import { httpErrorMessages } from 'soapbox/utils/errors';
|
||||
|
||||
import type { SnackbarActionSeverity } from './snackbar';
|
||||
import type { AnyAction } from '@reduxjs/toolkit';
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { NotificationObject } from 'soapbox/react-notification';
|
||||
|
||||
const messages = defineMessages({
|
||||
unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' },
|
||||
unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' },
|
||||
});
|
||||
|
||||
export const ALERT_SHOW = 'ALERT_SHOW';
|
||||
export const ALERT_DISMISS = 'ALERT_DISMISS';
|
||||
export const ALERT_CLEAR = 'ALERT_CLEAR';
|
||||
|
||||
const noOp = () => { };
|
||||
|
||||
function dismissAlert(alert: NotificationObject) {
|
||||
return {
|
||||
type: ALERT_DISMISS,
|
||||
alert,
|
||||
};
|
||||
}
|
||||
|
||||
function showAlert(
|
||||
title: MessageDescriptor | string = messages.unexpectedTitle,
|
||||
message: MessageDescriptor | string = messages.unexpectedMessage,
|
||||
severity: SnackbarActionSeverity = 'info',
|
||||
) {
|
||||
return {
|
||||
type: ALERT_SHOW,
|
||||
title,
|
||||
message,
|
||||
severity,
|
||||
};
|
||||
}
|
||||
|
||||
const showAlertForError = (error: AxiosError<any>) => (dispatch: React.Dispatch<AnyAction>, _getState: any) => {
|
||||
if (error?.response) {
|
||||
const { data, status, statusText } = error.response;
|
||||
|
||||
if (status === 502) {
|
||||
return dispatch(showAlert('', 'The server is down', 'error'));
|
||||
}
|
||||
|
||||
if (status === 404 || status === 410) {
|
||||
// Skip these errors as they are reflected in the UI
|
||||
return dispatch(noOp as any);
|
||||
}
|
||||
|
||||
let message: string | undefined = statusText;
|
||||
|
||||
if (data?.error) {
|
||||
message = data.error;
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
message = httpErrorMessages.find((httpError) => httpError.code === status)?.description;
|
||||
}
|
||||
|
||||
return dispatch(showAlert('', message, 'error'));
|
||||
} else {
|
||||
console.error(error);
|
||||
return dispatch(showAlert(undefined, undefined, 'error'));
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
dismissAlert,
|
||||
showAlert,
|
||||
showAlertForError,
|
||||
};
|
|
@ -1,196 +0,0 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
import { showAlertForError } from './alerts';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
import { patchMeSuccess } from './me';
|
||||
import snackbar from './snackbar';
|
||||
|
||||
export const ALIASES_FETCH_REQUEST = 'ALIASES_FETCH_REQUEST';
|
||||
export const ALIASES_FETCH_SUCCESS = 'ALIASES_FETCH_SUCCESS';
|
||||
export const ALIASES_FETCH_FAIL = 'ALIASES_FETCH_FAIL';
|
||||
|
||||
export const ALIASES_SUGGESTIONS_CHANGE = 'ALIASES_SUGGESTIONS_CHANGE';
|
||||
export const ALIASES_SUGGESTIONS_READY = 'ALIASES_SUGGESTIONS_READY';
|
||||
export const ALIASES_SUGGESTIONS_CLEAR = 'ALIASES_SUGGESTIONS_CLEAR';
|
||||
|
||||
export const ALIASES_ADD_REQUEST = 'ALIASES_ADD_REQUEST';
|
||||
export const ALIASES_ADD_SUCCESS = 'ALIASES_ADD_SUCCESS';
|
||||
export const ALIASES_ADD_FAIL = 'ALIASES_ADD_FAIL';
|
||||
|
||||
export const ALIASES_REMOVE_REQUEST = 'ALIASES_REMOVE_REQUEST';
|
||||
export const ALIASES_REMOVE_SUCCESS = 'ALIASES_REMOVE_SUCCESS';
|
||||
export const ALIASES_REMOVE_FAIL = 'ALIASES_REMOVE_FAIL';
|
||||
|
||||
const messages = defineMessages({
|
||||
createSuccess: { id: 'aliases.success.add', defaultMessage: 'Account alias created successfully' },
|
||||
removeSuccess: { id: 'aliases.success.remove', defaultMessage: 'Account alias removed successfully' },
|
||||
});
|
||||
|
||||
export const fetchAliases = (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
const state = getState();
|
||||
|
||||
const instance = state.get('instance');
|
||||
const features = getFeatures(instance);
|
||||
|
||||
if (!features.accountMoving) return;
|
||||
|
||||
dispatch(fetchAliasesRequest());
|
||||
|
||||
api(getState).get('/api/pleroma/aliases')
|
||||
.then(response => {
|
||||
dispatch(fetchAliasesSuccess(response.data.aliases));
|
||||
})
|
||||
.catch(err => dispatch(fetchAliasesFail(err)));
|
||||
};
|
||||
|
||||
export const fetchAliasesRequest = () => ({
|
||||
type: ALIASES_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
export const fetchAliasesSuccess = aliases => ({
|
||||
type: ALIASES_FETCH_SUCCESS,
|
||||
value: aliases,
|
||||
});
|
||||
|
||||
export const fetchAliasesFail = error => ({
|
||||
type: ALIASES_FETCH_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export const fetchAliasesSuggestions = q => (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const params = {
|
||||
q,
|
||||
resolve: true,
|
||||
limit: 4,
|
||||
};
|
||||
|
||||
api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => {
|
||||
dispatch(importFetchedAccounts(data));
|
||||
dispatch(fetchAliasesSuggestionsReady(q, data));
|
||||
}).catch(error => dispatch(showAlertForError(error)));
|
||||
};
|
||||
|
||||
export const fetchAliasesSuggestionsReady = (query, accounts) => ({
|
||||
type: ALIASES_SUGGESTIONS_READY,
|
||||
query,
|
||||
accounts,
|
||||
});
|
||||
|
||||
export const clearAliasesSuggestions = () => ({
|
||||
type: ALIASES_SUGGESTIONS_CLEAR,
|
||||
});
|
||||
|
||||
export const changeAliasesSuggestions = value => ({
|
||||
type: ALIASES_SUGGESTIONS_CHANGE,
|
||||
value,
|
||||
});
|
||||
|
||||
export const addToAliases = (intl, account) => (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
const state = getState();
|
||||
|
||||
const instance = state.get('instance');
|
||||
const features = getFeatures(instance);
|
||||
|
||||
if (!features.accountMoving) {
|
||||
const me = state.get('me');
|
||||
const alsoKnownAs = state.getIn(['accounts_meta', me, 'pleroma', 'also_known_as']);
|
||||
|
||||
dispatch(addToAliasesRequest());
|
||||
|
||||
api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: [...alsoKnownAs, account.getIn(['pleroma', 'ap_id'])] })
|
||||
.then((response => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.createSuccess)));
|
||||
dispatch(addToAliasesSuccess);
|
||||
dispatch(patchMeSuccess(response.data));
|
||||
}))
|
||||
.catch(err => dispatch(addToAliasesFail(err)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(addToAliasesRequest());
|
||||
|
||||
api(getState).put('/api/pleroma/aliases', {
|
||||
alias: account.get('acct'),
|
||||
})
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.createSuccess)));
|
||||
dispatch(addToAliasesSuccess);
|
||||
dispatch(fetchAliases);
|
||||
})
|
||||
.catch(err => dispatch(fetchAliasesFail(err)));
|
||||
};
|
||||
|
||||
export const addToAliasesRequest = () => ({
|
||||
type: ALIASES_ADD_REQUEST,
|
||||
});
|
||||
|
||||
export const addToAliasesSuccess = () => ({
|
||||
type: ALIASES_ADD_SUCCESS,
|
||||
});
|
||||
|
||||
export const addToAliasesFail = error => ({
|
||||
type: ALIASES_ADD_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export const removeFromAliases = (intl, account) => (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
const state = getState();
|
||||
|
||||
const instance = state.get('instance');
|
||||
const features = getFeatures(instance);
|
||||
|
||||
if (!features.accountMoving) {
|
||||
const me = state.get('me');
|
||||
const alsoKnownAs = state.getIn(['accounts_meta', me, 'pleroma', 'also_known_as']);
|
||||
|
||||
dispatch(removeFromAliasesRequest());
|
||||
|
||||
api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: alsoKnownAs.filter(id => id !== account) })
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.removeSuccess)));
|
||||
dispatch(removeFromAliasesSuccess);
|
||||
dispatch(patchMeSuccess(response.data));
|
||||
})
|
||||
.catch(err => dispatch(removeFromAliasesFail(err)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(addToAliasesRequest());
|
||||
|
||||
api(getState).delete('/api/pleroma/aliases', {
|
||||
data: {
|
||||
alias: account,
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.removeSuccess)));
|
||||
dispatch(removeFromAliasesSuccess);
|
||||
dispatch(fetchAliases);
|
||||
})
|
||||
.catch(err => dispatch(fetchAliasesFail(err)));
|
||||
};
|
||||
|
||||
export const removeFromAliasesRequest = () => ({
|
||||
type: ALIASES_REMOVE_REQUEST,
|
||||
});
|
||||
|
||||
export const removeFromAliasesSuccess = () => ({
|
||||
type: ALIASES_REMOVE_SUCCESS,
|
||||
});
|
||||
|
||||
export const removeFromAliasesFail = error => ({
|
||||
type: ALIASES_REMOVE_FAIL,
|
||||
error,
|
||||
});
|
234
app/soapbox/actions/aliases.ts
Normal file
|
@ -0,0 +1,234 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
import { showAlertForError } from './alerts';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
import { patchMeSuccess } from './me';
|
||||
import snackbar from './snackbar';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { APIEntity, Account } from 'soapbox/types/entities';
|
||||
|
||||
const ALIASES_FETCH_REQUEST = 'ALIASES_FETCH_REQUEST';
|
||||
const ALIASES_FETCH_SUCCESS = 'ALIASES_FETCH_SUCCESS';
|
||||
const ALIASES_FETCH_FAIL = 'ALIASES_FETCH_FAIL';
|
||||
|
||||
const ALIASES_SUGGESTIONS_CHANGE = 'ALIASES_SUGGESTIONS_CHANGE';
|
||||
const ALIASES_SUGGESTIONS_READY = 'ALIASES_SUGGESTIONS_READY';
|
||||
const ALIASES_SUGGESTIONS_CLEAR = 'ALIASES_SUGGESTIONS_CLEAR';
|
||||
|
||||
const ALIASES_ADD_REQUEST = 'ALIASES_ADD_REQUEST';
|
||||
const ALIASES_ADD_SUCCESS = 'ALIASES_ADD_SUCCESS';
|
||||
const ALIASES_ADD_FAIL = 'ALIASES_ADD_FAIL';
|
||||
|
||||
const ALIASES_REMOVE_REQUEST = 'ALIASES_REMOVE_REQUEST';
|
||||
const ALIASES_REMOVE_SUCCESS = 'ALIASES_REMOVE_SUCCESS';
|
||||
const ALIASES_REMOVE_FAIL = 'ALIASES_REMOVE_FAIL';
|
||||
|
||||
const messages = defineMessages({
|
||||
createSuccess: { id: 'aliases.success.add', defaultMessage: 'Account alias created successfully' },
|
||||
removeSuccess: { id: 'aliases.success.remove', defaultMessage: 'Account alias removed successfully' },
|
||||
});
|
||||
|
||||
const fetchAliases = (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
const state = getState();
|
||||
|
||||
const instance = state.instance;
|
||||
const features = getFeatures(instance);
|
||||
|
||||
if (!features.accountMoving) return;
|
||||
|
||||
dispatch(fetchAliasesRequest());
|
||||
|
||||
api(getState).get('/api/pleroma/aliases')
|
||||
.then(response => {
|
||||
dispatch(fetchAliasesSuccess(response.data.aliases));
|
||||
})
|
||||
.catch(err => dispatch(fetchAliasesFail(err)));
|
||||
};
|
||||
|
||||
const fetchAliasesRequest = () => ({
|
||||
type: ALIASES_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
const fetchAliasesSuccess = (aliases: APIEntity[]) => ({
|
||||
type: ALIASES_FETCH_SUCCESS,
|
||||
value: aliases,
|
||||
});
|
||||
|
||||
const fetchAliasesFail = (error: AxiosError) => ({
|
||||
type: ALIASES_FETCH_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
const fetchAliasesSuggestions = (q: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const params = {
|
||||
q,
|
||||
resolve: true,
|
||||
limit: 4,
|
||||
};
|
||||
|
||||
api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => {
|
||||
dispatch(importFetchedAccounts(data));
|
||||
dispatch(fetchAliasesSuggestionsReady(q, data));
|
||||
}).catch(error => dispatch(showAlertForError(error)));
|
||||
};
|
||||
|
||||
const fetchAliasesSuggestionsReady = (query: string, accounts: APIEntity[]) => ({
|
||||
type: ALIASES_SUGGESTIONS_READY,
|
||||
query,
|
||||
accounts,
|
||||
});
|
||||
|
||||
const clearAliasesSuggestions = () => ({
|
||||
type: ALIASES_SUGGESTIONS_CLEAR,
|
||||
});
|
||||
|
||||
const changeAliasesSuggestions = (value: string) => ({
|
||||
type: ALIASES_SUGGESTIONS_CHANGE,
|
||||
value,
|
||||
});
|
||||
|
||||
const addToAliases = (account: Account) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
const state = getState();
|
||||
|
||||
const instance = state.instance;
|
||||
const features = getFeatures(instance);
|
||||
|
||||
if (!features.accountMoving) {
|
||||
const me = state.me;
|
||||
const alsoKnownAs = state.accounts_meta.get(me as string)!.pleroma.get('also_known_as');
|
||||
|
||||
dispatch(addToAliasesRequest());
|
||||
|
||||
api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: [...alsoKnownAs, account.pleroma.get('ap_id')] })
|
||||
.then((response => {
|
||||
dispatch(snackbar.success(messages.createSuccess));
|
||||
dispatch(addToAliasesSuccess);
|
||||
dispatch(patchMeSuccess(response.data));
|
||||
}))
|
||||
.catch(err => dispatch(addToAliasesFail(err)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(addToAliasesRequest());
|
||||
|
||||
api(getState).put('/api/pleroma/aliases', {
|
||||
alias: account.acct,
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(snackbar.success(messages.createSuccess));
|
||||
dispatch(addToAliasesSuccess);
|
||||
dispatch(fetchAliases);
|
||||
})
|
||||
.catch(err => dispatch(fetchAliasesFail(err)));
|
||||
};
|
||||
|
||||
const addToAliasesRequest = () => ({
|
||||
type: ALIASES_ADD_REQUEST,
|
||||
});
|
||||
|
||||
const addToAliasesSuccess = () => ({
|
||||
type: ALIASES_ADD_SUCCESS,
|
||||
});
|
||||
|
||||
const addToAliasesFail = (error: AxiosError) => ({
|
||||
type: ALIASES_ADD_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
const removeFromAliases = (account: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
const state = getState();
|
||||
|
||||
const instance = state.instance;
|
||||
const features = getFeatures(instance);
|
||||
|
||||
if (!features.accountMoving) {
|
||||
const me = state.me;
|
||||
const alsoKnownAs = state.accounts_meta.get(me as string)!.pleroma.get('also_known_as');
|
||||
|
||||
dispatch(removeFromAliasesRequest());
|
||||
|
||||
api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: alsoKnownAs.filter((id: string) => id !== account) })
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(messages.removeSuccess));
|
||||
dispatch(removeFromAliasesSuccess);
|
||||
dispatch(patchMeSuccess(response.data));
|
||||
})
|
||||
.catch(err => dispatch(removeFromAliasesFail(err)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(addToAliasesRequest());
|
||||
|
||||
api(getState).delete('/api/pleroma/aliases', {
|
||||
data: {
|
||||
alias: account,
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(messages.removeSuccess));
|
||||
dispatch(removeFromAliasesSuccess);
|
||||
dispatch(fetchAliases);
|
||||
})
|
||||
.catch(err => dispatch(fetchAliasesFail(err)));
|
||||
};
|
||||
|
||||
const removeFromAliasesRequest = () => ({
|
||||
type: ALIASES_REMOVE_REQUEST,
|
||||
});
|
||||
|
||||
const removeFromAliasesSuccess = () => ({
|
||||
type: ALIASES_REMOVE_SUCCESS,
|
||||
});
|
||||
|
||||
const removeFromAliasesFail = (error: AxiosError) => ({
|
||||
type: ALIASES_REMOVE_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export {
|
||||
ALIASES_FETCH_REQUEST,
|
||||
ALIASES_FETCH_SUCCESS,
|
||||
ALIASES_FETCH_FAIL,
|
||||
ALIASES_SUGGESTIONS_CHANGE,
|
||||
ALIASES_SUGGESTIONS_READY,
|
||||
ALIASES_SUGGESTIONS_CLEAR,
|
||||
ALIASES_ADD_REQUEST,
|
||||
ALIASES_ADD_SUCCESS,
|
||||
ALIASES_ADD_FAIL,
|
||||
ALIASES_REMOVE_REQUEST,
|
||||
ALIASES_REMOVE_SUCCESS,
|
||||
ALIASES_REMOVE_FAIL,
|
||||
fetchAliases,
|
||||
fetchAliasesRequest,
|
||||
fetchAliasesSuccess,
|
||||
fetchAliasesFail,
|
||||
fetchAliasesSuggestions,
|
||||
fetchAliasesSuggestionsReady,
|
||||
clearAliasesSuggestions,
|
||||
changeAliasesSuggestions,
|
||||
addToAliases,
|
||||
addToAliasesRequest,
|
||||
addToAliasesSuccess,
|
||||
addToAliasesFail,
|
||||
removeFromAliases,
|
||||
removeFromAliasesRequest,
|
||||
removeFromAliasesSuccess,
|
||||
removeFromAliasesFail,
|
||||
};
|
197
app/soapbox/actions/announcements.ts
Normal file
|
@ -0,0 +1,197 @@
|
|||
import api from 'soapbox/api';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import { importFetchedStatuses } from './importer';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
export const ANNOUNCEMENTS_FETCH_REQUEST = 'ANNOUNCEMENTS_FETCH_REQUEST';
|
||||
export const ANNOUNCEMENTS_FETCH_SUCCESS = 'ANNOUNCEMENTS_FETCH_SUCCESS';
|
||||
export const ANNOUNCEMENTS_FETCH_FAIL = 'ANNOUNCEMENTS_FETCH_FAIL';
|
||||
export const ANNOUNCEMENTS_UPDATE = 'ANNOUNCEMENTS_UPDATE';
|
||||
export const ANNOUNCEMENTS_DELETE = 'ANNOUNCEMENTS_DELETE';
|
||||
|
||||
export const ANNOUNCEMENTS_DISMISS_REQUEST = 'ANNOUNCEMENTS_DISMISS_REQUEST';
|
||||
export const ANNOUNCEMENTS_DISMISS_SUCCESS = 'ANNOUNCEMENTS_DISMISS_SUCCESS';
|
||||
export const ANNOUNCEMENTS_DISMISS_FAIL = 'ANNOUNCEMENTS_DISMISS_FAIL';
|
||||
|
||||
export const ANNOUNCEMENTS_REACTION_ADD_REQUEST = 'ANNOUNCEMENTS_REACTION_ADD_REQUEST';
|
||||
export const ANNOUNCEMENTS_REACTION_ADD_SUCCESS = 'ANNOUNCEMENTS_REACTION_ADD_SUCCESS';
|
||||
export const ANNOUNCEMENTS_REACTION_ADD_FAIL = 'ANNOUNCEMENTS_REACTION_ADD_FAIL';
|
||||
|
||||
export const ANNOUNCEMENTS_REACTION_REMOVE_REQUEST = 'ANNOUNCEMENTS_REACTION_REMOVE_REQUEST';
|
||||
export const ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS = 'ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS';
|
||||
export const ANNOUNCEMENTS_REACTION_REMOVE_FAIL = 'ANNOUNCEMENTS_REACTION_REMOVE_FAIL';
|
||||
|
||||
export const ANNOUNCEMENTS_REACTION_UPDATE = 'ANNOUNCEMENTS_REACTION_UPDATE';
|
||||
|
||||
export const ANNOUNCEMENTS_TOGGLE_SHOW = 'ANNOUNCEMENTS_TOGGLE_SHOW';
|
||||
|
||||
const noOp = () => {};
|
||||
|
||||
export const fetchAnnouncements = (done = noOp) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const { instance } = getState();
|
||||
const features = getFeatures(instance);
|
||||
|
||||
if (!features.announcements) return null;
|
||||
|
||||
dispatch(fetchAnnouncementsRequest());
|
||||
|
||||
return api(getState).get('/api/v1/announcements').then(response => {
|
||||
dispatch(fetchAnnouncementsSuccess(response.data));
|
||||
dispatch(importFetchedStatuses(response.data.map(({ statuses }: APIEntity) => statuses)));
|
||||
}).catch(error => {
|
||||
dispatch(fetchAnnouncementsFail(error));
|
||||
}).finally(() => {
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchAnnouncementsRequest = () => ({
|
||||
type: ANNOUNCEMENTS_FETCH_REQUEST,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
export const fetchAnnouncementsSuccess = (announcements: APIEntity) => ({
|
||||
type: ANNOUNCEMENTS_FETCH_SUCCESS,
|
||||
announcements,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
export const fetchAnnouncementsFail = (error: AxiosError) => ({
|
||||
type: ANNOUNCEMENTS_FETCH_FAIL,
|
||||
error,
|
||||
skipLoading: true,
|
||||
skipAlert: true,
|
||||
});
|
||||
|
||||
export const updateAnnouncements = (announcement: APIEntity) => ({
|
||||
type: ANNOUNCEMENTS_UPDATE,
|
||||
announcement: announcement,
|
||||
});
|
||||
|
||||
export const dismissAnnouncement = (announcementId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(dismissAnnouncementRequest(announcementId));
|
||||
|
||||
return api(getState).post(`/api/v1/announcements/${announcementId}/dismiss`).then(() => {
|
||||
dispatch(dismissAnnouncementSuccess(announcementId));
|
||||
}).catch(error => {
|
||||
dispatch(dismissAnnouncementFail(announcementId, error));
|
||||
});
|
||||
};
|
||||
|
||||
export const dismissAnnouncementRequest = (announcementId: string) => ({
|
||||
type: ANNOUNCEMENTS_DISMISS_REQUEST,
|
||||
id: announcementId,
|
||||
});
|
||||
|
||||
export const dismissAnnouncementSuccess = (announcementId: string) => ({
|
||||
type: ANNOUNCEMENTS_DISMISS_SUCCESS,
|
||||
id: announcementId,
|
||||
});
|
||||
|
||||
export const dismissAnnouncementFail = (announcementId: string, error: AxiosError) => ({
|
||||
type: ANNOUNCEMENTS_DISMISS_FAIL,
|
||||
id: announcementId,
|
||||
error,
|
||||
});
|
||||
|
||||
export const addReaction = (announcementId: string, name: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const announcement = getState().announcements.items.find(x => x.get('id') === announcementId);
|
||||
|
||||
let alreadyAdded = false;
|
||||
|
||||
if (announcement) {
|
||||
const reaction = announcement.reactions.find(x => x.name === name);
|
||||
|
||||
if (reaction && reaction.me) {
|
||||
alreadyAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!alreadyAdded) {
|
||||
dispatch(addReactionRequest(announcementId, name, alreadyAdded));
|
||||
}
|
||||
|
||||
return api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
|
||||
dispatch(addReactionSuccess(announcementId, name, alreadyAdded));
|
||||
}).catch(err => {
|
||||
if (!alreadyAdded) {
|
||||
dispatch(addReactionFail(announcementId, name, err));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const addReactionRequest = (announcementId: string, name: string, alreadyAdded?: boolean) => ({
|
||||
type: ANNOUNCEMENTS_REACTION_ADD_REQUEST,
|
||||
id: announcementId,
|
||||
name,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
export const addReactionSuccess = (announcementId: string, name: string, alreadyAdded?: boolean) => ({
|
||||
type: ANNOUNCEMENTS_REACTION_ADD_SUCCESS,
|
||||
id: announcementId,
|
||||
name,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
export const addReactionFail = (announcementId: string, name: string, error: AxiosError) => ({
|
||||
type: ANNOUNCEMENTS_REACTION_ADD_FAIL,
|
||||
id: announcementId,
|
||||
name,
|
||||
error,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
export const removeReaction = (announcementId: string, name: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(removeReactionRequest(announcementId, name));
|
||||
|
||||
return api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
|
||||
dispatch(removeReactionSuccess(announcementId, name));
|
||||
}).catch(err => {
|
||||
dispatch(removeReactionFail(announcementId, name, err));
|
||||
});
|
||||
};
|
||||
|
||||
export const removeReactionRequest = (announcementId: string, name: string) => ({
|
||||
type: ANNOUNCEMENTS_REACTION_REMOVE_REQUEST,
|
||||
id: announcementId,
|
||||
name,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
export const removeReactionSuccess = (announcementId: string, name: string) => ({
|
||||
type: ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS,
|
||||
id: announcementId,
|
||||
name,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
export const removeReactionFail = (announcementId: string, name: string, error: AxiosError) => ({
|
||||
type: ANNOUNCEMENTS_REACTION_REMOVE_FAIL,
|
||||
id: announcementId,
|
||||
name,
|
||||
error,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
export const updateReaction = (reaction: APIEntity) => ({
|
||||
type: ANNOUNCEMENTS_REACTION_UPDATE,
|
||||
reaction,
|
||||
});
|
||||
|
||||
export const toggleShowAnnouncements = () => ({
|
||||
type: ANNOUNCEMENTS_TOGGLE_SHOW,
|
||||
});
|
||||
|
||||
export const deleteAnnouncement = (id: string) => ({
|
||||
type: ANNOUNCEMENTS_DELETE,
|
||||
id,
|
||||
});
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
import { baseClient } from '../api';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
|
||||
export const APP_CREATE_REQUEST = 'APP_CREATE_REQUEST';
|
||||
export const APP_CREATE_SUCCESS = 'APP_CREATE_SUCCESS';
|
||||
export const APP_CREATE_FAIL = 'APP_CREATE_FAIL';
|
||||
|
@ -16,12 +18,12 @@ export const APP_VERIFY_CREDENTIALS_REQUEST = 'APP_VERIFY_CREDENTIALS_REQUEST';
|
|||
export const APP_VERIFY_CREDENTIALS_SUCCESS = 'APP_VERIFY_CREDENTIALS_SUCCESS';
|
||||
export const APP_VERIFY_CREDENTIALS_FAIL = 'APP_VERIFY_CREDENTIALS_FAIL';
|
||||
|
||||
export function createApp(params, baseURL) {
|
||||
return (dispatch, getState) => {
|
||||
export function createApp(params?: Record<string, string>, baseURL?: string) {
|
||||
return (dispatch: React.Dispatch<AnyAction>) => {
|
||||
dispatch({ type: APP_CREATE_REQUEST, params });
|
||||
return baseClient(null, baseURL).post('/api/v1/apps', params).then(({ data: app }) => {
|
||||
dispatch({ type: APP_CREATE_SUCCESS, params, app });
|
||||
return app;
|
||||
return app as Record<string, string>;
|
||||
}).catch(error => {
|
||||
dispatch({ type: APP_CREATE_FAIL, params, error });
|
||||
throw error;
|
||||
|
@ -29,8 +31,8 @@ export function createApp(params, baseURL) {
|
|||
};
|
||||
}
|
||||
|
||||
export function verifyAppCredentials(token) {
|
||||
return (dispatch, getState) => {
|
||||
export function verifyAppCredentials(token: string) {
|
||||
return (dispatch: React.Dispatch<AnyAction>) => {
|
||||
dispatch({ type: APP_VERIFY_CREDENTIALS_REQUEST, token });
|
||||
return baseClient(token).get('/api/v1/apps/verify_credentials').then(({ data: app }) => {
|
||||
dispatch({ type: APP_VERIFY_CREDENTIALS_SUCCESS, token, app });
|
|
@ -26,6 +26,10 @@ import api, { baseClient } from '../api';
|
|||
|
||||
import { importFetchedAccount } from './importer';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
|
||||
export const SWITCH_ACCOUNT = 'SWITCH_ACCOUNT';
|
||||
|
||||
export const AUTH_APP_CREATED = 'AUTH_APP_CREATED';
|
||||
|
@ -48,35 +52,32 @@ export const messages = defineMessages({
|
|||
invalidCredentials: { id: 'auth.invalid_credentials', defaultMessage: 'Wrong username or password' },
|
||||
});
|
||||
|
||||
const noOp = () => new Promise(f => f());
|
||||
const noOp = () => new Promise(f => f(undefined));
|
||||
|
||||
const getScopes = state => {
|
||||
const instance = state.get('instance');
|
||||
const getScopes = (state: RootState) => {
|
||||
const instance = state.instance;
|
||||
const { scopes } = getFeatures(instance);
|
||||
return scopes;
|
||||
};
|
||||
|
||||
function createAppAndToken() {
|
||||
return (dispatch, getState) => {
|
||||
return dispatch(getAuthApp()).then(() => {
|
||||
return dispatch(createAppToken());
|
||||
});
|
||||
};
|
||||
}
|
||||
const createAppAndToken = () =>
|
||||
(dispatch: AppDispatch) =>
|
||||
dispatch(getAuthApp()).then(() =>
|
||||
dispatch(createAppToken()),
|
||||
);
|
||||
|
||||
/** Create an auth app, or use it from build config */
|
||||
function getAuthApp() {
|
||||
return (dispatch, getState) => {
|
||||
const getAuthApp = () =>
|
||||
(dispatch: AppDispatch) => {
|
||||
if (customApp?.client_secret) {
|
||||
return noOp().then(() => dispatch({ type: AUTH_APP_CREATED, app: customApp }));
|
||||
} else {
|
||||
return dispatch(createAuthApp());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createAuthApp() {
|
||||
return (dispatch, getState) => {
|
||||
const createAuthApp = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const params = {
|
||||
client_name: sourceCode.displayName,
|
||||
redirect_uris: 'urn:ietf:wg:oauth:2.0:oob',
|
||||
|
@ -84,15 +85,14 @@ function createAuthApp() {
|
|||
website: sourceCode.homepage,
|
||||
};
|
||||
|
||||
return dispatch(createApp(params)).then(app => {
|
||||
return dispatch({ type: AUTH_APP_CREATED, app });
|
||||
});
|
||||
return dispatch(createApp(params)).then((app: Record<string, string>) =>
|
||||
dispatch({ type: AUTH_APP_CREATED, app }),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function createAppToken() {
|
||||
return (dispatch, getState) => {
|
||||
const app = getState().getIn(['auth', 'app']);
|
||||
const createAppToken = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const app = getState().auth.get('app');
|
||||
|
||||
const params = {
|
||||
client_id: app.get('client_id'),
|
||||
|
@ -102,15 +102,14 @@ function createAppToken() {
|
|||
scope: getScopes(getState()),
|
||||
};
|
||||
|
||||
return dispatch(obtainOAuthToken(params)).then(token => {
|
||||
return dispatch({ type: AUTH_APP_AUTHORIZED, app, token });
|
||||
});
|
||||
return dispatch(obtainOAuthToken(params)).then((token: Record<string, string | number>) =>
|
||||
dispatch({ type: AUTH_APP_AUTHORIZED, app, token }),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function createUserToken(username, password) {
|
||||
return (dispatch, getState) => {
|
||||
const app = getState().getIn(['auth', 'app']);
|
||||
const createUserToken = (username: string, password: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const app = getState().auth.get('app');
|
||||
|
||||
const params = {
|
||||
client_id: app.get('client_id'),
|
||||
|
@ -123,14 +122,13 @@ function createUserToken(username, password) {
|
|||
};
|
||||
|
||||
return dispatch(obtainOAuthToken(params))
|
||||
.then(token => dispatch(authLoggedIn(token)));
|
||||
.then((token: Record<string, string | number>) => dispatch(authLoggedIn(token)));
|
||||
};
|
||||
}
|
||||
|
||||
export function refreshUserToken() {
|
||||
return (dispatch, getState) => {
|
||||
const refreshToken = getState().getIn(['auth', 'user', 'refresh_token']);
|
||||
const app = getState().getIn(['auth', 'app']);
|
||||
export const refreshUserToken = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const refreshToken = getState().auth.getIn(['user', 'refresh_token']);
|
||||
const app = getState().auth.get('app');
|
||||
|
||||
if (!refreshToken) return dispatch(noOp);
|
||||
|
||||
|
@ -144,13 +142,12 @@ export function refreshUserToken() {
|
|||
};
|
||||
|
||||
return dispatch(obtainOAuthToken(params))
|
||||
.then(token => dispatch(authLoggedIn(token)));
|
||||
.then((token: Record<string, string | number>) => dispatch(authLoggedIn(token)));
|
||||
};
|
||||
}
|
||||
|
||||
export function otpVerify(code, mfa_token) {
|
||||
return (dispatch, getState) => {
|
||||
const app = getState().getIn(['auth', 'app']);
|
||||
export const otpVerify = (code: string, mfa_token: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const app = getState().auth.get('app');
|
||||
return api(getState, 'app').post('/oauth/mfa/challenge', {
|
||||
client_id: app.get('client_id'),
|
||||
client_secret: app.get('client_secret'),
|
||||
|
@ -161,18 +158,17 @@ export function otpVerify(code, mfa_token) {
|
|||
scope: getScopes(getState()),
|
||||
}).then(({ data: token }) => dispatch(authLoggedIn(token)));
|
||||
};
|
||||
}
|
||||
|
||||
export function verifyCredentials(token, accountUrl) {
|
||||
export const verifyCredentials = (token: string, accountUrl?: string) => {
|
||||
const baseURL = parseBaseURL(accountUrl);
|
||||
|
||||
return (dispatch, getState) => {
|
||||
return (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: VERIFY_CREDENTIALS_REQUEST, token });
|
||||
|
||||
return baseClient(token, baseURL).get('/api/v1/accounts/verify_credentials').then(({ data: account }) => {
|
||||
dispatch(importFetchedAccount(account));
|
||||
dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account });
|
||||
if (account.id === getState().get('me')) dispatch(fetchMeSuccess(account));
|
||||
if (account.id === getState().me) dispatch(fetchMeSuccess(account));
|
||||
return account;
|
||||
}).catch(error => {
|
||||
if (error?.response?.status === 403 && error?.response?.data?.id) {
|
||||
|
@ -180,72 +176,66 @@ export function verifyCredentials(token, accountUrl) {
|
|||
const account = error.response.data;
|
||||
dispatch(importFetchedAccount(account));
|
||||
dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account });
|
||||
if (account.id === getState().get('me')) dispatch(fetchMeSuccess(account));
|
||||
if (account.id === getState().me) dispatch(fetchMeSuccess(account));
|
||||
return account;
|
||||
} else {
|
||||
if (getState().get('me') === null) dispatch(fetchMeFail(error));
|
||||
dispatch({ type: VERIFY_CREDENTIALS_FAIL, token, error, skipAlert: true });
|
||||
return error;
|
||||
if (getState().me === null) dispatch(fetchMeFail(error));
|
||||
dispatch({ type: VERIFY_CREDENTIALS_FAIL, token, error });
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export function rememberAuthAccount(accountUrl) {
|
||||
return (dispatch, getState) => {
|
||||
export const rememberAuthAccount = (accountUrl: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
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));
|
||||
if (account.id === getState().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 const loadCredentials = (token: string, accountUrl: string) =>
|
||||
(dispatch: AppDispatch) => dispatch(rememberAuthAccount(accountUrl))
|
||||
.then(() => {
|
||||
dispatch(verifyCredentials(token, accountUrl));
|
||||
})
|
||||
.catch(() => dispatch(verifyCredentials(token, accountUrl)));
|
||||
|
||||
export function logIn(intl, username, password) {
|
||||
return (dispatch, getState) => {
|
||||
return dispatch(getAuthApp()).then(() => {
|
||||
return dispatch(createUserToken(username, password));
|
||||
}).catch(error => {
|
||||
if (error.response.data.error === 'mfa_required') {
|
||||
// If MFA is required, throw the error and handle it in the component.
|
||||
throw error;
|
||||
} else if (error.response.data.error === 'invalid_grant') {
|
||||
// Mastodon returns this user-unfriendly error as a catch-all
|
||||
// for everything from "bad request" to "wrong password".
|
||||
// Assume our code is correct and it's a wrong password.
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.invalidCredentials)));
|
||||
} else if (error.response.data.error) {
|
||||
// If the backend returns an error, display it.
|
||||
dispatch(snackbar.error(error.response.data.error));
|
||||
} else {
|
||||
// Return "wrong password" message.
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.invalidCredentials)));
|
||||
}
|
||||
/** Trim the username and strip the leading @. */
|
||||
const normalizeUsername = (username: string): string => {
|
||||
const trimmed = username.trim();
|
||||
if (trimmed[0] === '@') {
|
||||
return trimmed.slice(1);
|
||||
} else {
|
||||
return trimmed;
|
||||
}
|
||||
};
|
||||
|
||||
export const logIn = (username: string, password: string) =>
|
||||
(dispatch: AppDispatch) => dispatch(getAuthApp()).then(() => {
|
||||
return dispatch(createUserToken(normalizeUsername(username), password));
|
||||
}).catch((error: AxiosError) => {
|
||||
if ((error.response?.data as any).error === 'mfa_required') {
|
||||
// If MFA is required, throw the error and handle it in the component.
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Return "wrong password" message.
|
||||
dispatch(snackbar.error(messages.invalidCredentials));
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
export function deleteSession() {
|
||||
return (dispatch, getState) => {
|
||||
return api(getState).delete('/api/sign_out');
|
||||
};
|
||||
}
|
||||
export const deleteSession = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => api(getState).delete('/api/sign_out');
|
||||
|
||||
export function logOut(intl) {
|
||||
return (dispatch, getState) => {
|
||||
export const logOut = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const account = getLoggedInAccount(state);
|
||||
const standalone = isStandalone(state);
|
||||
|
@ -253,62 +243,53 @@ export function logOut(intl) {
|
|||
if (!account) return dispatch(noOp);
|
||||
|
||||
const params = {
|
||||
client_id: state.getIn(['auth', 'app', 'client_id']),
|
||||
client_secret: state.getIn(['auth', 'app', 'client_secret']),
|
||||
token: state.getIn(['auth', 'users', account.url, 'access_token']),
|
||||
client_id: state.auth.getIn(['app', 'client_id']),
|
||||
client_secret: state.auth.getIn(['app', 'client_secret']),
|
||||
token: state.auth.getIn(['users', account.url, 'access_token']),
|
||||
};
|
||||
|
||||
return Promise.all([
|
||||
dispatch(revokeOAuthToken(params)),
|
||||
dispatch(deleteSession()),
|
||||
]).finally(() => {
|
||||
return dispatch(revokeOAuthToken(params)).finally(() => {
|
||||
dispatch({ type: AUTH_LOGGED_OUT, account, standalone });
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.loggedOut)));
|
||||
return dispatch(snackbar.success(messages.loggedOut));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function switchAccount(accountId, background = false) {
|
||||
return (dispatch, getState) => {
|
||||
const account = getState().getIn(['accounts', accountId]);
|
||||
dispatch({ type: SWITCH_ACCOUNT, account, background });
|
||||
export const switchAccount = (accountId: string, background = false) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const account = getState().accounts.get(accountId);
|
||||
return dispatch({ type: SWITCH_ACCOUNT, account, background });
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchOwnAccounts() {
|
||||
return (dispatch, getState) => {
|
||||
export const fetchOwnAccounts = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
state.getIn(['auth', 'users']).forEach(user => {
|
||||
const account = state.getIn(['accounts', user.get('id')]);
|
||||
return state.auth.get('users').forEach((user: ImmutableMap<string, string>) => {
|
||||
const account = state.accounts.get(user.get('id'));
|
||||
if (!account) {
|
||||
dispatch(verifyCredentials(user.get('access_token'), user.get('url')));
|
||||
dispatch(verifyCredentials(user.get('access_token')!, user.get('url')));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function register(params) {
|
||||
return (dispatch, getState) => {
|
||||
export const register = (params: Record<string, any>) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
params.fullname = params.username;
|
||||
|
||||
return dispatch(createAppAndToken())
|
||||
.then(() => dispatch(createAccount(params)))
|
||||
.then(({ token }) => {
|
||||
.then(({ token }: { token: Record<string, string | number> }) => {
|
||||
dispatch(startOnboarding());
|
||||
return dispatch(authLoggedIn(token));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchCaptcha() {
|
||||
return (dispatch, getState) => {
|
||||
export const fetchCaptcha = () =>
|
||||
(_dispatch: AppDispatch, getState: () => RootState) => {
|
||||
return api(getState).get('/api/pleroma/captcha');
|
||||
};
|
||||
}
|
||||
|
||||
export function authLoggedIn(token) {
|
||||
return (dispatch, getState) => {
|
||||
export const authLoggedIn = (token: Record<string, string | number>) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
dispatch({ type: AUTH_LOGGED_IN, token });
|
||||
return token;
|
||||
};
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import api from '../api';
|
||||
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
|
||||
export const BACKUPS_FETCH_REQUEST = 'BACKUPS_FETCH_REQUEST';
|
||||
export const BACKUPS_FETCH_SUCCESS = 'BACKUPS_FETCH_SUCCESS';
|
||||
export const BACKUPS_FETCH_FAIL = 'BACKUPS_FETCH_FAIL';
|
||||
|
@ -8,24 +10,22 @@ export const BACKUPS_CREATE_REQUEST = 'BACKUPS_CREATE_REQUEST';
|
|||
export const BACKUPS_CREATE_SUCCESS = 'BACKUPS_CREATE_SUCCESS';
|
||||
export const BACKUPS_CREATE_FAIL = 'BACKUPS_CREATE_FAIL';
|
||||
|
||||
export function fetchBackups() {
|
||||
return (dispatch, getState) => {
|
||||
export const fetchBackups = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: BACKUPS_FETCH_REQUEST });
|
||||
return api(getState).get('/api/v1/pleroma/backups').then(({ data: backups }) => {
|
||||
dispatch({ type: BACKUPS_FETCH_SUCCESS, backups });
|
||||
}).catch(error => {
|
||||
return api(getState).get('/api/v1/pleroma/backups').then(({ data: backups }) =>
|
||||
dispatch({ type: BACKUPS_FETCH_SUCCESS, backups }),
|
||||
).catch(error => {
|
||||
dispatch({ type: BACKUPS_FETCH_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function createBackup() {
|
||||
return (dispatch, getState) => {
|
||||
export const createBackup = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: BACKUPS_CREATE_REQUEST });
|
||||
return api(getState).post('/api/v1/pleroma/backups').then(({ data: backups }) => {
|
||||
dispatch({ type: BACKUPS_CREATE_SUCCESS, backups });
|
||||
}).catch(error => {
|
||||
return api(getState).post('/api/v1/pleroma/backups').then(({ data: backups }) =>
|
||||
dispatch({ type: BACKUPS_CREATE_SUCCESS, backups }),
|
||||
).catch(error => {
|
||||
dispatch({ type: BACKUPS_CREATE_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import { staticClient } from '../api';
|
||||
|
||||
export const FETCH_BETA_PAGE_REQUEST = 'FETCH_BETA_PAGE_REQUEST';
|
||||
export const FETCH_BETA_PAGE_SUCCESS = 'FETCH_BETA_PAGE_SUCCESS';
|
||||
export const FETCH_BETA_PAGE_FAIL = 'FETCH_BETA_PAGE_FAIL';
|
||||
|
||||
export function fetchBetaPage(slug = 'index', locale) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: FETCH_BETA_PAGE_REQUEST, slug, locale });
|
||||
const filename = `${slug}${locale ? `.${locale}` : ''}.html`;
|
||||
return staticClient.get(`/instance/beta/${filename}`).then(({ data: html }) => {
|
||||
dispatch({ type: FETCH_BETA_PAGE_SUCCESS, slug, locale, html });
|
||||
return html;
|
||||
}).catch(error => {
|
||||
dispatch({ type: FETCH_BETA_PAGE_FAIL, slug, locale, error });
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
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_SUCCESS = 'BLOCKS_FETCH_SUCCESS';
|
||||
export const BLOCKS_FETCH_FAIL = 'BLOCKS_FETCH_FAIL';
|
||||
|
||||
export const BLOCKS_EXPAND_REQUEST = 'BLOCKS_EXPAND_REQUEST';
|
||||
export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS';
|
||||
export const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL';
|
||||
|
||||
export function fetchBlocks() {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
const nextLinkName = getNextLinkName(getState);
|
||||
|
||||
dispatch(fetchBlocksRequest());
|
||||
|
||||
api(getState).get('/api/v1/blocks').then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === nextLinkName);
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null));
|
||||
dispatch(fetchRelationships(response.data.map(item => item.id)));
|
||||
}).catch(error => dispatch(fetchBlocksFail(error)));
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBlocksRequest() {
|
||||
return {
|
||||
type: BLOCKS_FETCH_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBlocksSuccess(accounts, next) {
|
||||
return {
|
||||
type: BLOCKS_FETCH_SUCCESS,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBlocksFail(error) {
|
||||
return {
|
||||
type: BLOCKS_FETCH_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandBlocks() {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
const nextLinkName = getNextLinkName(getState);
|
||||
|
||||
const url = getState().getIn(['user_lists', 'blocks', 'next']);
|
||||
|
||||
if (url === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandBlocksRequest());
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === nextLinkName);
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(expandBlocksSuccess(response.data, next ? next.uri : null));
|
||||
dispatch(fetchRelationships(response.data.map(item => item.id)));
|
||||
}).catch(error => dispatch(expandBlocksFail(error)));
|
||||
};
|
||||
}
|
||||
|
||||
export function expandBlocksRequest() {
|
||||
return {
|
||||
type: BLOCKS_EXPAND_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandBlocksSuccess(accounts, next) {
|
||||
return {
|
||||
type: BLOCKS_EXPAND_SUCCESS,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandBlocksFail(error) {
|
||||
return {
|
||||
type: BLOCKS_EXPAND_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
110
app/soapbox/actions/blocks.ts
Normal file
|
@ -0,0 +1,110 @@
|
|||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
import { getNextLinkName } from 'soapbox/utils/quirks';
|
||||
|
||||
import api, { getLinks } from '../api';
|
||||
|
||||
import { fetchRelationships } from './accounts';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
|
||||
import type { AnyAction } from '@reduxjs/toolkit';
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { RootState } from 'soapbox/store';
|
||||
|
||||
const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST';
|
||||
const BLOCKS_FETCH_SUCCESS = 'BLOCKS_FETCH_SUCCESS';
|
||||
const BLOCKS_FETCH_FAIL = 'BLOCKS_FETCH_FAIL';
|
||||
|
||||
const BLOCKS_EXPAND_REQUEST = 'BLOCKS_EXPAND_REQUEST';
|
||||
const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS';
|
||||
const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL';
|
||||
|
||||
const fetchBlocks = () => (dispatch: React.Dispatch<AnyAction>, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return null;
|
||||
const nextLinkName = getNextLinkName(getState);
|
||||
|
||||
dispatch(fetchBlocksRequest());
|
||||
|
||||
return api(getState)
|
||||
.get('/api/v1/blocks')
|
||||
.then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === nextLinkName);
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null));
|
||||
dispatch(fetchRelationships(response.data.map((item: any) => item.id)) as any);
|
||||
})
|
||||
.catch(error => dispatch(fetchBlocksFail(error)));
|
||||
};
|
||||
|
||||
function fetchBlocksRequest() {
|
||||
return { type: BLOCKS_FETCH_REQUEST };
|
||||
}
|
||||
|
||||
function fetchBlocksSuccess(accounts: any, next: any) {
|
||||
return {
|
||||
type: BLOCKS_FETCH_SUCCESS,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
function fetchBlocksFail(error: AxiosError) {
|
||||
return {
|
||||
type: BLOCKS_FETCH_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
const expandBlocks = () => (dispatch: React.Dispatch<AnyAction>, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return null;
|
||||
const nextLinkName = getNextLinkName(getState);
|
||||
|
||||
const url = getState().user_lists.blocks.next;
|
||||
|
||||
if (url === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
dispatch(expandBlocksRequest());
|
||||
|
||||
return api(getState)
|
||||
.get(url)
|
||||
.then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === nextLinkName);
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(expandBlocksSuccess(response.data, next ? next.uri : null));
|
||||
dispatch(fetchRelationships(response.data.map((item: any) => item.id)) as any);
|
||||
})
|
||||
.catch(error => dispatch(expandBlocksFail(error)));
|
||||
};
|
||||
|
||||
function expandBlocksRequest() {
|
||||
return {
|
||||
type: BLOCKS_EXPAND_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
function expandBlocksSuccess(accounts: any, next: any) {
|
||||
return {
|
||||
type: BLOCKS_EXPAND_SUCCESS,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
function expandBlocksFail(error: AxiosError) {
|
||||
return {
|
||||
type: BLOCKS_EXPAND_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
fetchBlocks,
|
||||
expandBlocks,
|
||||
BLOCKS_FETCH_REQUEST,
|
||||
BLOCKS_FETCH_SUCCESS,
|
||||
BLOCKS_FETCH_FAIL,
|
||||
BLOCKS_EXPAND_REQUEST,
|
||||
BLOCKS_EXPAND_SUCCESS,
|
||||
BLOCKS_EXPAND_FAIL,
|
||||
};
|
|
@ -1,93 +0,0 @@
|
|||
import api, { getLinks } from '../api';
|
||||
|
||||
import { importFetchedStatuses } from './importer';
|
||||
|
||||
export const BOOKMARKED_STATUSES_FETCH_REQUEST = 'BOOKMARKED_STATUSES_FETCH_REQUEST';
|
||||
export const BOOKMARKED_STATUSES_FETCH_SUCCESS = 'BOOKMARKED_STATUSES_FETCH_SUCCESS';
|
||||
export const BOOKMARKED_STATUSES_FETCH_FAIL = 'BOOKMARKED_STATUSES_FETCH_FAIL';
|
||||
|
||||
export const BOOKMARKED_STATUSES_EXPAND_REQUEST = 'BOOKMARKED_STATUSES_EXPAND_REQUEST';
|
||||
export const BOOKMARKED_STATUSES_EXPAND_SUCCESS = 'BOOKMARKED_STATUSES_EXPAND_SUCCESS';
|
||||
export const BOOKMARKED_STATUSES_EXPAND_FAIL = 'BOOKMARKED_STATUSES_EXPAND_FAIL';
|
||||
|
||||
const noOp = () => new Promise(f => f());
|
||||
|
||||
export function fetchBookmarkedStatuses() {
|
||||
return (dispatch, getState) => {
|
||||
if (getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) {
|
||||
return dispatch(noOp);
|
||||
}
|
||||
|
||||
dispatch(fetchBookmarkedStatusesRequest());
|
||||
|
||||
return api(getState).get('/api/v1/bookmarks').then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(fetchBookmarkedStatusesFail(error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBookmarkedStatusesRequest() {
|
||||
return {
|
||||
type: BOOKMARKED_STATUSES_FETCH_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBookmarkedStatusesSuccess(statuses, next) {
|
||||
return {
|
||||
type: BOOKMARKED_STATUSES_FETCH_SUCCESS,
|
||||
statuses,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBookmarkedStatusesFail(error) {
|
||||
return {
|
||||
type: BOOKMARKED_STATUSES_FETCH_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandBookmarkedStatuses() {
|
||||
return (dispatch, getState) => {
|
||||
const url = getState().getIn(['status_lists', 'bookmarks', 'next'], null);
|
||||
|
||||
if (url === null || getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) {
|
||||
return dispatch(noOp);
|
||||
}
|
||||
|
||||
dispatch(expandBookmarkedStatusesRequest());
|
||||
|
||||
return api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(expandBookmarkedStatusesFail(error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function expandBookmarkedStatusesRequest() {
|
||||
return {
|
||||
type: BOOKMARKED_STATUSES_EXPAND_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandBookmarkedStatusesSuccess(statuses, next) {
|
||||
return {
|
||||
type: BOOKMARKED_STATUSES_EXPAND_SUCCESS,
|
||||
statuses,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandBookmarkedStatusesFail(error) {
|
||||
return {
|
||||
type: BOOKMARKED_STATUSES_EXPAND_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
100
app/soapbox/actions/bookmarks.ts
Normal file
|
@ -0,0 +1,100 @@
|
|||
import api, { getLinks } from '../api';
|
||||
|
||||
import { importFetchedStatuses } from './importer';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
const BOOKMARKED_STATUSES_FETCH_REQUEST = 'BOOKMARKED_STATUSES_FETCH_REQUEST';
|
||||
const BOOKMARKED_STATUSES_FETCH_SUCCESS = 'BOOKMARKED_STATUSES_FETCH_SUCCESS';
|
||||
const BOOKMARKED_STATUSES_FETCH_FAIL = 'BOOKMARKED_STATUSES_FETCH_FAIL';
|
||||
|
||||
const BOOKMARKED_STATUSES_EXPAND_REQUEST = 'BOOKMARKED_STATUSES_EXPAND_REQUEST';
|
||||
const BOOKMARKED_STATUSES_EXPAND_SUCCESS = 'BOOKMARKED_STATUSES_EXPAND_SUCCESS';
|
||||
const BOOKMARKED_STATUSES_EXPAND_FAIL = 'BOOKMARKED_STATUSES_EXPAND_FAIL';
|
||||
|
||||
const noOp = () => new Promise(f => f(undefined));
|
||||
|
||||
const fetchBookmarkedStatuses = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (getState().status_lists.get('bookmarks')?.isLoading) {
|
||||
return dispatch(noOp);
|
||||
}
|
||||
|
||||
dispatch(fetchBookmarkedStatusesRequest());
|
||||
|
||||
return api(getState).get('/api/v1/bookmarks').then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
return dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(fetchBookmarkedStatusesFail(error));
|
||||
});
|
||||
};
|
||||
|
||||
const fetchBookmarkedStatusesRequest = () => ({
|
||||
type: BOOKMARKED_STATUSES_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
const fetchBookmarkedStatusesSuccess = (statuses: APIEntity[], next: string | null) => ({
|
||||
type: BOOKMARKED_STATUSES_FETCH_SUCCESS,
|
||||
statuses,
|
||||
next,
|
||||
});
|
||||
|
||||
const fetchBookmarkedStatusesFail = (error: AxiosError) => ({
|
||||
type: BOOKMARKED_STATUSES_FETCH_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
const expandBookmarkedStatuses = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const url = getState().status_lists.get('bookmarks')?.next || null;
|
||||
|
||||
if (url === null || getState().status_lists.get('bookmarks')?.isLoading) {
|
||||
return dispatch(noOp);
|
||||
}
|
||||
|
||||
dispatch(expandBookmarkedStatusesRequest());
|
||||
|
||||
return api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
return dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(expandBookmarkedStatusesFail(error));
|
||||
});
|
||||
};
|
||||
|
||||
const expandBookmarkedStatusesRequest = () => ({
|
||||
type: BOOKMARKED_STATUSES_EXPAND_REQUEST,
|
||||
});
|
||||
|
||||
const expandBookmarkedStatusesSuccess = (statuses: APIEntity[], next: string | null) => ({
|
||||
type: BOOKMARKED_STATUSES_EXPAND_SUCCESS,
|
||||
statuses,
|
||||
next,
|
||||
});
|
||||
|
||||
const expandBookmarkedStatusesFail = (error: AxiosError) => ({
|
||||
type: BOOKMARKED_STATUSES_EXPAND_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export {
|
||||
BOOKMARKED_STATUSES_FETCH_REQUEST,
|
||||
BOOKMARKED_STATUSES_FETCH_SUCCESS,
|
||||
BOOKMARKED_STATUSES_FETCH_FAIL,
|
||||
BOOKMARKED_STATUSES_EXPAND_REQUEST,
|
||||
BOOKMARKED_STATUSES_EXPAND_SUCCESS,
|
||||
BOOKMARKED_STATUSES_EXPAND_FAIL,
|
||||
fetchBookmarkedStatuses,
|
||||
fetchBookmarkedStatusesRequest,
|
||||
fetchBookmarkedStatusesSuccess,
|
||||
fetchBookmarkedStatusesFail,
|
||||
expandBookmarkedStatuses,
|
||||
expandBookmarkedStatusesRequest,
|
||||
expandBookmarkedStatusesSuccess,
|
||||
expandBookmarkedStatusesFail,
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
export const BUNDLE_FETCH_REQUEST = 'BUNDLE_FETCH_REQUEST';
|
||||
export const BUNDLE_FETCH_SUCCESS = 'BUNDLE_FETCH_SUCCESS';
|
||||
export const BUNDLE_FETCH_FAIL = 'BUNDLE_FETCH_FAIL';
|
||||
|
||||
export function fetchBundleRequest(skipLoading) {
|
||||
return {
|
||||
type: BUNDLE_FETCH_REQUEST,
|
||||
skipLoading,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBundleSuccess(skipLoading) {
|
||||
return {
|
||||
type: BUNDLE_FETCH_SUCCESS,
|
||||
skipLoading,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchBundleFail(error, skipLoading) {
|
||||
return {
|
||||
type: BUNDLE_FETCH_FAIL,
|
||||
error,
|
||||
skipLoading,
|
||||
};
|
||||
}
|
28
app/soapbox/actions/bundles.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
const BUNDLE_FETCH_REQUEST = 'BUNDLE_FETCH_REQUEST';
|
||||
const BUNDLE_FETCH_SUCCESS = 'BUNDLE_FETCH_SUCCESS';
|
||||
const BUNDLE_FETCH_FAIL = 'BUNDLE_FETCH_FAIL';
|
||||
|
||||
const fetchBundleRequest = (skipLoading?: boolean) => ({
|
||||
type: BUNDLE_FETCH_REQUEST,
|
||||
skipLoading,
|
||||
});
|
||||
|
||||
const fetchBundleSuccess = (skipLoading?: boolean) => ({
|
||||
type: BUNDLE_FETCH_SUCCESS,
|
||||
skipLoading,
|
||||
});
|
||||
|
||||
const fetchBundleFail = (error: any, skipLoading?: boolean) => ({
|
||||
type: BUNDLE_FETCH_FAIL,
|
||||
error,
|
||||
skipLoading,
|
||||
});
|
||||
|
||||
export {
|
||||
BUNDLE_FETCH_REQUEST,
|
||||
BUNDLE_FETCH_SUCCESS,
|
||||
BUNDLE_FETCH_FAIL,
|
||||
fetchBundleRequest,
|
||||
fetchBundleSuccess,
|
||||
fetchBundleFail,
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { getSettings, changeSetting } from 'soapbox/actions/settings';
|
||||
|
@ -6,47 +6,49 @@ import { getFeatures } from 'soapbox/utils/features';
|
|||
|
||||
import api, { getLinks } from '../api';
|
||||
|
||||
export const CHATS_FETCH_REQUEST = 'CHATS_FETCH_REQUEST';
|
||||
export const CHATS_FETCH_SUCCESS = 'CHATS_FETCH_SUCCESS';
|
||||
export const CHATS_FETCH_FAIL = 'CHATS_FETCH_FAIL';
|
||||
import type { History } from 'history';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
|
||||
export const CHATS_EXPAND_REQUEST = 'CHATS_EXPAND_REQUEST';
|
||||
export const CHATS_EXPAND_SUCCESS = 'CHATS_EXPAND_SUCCESS';
|
||||
export const CHATS_EXPAND_FAIL = 'CHATS_EXPAND_FAIL';
|
||||
const CHATS_FETCH_REQUEST = 'CHATS_FETCH_REQUEST';
|
||||
const CHATS_FETCH_SUCCESS = 'CHATS_FETCH_SUCCESS';
|
||||
const CHATS_FETCH_FAIL = 'CHATS_FETCH_FAIL';
|
||||
|
||||
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_FAIL = 'CHAT_MESSAGES_FETCH_FAIL';
|
||||
const CHATS_EXPAND_REQUEST = 'CHATS_EXPAND_REQUEST';
|
||||
const CHATS_EXPAND_SUCCESS = 'CHATS_EXPAND_SUCCESS';
|
||||
const CHATS_EXPAND_FAIL = 'CHATS_EXPAND_FAIL';
|
||||
|
||||
export const CHAT_MESSAGE_SEND_REQUEST = 'CHAT_MESSAGE_SEND_REQUEST';
|
||||
export const CHAT_MESSAGE_SEND_SUCCESS = 'CHAT_MESSAGE_SEND_SUCCESS';
|
||||
export const CHAT_MESSAGE_SEND_FAIL = 'CHAT_MESSAGE_SEND_FAIL';
|
||||
const CHAT_MESSAGES_FETCH_REQUEST = 'CHAT_MESSAGES_FETCH_REQUEST';
|
||||
const CHAT_MESSAGES_FETCH_SUCCESS = 'CHAT_MESSAGES_FETCH_SUCCESS';
|
||||
const CHAT_MESSAGES_FETCH_FAIL = 'CHAT_MESSAGES_FETCH_FAIL';
|
||||
|
||||
export const CHAT_FETCH_REQUEST = 'CHAT_FETCH_REQUEST';
|
||||
export const CHAT_FETCH_SUCCESS = 'CHAT_FETCH_SUCCESS';
|
||||
export const CHAT_FETCH_FAIL = 'CHAT_FETCH_FAIL';
|
||||
const CHAT_MESSAGE_SEND_REQUEST = 'CHAT_MESSAGE_SEND_REQUEST';
|
||||
const CHAT_MESSAGE_SEND_SUCCESS = 'CHAT_MESSAGE_SEND_SUCCESS';
|
||||
const CHAT_MESSAGE_SEND_FAIL = 'CHAT_MESSAGE_SEND_FAIL';
|
||||
|
||||
export const CHAT_READ_REQUEST = 'CHAT_READ_REQUEST';
|
||||
export const CHAT_READ_SUCCESS = 'CHAT_READ_SUCCESS';
|
||||
export const CHAT_READ_FAIL = 'CHAT_READ_FAIL';
|
||||
const CHAT_FETCH_REQUEST = 'CHAT_FETCH_REQUEST';
|
||||
const CHAT_FETCH_SUCCESS = 'CHAT_FETCH_SUCCESS';
|
||||
const CHAT_FETCH_FAIL = 'CHAT_FETCH_FAIL';
|
||||
|
||||
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_FAIL = 'CHAT_MESSAGE_DELETE_FAIL';
|
||||
const CHAT_READ_REQUEST = 'CHAT_READ_REQUEST';
|
||||
const CHAT_READ_SUCCESS = 'CHAT_READ_SUCCESS';
|
||||
const CHAT_READ_FAIL = 'CHAT_READ_FAIL';
|
||||
|
||||
export function fetchChatsV1() {
|
||||
return (dispatch, getState) =>
|
||||
const CHAT_MESSAGE_DELETE_REQUEST = 'CHAT_MESSAGE_DELETE_REQUEST';
|
||||
const CHAT_MESSAGE_DELETE_SUCCESS = 'CHAT_MESSAGE_DELETE_SUCCESS';
|
||||
const CHAT_MESSAGE_DELETE_FAIL = 'CHAT_MESSAGE_DELETE_FAIL';
|
||||
|
||||
const fetchChatsV1 = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
api(getState).get('/api/v1/pleroma/chats').then((response) => {
|
||||
dispatch({ type: CHATS_FETCH_SUCCESS, chats: response.data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: CHATS_FETCH_FAIL, error });
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchChatsV2() {
|
||||
return (dispatch, getState) =>
|
||||
const fetchChatsV2 = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||
api(getState).get('/api/v2/pleroma/chats').then((response) => {
|
||||
let next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
let next: { uri: string } | undefined = 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` };
|
||||
|
@ -56,10 +58,9 @@ export function fetchChatsV2() {
|
|||
}).catch(error => {
|
||||
dispatch({ type: CHATS_FETCH_FAIL, error });
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchChats() {
|
||||
return (dispatch, getState) => {
|
||||
const fetchChats = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const { instance } = state;
|
||||
const features = getFeatures(instance);
|
||||
|
@ -71,11 +72,10 @@ export function fetchChats() {
|
|||
return dispatch(fetchChatsV1());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function expandChats() {
|
||||
return (dispatch, getState) => {
|
||||
const url = getState().getIn(['chats', 'next']);
|
||||
const expandChats = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const url = getState().chats.next;
|
||||
|
||||
if (url === null) {
|
||||
return;
|
||||
|
@ -90,10 +90,9 @@ export function expandChats() {
|
|||
dispatch({ type: CHATS_EXPAND_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchChatMessages(chatId, maxId = null) {
|
||||
return (dispatch, getState) => {
|
||||
const fetchChatMessages = (chatId: string, maxId: string | null = null) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: CHAT_MESSAGES_FETCH_REQUEST, chatId, maxId });
|
||||
return api(getState).get(`/api/v1/pleroma/chats/${chatId}/messages`, { params: { max_id: maxId } }).then(({ data }) => {
|
||||
dispatch({ type: CHAT_MESSAGES_FETCH_SUCCESS, chatId, maxId, chatMessages: data });
|
||||
|
@ -101,12 +100,11 @@ export function fetchChatMessages(chatId, maxId = null) {
|
|||
dispatch({ type: CHAT_MESSAGES_FETCH_FAIL, chatId, maxId, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function sendChatMessage(chatId, params) {
|
||||
return (dispatch, getState) => {
|
||||
const sendChatMessage = (chatId: string, params: Record<string, any>) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const uuid = `末_${Date.now()}_${uuidv4()}`;
|
||||
const me = getState().get('me');
|
||||
const me = getState().me;
|
||||
dispatch({ type: CHAT_MESSAGE_SEND_REQUEST, chatId, params, uuid, me });
|
||||
return api(getState).post(`/api/v1/pleroma/chats/${chatId}/messages`, params).then(({ data }) => {
|
||||
dispatch({ type: CHAT_MESSAGE_SEND_SUCCESS, chatId, chatMessage: data, uuid });
|
||||
|
@ -114,28 +112,26 @@ export function sendChatMessage(chatId, params) {
|
|||
dispatch({ type: CHAT_MESSAGE_SEND_FAIL, chatId, error, uuid });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function openChat(chatId) {
|
||||
return (dispatch, getState) => {
|
||||
const openChat = (chatId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const panes = getSettings(state).getIn(['chats', 'panes']);
|
||||
const panes = getSettings(state).getIn(['chats', 'panes']) as ImmutableList<ImmutableMap<string, any>>;
|
||||
const idx = panes.findIndex(pane => pane.get('chat_id') === chatId);
|
||||
|
||||
dispatch(markChatRead(chatId));
|
||||
|
||||
if (idx > -1) {
|
||||
return dispatch(changeSetting(['chats', 'panes', idx, 'state'], 'open'));
|
||||
return dispatch(changeSetting(['chats', 'panes', idx as any, 'state'], 'open'));
|
||||
} else {
|
||||
const newPane = ImmutableMap({ chat_id: chatId, state: 'open' });
|
||||
return dispatch(changeSetting(['chats', 'panes'], panes.push(newPane)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function closeChat(chatId) {
|
||||
return (dispatch, getState) => {
|
||||
const panes = getSettings(getState()).getIn(['chats', 'panes']);
|
||||
const closeChat = (chatId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const panes = getSettings(getState()).getIn(['chats', 'panes']) as ImmutableList<ImmutableMap<string, any>>;
|
||||
const idx = panes.findIndex(pane => pane.get('chat_id') === chatId);
|
||||
|
||||
if (idx > -1) {
|
||||
|
@ -144,33 +140,30 @@ export function closeChat(chatId) {
|
|||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleChat(chatId) {
|
||||
return (dispatch, getState) => {
|
||||
const panes = getSettings(getState()).getIn(['chats', 'panes']);
|
||||
const [idx, pane] = panes.findEntry(pane => pane.get('chat_id') === chatId);
|
||||
const toggleChat = (chatId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const panes = getSettings(getState()).getIn(['chats', 'panes']) as ImmutableList<ImmutableMap<string, any>>;
|
||||
const [idx, pane] = panes.findEntry(pane => pane.get('chat_id') === chatId)!;
|
||||
|
||||
if (idx > -1) {
|
||||
const state = pane.get('state') === 'minimized' ? 'open' : 'minimized';
|
||||
if (state === 'open') dispatch(markChatRead(chatId));
|
||||
return dispatch(changeSetting(['chats', 'panes', idx, 'state'], state));
|
||||
return dispatch(changeSetting(['chats', 'panes', idx as any, 'state'], state));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleMainWindow() {
|
||||
return (dispatch, getState) => {
|
||||
const main = getSettings(getState()).getIn(['chats', 'mainWindow']);
|
||||
const toggleMainWindow = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const main = getSettings(getState()).getIn(['chats', 'mainWindow']) as 'minimized' | 'open';
|
||||
const state = main === 'minimized' ? 'open' : 'minimized';
|
||||
return dispatch(changeSetting(['chats', 'mainWindow'], state));
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchChat(chatId) {
|
||||
return (dispatch, getState) => {
|
||||
const fetchChat = (chatId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: CHAT_FETCH_REQUEST, chatId });
|
||||
return api(getState).get(`/api/v1/pleroma/chats/${chatId}`).then(({ data }) => {
|
||||
dispatch({ type: CHAT_FETCH_SUCCESS, chat: data });
|
||||
|
@ -178,10 +171,9 @@ export function fetchChat(chatId) {
|
|||
dispatch({ type: CHAT_FETCH_FAIL, chatId, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function startChat(accountId) {
|
||||
return (dispatch, getState) => {
|
||||
const startChat = (accountId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: CHAT_FETCH_REQUEST, accountId });
|
||||
return api(getState).post(`/api/v1/pleroma/chats/by-account-id/${accountId}`).then(({ data }) => {
|
||||
dispatch({ type: CHAT_FETCH_SUCCESS, chat: data });
|
||||
|
@ -190,12 +182,11 @@ export function startChat(accountId) {
|
|||
dispatch({ type: CHAT_FETCH_FAIL, accountId, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function markChatRead(chatId, lastReadId) {
|
||||
return (dispatch, getState) => {
|
||||
const chat = getState().getIn(['chats', 'items', chatId]);
|
||||
if (!lastReadId) lastReadId = chat.get('last_message');
|
||||
const markChatRead = (chatId: string, lastReadId?: string | null) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const chat = getState().chats.items.get(chatId)!;
|
||||
if (!lastReadId) lastReadId = chat.last_message;
|
||||
|
||||
if (chat.get('unread') < 1) return;
|
||||
if (!lastReadId) return;
|
||||
|
@ -207,10 +198,9 @@ export function markChatRead(chatId, lastReadId) {
|
|||
dispatch({ type: CHAT_READ_FAIL, chatId, error, lastReadId });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteChatMessage(chatId, messageId) {
|
||||
return (dispatch, getState) => {
|
||||
const deleteChatMessage = (chatId: string, messageId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: CHAT_MESSAGE_DELETE_REQUEST, chatId, messageId });
|
||||
api(getState).delete(`/api/v1/pleroma/chats/${chatId}/messages/${messageId}`).then(({ data }) => {
|
||||
dispatch({ type: CHAT_MESSAGE_DELETE_SUCCESS, chatId, messageId, chatMessage: data });
|
||||
|
@ -218,13 +208,12 @@ export function deleteChatMessage(chatId, messageId) {
|
|||
dispatch({ type: CHAT_MESSAGE_DELETE_FAIL, chatId, messageId, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/** Start a chat and launch it in the UI */
|
||||
export function launchChat(accountId, router, forceNavigate = false) {
|
||||
const isMobile = width => width <= 1190;
|
||||
const launchChat = (accountId: string, router: History, forceNavigate = false) => {
|
||||
const isMobile = (width: number) => width <= 1190;
|
||||
|
||||
return (dispatch, getState) => {
|
||||
return (dispatch: AppDispatch) => {
|
||||
// TODO: make this faster
|
||||
return dispatch(startChat(accountId)).then(chat => {
|
||||
if (forceNavigate || isMobile(window.innerWidth)) {
|
||||
|
@ -234,4 +223,43 @@ export function launchChat(accountId, router, forceNavigate = false) {
|
|||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
CHATS_FETCH_REQUEST,
|
||||
CHATS_FETCH_SUCCESS,
|
||||
CHATS_FETCH_FAIL,
|
||||
CHATS_EXPAND_REQUEST,
|
||||
CHATS_EXPAND_SUCCESS,
|
||||
CHATS_EXPAND_FAIL,
|
||||
CHAT_MESSAGES_FETCH_REQUEST,
|
||||
CHAT_MESSAGES_FETCH_SUCCESS,
|
||||
CHAT_MESSAGES_FETCH_FAIL,
|
||||
CHAT_MESSAGE_SEND_REQUEST,
|
||||
CHAT_MESSAGE_SEND_SUCCESS,
|
||||
CHAT_MESSAGE_SEND_FAIL,
|
||||
CHAT_FETCH_REQUEST,
|
||||
CHAT_FETCH_SUCCESS,
|
||||
CHAT_FETCH_FAIL,
|
||||
CHAT_READ_REQUEST,
|
||||
CHAT_READ_SUCCESS,
|
||||
CHAT_READ_FAIL,
|
||||
CHAT_MESSAGE_DELETE_REQUEST,
|
||||
CHAT_MESSAGE_DELETE_SUCCESS,
|
||||
CHAT_MESSAGE_DELETE_FAIL,
|
||||
fetchChatsV1,
|
||||
fetchChatsV2,
|
||||
fetchChats,
|
||||
expandChats,
|
||||
fetchChatMessages,
|
||||
sendChatMessage,
|
||||
openChat,
|
||||
closeChat,
|
||||
toggleChat,
|
||||
toggleMainWindow,
|
||||
fetchChat,
|
||||
startChat,
|
||||
markChatRead,
|
||||
deleteChatMessage,
|
||||
launchChat,
|
||||
};
|
|
@ -1,759 +0,0 @@
|
|||
import { CancelToken, isCancel } from 'axios';
|
||||
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
import { throttle } from 'lodash';
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
import { getFeatures, parseVersion } from 'soapbox/utils/features';
|
||||
import { formatBytes } from 'soapbox/utils/media';
|
||||
|
||||
import api from '../api';
|
||||
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
|
||||
import { tagHistory } from '../settings';
|
||||
import resizeImage from '../utils/resize_image';
|
||||
|
||||
import { showAlert, showAlertForError } from './alerts';
|
||||
import { useEmoji } from './emojis';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
import { uploadMedia, fetchMedia, updateMedia } from './media';
|
||||
import { openModal, closeModal } from './modals';
|
||||
import { getSettings } from './settings';
|
||||
import { createStatus } from './statuses';
|
||||
|
||||
let cancelFetchComposeSuggestionsAccounts;
|
||||
|
||||
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
|
||||
export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
|
||||
export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
|
||||
export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL';
|
||||
export const COMPOSE_REPLY = 'COMPOSE_REPLY';
|
||||
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_MENTION = 'COMPOSE_MENTION';
|
||||
export const COMPOSE_RESET = 'COMPOSE_RESET';
|
||||
export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST';
|
||||
export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS';
|
||||
export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL';
|
||||
export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
|
||||
export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
|
||||
|
||||
export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
|
||||
export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
|
||||
export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT';
|
||||
export const COMPOSE_SUGGESTION_TAGS_UPDATE = 'COMPOSE_SUGGESTION_TAGS_UPDATE';
|
||||
|
||||
export const COMPOSE_TAG_HISTORY_UPDATE = 'COMPOSE_TAG_HISTORY_UPDATE';
|
||||
|
||||
export const COMPOSE_MOUNT = 'COMPOSE_MOUNT';
|
||||
export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
|
||||
|
||||
export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE';
|
||||
export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
|
||||
export const COMPOSE_TYPE_CHANGE = 'COMPOSE_TYPE_CHANGE';
|
||||
export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
|
||||
export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
|
||||
export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE';
|
||||
export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE';
|
||||
|
||||
export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT';
|
||||
|
||||
export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST';
|
||||
export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS';
|
||||
export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL';
|
||||
|
||||
export const COMPOSE_POLL_ADD = 'COMPOSE_POLL_ADD';
|
||||
export const COMPOSE_POLL_REMOVE = 'COMPOSE_POLL_REMOVE';
|
||||
export const COMPOSE_POLL_OPTION_ADD = 'COMPOSE_POLL_OPTION_ADD';
|
||||
export const COMPOSE_POLL_OPTION_CHANGE = 'COMPOSE_POLL_OPTION_CHANGE';
|
||||
export const COMPOSE_POLL_OPTION_REMOVE = 'COMPOSE_POLL_OPTION_REMOVE';
|
||||
export const COMPOSE_POLL_SETTINGS_CHANGE = 'COMPOSE_POLL_SETTINGS_CHANGE';
|
||||
|
||||
export const COMPOSE_SCHEDULE_ADD = 'COMPOSE_SCHEDULE_ADD';
|
||||
export const COMPOSE_SCHEDULE_SET = 'COMPOSE_SCHEDULE_SET';
|
||||
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';
|
||||
|
||||
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
|
||||
|
||||
const messages = defineMessages({
|
||||
exceededImageSizeLimit: { id: 'upload_error.image_size_limit', defaultMessage: 'Image exceeds the current file size limit ({limit})' },
|
||||
exceededVideoSizeLimit: { id: 'upload_error.video_size_limit', defaultMessage: 'Video exceeds the current file size limit ({limit})' },
|
||||
scheduleError: { id: 'compose.invalid_schedule', defaultMessage: 'You must schedule a post at least 5 minutes out.' },
|
||||
success: { id: 'compose.submit_success', defaultMessage: 'Your post was sent' },
|
||||
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
||||
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
|
||||
view: { id: 'snackbar.view', defaultMessage: 'View' },
|
||||
});
|
||||
|
||||
const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1);
|
||||
|
||||
export const ensureComposeIsVisible = (getState, routerHistory) => {
|
||||
if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) {
|
||||
routerHistory.push('/posts/new');
|
||||
}
|
||||
};
|
||||
|
||||
export function setComposeToStatus(status, rawText, spoilerText, contentType) {
|
||||
return (dispatch, getState) => {
|
||||
const { instance } = getState();
|
||||
const { explicitAddressing } = getFeatures(instance);
|
||||
|
||||
dispatch({
|
||||
type: COMPOSE_SET_STATUS,
|
||||
status,
|
||||
rawText,
|
||||
explicitAddressing,
|
||||
spoilerText,
|
||||
contentType,
|
||||
v: parseVersion(instance.version),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function changeCompose(text) {
|
||||
return {
|
||||
type: COMPOSE_CHANGE,
|
||||
text: text,
|
||||
};
|
||||
}
|
||||
|
||||
export function replyCompose(status, routerHistory) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const instance = state.get('instance');
|
||||
const { explicitAddressing } = getFeatures(instance);
|
||||
|
||||
dispatch({
|
||||
type: COMPOSE_REPLY,
|
||||
status: status,
|
||||
account: state.getIn(['accounts', state.get('me')]),
|
||||
explicitAddressing,
|
||||
});
|
||||
|
||||
dispatch(openModal('COMPOSE'));
|
||||
};
|
||||
}
|
||||
|
||||
export function cancelReplyCompose() {
|
||||
return {
|
||||
type: COMPOSE_REPLY_CANCEL,
|
||||
};
|
||||
}
|
||||
|
||||
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() {
|
||||
return {
|
||||
type: COMPOSE_RESET,
|
||||
};
|
||||
}
|
||||
|
||||
export function mentionCompose(account, routerHistory) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: COMPOSE_MENTION,
|
||||
account: account,
|
||||
});
|
||||
|
||||
dispatch(openModal('COMPOSE'));
|
||||
};
|
||||
}
|
||||
|
||||
export function directCompose(account, routerHistory) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: COMPOSE_DIRECT,
|
||||
account: account,
|
||||
});
|
||||
|
||||
dispatch(openModal('COMPOSE'));
|
||||
};
|
||||
}
|
||||
|
||||
export function directComposeById(accountId) {
|
||||
return (dispatch, getState) => {
|
||||
const account = getState().getIn(['accounts', accountId]);
|
||||
|
||||
dispatch({
|
||||
type: COMPOSE_DIRECT,
|
||||
account: account,
|
||||
});
|
||||
|
||||
dispatch(openModal('COMPOSE'));
|
||||
};
|
||||
}
|
||||
|
||||
export function handleComposeSubmit(dispatch, getState, data, status) {
|
||||
if (!dispatch || !getState) return;
|
||||
|
||||
dispatch(insertIntoTagHistory(data.tags || [], status));
|
||||
dispatch(submitComposeSuccess({ ...data }));
|
||||
dispatch(snackbar.success(messages.success, messages.view, `/@${data.account.acct}/posts/${data.id}`));
|
||||
}
|
||||
|
||||
const needsDescriptions = state => {
|
||||
const media = state.getIn(['compose', 'media_attachments']);
|
||||
const missingDescriptionModal = getSettings(state).get('missingDescriptionModal');
|
||||
|
||||
const hasMissing = media.filter(item => !item.get('description')).size > 0;
|
||||
|
||||
return missingDescriptionModal && hasMissing;
|
||||
};
|
||||
|
||||
const validateSchedule = state => {
|
||||
const schedule = state.getIn(['compose', 'schedule']);
|
||||
if (!schedule) return true;
|
||||
|
||||
const fiveMinutesFromNow = new Date(new Date().getTime() + 300000);
|
||||
|
||||
return schedule.getTime() > fiveMinutesFromNow.getTime();
|
||||
};
|
||||
|
||||
export function submitCompose(routerHistory, force = false) {
|
||||
return function(dispatch, getState) {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
const state = getState();
|
||||
|
||||
const status = state.getIn(['compose', 'text'], '');
|
||||
const media = state.getIn(['compose', 'media_attachments']);
|
||||
const statusId = state.getIn(['compose', 'id'], null);
|
||||
let to = state.getIn(['compose', 'to'], ImmutableOrderedSet());
|
||||
|
||||
if (!validateSchedule(state)) {
|
||||
dispatch(snackbar.error(messages.scheduleError));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!status || !status.length) && media.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force && needsDescriptions(state)) {
|
||||
dispatch(openModal('MISSING_DESCRIPTION', {
|
||||
onContinue: () => {
|
||||
dispatch(closeModal('MISSING_DESCRIPTION'));
|
||||
dispatch(submitCompose(routerHistory, true));
|
||||
},
|
||||
}));
|
||||
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(closeModal());
|
||||
|
||||
const idempotencyKey = state.getIn(['compose', 'idempotencyKey']);
|
||||
|
||||
const params = {
|
||||
status,
|
||||
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')),
|
||||
sensitive: state.getIn(['compose', 'sensitive']),
|
||||
spoiler_text: state.getIn(['compose', 'spoiler_text'], ''),
|
||||
visibility: state.getIn(['compose', 'privacy']),
|
||||
content_type: state.getIn(['compose', 'content_type']),
|
||||
poll: state.getIn(['compose', 'poll'], null),
|
||||
scheduled_at: state.getIn(['compose', 'schedule'], null),
|
||||
to,
|
||||
};
|
||||
|
||||
dispatch(createStatus(params, idempotencyKey, statusId)).then(function(data) {
|
||||
if (!statusId && data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
|
||||
routerHistory.push('/messages');
|
||||
}
|
||||
handleComposeSubmit(dispatch, getState, data, status);
|
||||
}).catch(function(error) {
|
||||
dispatch(submitComposeFail(error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function submitComposeRequest() {
|
||||
return {
|
||||
type: COMPOSE_SUBMIT_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
export function submitComposeSuccess(status) {
|
||||
return {
|
||||
type: COMPOSE_SUBMIT_SUCCESS,
|
||||
status: status,
|
||||
};
|
||||
}
|
||||
|
||||
export function submitComposeFail(error) {
|
||||
return {
|
||||
type: COMPOSE_SUBMIT_FAIL,
|
||||
error: error,
|
||||
};
|
||||
}
|
||||
|
||||
export function uploadCompose(files, intl) {
|
||||
return function(dispatch, getState) {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
const attachmentLimit = getState().getIn(['instance', 'configuration', 'statuses', 'max_media_attachments']);
|
||||
const maxImageSize = getState().getIn(['instance', 'configuration', 'media_attachments', 'image_size_limit']);
|
||||
const maxVideoSize = getState().getIn(['instance', 'configuration', 'media_attachments', 'video_size_limit']);
|
||||
|
||||
const media = getState().getIn(['compose', 'media_attachments']);
|
||||
const progress = new Array(files.length).fill(0);
|
||||
let total = Array.from(files).reduce((a, v) => a + v.size, 0);
|
||||
|
||||
if (files.length + media.size > attachmentLimit) {
|
||||
dispatch(showAlert(undefined, messages.uploadErrorLimit, 'error'));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(uploadComposeRequest());
|
||||
|
||||
Array.from(files).forEach((f, i) => {
|
||||
if (media.size + i > attachmentLimit - 1) return;
|
||||
|
||||
const isImage = f.type.match(/image.*/);
|
||||
const isVideo = f.type.match(/video.*/);
|
||||
if (isImage && maxImageSize && (f.size > maxImageSize)) {
|
||||
const limit = formatBytes(maxImageSize);
|
||||
const message = intl.formatMessage(messages.exceededImageSizeLimit, { limit });
|
||||
dispatch(snackbar.error(message));
|
||||
dispatch(uploadComposeFail(true));
|
||||
return;
|
||||
} else if (isVideo && maxVideoSize && (f.size > maxVideoSize)) {
|
||||
const limit = formatBytes(maxVideoSize);
|
||||
const message = intl.formatMessage(messages.exceededVideoSizeLimit, { limit });
|
||||
dispatch(snackbar.error(message));
|
||||
dispatch(uploadComposeFail(true));
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Don't define function in loop
|
||||
/* eslint-disable no-loop-func */
|
||||
resizeImage(f).then(file => {
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
// Account for disparity in size of original image and resized data
|
||||
total += file.size - f.size;
|
||||
|
||||
const onUploadProgress = function({ loaded }) {
|
||||
progress[i] = loaded;
|
||||
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
|
||||
};
|
||||
|
||||
return dispatch(uploadMedia(data, onUploadProgress))
|
||||
.then(({ status, data }) => {
|
||||
// If server-side processing of the media attachment has not completed yet,
|
||||
// poll the server until it is, before showing the media attachment as uploaded
|
||||
if (status === 200) {
|
||||
dispatch(uploadComposeSuccess(data, f));
|
||||
} else if (status === 202) {
|
||||
const poll = () => {
|
||||
dispatch(fetchMedia(data.id)).then(({ status, data }) => {
|
||||
if (status === 200) {
|
||||
dispatch(uploadComposeSuccess(data, f));
|
||||
} else if (status === 206) {
|
||||
setTimeout(() => poll(), 1000);
|
||||
}
|
||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||
};
|
||||
|
||||
poll();
|
||||
}
|
||||
});
|
||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||
/* eslint-enable no-loop-func */
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function changeUploadCompose(id, params) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(changeUploadComposeRequest());
|
||||
|
||||
dispatch(updateMedia(id, params)).then(response => {
|
||||
dispatch(changeUploadComposeSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(changeUploadComposeFail(id, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function changeUploadComposeRequest() {
|
||||
return {
|
||||
type: COMPOSE_UPLOAD_CHANGE_REQUEST,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
export function changeUploadComposeSuccess(media) {
|
||||
return {
|
||||
type: COMPOSE_UPLOAD_CHANGE_SUCCESS,
|
||||
media: media,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function changeUploadComposeFail(error) {
|
||||
return {
|
||||
type: COMPOSE_UPLOAD_CHANGE_FAIL,
|
||||
error: error,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function uploadComposeRequest() {
|
||||
return {
|
||||
type: COMPOSE_UPLOAD_REQUEST,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function uploadComposeProgress(loaded, total) {
|
||||
return {
|
||||
type: COMPOSE_UPLOAD_PROGRESS,
|
||||
loaded: loaded,
|
||||
total: total,
|
||||
};
|
||||
}
|
||||
|
||||
export function uploadComposeSuccess(media) {
|
||||
return {
|
||||
type: COMPOSE_UPLOAD_SUCCESS,
|
||||
media: media,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function uploadComposeFail(error) {
|
||||
return {
|
||||
type: COMPOSE_UPLOAD_FAIL,
|
||||
error: error,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function undoUploadCompose(media_id) {
|
||||
return {
|
||||
type: COMPOSE_UPLOAD_UNDO,
|
||||
media_id: media_id,
|
||||
};
|
||||
}
|
||||
|
||||
export function clearComposeSuggestions() {
|
||||
if (cancelFetchComposeSuggestionsAccounts) {
|
||||
cancelFetchComposeSuggestionsAccounts();
|
||||
}
|
||||
return {
|
||||
type: COMPOSE_SUGGESTIONS_CLEAR,
|
||||
};
|
||||
}
|
||||
|
||||
const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
|
||||
if (cancelFetchComposeSuggestionsAccounts) {
|
||||
cancelFetchComposeSuggestionsAccounts();
|
||||
}
|
||||
api(getState).get('/api/v1/accounts/search', {
|
||||
cancelToken: new CancelToken(cancel => {
|
||||
cancelFetchComposeSuggestionsAccounts = cancel;
|
||||
}),
|
||||
params: {
|
||||
q: token.slice(1),
|
||||
resolve: false,
|
||||
limit: 4,
|
||||
},
|
||||
}).then(response => {
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(readyComposeSuggestionsAccounts(token, response.data));
|
||||
}).catch(error => {
|
||||
if (!isCancel(error)) {
|
||||
dispatch(showAlertForError(error));
|
||||
}
|
||||
});
|
||||
}, 200, { leading: true, trailing: true });
|
||||
|
||||
const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => {
|
||||
const results = emojiSearch(token.replace(':', ''), { maxResults: 5 });
|
||||
dispatch(readyComposeSuggestionsEmojis(token, results));
|
||||
};
|
||||
|
||||
const fetchComposeSuggestionsTags = (dispatch, getState, token) => {
|
||||
dispatch(updateSuggestionTags(token));
|
||||
};
|
||||
|
||||
export function fetchComposeSuggestions(token) {
|
||||
return (dispatch, getState) => {
|
||||
switch (token[0]) {
|
||||
case ':':
|
||||
fetchComposeSuggestionsEmojis(dispatch, getState, token);
|
||||
break;
|
||||
case '#':
|
||||
fetchComposeSuggestionsTags(dispatch, getState, token);
|
||||
break;
|
||||
default:
|
||||
fetchComposeSuggestionsAccounts(dispatch, getState, token);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function readyComposeSuggestionsEmojis(token, emojis) {
|
||||
return {
|
||||
type: COMPOSE_SUGGESTIONS_READY,
|
||||
token,
|
||||
emojis,
|
||||
};
|
||||
}
|
||||
|
||||
export function readyComposeSuggestionsAccounts(token, accounts) {
|
||||
return {
|
||||
type: COMPOSE_SUGGESTIONS_READY,
|
||||
token,
|
||||
accounts,
|
||||
};
|
||||
}
|
||||
|
||||
export function selectComposeSuggestion(position, token, suggestion, path) {
|
||||
return (dispatch, getState) => {
|
||||
let completion, startPosition;
|
||||
|
||||
if (typeof suggestion === 'object' && suggestion.id) {
|
||||
completion = suggestion.native || suggestion.colons;
|
||||
startPosition = position - 1;
|
||||
|
||||
dispatch(useEmoji(suggestion));
|
||||
} else if (suggestion[0] === '#') {
|
||||
completion = suggestion;
|
||||
startPosition = position - 1;
|
||||
} else {
|
||||
completion = getState().getIn(['accounts', suggestion, 'acct']);
|
||||
startPosition = position;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: COMPOSE_SUGGESTION_SELECT,
|
||||
position: startPosition,
|
||||
token,
|
||||
completion,
|
||||
path,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function updateSuggestionTags(token) {
|
||||
return {
|
||||
type: COMPOSE_SUGGESTION_TAGS_UPDATE,
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateTagHistory(tags) {
|
||||
return {
|
||||
type: COMPOSE_TAG_HISTORY_UPDATE,
|
||||
tags,
|
||||
};
|
||||
}
|
||||
|
||||
function insertIntoTagHistory(recognizedTags, text) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const oldHistory = state.getIn(['compose', 'tagHistory']);
|
||||
const me = state.get('me');
|
||||
const names = recognizedTags
|
||||
.filter(tag => text.match(new RegExp(`#${tag.name}`, 'i')))
|
||||
.map(tag => tag.name);
|
||||
const intersectedOldHistory = oldHistory.filter(name => names.findIndex(newName => newName.toLowerCase() === name.toLowerCase()) === -1);
|
||||
|
||||
names.push(...intersectedOldHistory.toJS());
|
||||
|
||||
const newHistory = names.slice(0, 1000);
|
||||
|
||||
tagHistory.set(me, newHistory);
|
||||
dispatch(updateTagHistory(newHistory));
|
||||
};
|
||||
}
|
||||
|
||||
export function mountCompose() {
|
||||
return {
|
||||
type: COMPOSE_MOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
export function unmountCompose() {
|
||||
return {
|
||||
type: COMPOSE_UNMOUNT,
|
||||
};
|
||||
}
|
||||
|
||||
export function changeComposeSensitivity() {
|
||||
return {
|
||||
type: COMPOSE_SENSITIVITY_CHANGE,
|
||||
};
|
||||
}
|
||||
|
||||
export function changeComposeSpoilerness() {
|
||||
return {
|
||||
type: COMPOSE_SPOILERNESS_CHANGE,
|
||||
};
|
||||
}
|
||||
|
||||
export function changeComposeContentType(value) {
|
||||
return {
|
||||
type: COMPOSE_TYPE_CHANGE,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function changeComposeSpoilerText(text) {
|
||||
return {
|
||||
type: COMPOSE_SPOILER_TEXT_CHANGE,
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
export function changeComposeVisibility(value) {
|
||||
return {
|
||||
type: COMPOSE_VISIBILITY_CHANGE,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function insertEmojiCompose(position, emoji, needsSpace) {
|
||||
return {
|
||||
type: COMPOSE_EMOJI_INSERT,
|
||||
position,
|
||||
emoji,
|
||||
needsSpace,
|
||||
};
|
||||
}
|
||||
|
||||
export function changeComposing(value) {
|
||||
return {
|
||||
type: COMPOSE_COMPOSING_CHANGE,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function addPoll() {
|
||||
return {
|
||||
type: COMPOSE_POLL_ADD,
|
||||
};
|
||||
}
|
||||
|
||||
export function removePoll() {
|
||||
return {
|
||||
type: COMPOSE_POLL_REMOVE,
|
||||
};
|
||||
}
|
||||
|
||||
export function addSchedule() {
|
||||
return {
|
||||
type: COMPOSE_SCHEDULE_ADD,
|
||||
};
|
||||
}
|
||||
|
||||
export function setSchedule(date) {
|
||||
return {
|
||||
type: COMPOSE_SCHEDULE_SET,
|
||||
date: date,
|
||||
};
|
||||
}
|
||||
|
||||
export function removeSchedule() {
|
||||
return {
|
||||
type: COMPOSE_SCHEDULE_REMOVE,
|
||||
};
|
||||
}
|
||||
|
||||
export function addPollOption(title) {
|
||||
return {
|
||||
type: COMPOSE_POLL_OPTION_ADD,
|
||||
title,
|
||||
};
|
||||
}
|
||||
|
||||
export function changePollOption(index, title) {
|
||||
return {
|
||||
type: COMPOSE_POLL_OPTION_CHANGE,
|
||||
index,
|
||||
title,
|
||||
};
|
||||
}
|
||||
|
||||
export function removePollOption(index) {
|
||||
return {
|
||||
type: COMPOSE_POLL_OPTION_REMOVE,
|
||||
index,
|
||||
};
|
||||
}
|
||||
|
||||
export function changePollSettings(expiresIn, isMultiple) {
|
||||
return {
|
||||
type: COMPOSE_POLL_SETTINGS_CHANGE,
|
||||
expiresIn,
|
||||
isMultiple,
|
||||
};
|
||||
}
|
||||
|
||||
export function openComposeWithText(text = '') {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(resetCompose());
|
||||
dispatch(openModal('COMPOSE'));
|
||||
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,
|
||||
});
|
||||
};
|
||||
}
|
809
app/soapbox/actions/compose.ts
Normal file
|
@ -0,0 +1,809 @@
|
|||
import axios, { AxiosError, Canceler } from 'axios';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { defineMessages, IntlShape } from 'react-intl';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import api from 'soapbox/api';
|
||||
import { search as emojiSearch } from 'soapbox/features/emoji/emoji_mart_search_light';
|
||||
import { tagHistory } from 'soapbox/settings';
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
import { getFeatures, parseVersion } from 'soapbox/utils/features';
|
||||
import { formatBytes, getVideoDuration } from 'soapbox/utils/media';
|
||||
import resizeImage from 'soapbox/utils/resize_image';
|
||||
|
||||
import { showAlert, showAlertForError } from './alerts';
|
||||
import { useEmoji } from './emojis';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
import { uploadMedia, fetchMedia, updateMedia } from './media';
|
||||
import { openModal, closeModal } from './modals';
|
||||
import { getSettings } from './settings';
|
||||
import { createStatus } from './statuses';
|
||||
|
||||
import type { History } from 'history';
|
||||
import type { Emoji } from 'soapbox/components/autosuggest_emoji';
|
||||
import type { AutoSuggestion } from 'soapbox/components/autosuggest_input';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { Account, APIEntity, Status } from 'soapbox/types/entities';
|
||||
|
||||
const { CancelToken, isCancel } = axios;
|
||||
|
||||
let cancelFetchComposeSuggestionsAccounts: Canceler;
|
||||
|
||||
const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
|
||||
const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
|
||||
const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
|
||||
const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL';
|
||||
const COMPOSE_REPLY = 'COMPOSE_REPLY';
|
||||
const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL';
|
||||
const COMPOSE_QUOTE = 'COMPOSE_QUOTE';
|
||||
const COMPOSE_QUOTE_CANCEL = 'COMPOSE_QUOTE_CANCEL';
|
||||
const COMPOSE_DIRECT = 'COMPOSE_DIRECT';
|
||||
const COMPOSE_MENTION = 'COMPOSE_MENTION';
|
||||
const COMPOSE_RESET = 'COMPOSE_RESET';
|
||||
const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST';
|
||||
const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS';
|
||||
const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL';
|
||||
const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
|
||||
const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
|
||||
|
||||
const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
|
||||
const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
|
||||
const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT';
|
||||
const COMPOSE_SUGGESTION_TAGS_UPDATE = 'COMPOSE_SUGGESTION_TAGS_UPDATE';
|
||||
|
||||
const COMPOSE_TAG_HISTORY_UPDATE = 'COMPOSE_TAG_HISTORY_UPDATE';
|
||||
|
||||
const COMPOSE_MOUNT = 'COMPOSE_MOUNT';
|
||||
const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
|
||||
|
||||
const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE';
|
||||
const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
|
||||
const COMPOSE_TYPE_CHANGE = 'COMPOSE_TYPE_CHANGE';
|
||||
const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
|
||||
const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
|
||||
const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE';
|
||||
const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE';
|
||||
|
||||
const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT';
|
||||
|
||||
const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST';
|
||||
const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS';
|
||||
const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL';
|
||||
|
||||
const COMPOSE_POLL_ADD = 'COMPOSE_POLL_ADD';
|
||||
const COMPOSE_POLL_REMOVE = 'COMPOSE_POLL_REMOVE';
|
||||
const COMPOSE_POLL_OPTION_ADD = 'COMPOSE_POLL_OPTION_ADD';
|
||||
const COMPOSE_POLL_OPTION_CHANGE = 'COMPOSE_POLL_OPTION_CHANGE';
|
||||
const COMPOSE_POLL_OPTION_REMOVE = 'COMPOSE_POLL_OPTION_REMOVE';
|
||||
const COMPOSE_POLL_SETTINGS_CHANGE = 'COMPOSE_POLL_SETTINGS_CHANGE';
|
||||
|
||||
const COMPOSE_SCHEDULE_ADD = 'COMPOSE_SCHEDULE_ADD';
|
||||
const COMPOSE_SCHEDULE_SET = 'COMPOSE_SCHEDULE_SET';
|
||||
const COMPOSE_SCHEDULE_REMOVE = 'COMPOSE_SCHEDULE_REMOVE';
|
||||
|
||||
const COMPOSE_ADD_TO_MENTIONS = 'COMPOSE_ADD_TO_MENTIONS';
|
||||
const COMPOSE_REMOVE_FROM_MENTIONS = 'COMPOSE_REMOVE_FROM_MENTIONS';
|
||||
|
||||
const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
|
||||
|
||||
const messages = defineMessages({
|
||||
exceededImageSizeLimit: { id: 'upload_error.image_size_limit', defaultMessage: 'Image exceeds the current file size limit ({limit})' },
|
||||
exceededVideoSizeLimit: { id: 'upload_error.video_size_limit', defaultMessage: 'Video exceeds the current file size limit ({limit})' },
|
||||
exceededVideoDurationLimit: { id: 'upload_error.video_duration_limit', defaultMessage: 'Video exceeds the current duration limit ({limit} seconds)' },
|
||||
scheduleError: { id: 'compose.invalid_schedule', defaultMessage: 'You must schedule a post at least 5 minutes out.' },
|
||||
success: { id: 'compose.submit_success', defaultMessage: 'Your post was sent' },
|
||||
editSuccess: { id: 'compose.edit_success', defaultMessage: 'Your post was edited' },
|
||||
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
||||
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
|
||||
view: { id: 'snackbar.view', defaultMessage: 'View' },
|
||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||
});
|
||||
|
||||
const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1);
|
||||
|
||||
const ensureComposeIsVisible = (getState: () => RootState, routerHistory: History) => {
|
||||
if (!getState().compose.mounted && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) {
|
||||
routerHistory.push('/posts/new');
|
||||
}
|
||||
};
|
||||
|
||||
const setComposeToStatus = (status: Status, rawText: string, spoilerText?: string, contentType?: string | false, withRedraft?: boolean) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const { instance } = getState();
|
||||
const { explicitAddressing } = getFeatures(instance);
|
||||
|
||||
dispatch({
|
||||
type: COMPOSE_SET_STATUS,
|
||||
status,
|
||||
rawText,
|
||||
explicitAddressing,
|
||||
spoilerText,
|
||||
contentType,
|
||||
v: parseVersion(instance.version),
|
||||
withRedraft,
|
||||
});
|
||||
};
|
||||
|
||||
const changeCompose = (text: string) => ({
|
||||
type: COMPOSE_CHANGE,
|
||||
text: text,
|
||||
});
|
||||
|
||||
const replyCompose = (status: Status) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const instance = state.instance;
|
||||
const { explicitAddressing } = getFeatures(instance);
|
||||
|
||||
dispatch({
|
||||
type: COMPOSE_REPLY,
|
||||
status: status,
|
||||
account: state.accounts.get(state.me),
|
||||
explicitAddressing,
|
||||
});
|
||||
|
||||
dispatch(openModal('COMPOSE'));
|
||||
};
|
||||
|
||||
const replyComposeWithConfirmation = (status: Status, intl: IntlShape) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
if (state.compose.text.trim().length !== 0) {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: intl.formatMessage(messages.replyMessage),
|
||||
confirm: intl.formatMessage(messages.replyConfirm),
|
||||
onConfirm: () => dispatch(replyCompose(status)),
|
||||
}));
|
||||
} else {
|
||||
dispatch(replyCompose(status));
|
||||
}
|
||||
};
|
||||
|
||||
const cancelReplyCompose = () => ({
|
||||
type: COMPOSE_REPLY_CANCEL,
|
||||
});
|
||||
|
||||
const quoteCompose = (status: Status) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const instance = state.instance;
|
||||
const { explicitAddressing } = getFeatures(instance);
|
||||
|
||||
dispatch({
|
||||
type: COMPOSE_QUOTE,
|
||||
status: status,
|
||||
account: state.accounts.get(state.me),
|
||||
explicitAddressing,
|
||||
});
|
||||
|
||||
dispatch(openModal('COMPOSE'));
|
||||
};
|
||||
|
||||
const cancelQuoteCompose = () => ({
|
||||
type: COMPOSE_QUOTE_CANCEL,
|
||||
});
|
||||
|
||||
const resetCompose = () => ({
|
||||
type: COMPOSE_RESET,
|
||||
});
|
||||
|
||||
const mentionCompose = (account: Account) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
dispatch({
|
||||
type: COMPOSE_MENTION,
|
||||
account: account,
|
||||
});
|
||||
|
||||
dispatch(openModal('COMPOSE'));
|
||||
};
|
||||
|
||||
const directCompose = (account: Account) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
dispatch({
|
||||
type: COMPOSE_DIRECT,
|
||||
account: account,
|
||||
});
|
||||
|
||||
dispatch(openModal('COMPOSE'));
|
||||
};
|
||||
|
||||
const directComposeById = (accountId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const account = getState().accounts.get(accountId);
|
||||
|
||||
dispatch({
|
||||
type: COMPOSE_DIRECT,
|
||||
account: account,
|
||||
});
|
||||
|
||||
dispatch(openModal('COMPOSE'));
|
||||
};
|
||||
|
||||
const handleComposeSubmit = (dispatch: AppDispatch, getState: () => RootState, data: APIEntity, status: string, edit?: boolean) => {
|
||||
if (!dispatch || !getState) return;
|
||||
|
||||
dispatch(insertIntoTagHistory(data.tags || [], status));
|
||||
dispatch(submitComposeSuccess({ ...data }));
|
||||
dispatch(snackbar.success(edit ? messages.editSuccess : messages.success, messages.view, `/@${data.account.acct}/posts/${data.id}`));
|
||||
};
|
||||
|
||||
const needsDescriptions = (state: RootState) => {
|
||||
const media = state.compose.media_attachments;
|
||||
const missingDescriptionModal = getSettings(state).get('missingDescriptionModal');
|
||||
|
||||
const hasMissing = media.filter(item => !item.description).size > 0;
|
||||
|
||||
return missingDescriptionModal && hasMissing;
|
||||
};
|
||||
|
||||
const validateSchedule = (state: RootState) => {
|
||||
const schedule = state.compose.schedule;
|
||||
if (!schedule) return true;
|
||||
|
||||
const fiveMinutesFromNow = new Date(new Date().getTime() + 300000);
|
||||
|
||||
return schedule.getTime() > fiveMinutesFromNow.getTime();
|
||||
};
|
||||
|
||||
const submitCompose = (routerHistory?: History, force = false) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
const state = getState();
|
||||
|
||||
const status = state.compose.text;
|
||||
const media = state.compose.media_attachments;
|
||||
const statusId = state.compose.id;
|
||||
let to = state.compose.to;
|
||||
|
||||
if (!validateSchedule(state)) {
|
||||
dispatch(snackbar.error(messages.scheduleError));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!status || !status.length) && media.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force && needsDescriptions(state)) {
|
||||
dispatch(openModal('MISSING_DESCRIPTION', {
|
||||
onContinue: () => {
|
||||
dispatch(closeModal('MISSING_DESCRIPTION'));
|
||||
dispatch(submitCompose(routerHistory, true));
|
||||
},
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const mentions: string[] | null = status.match(/(?:^|\s)@([a-z\d_-]+(?:@[^@\s]+)?)/gi);
|
||||
|
||||
if (mentions) {
|
||||
to = to.union(mentions.map(mention => mention.trim().slice(1)));
|
||||
}
|
||||
|
||||
dispatch(submitComposeRequest());
|
||||
dispatch(closeModal());
|
||||
|
||||
const idempotencyKey = state.compose.idempotencyKey;
|
||||
|
||||
const params = {
|
||||
status,
|
||||
in_reply_to_id: state.compose.in_reply_to,
|
||||
quote_id: state.compose.quote,
|
||||
media_ids: media.map(item => item.id),
|
||||
sensitive: state.compose.sensitive,
|
||||
spoiler_text: state.compose.spoiler_text,
|
||||
visibility: state.compose.privacy,
|
||||
content_type: state.compose.content_type,
|
||||
poll: state.compose.poll,
|
||||
scheduled_at: state.compose.schedule,
|
||||
to,
|
||||
};
|
||||
|
||||
dispatch(createStatus(params, idempotencyKey, statusId)).then(function(data) {
|
||||
if (!statusId && data.visibility === 'direct' && getState().conversations.mounted <= 0 && routerHistory) {
|
||||
routerHistory.push('/messages');
|
||||
}
|
||||
handleComposeSubmit(dispatch, getState, data, status, !!statusId);
|
||||
}).catch(function(error) {
|
||||
dispatch(submitComposeFail(error));
|
||||
});
|
||||
};
|
||||
|
||||
const submitComposeRequest = () => ({
|
||||
type: COMPOSE_SUBMIT_REQUEST,
|
||||
});
|
||||
|
||||
const submitComposeSuccess = (status: APIEntity) => ({
|
||||
type: COMPOSE_SUBMIT_SUCCESS,
|
||||
status: status,
|
||||
});
|
||||
|
||||
const submitComposeFail = (error: AxiosError) => ({
|
||||
type: COMPOSE_SUBMIT_FAIL,
|
||||
error: error,
|
||||
});
|
||||
|
||||
const uploadCompose = (files: FileList, intl: IntlShape) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
const attachmentLimit = getState().instance.configuration.getIn(['statuses', 'max_media_attachments']) as number;
|
||||
const maxImageSize = getState().instance.configuration.getIn(['media_attachments', 'image_size_limit']) as number | undefined;
|
||||
const maxVideoSize = getState().instance.configuration.getIn(['media_attachments', 'video_size_limit']) as number | undefined;
|
||||
const maxVideoDuration = getState().instance.configuration.getIn(['media_attachments', 'video_duration_limit']) as number | undefined;
|
||||
|
||||
const media = getState().compose.media_attachments;
|
||||
const progress = new Array(files.length).fill(0);
|
||||
let total = Array.from(files).reduce((a, v) => a + v.size, 0);
|
||||
|
||||
if (files.length + media.size > attachmentLimit) {
|
||||
dispatch(showAlert(undefined, messages.uploadErrorLimit, 'error'));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(uploadComposeRequest());
|
||||
|
||||
Array.from(files).forEach(async(f, i) => {
|
||||
if (media.size + i > attachmentLimit - 1) return;
|
||||
|
||||
const isImage = f.type.match(/image.*/);
|
||||
const isVideo = f.type.match(/video.*/);
|
||||
const videoDurationInSeconds = (isVideo && maxVideoDuration) ? await getVideoDuration(f) : 0;
|
||||
|
||||
if (isImage && maxImageSize && (f.size > maxImageSize)) {
|
||||
const limit = formatBytes(maxImageSize);
|
||||
const message = intl.formatMessage(messages.exceededImageSizeLimit, { limit });
|
||||
dispatch(snackbar.error(message));
|
||||
dispatch(uploadComposeFail(true));
|
||||
return;
|
||||
} else if (isVideo && maxVideoSize && (f.size > maxVideoSize)) {
|
||||
const limit = formatBytes(maxVideoSize);
|
||||
const message = intl.formatMessage(messages.exceededVideoSizeLimit, { limit });
|
||||
dispatch(snackbar.error(message));
|
||||
dispatch(uploadComposeFail(true));
|
||||
return;
|
||||
} else if (isVideo && maxVideoDuration && (videoDurationInSeconds > maxVideoDuration)) {
|
||||
const message = intl.formatMessage(messages.exceededVideoDurationLimit, { limit: maxVideoDuration });
|
||||
dispatch(snackbar.error(message));
|
||||
dispatch(uploadComposeFail(true));
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Don't define const in loop
|
||||
/* eslint-disable no-loop-func */
|
||||
resizeImage(f).then(file => {
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
// Account for disparity in size of original image and resized data
|
||||
total += file.size - f.size;
|
||||
|
||||
const onUploadProgress = ({ loaded }: any) => {
|
||||
progress[i] = loaded;
|
||||
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
|
||||
};
|
||||
|
||||
return dispatch(uploadMedia(data, onUploadProgress))
|
||||
.then(({ status, data }) => {
|
||||
// If server-side processing of the media attachment has not completed yet,
|
||||
// poll the server until it is, before showing the media attachment as uploaded
|
||||
if (status === 200) {
|
||||
dispatch(uploadComposeSuccess(data, f));
|
||||
} else if (status === 202) {
|
||||
const poll = () => {
|
||||
dispatch(fetchMedia(data.id)).then(({ status, data }) => {
|
||||
if (status === 200) {
|
||||
dispatch(uploadComposeSuccess(data, f));
|
||||
} else if (status === 206) {
|
||||
setTimeout(() => poll(), 1000);
|
||||
}
|
||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||
};
|
||||
|
||||
poll();
|
||||
}
|
||||
});
|
||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||
/* eslint-enable no-loop-func */
|
||||
});
|
||||
};
|
||||
|
||||
const changeUploadCompose = (id: string, params: Record<string, any>) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(changeUploadComposeRequest());
|
||||
|
||||
dispatch(updateMedia(id, params)).then(response => {
|
||||
dispatch(changeUploadComposeSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(changeUploadComposeFail(id, error));
|
||||
});
|
||||
};
|
||||
|
||||
const changeUploadComposeRequest = () => ({
|
||||
type: COMPOSE_UPLOAD_CHANGE_REQUEST,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const changeUploadComposeSuccess = (media: APIEntity) => ({
|
||||
type: COMPOSE_UPLOAD_CHANGE_SUCCESS,
|
||||
media: media,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const changeUploadComposeFail = (id: string, error: AxiosError) => ({
|
||||
type: COMPOSE_UPLOAD_CHANGE_FAIL,
|
||||
id,
|
||||
error: error,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const uploadComposeRequest = () => ({
|
||||
type: COMPOSE_UPLOAD_REQUEST,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const uploadComposeProgress = (loaded: number, total: number) => ({
|
||||
type: COMPOSE_UPLOAD_PROGRESS,
|
||||
loaded: loaded,
|
||||
total: total,
|
||||
});
|
||||
|
||||
const uploadComposeSuccess = (media: APIEntity, file: File) => ({
|
||||
type: COMPOSE_UPLOAD_SUCCESS,
|
||||
media: media,
|
||||
file,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const uploadComposeFail = (error: AxiosError | true) => ({
|
||||
type: COMPOSE_UPLOAD_FAIL,
|
||||
error: error,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const undoUploadCompose = (media_id: string) => ({
|
||||
type: COMPOSE_UPLOAD_UNDO,
|
||||
media_id: media_id,
|
||||
});
|
||||
|
||||
const clearComposeSuggestions = () => {
|
||||
if (cancelFetchComposeSuggestionsAccounts) {
|
||||
cancelFetchComposeSuggestionsAccounts();
|
||||
}
|
||||
return {
|
||||
type: COMPOSE_SUGGESTIONS_CLEAR,
|
||||
};
|
||||
};
|
||||
|
||||
const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
|
||||
if (cancelFetchComposeSuggestionsAccounts) {
|
||||
cancelFetchComposeSuggestionsAccounts();
|
||||
}
|
||||
api(getState).get('/api/v1/accounts/search', {
|
||||
cancelToken: new CancelToken(cancel => {
|
||||
cancelFetchComposeSuggestionsAccounts = cancel;
|
||||
}),
|
||||
params: {
|
||||
q: token.slice(1),
|
||||
resolve: false,
|
||||
limit: 4,
|
||||
},
|
||||
}).then(response => {
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(readyComposeSuggestionsAccounts(token, response.data));
|
||||
}).catch(error => {
|
||||
if (!isCancel(error)) {
|
||||
dispatch(showAlertForError(error));
|
||||
}
|
||||
});
|
||||
}, 200, { leading: true, trailing: true });
|
||||
|
||||
const fetchComposeSuggestionsEmojis = (dispatch: AppDispatch, getState: () => RootState, token: string) => {
|
||||
const results = emojiSearch(token.replace(':', ''), { maxResults: 5 } as any);
|
||||
dispatch(readyComposeSuggestionsEmojis(token, results));
|
||||
};
|
||||
|
||||
const fetchComposeSuggestionsTags = (dispatch: AppDispatch, getState: () => RootState, token: string) => {
|
||||
dispatch(updateSuggestionTags(token));
|
||||
};
|
||||
|
||||
const fetchComposeSuggestions = (token: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
switch (token[0]) {
|
||||
case ':':
|
||||
fetchComposeSuggestionsEmojis(dispatch, getState, token);
|
||||
break;
|
||||
case '#':
|
||||
fetchComposeSuggestionsTags(dispatch, getState, token);
|
||||
break;
|
||||
default:
|
||||
fetchComposeSuggestionsAccounts(dispatch, getState, token);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const readyComposeSuggestionsEmojis = (token: string, emojis: Emoji[]) => ({
|
||||
type: COMPOSE_SUGGESTIONS_READY,
|
||||
token,
|
||||
emojis,
|
||||
});
|
||||
|
||||
const readyComposeSuggestionsAccounts = (token: string, accounts: APIEntity[]) => ({
|
||||
type: COMPOSE_SUGGESTIONS_READY,
|
||||
token,
|
||||
accounts,
|
||||
});
|
||||
|
||||
const selectComposeSuggestion = (position: number, token: string | null, suggestion: AutoSuggestion, path: Array<string | number>) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
let completion, startPosition;
|
||||
|
||||
if (typeof suggestion === 'object' && suggestion.id) {
|
||||
completion = suggestion.native || suggestion.colons;
|
||||
startPosition = position - 1;
|
||||
|
||||
dispatch(useEmoji(suggestion));
|
||||
} else if (typeof suggestion === 'string' && suggestion[0] === '#') {
|
||||
completion = suggestion;
|
||||
startPosition = position - 1;
|
||||
} else {
|
||||
completion = getState().accounts.get(suggestion)!.acct;
|
||||
startPosition = position;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: COMPOSE_SUGGESTION_SELECT,
|
||||
position: startPosition,
|
||||
token,
|
||||
completion,
|
||||
path,
|
||||
});
|
||||
};
|
||||
|
||||
const updateSuggestionTags = (token: string) => ({
|
||||
type: COMPOSE_SUGGESTION_TAGS_UPDATE,
|
||||
token,
|
||||
});
|
||||
|
||||
const updateTagHistory = (tags: string[]) => ({
|
||||
type: COMPOSE_TAG_HISTORY_UPDATE,
|
||||
tags,
|
||||
});
|
||||
|
||||
const insertIntoTagHistory = (recognizedTags: APIEntity[], text: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const oldHistory = state.compose.tagHistory;
|
||||
const me = state.me;
|
||||
const names = recognizedTags
|
||||
.filter(tag => text.match(new RegExp(`#${tag.name}`, 'i')))
|
||||
.map(tag => tag.name);
|
||||
const intersectedOldHistory = oldHistory.filter(name => names.findIndex(newName => newName.toLowerCase() === name.toLowerCase()) === -1);
|
||||
|
||||
names.push(...intersectedOldHistory.toJS());
|
||||
|
||||
const newHistory = names.slice(0, 1000);
|
||||
|
||||
tagHistory.set(me as string, newHistory);
|
||||
dispatch(updateTagHistory(newHistory));
|
||||
};
|
||||
|
||||
const mountCompose = () => ({
|
||||
type: COMPOSE_MOUNT,
|
||||
});
|
||||
|
||||
const unmountCompose = () => ({
|
||||
type: COMPOSE_UNMOUNT,
|
||||
});
|
||||
|
||||
const changeComposeSensitivity = () => ({
|
||||
type: COMPOSE_SENSITIVITY_CHANGE,
|
||||
});
|
||||
|
||||
const changeComposeSpoilerness = () => ({
|
||||
type: COMPOSE_SPOILERNESS_CHANGE,
|
||||
});
|
||||
|
||||
const changeComposeContentType = (value: string) => ({
|
||||
type: COMPOSE_TYPE_CHANGE,
|
||||
value,
|
||||
});
|
||||
|
||||
const changeComposeSpoilerText = (text: string) => ({
|
||||
type: COMPOSE_SPOILER_TEXT_CHANGE,
|
||||
text,
|
||||
});
|
||||
|
||||
const changeComposeVisibility = (value: string) => ({
|
||||
type: COMPOSE_VISIBILITY_CHANGE,
|
||||
value,
|
||||
});
|
||||
|
||||
const insertEmojiCompose = (position: number, emoji: string, needsSpace: boolean) => ({
|
||||
type: COMPOSE_EMOJI_INSERT,
|
||||
position,
|
||||
emoji,
|
||||
needsSpace,
|
||||
});
|
||||
|
||||
const changeComposing = (value: string) => ({
|
||||
type: COMPOSE_COMPOSING_CHANGE,
|
||||
value,
|
||||
});
|
||||
|
||||
const addPoll = () => ({
|
||||
type: COMPOSE_POLL_ADD,
|
||||
});
|
||||
|
||||
const removePoll = () => ({
|
||||
type: COMPOSE_POLL_REMOVE,
|
||||
});
|
||||
|
||||
const addSchedule = () => ({
|
||||
type: COMPOSE_SCHEDULE_ADD,
|
||||
});
|
||||
|
||||
const setSchedule = (date: Date) => ({
|
||||
type: COMPOSE_SCHEDULE_SET,
|
||||
date: date,
|
||||
});
|
||||
|
||||
const removeSchedule = () => ({
|
||||
type: COMPOSE_SCHEDULE_REMOVE,
|
||||
});
|
||||
|
||||
const addPollOption = (title: string) => ({
|
||||
type: COMPOSE_POLL_OPTION_ADD,
|
||||
title,
|
||||
});
|
||||
|
||||
const changePollOption = (index: number, title: string) => ({
|
||||
type: COMPOSE_POLL_OPTION_CHANGE,
|
||||
index,
|
||||
title,
|
||||
});
|
||||
|
||||
const removePollOption = (index: number) => ({
|
||||
type: COMPOSE_POLL_OPTION_REMOVE,
|
||||
index,
|
||||
});
|
||||
|
||||
const changePollSettings = (expiresIn?: string | number, isMultiple?: boolean) => ({
|
||||
type: COMPOSE_POLL_SETTINGS_CHANGE,
|
||||
expiresIn,
|
||||
isMultiple,
|
||||
});
|
||||
|
||||
const openComposeWithText = (text = '') =>
|
||||
(dispatch: AppDispatch) => {
|
||||
dispatch(resetCompose());
|
||||
dispatch(openModal('COMPOSE'));
|
||||
dispatch(changeCompose(text));
|
||||
};
|
||||
|
||||
const addToMentions = (accountId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const acct = state.accounts.get(accountId)!.acct;
|
||||
|
||||
return dispatch({
|
||||
type: COMPOSE_ADD_TO_MENTIONS,
|
||||
account: acct,
|
||||
});
|
||||
};
|
||||
|
||||
const removeFromMentions = (accountId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const state = getState();
|
||||
const acct = state.accounts.get(accountId)!.acct;
|
||||
|
||||
return dispatch({
|
||||
type: COMPOSE_REMOVE_FROM_MENTIONS,
|
||||
account: acct,
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
COMPOSE_CHANGE,
|
||||
COMPOSE_SUBMIT_REQUEST,
|
||||
COMPOSE_SUBMIT_SUCCESS,
|
||||
COMPOSE_SUBMIT_FAIL,
|
||||
COMPOSE_REPLY,
|
||||
COMPOSE_REPLY_CANCEL,
|
||||
COMPOSE_QUOTE,
|
||||
COMPOSE_QUOTE_CANCEL,
|
||||
COMPOSE_DIRECT,
|
||||
COMPOSE_MENTION,
|
||||
COMPOSE_RESET,
|
||||
COMPOSE_UPLOAD_REQUEST,
|
||||
COMPOSE_UPLOAD_SUCCESS,
|
||||
COMPOSE_UPLOAD_FAIL,
|
||||
COMPOSE_UPLOAD_PROGRESS,
|
||||
COMPOSE_UPLOAD_UNDO,
|
||||
COMPOSE_SUGGESTIONS_CLEAR,
|
||||
COMPOSE_SUGGESTIONS_READY,
|
||||
COMPOSE_SUGGESTION_SELECT,
|
||||
COMPOSE_SUGGESTION_TAGS_UPDATE,
|
||||
COMPOSE_TAG_HISTORY_UPDATE,
|
||||
COMPOSE_MOUNT,
|
||||
COMPOSE_UNMOUNT,
|
||||
COMPOSE_SENSITIVITY_CHANGE,
|
||||
COMPOSE_SPOILERNESS_CHANGE,
|
||||
COMPOSE_TYPE_CHANGE,
|
||||
COMPOSE_SPOILER_TEXT_CHANGE,
|
||||
COMPOSE_VISIBILITY_CHANGE,
|
||||
COMPOSE_LISTABILITY_CHANGE,
|
||||
COMPOSE_COMPOSING_CHANGE,
|
||||
COMPOSE_EMOJI_INSERT,
|
||||
COMPOSE_UPLOAD_CHANGE_REQUEST,
|
||||
COMPOSE_UPLOAD_CHANGE_SUCCESS,
|
||||
COMPOSE_UPLOAD_CHANGE_FAIL,
|
||||
COMPOSE_POLL_ADD,
|
||||
COMPOSE_POLL_REMOVE,
|
||||
COMPOSE_POLL_OPTION_ADD,
|
||||
COMPOSE_POLL_OPTION_CHANGE,
|
||||
COMPOSE_POLL_OPTION_REMOVE,
|
||||
COMPOSE_POLL_SETTINGS_CHANGE,
|
||||
COMPOSE_SCHEDULE_ADD,
|
||||
COMPOSE_SCHEDULE_SET,
|
||||
COMPOSE_SCHEDULE_REMOVE,
|
||||
COMPOSE_ADD_TO_MENTIONS,
|
||||
COMPOSE_REMOVE_FROM_MENTIONS,
|
||||
COMPOSE_SET_STATUS,
|
||||
ensureComposeIsVisible,
|
||||
setComposeToStatus,
|
||||
changeCompose,
|
||||
replyCompose,
|
||||
replyComposeWithConfirmation,
|
||||
cancelReplyCompose,
|
||||
quoteCompose,
|
||||
cancelQuoteCompose,
|
||||
resetCompose,
|
||||
mentionCompose,
|
||||
directCompose,
|
||||
directComposeById,
|
||||
handleComposeSubmit,
|
||||
submitCompose,
|
||||
submitComposeRequest,
|
||||
submitComposeSuccess,
|
||||
submitComposeFail,
|
||||
uploadCompose,
|
||||
changeUploadCompose,
|
||||
changeUploadComposeRequest,
|
||||
changeUploadComposeSuccess,
|
||||
changeUploadComposeFail,
|
||||
uploadComposeRequest,
|
||||
uploadComposeProgress,
|
||||
uploadComposeSuccess,
|
||||
uploadComposeFail,
|
||||
undoUploadCompose,
|
||||
clearComposeSuggestions,
|
||||
fetchComposeSuggestions,
|
||||
readyComposeSuggestionsEmojis,
|
||||
readyComposeSuggestionsAccounts,
|
||||
selectComposeSuggestion,
|
||||
updateSuggestionTags,
|
||||
updateTagHistory,
|
||||
mountCompose,
|
||||
unmountCompose,
|
||||
changeComposeSensitivity,
|
||||
changeComposeSpoilerness,
|
||||
changeComposeContentType,
|
||||
changeComposeSpoilerText,
|
||||
changeComposeVisibility,
|
||||
insertEmojiCompose,
|
||||
changeComposing,
|
||||
addPoll,
|
||||
removePoll,
|
||||
addSchedule,
|
||||
setSchedule,
|
||||
removeSchedule,
|
||||
addPollOption,
|
||||
changePollOption,
|
||||
removePollOption,
|
||||
changePollSettings,
|
||||
openComposeWithText,
|
||||
addToMentions,
|
||||
removeFromMentions,
|
||||
};
|
|
@ -1,91 +0,0 @@
|
|||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import api, { getLinks } from '../api';
|
||||
|
||||
import {
|
||||
importFetchedAccounts,
|
||||
importFetchedStatuses,
|
||||
importFetchedStatus,
|
||||
} from './importer';
|
||||
|
||||
export const CONVERSATIONS_MOUNT = 'CONVERSATIONS_MOUNT';
|
||||
export const CONVERSATIONS_UNMOUNT = 'CONVERSATIONS_UNMOUNT';
|
||||
|
||||
export const CONVERSATIONS_FETCH_REQUEST = 'CONVERSATIONS_FETCH_REQUEST';
|
||||
export const CONVERSATIONS_FETCH_SUCCESS = 'CONVERSATIONS_FETCH_SUCCESS';
|
||||
export const CONVERSATIONS_FETCH_FAIL = 'CONVERSATIONS_FETCH_FAIL';
|
||||
export const CONVERSATIONS_UPDATE = 'CONVERSATIONS_UPDATE';
|
||||
|
||||
export const CONVERSATIONS_READ = 'CONVERSATIONS_READ';
|
||||
|
||||
export const mountConversations = () => ({
|
||||
type: CONVERSATIONS_MOUNT,
|
||||
});
|
||||
|
||||
export const unmountConversations = () => ({
|
||||
type: CONVERSATIONS_UNMOUNT,
|
||||
});
|
||||
|
||||
export const markConversationRead = conversationId => (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch({
|
||||
type: CONVERSATIONS_READ,
|
||||
id: conversationId,
|
||||
});
|
||||
|
||||
api(getState).post(`/api/v1/conversations/${conversationId}/read`);
|
||||
};
|
||||
|
||||
export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(expandConversationsRequest());
|
||||
|
||||
const params = { max_id: maxId };
|
||||
|
||||
if (!maxId) {
|
||||
params.since_id = getState().getIn(['conversations', 'items', 0, 'id']);
|
||||
}
|
||||
|
||||
const isLoadingRecent = !!params.since_id;
|
||||
|
||||
api(getState).get('/api/v1/conversations', { params })
|
||||
.then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data.reduce((aggr, item) => aggr.concat(item.accounts), [])));
|
||||
dispatch(importFetchedStatuses(response.data.map(item => item.last_status).filter(x => !!x)));
|
||||
dispatch(expandConversationsSuccess(response.data, next ? next.uri : null, isLoadingRecent));
|
||||
})
|
||||
.catch(err => dispatch(expandConversationsFail(err)));
|
||||
};
|
||||
|
||||
export const expandConversationsRequest = () => ({
|
||||
type: CONVERSATIONS_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
export const expandConversationsSuccess = (conversations, next, isLoadingRecent) => ({
|
||||
type: CONVERSATIONS_FETCH_SUCCESS,
|
||||
conversations,
|
||||
next,
|
||||
isLoadingRecent,
|
||||
});
|
||||
|
||||
export const expandConversationsFail = error => ({
|
||||
type: CONVERSATIONS_FETCH_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export const updateConversations = conversation => dispatch => {
|
||||
dispatch(importFetchedAccounts(conversation.accounts));
|
||||
|
||||
if (conversation.last_status) {
|
||||
dispatch(importFetchedStatus(conversation.last_status));
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: CONVERSATIONS_UPDATE,
|
||||
conversation,
|
||||
});
|
||||
};
|
113
app/soapbox/actions/conversations.ts
Normal file
|
@ -0,0 +1,113 @@
|
|||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import api, { getLinks } from '../api';
|
||||
|
||||
import {
|
||||
importFetchedAccounts,
|
||||
importFetchedStatuses,
|
||||
importFetchedStatus,
|
||||
} from './importer';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
const CONVERSATIONS_MOUNT = 'CONVERSATIONS_MOUNT';
|
||||
const CONVERSATIONS_UNMOUNT = 'CONVERSATIONS_UNMOUNT';
|
||||
|
||||
const CONVERSATIONS_FETCH_REQUEST = 'CONVERSATIONS_FETCH_REQUEST';
|
||||
const CONVERSATIONS_FETCH_SUCCESS = 'CONVERSATIONS_FETCH_SUCCESS';
|
||||
const CONVERSATIONS_FETCH_FAIL = 'CONVERSATIONS_FETCH_FAIL';
|
||||
const CONVERSATIONS_UPDATE = 'CONVERSATIONS_UPDATE';
|
||||
|
||||
const CONVERSATIONS_READ = 'CONVERSATIONS_READ';
|
||||
|
||||
const mountConversations = () => ({
|
||||
type: CONVERSATIONS_MOUNT,
|
||||
});
|
||||
|
||||
const unmountConversations = () => ({
|
||||
type: CONVERSATIONS_UNMOUNT,
|
||||
});
|
||||
|
||||
const markConversationRead = (conversationId: string) => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch({
|
||||
type: CONVERSATIONS_READ,
|
||||
id: conversationId,
|
||||
});
|
||||
|
||||
api(getState).post(`/api/v1/conversations/${conversationId}/read`);
|
||||
};
|
||||
|
||||
const expandConversations = ({ maxId }: Record<string, any> = {}) => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(expandConversationsRequest());
|
||||
|
||||
const params: Record<string, any> = { max_id: maxId };
|
||||
|
||||
if (!maxId) {
|
||||
params.since_id = getState().conversations.items.getIn([0, 'id']);
|
||||
}
|
||||
|
||||
const isLoadingRecent = !!params.since_id;
|
||||
|
||||
api(getState).get('/api/v1/conversations', { params })
|
||||
.then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data.reduce((aggr: Array<APIEntity>, item: APIEntity) => aggr.concat(item.accounts), [])));
|
||||
dispatch(importFetchedStatuses(response.data.map((item: Record<string, any>) => item.last_status).filter((x?: APIEntity) => !!x)));
|
||||
dispatch(expandConversationsSuccess(response.data, next ? next.uri : null, isLoadingRecent));
|
||||
})
|
||||
.catch(err => dispatch(expandConversationsFail(err)));
|
||||
};
|
||||
|
||||
const expandConversationsRequest = () => ({
|
||||
type: CONVERSATIONS_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
const expandConversationsSuccess = (conversations: APIEntity[], next: string | null, isLoadingRecent: boolean) => ({
|
||||
type: CONVERSATIONS_FETCH_SUCCESS,
|
||||
conversations,
|
||||
next,
|
||||
isLoadingRecent,
|
||||
});
|
||||
|
||||
const expandConversationsFail = (error: AxiosError) => ({
|
||||
type: CONVERSATIONS_FETCH_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
const updateConversations = (conversation: APIEntity) => (dispatch: AppDispatch) => {
|
||||
dispatch(importFetchedAccounts(conversation.accounts));
|
||||
|
||||
if (conversation.last_status) {
|
||||
dispatch(importFetchedStatus(conversation.last_status));
|
||||
}
|
||||
|
||||
return dispatch({
|
||||
type: CONVERSATIONS_UPDATE,
|
||||
conversation,
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
CONVERSATIONS_MOUNT,
|
||||
CONVERSATIONS_UNMOUNT,
|
||||
CONVERSATIONS_FETCH_REQUEST,
|
||||
CONVERSATIONS_FETCH_SUCCESS,
|
||||
CONVERSATIONS_FETCH_FAIL,
|
||||
CONVERSATIONS_UPDATE,
|
||||
CONVERSATIONS_READ,
|
||||
mountConversations,
|
||||
unmountConversations,
|
||||
markConversationRead,
|
||||
expandConversations,
|
||||
expandConversationsRequest,
|
||||
expandConversationsSuccess,
|
||||
expandConversationsFail,
|
||||
updateConversations,
|
||||
};
|
|
@ -1,40 +0,0 @@
|
|||
import api from '../api';
|
||||
|
||||
export const CUSTOM_EMOJIS_FETCH_REQUEST = 'CUSTOM_EMOJIS_FETCH_REQUEST';
|
||||
export const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS';
|
||||
export const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL';
|
||||
|
||||
export function fetchCustomEmojis() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchCustomEmojisRequest());
|
||||
|
||||
api(getState).get('/api/v1/custom_emojis').then(response => {
|
||||
dispatch(fetchCustomEmojisSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(fetchCustomEmojisFail(error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchCustomEmojisRequest() {
|
||||
return {
|
||||
type: CUSTOM_EMOJIS_FETCH_REQUEST,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchCustomEmojisSuccess(custom_emojis) {
|
||||
return {
|
||||
type: CUSTOM_EMOJIS_FETCH_SUCCESS,
|
||||
custom_emojis,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchCustomEmojisFail(error) {
|
||||
return {
|
||||
type: CUSTOM_EMOJIS_FETCH_FAIL,
|
||||
error,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
50
app/soapbox/actions/custom_emojis.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import api from '../api';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
const CUSTOM_EMOJIS_FETCH_REQUEST = 'CUSTOM_EMOJIS_FETCH_REQUEST';
|
||||
const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS';
|
||||
const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL';
|
||||
|
||||
const fetchCustomEmojis = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const me = getState().me;
|
||||
if (!me) return;
|
||||
|
||||
dispatch(fetchCustomEmojisRequest());
|
||||
|
||||
api(getState).get('/api/v1/custom_emojis').then(response => {
|
||||
dispatch(fetchCustomEmojisSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(fetchCustomEmojisFail(error));
|
||||
});
|
||||
};
|
||||
|
||||
const fetchCustomEmojisRequest = () => ({
|
||||
type: CUSTOM_EMOJIS_FETCH_REQUEST,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const fetchCustomEmojisSuccess = (custom_emojis: APIEntity[]) => ({
|
||||
type: CUSTOM_EMOJIS_FETCH_SUCCESS,
|
||||
custom_emojis,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const fetchCustomEmojisFail = (error: AxiosError) => ({
|
||||
type: CUSTOM_EMOJIS_FETCH_FAIL,
|
||||
error,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
export {
|
||||
CUSTOM_EMOJIS_FETCH_REQUEST,
|
||||
CUSTOM_EMOJIS_FETCH_SUCCESS,
|
||||
CUSTOM_EMOJIS_FETCH_FAIL,
|
||||
fetchCustomEmojis,
|
||||
fetchCustomEmojisRequest,
|
||||
fetchCustomEmojisSuccess,
|
||||
fetchCustomEmojisFail,
|
||||
};
|
|
@ -1,62 +0,0 @@
|
|||
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,
|
||||
});
|
85
app/soapbox/actions/directory.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import api from '../api';
|
||||
|
||||
import { fetchRelationships } from './accounts';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
const DIRECTORY_FETCH_REQUEST = 'DIRECTORY_FETCH_REQUEST';
|
||||
const DIRECTORY_FETCH_SUCCESS = 'DIRECTORY_FETCH_SUCCESS';
|
||||
const DIRECTORY_FETCH_FAIL = 'DIRECTORY_FETCH_FAIL';
|
||||
|
||||
const DIRECTORY_EXPAND_REQUEST = 'DIRECTORY_EXPAND_REQUEST';
|
||||
const DIRECTORY_EXPAND_SUCCESS = 'DIRECTORY_EXPAND_SUCCESS';
|
||||
const DIRECTORY_EXPAND_FAIL = 'DIRECTORY_EXPAND_FAIL';
|
||||
|
||||
const fetchDirectory = (params: Record<string, any>) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
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: APIEntity) => x.id)));
|
||||
}).catch(error => dispatch(fetchDirectoryFail(error)));
|
||||
};
|
||||
|
||||
const fetchDirectoryRequest = () => ({
|
||||
type: DIRECTORY_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
const fetchDirectorySuccess = (accounts: APIEntity[]) => ({
|
||||
type: DIRECTORY_FETCH_SUCCESS,
|
||||
accounts,
|
||||
});
|
||||
|
||||
const fetchDirectoryFail = (error: AxiosError) => ({
|
||||
type: DIRECTORY_FETCH_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
const expandDirectory = (params: Record<string, any>) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch(expandDirectoryRequest());
|
||||
|
||||
const loadedItems = getState().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: APIEntity) => x.id)));
|
||||
}).catch(error => dispatch(expandDirectoryFail(error)));
|
||||
};
|
||||
|
||||
const expandDirectoryRequest = () => ({
|
||||
type: DIRECTORY_EXPAND_REQUEST,
|
||||
});
|
||||
|
||||
const expandDirectorySuccess = (accounts: APIEntity[]) => ({
|
||||
type: DIRECTORY_EXPAND_SUCCESS,
|
||||
accounts,
|
||||
});
|
||||
|
||||
const expandDirectoryFail = (error: AxiosError) => ({
|
||||
type: DIRECTORY_EXPAND_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export {
|
||||
DIRECTORY_FETCH_REQUEST,
|
||||
DIRECTORY_FETCH_SUCCESS,
|
||||
DIRECTORY_FETCH_FAIL,
|
||||
DIRECTORY_EXPAND_REQUEST,
|
||||
DIRECTORY_EXPAND_SUCCESS,
|
||||
DIRECTORY_EXPAND_FAIL,
|
||||
fetchDirectory,
|
||||
fetchDirectoryRequest,
|
||||
fetchDirectorySuccess,
|
||||
fetchDirectoryFail,
|
||||
expandDirectory,
|
||||
expandDirectoryRequest,
|
||||
expandDirectorySuccess,
|
||||
expandDirectoryFail,
|
||||
};
|
|
@ -1,181 +0,0 @@
|
|||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import api, { getLinks } from '../api';
|
||||
|
||||
export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST';
|
||||
export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS';
|
||||
export const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL';
|
||||
|
||||
export const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST';
|
||||
export const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS';
|
||||
export const DOMAIN_UNBLOCK_FAIL = 'DOMAIN_UNBLOCK_FAIL';
|
||||
|
||||
export const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST';
|
||||
export const DOMAIN_BLOCKS_FETCH_SUCCESS = 'DOMAIN_BLOCKS_FETCH_SUCCESS';
|
||||
export const DOMAIN_BLOCKS_FETCH_FAIL = 'DOMAIN_BLOCKS_FETCH_FAIL';
|
||||
|
||||
export const DOMAIN_BLOCKS_EXPAND_REQUEST = 'DOMAIN_BLOCKS_EXPAND_REQUEST';
|
||||
export const DOMAIN_BLOCKS_EXPAND_SUCCESS = 'DOMAIN_BLOCKS_EXPAND_SUCCESS';
|
||||
export const DOMAIN_BLOCKS_EXPAND_FAIL = 'DOMAIN_BLOCKS_EXPAND_FAIL';
|
||||
|
||||
export function blockDomain(domain) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(blockDomainRequest(domain));
|
||||
|
||||
api(getState).post('/api/v1/domain_blocks', { domain }).then(() => {
|
||||
const at_domain = '@' + domain;
|
||||
const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id'));
|
||||
dispatch(blockDomainSuccess(domain, accounts));
|
||||
}).catch(err => {
|
||||
dispatch(blockDomainFail(domain, err));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function blockDomainRequest(domain) {
|
||||
return {
|
||||
type: DOMAIN_BLOCK_REQUEST,
|
||||
domain,
|
||||
};
|
||||
}
|
||||
|
||||
export function blockDomainSuccess(domain, accounts) {
|
||||
return {
|
||||
type: DOMAIN_BLOCK_SUCCESS,
|
||||
domain,
|
||||
accounts,
|
||||
};
|
||||
}
|
||||
|
||||
export function blockDomainFail(domain, error) {
|
||||
return {
|
||||
type: DOMAIN_BLOCK_FAIL,
|
||||
domain,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function unblockDomain(domain) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(unblockDomainRequest(domain));
|
||||
|
||||
// Do it both ways for maximum compatibility
|
||||
const params = {
|
||||
params: { domain },
|
||||
data: { domain },
|
||||
};
|
||||
|
||||
api(getState).delete('/api/v1/domain_blocks', params).then(() => {
|
||||
const at_domain = '@' + domain;
|
||||
const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id'));
|
||||
dispatch(unblockDomainSuccess(domain, accounts));
|
||||
}).catch(err => {
|
||||
dispatch(unblockDomainFail(domain, err));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function unblockDomainRequest(domain) {
|
||||
return {
|
||||
type: DOMAIN_UNBLOCK_REQUEST,
|
||||
domain,
|
||||
};
|
||||
}
|
||||
|
||||
export function unblockDomainSuccess(domain, accounts) {
|
||||
return {
|
||||
type: DOMAIN_UNBLOCK_SUCCESS,
|
||||
domain,
|
||||
accounts,
|
||||
};
|
||||
}
|
||||
|
||||
export function unblockDomainFail(domain, error) {
|
||||
return {
|
||||
type: DOMAIN_UNBLOCK_FAIL,
|
||||
domain,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchDomainBlocks() {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(fetchDomainBlocksRequest());
|
||||
|
||||
api(getState).get('/api/v1/domain_blocks').then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null));
|
||||
}).catch(err => {
|
||||
dispatch(fetchDomainBlocksFail(err));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchDomainBlocksRequest() {
|
||||
return {
|
||||
type: DOMAIN_BLOCKS_FETCH_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchDomainBlocksSuccess(domains, next) {
|
||||
return {
|
||||
type: DOMAIN_BLOCKS_FETCH_SUCCESS,
|
||||
domains,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchDomainBlocksFail(error) {
|
||||
return {
|
||||
type: DOMAIN_BLOCKS_FETCH_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandDomainBlocks() {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const url = getState().getIn(['domain_lists', 'blocks', 'next']);
|
||||
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandDomainBlocksRequest());
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(expandDomainBlocksSuccess(response.data, next ? next.uri : null));
|
||||
}).catch(err => {
|
||||
dispatch(expandDomainBlocksFail(err));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function expandDomainBlocksRequest() {
|
||||
return {
|
||||
type: DOMAIN_BLOCKS_EXPAND_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandDomainBlocksSuccess(domains, next) {
|
||||
return {
|
||||
type: DOMAIN_BLOCKS_EXPAND_SUCCESS,
|
||||
domains,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandDomainBlocksFail(error) {
|
||||
return {
|
||||
type: DOMAIN_BLOCKS_EXPAND_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
188
app/soapbox/actions/domain_blocks.ts
Normal file
|
@ -0,0 +1,188 @@
|
|||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import api, { getLinks } from '../api';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { List as ImmutableList } from 'immutable';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
|
||||
const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST';
|
||||
const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS';
|
||||
const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL';
|
||||
|
||||
const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST';
|
||||
const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS';
|
||||
const DOMAIN_UNBLOCK_FAIL = 'DOMAIN_UNBLOCK_FAIL';
|
||||
|
||||
const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST';
|
||||
const DOMAIN_BLOCKS_FETCH_SUCCESS = 'DOMAIN_BLOCKS_FETCH_SUCCESS';
|
||||
const DOMAIN_BLOCKS_FETCH_FAIL = 'DOMAIN_BLOCKS_FETCH_FAIL';
|
||||
|
||||
const DOMAIN_BLOCKS_EXPAND_REQUEST = 'DOMAIN_BLOCKS_EXPAND_REQUEST';
|
||||
const DOMAIN_BLOCKS_EXPAND_SUCCESS = 'DOMAIN_BLOCKS_EXPAND_SUCCESS';
|
||||
const DOMAIN_BLOCKS_EXPAND_FAIL = 'DOMAIN_BLOCKS_EXPAND_FAIL';
|
||||
|
||||
const blockDomain = (domain: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(blockDomainRequest(domain));
|
||||
|
||||
api(getState).post('/api/v1/domain_blocks', { domain }).then(() => {
|
||||
const at_domain = '@' + domain;
|
||||
const accounts = getState().accounts.filter(item => item.acct.endsWith(at_domain)).valueSeq().map(item => item.id);
|
||||
dispatch(blockDomainSuccess(domain, accounts.toList()));
|
||||
}).catch(err => {
|
||||
dispatch(blockDomainFail(domain, err));
|
||||
});
|
||||
};
|
||||
|
||||
const blockDomainRequest = (domain: string) => ({
|
||||
type: DOMAIN_BLOCK_REQUEST,
|
||||
domain,
|
||||
});
|
||||
|
||||
const blockDomainSuccess = (domain: string, accounts: ImmutableList<string>) => ({
|
||||
type: DOMAIN_BLOCK_SUCCESS,
|
||||
domain,
|
||||
accounts,
|
||||
});
|
||||
|
||||
const blockDomainFail = (domain: string, error: AxiosError) => ({
|
||||
type: DOMAIN_BLOCK_FAIL,
|
||||
domain,
|
||||
error,
|
||||
});
|
||||
|
||||
const unblockDomain = (domain: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(unblockDomainRequest(domain));
|
||||
|
||||
// Do it both ways for maximum compatibility
|
||||
const params = {
|
||||
params: { domain },
|
||||
data: { domain },
|
||||
};
|
||||
|
||||
api(getState).delete('/api/v1/domain_blocks', params).then(() => {
|
||||
const at_domain = '@' + domain;
|
||||
const accounts = getState().accounts.filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id'));
|
||||
dispatch(unblockDomainSuccess(domain, accounts.toList()));
|
||||
}).catch(err => {
|
||||
dispatch(unblockDomainFail(domain, err));
|
||||
});
|
||||
};
|
||||
|
||||
const unblockDomainRequest = (domain: string) => ({
|
||||
type: DOMAIN_UNBLOCK_REQUEST,
|
||||
domain,
|
||||
});
|
||||
|
||||
const unblockDomainSuccess = (domain: string, accounts: ImmutableList<string>) => ({
|
||||
type: DOMAIN_UNBLOCK_SUCCESS,
|
||||
domain,
|
||||
accounts,
|
||||
});
|
||||
|
||||
const unblockDomainFail = (domain: string, error: AxiosError) => ({
|
||||
type: DOMAIN_UNBLOCK_FAIL,
|
||||
domain,
|
||||
error,
|
||||
});
|
||||
|
||||
const fetchDomainBlocks = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(fetchDomainBlocksRequest());
|
||||
|
||||
api(getState).get('/api/v1/domain_blocks').then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null));
|
||||
}).catch(err => {
|
||||
dispatch(fetchDomainBlocksFail(err));
|
||||
});
|
||||
};
|
||||
|
||||
const fetchDomainBlocksRequest = () => ({
|
||||
type: DOMAIN_BLOCKS_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
const fetchDomainBlocksSuccess = (domains: string[], next: string | null) => ({
|
||||
type: DOMAIN_BLOCKS_FETCH_SUCCESS,
|
||||
domains,
|
||||
next,
|
||||
});
|
||||
|
||||
const fetchDomainBlocksFail = (error: AxiosError) => ({
|
||||
type: DOMAIN_BLOCKS_FETCH_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
const expandDomainBlocks = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const url = getState().domain_lists.blocks.next;
|
||||
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandDomainBlocksRequest());
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(expandDomainBlocksSuccess(response.data, next ? next.uri : null));
|
||||
}).catch(err => {
|
||||
dispatch(expandDomainBlocksFail(err));
|
||||
});
|
||||
};
|
||||
|
||||
const expandDomainBlocksRequest = () => ({
|
||||
type: DOMAIN_BLOCKS_EXPAND_REQUEST,
|
||||
});
|
||||
|
||||
const expandDomainBlocksSuccess = (domains: string[], next: string | null) => ({
|
||||
type: DOMAIN_BLOCKS_EXPAND_SUCCESS,
|
||||
domains,
|
||||
next,
|
||||
});
|
||||
|
||||
const expandDomainBlocksFail = (error: AxiosError) => ({
|
||||
type: DOMAIN_BLOCKS_EXPAND_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export {
|
||||
DOMAIN_BLOCK_REQUEST,
|
||||
DOMAIN_BLOCK_SUCCESS,
|
||||
DOMAIN_BLOCK_FAIL,
|
||||
DOMAIN_UNBLOCK_REQUEST,
|
||||
DOMAIN_UNBLOCK_SUCCESS,
|
||||
DOMAIN_UNBLOCK_FAIL,
|
||||
DOMAIN_BLOCKS_FETCH_REQUEST,
|
||||
DOMAIN_BLOCKS_FETCH_SUCCESS,
|
||||
DOMAIN_BLOCKS_FETCH_FAIL,
|
||||
DOMAIN_BLOCKS_EXPAND_REQUEST,
|
||||
DOMAIN_BLOCKS_EXPAND_SUCCESS,
|
||||
DOMAIN_BLOCKS_EXPAND_FAIL,
|
||||
blockDomain,
|
||||
blockDomainRequest,
|
||||
blockDomainSuccess,
|
||||
blockDomainFail,
|
||||
unblockDomain,
|
||||
unblockDomainRequest,
|
||||
unblockDomainSuccess,
|
||||
unblockDomainFail,
|
||||
fetchDomainBlocks,
|
||||
fetchDomainBlocksRequest,
|
||||
fetchDomainBlocksSuccess,
|
||||
fetchDomainBlocksFail,
|
||||
expandDomainBlocks,
|
||||
expandDomainBlocksRequest,
|
||||
expandDomainBlocksSuccess,
|
||||
expandDomainBlocksFail,
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
|
||||
export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
|
||||
|
||||
export function openDropdownMenu(id, placement, keyboard) {
|
||||
return { type: DROPDOWN_MENU_OPEN, id, placement, keyboard };
|
||||
}
|
||||
|
||||
export function closeDropdownMenu(id) {
|
||||
return { type: DROPDOWN_MENU_CLOSE, id };
|
||||
}
|
17
app/soapbox/actions/dropdown_menu.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import type { DropdownPlacement } from 'soapbox/components/dropdown_menu';
|
||||
|
||||
const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
|
||||
const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
|
||||
|
||||
const openDropdownMenu = (id: number, placement: DropdownPlacement, keyboard: boolean) =>
|
||||
({ type: DROPDOWN_MENU_OPEN, id, placement, keyboard });
|
||||
|
||||
const closeDropdownMenu = (id: number) =>
|
||||
({ type: DROPDOWN_MENU_CLOSE, id });
|
||||
|
||||
export {
|
||||
DROPDOWN_MENU_OPEN,
|
||||
DROPDOWN_MENU_CLOSE,
|
||||
openDropdownMenu,
|
||||
closeDropdownMenu,
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
import api from '../api';
|
||||
|
||||
export function getSubscribersCsv() {
|
||||
return (dispatch, getState) => {
|
||||
return api(getState).get('/api/v1/pleroma/admin/email_list/subscribers.csv');
|
||||
};
|
||||
}
|
||||
|
||||
export function getUnsubscribersCsv() {
|
||||
return (dispatch, getState) => {
|
||||
return api(getState).get('/api/v1/pleroma/admin/email_list/unsubscribers.csv');
|
||||
};
|
||||
}
|
||||
|
||||
export function getCombinedCsv() {
|
||||
return (dispatch, getState) => {
|
||||
return api(getState).get('/api/v1/pleroma/admin/email_list/combined.csv');
|
||||
};
|
||||
}
|
21
app/soapbox/actions/email_list.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import api from '../api';
|
||||
|
||||
import type { RootState } from 'soapbox/store';
|
||||
|
||||
const getSubscribersCsv = () =>
|
||||
(dispatch: any, getState: () => RootState) =>
|
||||
api(getState).get('/api/v1/pleroma/admin/email_list/subscribers.csv');
|
||||
|
||||
const getUnsubscribersCsv = () =>
|
||||
(dispatch: any, getState: () => RootState) =>
|
||||
api(getState).get('/api/v1/pleroma/admin/email_list/unsubscribers.csv');
|
||||
|
||||
const getCombinedCsv = () =>
|
||||
(dispatch: any, getState: () => RootState) =>
|
||||
api(getState).get('/api/v1/pleroma/admin/email_list/combined.csv');
|
||||
|
||||
export {
|
||||
getSubscribersCsv,
|
||||
getUnsubscribersCsv,
|
||||
getCombinedCsv,
|
||||
};
|
|
@ -1,182 +0,0 @@
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
||||
import { favourite, unfavourite } from './interactions';
|
||||
|
||||
export const EMOJI_REACT_REQUEST = 'EMOJI_REACT_REQUEST';
|
||||
export const EMOJI_REACT_SUCCESS = 'EMOJI_REACT_SUCCESS';
|
||||
export const EMOJI_REACT_FAIL = 'EMOJI_REACT_FAIL';
|
||||
|
||||
export const UNEMOJI_REACT_REQUEST = 'UNEMOJI_REACT_REQUEST';
|
||||
export const UNEMOJI_REACT_SUCCESS = 'UNEMOJI_REACT_SUCCESS';
|
||||
export const UNEMOJI_REACT_FAIL = 'UNEMOJI_REACT_FAIL';
|
||||
|
||||
export const EMOJI_REACTS_FETCH_REQUEST = 'EMOJI_REACTS_FETCH_REQUEST';
|
||||
export const EMOJI_REACTS_FETCH_SUCCESS = 'EMOJI_REACTS_FETCH_SUCCESS';
|
||||
export const EMOJI_REACTS_FETCH_FAIL = 'EMOJI_REACTS_FETCH_FAIL';
|
||||
|
||||
const noOp = () => () => new Promise(f => f());
|
||||
|
||||
export const simpleEmojiReact = (status, emoji) => {
|
||||
return (dispatch, getState) => {
|
||||
const emojiReacts = status.getIn(['pleroma', 'emoji_reactions'], ImmutableList());
|
||||
|
||||
if (emoji === '👍' && status.get('favourited')) return dispatch(unfavourite(status));
|
||||
|
||||
const undo = emojiReacts.filter(e => e.get('me') === true && e.get('name') === emoji).count() > 0;
|
||||
if (undo) return dispatch(unEmojiReact(status, emoji));
|
||||
|
||||
return Promise.all(
|
||||
emojiReacts
|
||||
.filter(emojiReact => emojiReact.get('me') === true)
|
||||
.map(emojiReact => dispatch(unEmojiReact(status, emojiReact.get('name')))),
|
||||
status.get('favourited') && dispatch(unfavourite(status)),
|
||||
).then(() => {
|
||||
if (emoji === '👍') {
|
||||
dispatch(favourite(status));
|
||||
} else {
|
||||
dispatch(emojiReact(status, emoji));
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchEmojiReacts(id, emoji) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return dispatch(noOp());
|
||||
|
||||
dispatch(fetchEmojiReactsRequest(id, emoji));
|
||||
|
||||
const url = emoji
|
||||
? `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
|
||||
: `/api/v1/pleroma/statuses/${id}/reactions`;
|
||||
|
||||
return api(getState).get(url).then(response => {
|
||||
response.data.forEach(emojiReact => {
|
||||
dispatch(importFetchedAccounts(emojiReact.accounts));
|
||||
});
|
||||
dispatch(fetchEmojiReactsSuccess(id, response.data));
|
||||
}).catch(error => {
|
||||
dispatch(fetchEmojiReactsFail(id, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function emojiReact(status, emoji) {
|
||||
return function(dispatch, getState) {
|
||||
if (!isLoggedIn(getState)) return dispatch(noOp());
|
||||
|
||||
dispatch(emojiReactRequest(status, emoji));
|
||||
|
||||
return api(getState)
|
||||
.put(`/api/v1/pleroma/statuses/${status.get('id')}/reactions/${emoji}`)
|
||||
.then(function(response) {
|
||||
dispatch(importFetchedStatus(response.data));
|
||||
dispatch(emojiReactSuccess(status, emoji));
|
||||
}).catch(function(error) {
|
||||
dispatch(emojiReactFail(status, emoji, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function unEmojiReact(status, emoji) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return dispatch(noOp());
|
||||
|
||||
dispatch(unEmojiReactRequest(status, emoji));
|
||||
|
||||
return api(getState)
|
||||
.delete(`/api/v1/pleroma/statuses/${status.get('id')}/reactions/${emoji}`)
|
||||
.then(response => {
|
||||
dispatch(importFetchedStatus(response.data));
|
||||
dispatch(unEmojiReactSuccess(status, emoji));
|
||||
}).catch(error => {
|
||||
dispatch(unEmojiReactFail(status, emoji, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchEmojiReactsRequest(id, emoji) {
|
||||
return {
|
||||
type: EMOJI_REACTS_FETCH_REQUEST,
|
||||
id,
|
||||
emoji,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchEmojiReactsSuccess(id, emojiReacts) {
|
||||
return {
|
||||
type: EMOJI_REACTS_FETCH_SUCCESS,
|
||||
id,
|
||||
emojiReacts,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchEmojiReactsFail(id, error) {
|
||||
return {
|
||||
type: EMOJI_REACTS_FETCH_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function emojiReactRequest(status, emoji) {
|
||||
return {
|
||||
type: EMOJI_REACT_REQUEST,
|
||||
status,
|
||||
emoji,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function emojiReactSuccess(status, emoji) {
|
||||
return {
|
||||
type: EMOJI_REACT_SUCCESS,
|
||||
status,
|
||||
emoji,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function emojiReactFail(status, emoji, error) {
|
||||
return {
|
||||
type: EMOJI_REACT_FAIL,
|
||||
status,
|
||||
emoji,
|
||||
error,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function unEmojiReactRequest(status, emoji) {
|
||||
return {
|
||||
type: UNEMOJI_REACT_REQUEST,
|
||||
status,
|
||||
emoji,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function unEmojiReactSuccess(status, emoji) {
|
||||
return {
|
||||
type: UNEMOJI_REACT_SUCCESS,
|
||||
status,
|
||||
emoji,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function unEmojiReactFail(status, emoji, error) {
|
||||
return {
|
||||
type: UNEMOJI_REACT_FAIL,
|
||||
status,
|
||||
emoji,
|
||||
error,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
190
app/soapbox/actions/emoji_reacts.ts
Normal file
|
@ -0,0 +1,190 @@
|
|||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
||||
import { favourite, unfavourite } from './interactions';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { APIEntity, Status } from 'soapbox/types/entities';
|
||||
|
||||
const EMOJI_REACT_REQUEST = 'EMOJI_REACT_REQUEST';
|
||||
const EMOJI_REACT_SUCCESS = 'EMOJI_REACT_SUCCESS';
|
||||
const EMOJI_REACT_FAIL = 'EMOJI_REACT_FAIL';
|
||||
|
||||
const UNEMOJI_REACT_REQUEST = 'UNEMOJI_REACT_REQUEST';
|
||||
const UNEMOJI_REACT_SUCCESS = 'UNEMOJI_REACT_SUCCESS';
|
||||
const UNEMOJI_REACT_FAIL = 'UNEMOJI_REACT_FAIL';
|
||||
|
||||
const EMOJI_REACTS_FETCH_REQUEST = 'EMOJI_REACTS_FETCH_REQUEST';
|
||||
const EMOJI_REACTS_FETCH_SUCCESS = 'EMOJI_REACTS_FETCH_SUCCESS';
|
||||
const EMOJI_REACTS_FETCH_FAIL = 'EMOJI_REACTS_FETCH_FAIL';
|
||||
|
||||
const noOp = () => () => new Promise(f => f(undefined));
|
||||
|
||||
const simpleEmojiReact = (status: Status, emoji: string) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
const emojiReacts: ImmutableList<ImmutableMap<string, any>> = status.pleroma.get('emoji_reactions') || ImmutableList();
|
||||
|
||||
if (emoji === '👍' && status.favourited) return dispatch(unfavourite(status));
|
||||
|
||||
const undo = emojiReacts.filter(e => e.get('me') === true && e.get('name') === emoji).count() > 0;
|
||||
if (undo) return dispatch(unEmojiReact(status, emoji));
|
||||
|
||||
return Promise.all([
|
||||
...emojiReacts
|
||||
.filter((emojiReact) => emojiReact.get('me') === true)
|
||||
.map(emojiReact => dispatch(unEmojiReact(status, emojiReact.get('name')))).toArray(),
|
||||
status.favourited && dispatch(unfavourite(status)),
|
||||
]).then(() => {
|
||||
if (emoji === '👍') {
|
||||
dispatch(favourite(status));
|
||||
} else {
|
||||
dispatch(emojiReact(status, emoji));
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
const fetchEmojiReacts = (id: string, emoji: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return dispatch(noOp());
|
||||
|
||||
dispatch(fetchEmojiReactsRequest(id, emoji));
|
||||
|
||||
const url = emoji
|
||||
? `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
|
||||
: `/api/v1/pleroma/statuses/${id}/reactions`;
|
||||
|
||||
return api(getState).get(url).then(response => {
|
||||
response.data.forEach((emojiReact: APIEntity) => {
|
||||
dispatch(importFetchedAccounts(emojiReact.accounts));
|
||||
});
|
||||
dispatch(fetchEmojiReactsSuccess(id, response.data));
|
||||
}).catch(error => {
|
||||
dispatch(fetchEmojiReactsFail(id, error));
|
||||
});
|
||||
};
|
||||
|
||||
const emojiReact = (status: Status, emoji: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return dispatch(noOp());
|
||||
|
||||
dispatch(emojiReactRequest(status, emoji));
|
||||
|
||||
return api(getState)
|
||||
.put(`/api/v1/pleroma/statuses/${status.get('id')}/reactions/${emoji}`)
|
||||
.then(function(response) {
|
||||
dispatch(importFetchedStatus(response.data));
|
||||
dispatch(emojiReactSuccess(status, emoji));
|
||||
}).catch(function(error) {
|
||||
dispatch(emojiReactFail(status, emoji, error));
|
||||
});
|
||||
};
|
||||
|
||||
const unEmojiReact = (status: Status, emoji: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return dispatch(noOp());
|
||||
|
||||
dispatch(unEmojiReactRequest(status, emoji));
|
||||
|
||||
return api(getState)
|
||||
.delete(`/api/v1/pleroma/statuses/${status.get('id')}/reactions/${emoji}`)
|
||||
.then(response => {
|
||||
dispatch(importFetchedStatus(response.data));
|
||||
dispatch(unEmojiReactSuccess(status, emoji));
|
||||
}).catch(error => {
|
||||
dispatch(unEmojiReactFail(status, emoji, error));
|
||||
});
|
||||
};
|
||||
|
||||
const fetchEmojiReactsRequest = (id: string, emoji: string) => ({
|
||||
type: EMOJI_REACTS_FETCH_REQUEST,
|
||||
id,
|
||||
emoji,
|
||||
});
|
||||
|
||||
const fetchEmojiReactsSuccess = (id: string, emojiReacts: APIEntity[]) => ({
|
||||
type: EMOJI_REACTS_FETCH_SUCCESS,
|
||||
id,
|
||||
emojiReacts,
|
||||
});
|
||||
|
||||
const fetchEmojiReactsFail = (id: string, error: AxiosError) => ({
|
||||
type: EMOJI_REACTS_FETCH_FAIL,
|
||||
id,
|
||||
error,
|
||||
});
|
||||
|
||||
const emojiReactRequest = (status: Status, emoji: string) => ({
|
||||
type: EMOJI_REACT_REQUEST,
|
||||
status,
|
||||
emoji,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const emojiReactSuccess = (status: Status, emoji: string) => ({
|
||||
type: EMOJI_REACT_SUCCESS,
|
||||
status,
|
||||
emoji,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const emojiReactFail = (status: Status, emoji: string, error: AxiosError) => ({
|
||||
type: EMOJI_REACT_FAIL,
|
||||
status,
|
||||
emoji,
|
||||
error,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const unEmojiReactRequest = (status: Status, emoji: string) => ({
|
||||
type: UNEMOJI_REACT_REQUEST,
|
||||
status,
|
||||
emoji,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const unEmojiReactSuccess = (status: Status, emoji: string) => ({
|
||||
type: UNEMOJI_REACT_SUCCESS,
|
||||
status,
|
||||
emoji,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const unEmojiReactFail = (status: Status, emoji: string, error: AxiosError) => ({
|
||||
type: UNEMOJI_REACT_FAIL,
|
||||
status,
|
||||
emoji,
|
||||
error,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
export {
|
||||
EMOJI_REACT_REQUEST,
|
||||
EMOJI_REACT_SUCCESS,
|
||||
EMOJI_REACT_FAIL,
|
||||
UNEMOJI_REACT_REQUEST,
|
||||
UNEMOJI_REACT_SUCCESS,
|
||||
UNEMOJI_REACT_FAIL,
|
||||
EMOJI_REACTS_FETCH_REQUEST,
|
||||
EMOJI_REACTS_FETCH_SUCCESS,
|
||||
EMOJI_REACTS_FETCH_FAIL,
|
||||
simpleEmojiReact,
|
||||
fetchEmojiReacts,
|
||||
emojiReact,
|
||||
unEmojiReact,
|
||||
fetchEmojiReactsRequest,
|
||||
fetchEmojiReactsSuccess,
|
||||
fetchEmojiReactsFail,
|
||||
emojiReactRequest,
|
||||
emojiReactSuccess,
|
||||
emojiReactFail,
|
||||
unEmojiReactRequest,
|
||||
unEmojiReactSuccess,
|
||||
unEmojiReactFail,
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
import { saveSettings } from './settings';
|
||||
|
||||
export const EMOJI_USE = 'EMOJI_USE';
|
||||
|
||||
export function useEmoji(emoji) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: EMOJI_USE,
|
||||
emoji,
|
||||
});
|
||||
|
||||
dispatch(saveSettings());
|
||||
};
|
||||
}
|
21
app/soapbox/actions/emojis.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { saveSettings } from './settings';
|
||||
|
||||
import type { Emoji } from 'soapbox/components/autosuggest_emoji';
|
||||
import type { AppDispatch } from 'soapbox/store';
|
||||
|
||||
const EMOJI_USE = 'EMOJI_USE';
|
||||
|
||||
const useEmoji = (emoji: Emoji) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
dispatch({
|
||||
type: EMOJI_USE,
|
||||
emoji,
|
||||
});
|
||||
|
||||
dispatch(saveSettings());
|
||||
};
|
||||
|
||||
export {
|
||||
EMOJI_USE,
|
||||
useEmoji,
|
||||
};
|
|
@ -1,104 +0,0 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
|
||||
import api, { getLinks } from '../api';
|
||||
|
||||
export const EXPORT_FOLLOWS_REQUEST = 'EXPORT_FOLLOWS_REQUEST';
|
||||
export const EXPORT_FOLLOWS_SUCCESS = 'EXPORT_FOLLOWS_SUCCESS';
|
||||
export const EXPORT_FOLLOWS_FAIL = 'EXPORT_FOLLOWS_FAIL';
|
||||
|
||||
export const EXPORT_BLOCKS_REQUEST = 'EXPORT_BLOCKS_REQUEST';
|
||||
export const EXPORT_BLOCKS_SUCCESS = 'EXPORT_BLOCKS_SUCCESS';
|
||||
export const EXPORT_BLOCKS_FAIL = 'EXPORT_BLOCKS_FAIL';
|
||||
|
||||
export const EXPORT_MUTES_REQUEST = 'EXPORT_MUTES_REQUEST';
|
||||
export const EXPORT_MUTES_SUCCESS = 'EXPORT_MUTES_SUCCESS';
|
||||
export const EXPORT_MUTES_FAIL = 'EXPORT_MUTES_FAIL';
|
||||
|
||||
const messages = defineMessages({
|
||||
blocksSuccess: { id: 'export_data.success.blocks', defaultMessage: 'Blocks exported successfully' },
|
||||
followersSuccess: { id: 'export_data.success.followers', defaultMessage: 'Followers exported successfully' },
|
||||
mutesSuccess: { id: 'export_data.success.mutes', defaultMessage: 'Mutes exported successfully' },
|
||||
});
|
||||
|
||||
function fileExport(content, fileName) {
|
||||
const fileToDownload = document.createElement('a');
|
||||
|
||||
fileToDownload.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(content));
|
||||
fileToDownload.setAttribute('download', fileName);
|
||||
fileToDownload.style.display = 'none';
|
||||
document.body.appendChild(fileToDownload);
|
||||
fileToDownload.click();
|
||||
document.body.removeChild(fileToDownload);
|
||||
}
|
||||
|
||||
function listAccounts(state) {
|
||||
return async apiResponse => {
|
||||
const followings = apiResponse.data;
|
||||
let accounts = [];
|
||||
let next = getLinks(apiResponse).refs.find(link => link.rel === 'next');
|
||||
while (next) {
|
||||
apiResponse = await api(state).get(next.uri);
|
||||
next = getLinks(apiResponse).refs.find(link => link.rel === 'next');
|
||||
Array.prototype.push.apply(followings, apiResponse.data);
|
||||
}
|
||||
|
||||
accounts = followings.map(account => account.fqn);
|
||||
return [... new Set(accounts)];
|
||||
};
|
||||
}
|
||||
|
||||
export function exportFollows(intl) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: EXPORT_FOLLOWS_REQUEST });
|
||||
const me = getState().get('me');
|
||||
return api(getState)
|
||||
.get(`/api/v1/accounts/${me}/following?limit=40`)
|
||||
.then(listAccounts(getState))
|
||||
.then((followings) => {
|
||||
followings = followings.map(fqn => fqn + ',true');
|
||||
followings.unshift('Account address,Show boosts');
|
||||
fileExport(followings.join('\n'), 'export_followings.csv');
|
||||
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.followersSuccess)));
|
||||
dispatch({ type: EXPORT_FOLLOWS_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_FOLLOWS_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function exportBlocks(intl) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: EXPORT_BLOCKS_REQUEST });
|
||||
return api(getState)
|
||||
.get('/api/v1/blocks?limit=40')
|
||||
.then(listAccounts(getState))
|
||||
.then((blocks) => {
|
||||
fileExport(blocks.join('\n'), 'export_block.csv');
|
||||
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.blocksSuccess)));
|
||||
dispatch({ type: EXPORT_BLOCKS_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_BLOCKS_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function exportMutes(intl) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: EXPORT_MUTES_REQUEST });
|
||||
return api(getState)
|
||||
.get('/api/v1/mutes?limit=40')
|
||||
.then(listAccounts(getState))
|
||||
.then((mutes) => {
|
||||
fileExport(mutes.join('\n'), 'export_mutes.csv');
|
||||
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.mutesSuccess)));
|
||||
dispatch({ type: EXPORT_MUTES_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_MUTES_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
113
app/soapbox/actions/export_data.ts
Normal file
|
@ -0,0 +1,113 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import api, { getLinks } from 'soapbox/api';
|
||||
import { normalizeAccount } from 'soapbox/normalizers';
|
||||
|
||||
import type { SnackbarAction } from './snackbar';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { RootState } from 'soapbox/store';
|
||||
|
||||
export const EXPORT_FOLLOWS_REQUEST = 'EXPORT_FOLLOWS_REQUEST';
|
||||
export const EXPORT_FOLLOWS_SUCCESS = 'EXPORT_FOLLOWS_SUCCESS';
|
||||
export const EXPORT_FOLLOWS_FAIL = 'EXPORT_FOLLOWS_FAIL';
|
||||
|
||||
export const EXPORT_BLOCKS_REQUEST = 'EXPORT_BLOCKS_REQUEST';
|
||||
export const EXPORT_BLOCKS_SUCCESS = 'EXPORT_BLOCKS_SUCCESS';
|
||||
export const EXPORT_BLOCKS_FAIL = 'EXPORT_BLOCKS_FAIL';
|
||||
|
||||
export const EXPORT_MUTES_REQUEST = 'EXPORT_MUTES_REQUEST';
|
||||
export const EXPORT_MUTES_SUCCESS = 'EXPORT_MUTES_SUCCESS';
|
||||
export const EXPORT_MUTES_FAIL = 'EXPORT_MUTES_FAIL';
|
||||
|
||||
const messages = defineMessages({
|
||||
blocksSuccess: { id: 'export_data.success.blocks', defaultMessage: 'Blocks exported successfully' },
|
||||
followersSuccess: { id: 'export_data.success.followers', defaultMessage: 'Followers exported successfully' },
|
||||
mutesSuccess: { id: 'export_data.success.mutes', defaultMessage: 'Mutes exported successfully' },
|
||||
});
|
||||
|
||||
type ExportDataActions = {
|
||||
type: typeof EXPORT_FOLLOWS_REQUEST
|
||||
| typeof EXPORT_FOLLOWS_SUCCESS
|
||||
| typeof EXPORT_FOLLOWS_FAIL
|
||||
| typeof EXPORT_BLOCKS_REQUEST
|
||||
| typeof EXPORT_BLOCKS_SUCCESS
|
||||
| typeof EXPORT_BLOCKS_FAIL
|
||||
| typeof EXPORT_MUTES_REQUEST
|
||||
| typeof EXPORT_MUTES_SUCCESS
|
||||
| typeof EXPORT_MUTES_FAIL,
|
||||
error?: any,
|
||||
} | SnackbarAction
|
||||
|
||||
function fileExport(content: string, fileName: string) {
|
||||
const fileToDownload = document.createElement('a');
|
||||
|
||||
fileToDownload.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(content));
|
||||
fileToDownload.setAttribute('download', fileName);
|
||||
fileToDownload.style.display = 'none';
|
||||
document.body.appendChild(fileToDownload);
|
||||
fileToDownload.click();
|
||||
document.body.removeChild(fileToDownload);
|
||||
}
|
||||
|
||||
const listAccounts = (getState: () => RootState) => async(apiResponse: AxiosResponse<any, any>) => {
|
||||
const followings = apiResponse.data;
|
||||
let accounts = [];
|
||||
let next = getLinks(apiResponse).refs.find(link => link.rel === 'next');
|
||||
while (next) {
|
||||
apiResponse = await api(getState).get(next.uri);
|
||||
next = getLinks(apiResponse).refs.find(link => link.rel === 'next');
|
||||
Array.prototype.push.apply(followings, apiResponse.data);
|
||||
}
|
||||
|
||||
accounts = followings.map((account: any) => normalizeAccount(account).fqn);
|
||||
return Array.from(new Set(accounts));
|
||||
};
|
||||
|
||||
export const exportFollows = () => (dispatch: React.Dispatch<ExportDataActions>, getState: () => RootState) => {
|
||||
dispatch({ type: EXPORT_FOLLOWS_REQUEST });
|
||||
const me = getState().me;
|
||||
return api(getState)
|
||||
.get(`/api/v1/accounts/${me}/following?limit=40`)
|
||||
.then(listAccounts(getState))
|
||||
.then((followings) => {
|
||||
followings = followings.map(fqn => fqn + ',true');
|
||||
followings.unshift('Account address,Show boosts');
|
||||
fileExport(followings.join('\n'), 'export_followings.csv');
|
||||
|
||||
dispatch(snackbar.success(messages.followersSuccess));
|
||||
dispatch({ type: EXPORT_FOLLOWS_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_FOLLOWS_FAIL, error });
|
||||
});
|
||||
};
|
||||
|
||||
export const exportBlocks = () => (dispatch: React.Dispatch<ExportDataActions>, getState: () => RootState) => {
|
||||
dispatch({ type: EXPORT_BLOCKS_REQUEST });
|
||||
return api(getState)
|
||||
.get('/api/v1/blocks?limit=40')
|
||||
.then(listAccounts(getState))
|
||||
.then((blocks) => {
|
||||
fileExport(blocks.join('\n'), 'export_block.csv');
|
||||
|
||||
dispatch(snackbar.success(messages.blocksSuccess));
|
||||
dispatch({ type: EXPORT_BLOCKS_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_BLOCKS_FAIL, error });
|
||||
});
|
||||
};
|
||||
|
||||
export const exportMutes = () => (dispatch: React.Dispatch<ExportDataActions>, getState: () => RootState) => {
|
||||
dispatch({ type: EXPORT_MUTES_REQUEST });
|
||||
return api(getState)
|
||||
.get('/api/v1/mutes?limit=40')
|
||||
.then(listAccounts(getState))
|
||||
.then((mutes) => {
|
||||
fileExport(mutes.join('\n'), 'export_mutes.csv');
|
||||
|
||||
dispatch(snackbar.success(messages.mutesSuccess));
|
||||
dispatch({ type: EXPORT_MUTES_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_MUTES_FAIL, error });
|
||||
});
|
||||
};
|
|
@ -18,7 +18,10 @@ import { getQuirks } from 'soapbox/utils/quirks';
|
|||
|
||||
import { baseClient } from '../api';
|
||||
|
||||
const fetchExternalInstance = baseURL => {
|
||||
import type { AppDispatch } from 'soapbox/store';
|
||||
import type { Instance } from 'soapbox/types/entities';
|
||||
|
||||
const fetchExternalInstance = (baseURL?: string) => {
|
||||
return baseClient(null, baseURL)
|
||||
.get('/api/v1/instance')
|
||||
.then(({ data: instance }) => normalizeInstance(instance))
|
||||
|
@ -33,8 +36,8 @@ const fetchExternalInstance = baseURL => {
|
|||
});
|
||||
};
|
||||
|
||||
function createExternalApp(instance, baseURL) {
|
||||
return (dispatch, getState) => {
|
||||
const createExternalApp = (instance: Instance, baseURL?: string) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
// Mitra: skip creating the auth app
|
||||
if (getQuirks(instance).noApps) return new Promise(f => f({}));
|
||||
|
||||
|
@ -49,14 +52,13 @@ function createExternalApp(instance, baseURL) {
|
|||
|
||||
return dispatch(createApp(params, baseURL));
|
||||
};
|
||||
}
|
||||
|
||||
function externalAuthorize(instance, baseURL) {
|
||||
return (dispatch, getState) => {
|
||||
const externalAuthorize = (instance: Instance, baseURL: string) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
const { scopes } = getFeatures(instance);
|
||||
|
||||
return dispatch(createExternalApp(instance, baseURL)).then(app => {
|
||||
const { client_id, redirect_uri } = app;
|
||||
return dispatch(createExternalApp(instance, baseURL)).then((app) => {
|
||||
const { client_id, redirect_uri } = app as Record<string, string>;
|
||||
|
||||
const query = new URLSearchParams({
|
||||
client_id,
|
||||
|
@ -72,58 +74,56 @@ function externalAuthorize(instance, baseURL) {
|
|||
window.location.href = `${baseURL}/oauth/authorize?${query.toString()}`;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function externalEthereumLogin(instance, baseURL) {
|
||||
return (dispatch, getState) => {
|
||||
const loginMessage = instance.get('login_message');
|
||||
const externalEthereumLogin = (instance: Instance, baseURL?: string) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
const loginMessage = instance.login_message;
|
||||
|
||||
return getWalletAndSign(loginMessage).then(({ wallet, signature }) => {
|
||||
return dispatch(createExternalApp(instance, baseURL)).then(app => {
|
||||
return dispatch(createExternalApp(instance, baseURL)).then((app) => {
|
||||
const { client_id, client_secret } = app as Record<string, string>;
|
||||
const params = {
|
||||
grant_type: 'ethereum',
|
||||
wallet_address: wallet.toLowerCase(),
|
||||
client_id: app.client_id,
|
||||
client_secret: app.client_secret,
|
||||
password: signature,
|
||||
client_id: client_id,
|
||||
client_secret: client_secret,
|
||||
password: signature as string,
|
||||
redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
|
||||
scope: getFeatures(instance).scopes,
|
||||
};
|
||||
|
||||
return dispatch(obtainOAuthToken(params, baseURL))
|
||||
.then(token => dispatch(authLoggedIn(token)))
|
||||
.then(({ access_token }) => dispatch(verifyCredentials(access_token, baseURL)))
|
||||
.then(account => dispatch(switchAccount(account.id)))
|
||||
.then((token: Record<string, string | number>) => dispatch(authLoggedIn(token)))
|
||||
.then(({ access_token }: any) => dispatch(verifyCredentials(access_token, baseURL)))
|
||||
.then((account: { id: string }) => dispatch(switchAccount(account.id)))
|
||||
.then(() => window.location.href = '/');
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function externalLogin(host) {
|
||||
return (dispatch, getState) => {
|
||||
export const externalLogin = (host: string) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
const baseURL = parseBaseURL(host) || parseBaseURL(`https://${host}`);
|
||||
|
||||
return fetchExternalInstance(baseURL).then(instance => {
|
||||
return fetchExternalInstance(baseURL).then((instance) => {
|
||||
const features = getFeatures(instance);
|
||||
const quirks = getQuirks(instance);
|
||||
|
||||
if (features.ethereumLogin && quirks.noOAuthForm) {
|
||||
return dispatch(externalEthereumLogin(instance, baseURL));
|
||||
dispatch(externalEthereumLogin(instance, baseURL));
|
||||
} else {
|
||||
return dispatch(externalAuthorize(instance, baseURL));
|
||||
dispatch(externalAuthorize(instance, baseURL));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function loginWithCode(code) {
|
||||
return (dispatch, getState) => {
|
||||
const { client_id, client_secret, redirect_uri } = JSON.parse(localStorage.getItem('soapbox:external:app'));
|
||||
const baseURL = localStorage.getItem('soapbox:external:baseurl');
|
||||
const scope = localStorage.getItem('soapbox:external:scopes');
|
||||
export const loginWithCode = (code: string) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
const { client_id, client_secret, redirect_uri } = JSON.parse(localStorage.getItem('soapbox:external:app')!);
|
||||
const baseURL = localStorage.getItem('soapbox:external:baseurl')!;
|
||||
const scope = localStorage.getItem('soapbox:external:scopes')!;
|
||||
|
||||
const params = {
|
||||
const params: Record<string, string> = {
|
||||
client_id,
|
||||
client_secret,
|
||||
redirect_uri,
|
||||
|
@ -133,9 +133,8 @@ export function loginWithCode(code) {
|
|||
};
|
||||
|
||||
return dispatch(obtainOAuthToken(params, baseURL))
|
||||
.then(token => dispatch(authLoggedIn(token)))
|
||||
.then(({ access_token }) => dispatch(verifyCredentials(access_token, baseURL)))
|
||||
.then(account => dispatch(switchAccount(account.id)))
|
||||
.then((token: Record<string, string | number>) => dispatch(authLoggedIn(token)))
|
||||
.then(({ access_token }: any) => dispatch(verifyCredentials(access_token as string, baseURL)))
|
||||
.then((account: { id: string }) => dispatch(switchAccount(account.id)))
|
||||
.then(() => window.location.href = '/');
|
||||
};
|
||||
}
|
59
app/soapbox/actions/familiar_followers.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { RootState } from 'soapbox/store';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
import { ACCOUNTS_IMPORT, importFetchedAccounts } from './importer';
|
||||
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
export const FAMILIAR_FOLLOWERS_FETCH_REQUEST = 'FAMILIAR_FOLLOWERS_FETCH_REQUEST';
|
||||
export const FAMILIAR_FOLLOWERS_FETCH_SUCCESS = 'FAMILIAR_FOLLOWERS_FETCH_SUCCESS';
|
||||
export const FAMILIAR_FOLLOWERS_FETCH_FAIL = 'FAMILIAR_FOLLOWERS_FETCH_FAIL';
|
||||
|
||||
type FamiliarFollowersFetchRequestAction = {
|
||||
type: typeof FAMILIAR_FOLLOWERS_FETCH_REQUEST,
|
||||
id: string,
|
||||
}
|
||||
|
||||
type FamiliarFollowersFetchRequestSuccessAction = {
|
||||
type: typeof FAMILIAR_FOLLOWERS_FETCH_SUCCESS,
|
||||
id: string,
|
||||
accounts: Array<APIEntity>,
|
||||
}
|
||||
|
||||
type FamiliarFollowersFetchRequestFailAction = {
|
||||
type: typeof FAMILIAR_FOLLOWERS_FETCH_FAIL,
|
||||
id: string,
|
||||
error: any,
|
||||
}
|
||||
|
||||
type AccountsImportAction = {
|
||||
type: typeof ACCOUNTS_IMPORT,
|
||||
accounts: Array<APIEntity>,
|
||||
}
|
||||
|
||||
export type FamiliarFollowersActions = FamiliarFollowersFetchRequestAction | FamiliarFollowersFetchRequestSuccessAction | FamiliarFollowersFetchRequestFailAction | AccountsImportAction
|
||||
|
||||
export const fetchAccountFamiliarFollowers = (accountId: string) => (dispatch: React.Dispatch<FamiliarFollowersActions>, getState: () => RootState) => {
|
||||
dispatch({
|
||||
type: FAMILIAR_FOLLOWERS_FETCH_REQUEST,
|
||||
id: accountId,
|
||||
});
|
||||
|
||||
api(getState).get(`/api/v1/accounts/familiar_followers?id=${accountId}`)
|
||||
.then(({ data }) => {
|
||||
const accounts = data.find(({ id }: { id: string }) => id === accountId).accounts;
|
||||
|
||||
dispatch(importFetchedAccounts(accounts) as AccountsImportAction);
|
||||
dispatch({
|
||||
type: FAMILIAR_FOLLOWERS_FETCH_SUCCESS,
|
||||
id: accountId,
|
||||
accounts,
|
||||
});
|
||||
})
|
||||
.catch(error => dispatch({
|
||||
type: FAMILIAR_FOLLOWERS_FETCH_FAIL,
|
||||
id: accountId,
|
||||
error,
|
||||
}));
|
||||
};
|
|
@ -1,201 +0,0 @@
|
|||
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_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS';
|
||||
export const FAVOURITED_STATUSES_FETCH_FAIL = 'FAVOURITED_STATUSES_FETCH_FAIL';
|
||||
|
||||
export const FAVOURITED_STATUSES_EXPAND_REQUEST = 'FAVOURITED_STATUSES_EXPAND_REQUEST';
|
||||
export const FAVOURITED_STATUSES_EXPAND_SUCCESS = 'FAVOURITED_STATUSES_EXPAND_SUCCESS';
|
||||
export const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FAIL';
|
||||
|
||||
export const ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST';
|
||||
export const ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS';
|
||||
export const ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL';
|
||||
|
||||
export const ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST';
|
||||
export const ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS';
|
||||
export const ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL';
|
||||
|
||||
export function fetchFavouritedStatuses() {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
if (getState().getIn(['status_lists', 'favourites', 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchFavouritedStatusesRequest());
|
||||
|
||||
api(getState).get('/api/v1/favourites').then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(fetchFavouritedStatusesFail(error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchFavouritedStatusesRequest() {
|
||||
return {
|
||||
type: FAVOURITED_STATUSES_FETCH_REQUEST,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchFavouritedStatusesSuccess(statuses, next) {
|
||||
return {
|
||||
type: FAVOURITED_STATUSES_FETCH_SUCCESS,
|
||||
statuses,
|
||||
next,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchFavouritedStatusesFail(error) {
|
||||
return {
|
||||
type: FAVOURITED_STATUSES_FETCH_FAIL,
|
||||
error,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandFavouritedStatuses() {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const url = getState().getIn(['status_lists', 'favourites', 'next'], null);
|
||||
|
||||
if (url === null || getState().getIn(['status_lists', 'favourites', 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandFavouritedStatusesRequest());
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(expandFavouritedStatusesFail(error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function expandFavouritedStatusesRequest() {
|
||||
return {
|
||||
type: FAVOURITED_STATUSES_EXPAND_REQUEST,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandFavouritedStatusesSuccess(statuses, next) {
|
||||
return {
|
||||
type: FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||
statuses,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandFavouritedStatusesFail(error) {
|
||||
return {
|
||||
type: FAVOURITED_STATUSES_EXPAND_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAccountFavouritedStatuses(accountId) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
if (getState().getIn(['status_lists', `favourites:${accountId}`, 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchAccountFavouritedStatusesRequest(accountId));
|
||||
|
||||
api(getState).get(`/api/v1/pleroma/accounts/${accountId}/favourites`).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(fetchAccountFavouritedStatusesSuccess(accountId, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(fetchAccountFavouritedStatusesFail(accountId, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAccountFavouritedStatusesRequest(accountId) {
|
||||
return {
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST,
|
||||
accountId,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAccountFavouritedStatusesSuccess(accountId, statuses, next) {
|
||||
return {
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS,
|
||||
accountId,
|
||||
statuses,
|
||||
next,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAccountFavouritedStatusesFail(accountId, error) {
|
||||
return {
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL,
|
||||
accountId,
|
||||
error,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandAccountFavouritedStatuses(accountId) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const url = getState().getIn(['status_lists', `favourites:${accountId}`, 'next'], null);
|
||||
|
||||
if (url === null || getState().getIn(['status_lists', `favourites:${accountId}`, 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandAccountFavouritedStatusesRequest(accountId));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(expandAccountFavouritedStatusesSuccess(accountId, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(expandAccountFavouritedStatusesFail(accountId, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function expandAccountFavouritedStatusesRequest(accountId) {
|
||||
return {
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST,
|
||||
accountId,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandAccountFavouritedStatusesSuccess(accountId, statuses, next) {
|
||||
return {
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||
accountId,
|
||||
statuses,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandAccountFavouritedStatusesFail(accountId, error) {
|
||||
return {
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL,
|
||||
accountId,
|
||||
error,
|
||||
};
|
||||
}
|
208
app/soapbox/actions/favourites.ts
Normal file
|
@ -0,0 +1,208 @@
|
|||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import api, { getLinks } from '../api';
|
||||
|
||||
import { importFetchedStatuses } from './importer';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST';
|
||||
const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS';
|
||||
const FAVOURITED_STATUSES_FETCH_FAIL = 'FAVOURITED_STATUSES_FETCH_FAIL';
|
||||
|
||||
const FAVOURITED_STATUSES_EXPAND_REQUEST = 'FAVOURITED_STATUSES_EXPAND_REQUEST';
|
||||
const FAVOURITED_STATUSES_EXPAND_SUCCESS = 'FAVOURITED_STATUSES_EXPAND_SUCCESS';
|
||||
const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FAIL';
|
||||
|
||||
const ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST';
|
||||
const ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS';
|
||||
const ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL';
|
||||
|
||||
const ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST';
|
||||
const ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS';
|
||||
const ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL';
|
||||
|
||||
const fetchFavouritedStatuses = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
if (getState().status_lists.get('favourites')?.isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchFavouritedStatusesRequest());
|
||||
|
||||
api(getState).get('/api/v1/favourites').then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(fetchFavouritedStatusesFail(error));
|
||||
});
|
||||
};
|
||||
|
||||
const fetchFavouritedStatusesRequest = () => ({
|
||||
type: FAVOURITED_STATUSES_FETCH_REQUEST,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const fetchFavouritedStatusesSuccess = (statuses: APIEntity[], next: string | null) => ({
|
||||
type: FAVOURITED_STATUSES_FETCH_SUCCESS,
|
||||
statuses,
|
||||
next,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const fetchFavouritedStatusesFail = (error: AxiosError) => ({
|
||||
type: FAVOURITED_STATUSES_FETCH_FAIL,
|
||||
error,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const expandFavouritedStatuses = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const url = getState().status_lists.get('favourites')?.next || null;
|
||||
|
||||
if (url === null || getState().status_lists.get('favourites')?.isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandFavouritedStatusesRequest());
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(expandFavouritedStatusesFail(error));
|
||||
});
|
||||
};
|
||||
|
||||
const expandFavouritedStatusesRequest = () => ({
|
||||
type: FAVOURITED_STATUSES_EXPAND_REQUEST,
|
||||
});
|
||||
|
||||
const expandFavouritedStatusesSuccess = (statuses: APIEntity[], next: string | null) => ({
|
||||
type: FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||
statuses,
|
||||
next,
|
||||
});
|
||||
|
||||
const expandFavouritedStatusesFail = (error: AxiosError) => ({
|
||||
type: FAVOURITED_STATUSES_EXPAND_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
const fetchAccountFavouritedStatuses = (accountId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
if (getState().status_lists.get(`favourites:${accountId}`)?.isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchAccountFavouritedStatusesRequest(accountId));
|
||||
|
||||
api(getState).get(`/api/v1/pleroma/accounts/${accountId}/favourites`).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(fetchAccountFavouritedStatusesSuccess(accountId, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(fetchAccountFavouritedStatusesFail(accountId, error));
|
||||
});
|
||||
};
|
||||
|
||||
const fetchAccountFavouritedStatusesRequest = (accountId: string) => ({
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST,
|
||||
accountId,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const fetchAccountFavouritedStatusesSuccess = (accountId: string, statuses: APIEntity, next: string | null) => ({
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS,
|
||||
accountId,
|
||||
statuses,
|
||||
next,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const fetchAccountFavouritedStatusesFail = (accountId: string, error: AxiosError) => ({
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL,
|
||||
accountId,
|
||||
error,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const expandAccountFavouritedStatuses = (accountId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const url = getState().status_lists.get(`favourites:${accountId}`)?.next || null;
|
||||
|
||||
if (url === null || getState().status_lists.get(`favourites:${accountId}`)?.isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandAccountFavouritedStatusesRequest(accountId));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(importFetchedStatuses(response.data));
|
||||
dispatch(expandAccountFavouritedStatusesSuccess(accountId, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(expandAccountFavouritedStatusesFail(accountId, error));
|
||||
});
|
||||
};
|
||||
|
||||
const expandAccountFavouritedStatusesRequest = (accountId: string) => ({
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST,
|
||||
accountId,
|
||||
});
|
||||
|
||||
const expandAccountFavouritedStatusesSuccess = (accountId: string, statuses: APIEntity[], next: string | null) => ({
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||
accountId,
|
||||
statuses,
|
||||
next,
|
||||
});
|
||||
|
||||
const expandAccountFavouritedStatusesFail = (accountId: string, error: AxiosError) => ({
|
||||
type: ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL,
|
||||
accountId,
|
||||
error,
|
||||
});
|
||||
|
||||
export {
|
||||
FAVOURITED_STATUSES_FETCH_REQUEST,
|
||||
FAVOURITED_STATUSES_FETCH_SUCCESS,
|
||||
FAVOURITED_STATUSES_FETCH_FAIL,
|
||||
FAVOURITED_STATUSES_EXPAND_REQUEST,
|
||||
FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||
FAVOURITED_STATUSES_EXPAND_FAIL,
|
||||
ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST,
|
||||
ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS,
|
||||
ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL,
|
||||
ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST,
|
||||
ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||
ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL,
|
||||
fetchFavouritedStatuses,
|
||||
fetchFavouritedStatusesRequest,
|
||||
fetchFavouritedStatusesSuccess,
|
||||
fetchFavouritedStatusesFail,
|
||||
expandFavouritedStatuses,
|
||||
expandFavouritedStatusesRequest,
|
||||
expandFavouritedStatusesSuccess,
|
||||
expandFavouritedStatusesFail,
|
||||
fetchAccountFavouritedStatuses,
|
||||
fetchAccountFavouritedStatusesRequest,
|
||||
fetchAccountFavouritedStatusesSuccess,
|
||||
fetchAccountFavouritedStatusesFail,
|
||||
expandAccountFavouritedStatuses,
|
||||
expandAccountFavouritedStatusesRequest,
|
||||
expandAccountFavouritedStatusesSuccess,
|
||||
expandAccountFavouritedStatusesFail,
|
||||
};
|
|
@ -1,77 +0,0 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
export const FILTERS_FETCH_REQUEST = 'FILTERS_FETCH_REQUEST';
|
||||
export const FILTERS_FETCH_SUCCESS = 'FILTERS_FETCH_SUCCESS';
|
||||
export const FILTERS_FETCH_FAIL = 'FILTERS_FETCH_FAIL';
|
||||
|
||||
export const FILTERS_CREATE_REQUEST = 'FILTERS_CREATE_REQUEST';
|
||||
export const FILTERS_CREATE_SUCCESS = 'FILTERS_CREATE_SUCCESS';
|
||||
export const FILTERS_CREATE_FAIL = 'FILTERS_CREATE_FAIL';
|
||||
|
||||
export const FILTERS_DELETE_REQUEST = 'FILTERS_DELETE_REQUEST';
|
||||
export const FILTERS_DELETE_SUCCESS = 'FILTERS_DELETE_SUCCESS';
|
||||
export const FILTERS_DELETE_FAIL = 'FILTERS_DELETE_FAIL';
|
||||
|
||||
const messages = defineMessages({
|
||||
added: { id: 'filters.added', defaultMessage: 'Filter added.' },
|
||||
removed: { id: 'filters.removed', defaultMessage: 'Filter deleted.' },
|
||||
});
|
||||
|
||||
export const fetchFilters = () => (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch({
|
||||
type: FILTERS_FETCH_REQUEST,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
api(getState)
|
||||
.get('/api/v1/filters')
|
||||
.then(({ data }) => dispatch({
|
||||
type: FILTERS_FETCH_SUCCESS,
|
||||
filters: data,
|
||||
skipLoading: true,
|
||||
}))
|
||||
.catch(err => dispatch({
|
||||
type: FILTERS_FETCH_FAIL,
|
||||
err,
|
||||
skipLoading: true,
|
||||
skipAlert: true,
|
||||
}));
|
||||
};
|
||||
|
||||
export function createFilter(intl, phrase, expires_at, context, whole_word, irreversible) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: FILTERS_CREATE_REQUEST });
|
||||
return api(getState).post('/api/v1/filters', {
|
||||
phrase,
|
||||
context,
|
||||
irreversible,
|
||||
whole_word,
|
||||
expires_at,
|
||||
}).then(response => {
|
||||
dispatch({ type: FILTERS_CREATE_SUCCESS, filter: response.data });
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.added)));
|
||||
}).catch(error => {
|
||||
dispatch({ type: FILTERS_CREATE_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function deleteFilter(intl, id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: FILTERS_DELETE_REQUEST });
|
||||
return api(getState).delete('/api/v1/filters/'+id).then(response => {
|
||||
dispatch({ type: FILTERS_DELETE_SUCCESS, filter: response.data });
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.removed)));
|
||||
}).catch(error => {
|
||||
dispatch({ type: FILTERS_DELETE_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
99
app/soapbox/actions/filters.ts
Normal file
|
@ -0,0 +1,99 @@
|
|||
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 type { AppDispatch, RootState } from 'soapbox/store';
|
||||
|
||||
const FILTERS_FETCH_REQUEST = 'FILTERS_FETCH_REQUEST';
|
||||
const FILTERS_FETCH_SUCCESS = 'FILTERS_FETCH_SUCCESS';
|
||||
const FILTERS_FETCH_FAIL = 'FILTERS_FETCH_FAIL';
|
||||
|
||||
const FILTERS_CREATE_REQUEST = 'FILTERS_CREATE_REQUEST';
|
||||
const FILTERS_CREATE_SUCCESS = 'FILTERS_CREATE_SUCCESS';
|
||||
const FILTERS_CREATE_FAIL = 'FILTERS_CREATE_FAIL';
|
||||
|
||||
const FILTERS_DELETE_REQUEST = 'FILTERS_DELETE_REQUEST';
|
||||
const FILTERS_DELETE_SUCCESS = 'FILTERS_DELETE_SUCCESS';
|
||||
const FILTERS_DELETE_FAIL = 'FILTERS_DELETE_FAIL';
|
||||
|
||||
const messages = defineMessages({
|
||||
added: { id: 'filters.added', defaultMessage: 'Filter added.' },
|
||||
removed: { id: 'filters.removed', defaultMessage: 'Filter deleted.' },
|
||||
});
|
||||
|
||||
const fetchFilters = () =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const state = getState();
|
||||
const instance = state.instance;
|
||||
const features = getFeatures(instance);
|
||||
|
||||
if (!features.filters) return;
|
||||
|
||||
dispatch({
|
||||
type: FILTERS_FETCH_REQUEST,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
api(getState)
|
||||
.get('/api/v1/filters')
|
||||
.then(({ data }) => dispatch({
|
||||
type: FILTERS_FETCH_SUCCESS,
|
||||
filters: data,
|
||||
skipLoading: true,
|
||||
}))
|
||||
.catch(err => dispatch({
|
||||
type: FILTERS_FETCH_FAIL,
|
||||
err,
|
||||
skipLoading: true,
|
||||
skipAlert: true,
|
||||
}));
|
||||
};
|
||||
|
||||
const createFilter = (phrase: string, expires_at: string, context: Array<string>, whole_word: boolean, irreversible: boolean) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: FILTERS_CREATE_REQUEST });
|
||||
return api(getState).post('/api/v1/filters', {
|
||||
phrase,
|
||||
context,
|
||||
irreversible,
|
||||
whole_word,
|
||||
expires_at,
|
||||
}).then(response => {
|
||||
dispatch({ type: FILTERS_CREATE_SUCCESS, filter: response.data });
|
||||
dispatch(snackbar.success(messages.added));
|
||||
}).catch(error => {
|
||||
dispatch({ type: FILTERS_CREATE_FAIL, error });
|
||||
});
|
||||
};
|
||||
|
||||
const deleteFilter = (id: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: FILTERS_DELETE_REQUEST });
|
||||
return api(getState).delete(`/api/v1/filters/${id}`).then(response => {
|
||||
dispatch({ type: FILTERS_DELETE_SUCCESS, filter: response.data });
|
||||
dispatch(snackbar.success(messages.removed));
|
||||
}).catch(error => {
|
||||
dispatch({ type: FILTERS_DELETE_FAIL, error });
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
FILTERS_FETCH_REQUEST,
|
||||
FILTERS_FETCH_SUCCESS,
|
||||
FILTERS_FETCH_FAIL,
|
||||
FILTERS_CREATE_REQUEST,
|
||||
FILTERS_CREATE_SUCCESS,
|
||||
FILTERS_CREATE_FAIL,
|
||||
FILTERS_DELETE_REQUEST,
|
||||
FILTERS_DELETE_SUCCESS,
|
||||
FILTERS_DELETE_FAIL,
|
||||
fetchFilters,
|
||||
createFilter,
|
||||
deleteFilter,
|
||||
};
|
|
@ -1,114 +0,0 @@
|
|||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
export const GROUP_CREATE_REQUEST = 'GROUP_CREATE_REQUEST';
|
||||
export const GROUP_CREATE_SUCCESS = 'GROUP_CREATE_SUCCESS';
|
||||
export const GROUP_CREATE_FAIL = 'GROUP_CREATE_FAIL';
|
||||
|
||||
export const GROUP_UPDATE_REQUEST = 'GROUP_UPDATE_REQUEST';
|
||||
export const GROUP_UPDATE_SUCCESS = 'GROUP_UPDATE_SUCCESS';
|
||||
export const GROUP_UPDATE_FAIL = 'GROUP_UPDATE_FAIL';
|
||||
|
||||
export const GROUP_EDITOR_VALUE_CHANGE = 'GROUP_EDITOR_VALUE_CHANGE';
|
||||
export const GROUP_EDITOR_RESET = 'GROUP_EDITOR_RESET';
|
||||
export const GROUP_EDITOR_SETUP = 'GROUP_EDITOR_SETUP';
|
||||
|
||||
export const submit = (routerHistory) => (dispatch, getState) => {
|
||||
const groupId = getState().getIn(['group_editor', 'groupId']);
|
||||
const title = getState().getIn(['group_editor', 'title']);
|
||||
const description = getState().getIn(['group_editor', 'description']);
|
||||
const coverImage = getState().getIn(['group_editor', 'coverImage']);
|
||||
|
||||
if (groupId === null) {
|
||||
dispatch(create(title, description, coverImage, routerHistory));
|
||||
} else {
|
||||
dispatch(update(groupId, title, description, coverImage, routerHistory));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const create = (title, description, coverImage, routerHistory) => (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(createRequest());
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('title', title);
|
||||
formData.append('description', description);
|
||||
|
||||
if (coverImage !== null) {
|
||||
formData.append('cover_image', coverImage);
|
||||
}
|
||||
|
||||
api(getState).post('/api/v1/groups', formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(({ data }) => {
|
||||
dispatch(createSuccess(data));
|
||||
routerHistory.push(`/groups/${data.id}`);
|
||||
}).catch(err => dispatch(createFail(err)));
|
||||
};
|
||||
|
||||
|
||||
export const createRequest = id => ({
|
||||
type: GROUP_CREATE_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
export const createSuccess = group => ({
|
||||
type: GROUP_CREATE_SUCCESS,
|
||||
group,
|
||||
});
|
||||
|
||||
export const createFail = error => ({
|
||||
type: GROUP_CREATE_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export const update = (groupId, title, description, coverImage, routerHistory) => (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(updateRequest());
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('title', title);
|
||||
formData.append('description', description);
|
||||
|
||||
if (coverImage !== null) {
|
||||
formData.append('cover_image', coverImage);
|
||||
}
|
||||
|
||||
api(getState).put(`/api/v1/groups/${groupId}`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(({ data }) => {
|
||||
dispatch(updateSuccess(data));
|
||||
routerHistory.push(`/groups/${data.id}`);
|
||||
}).catch(err => dispatch(updateFail(err)));
|
||||
};
|
||||
|
||||
|
||||
export const updateRequest = id => ({
|
||||
type: GROUP_UPDATE_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
export const updateSuccess = group => ({
|
||||
type: GROUP_UPDATE_SUCCESS,
|
||||
group,
|
||||
});
|
||||
|
||||
export const updateFail = error => ({
|
||||
type: GROUP_UPDATE_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export const changeValue = (field, value) => ({
|
||||
type: GROUP_EDITOR_VALUE_CHANGE,
|
||||
field,
|
||||
value,
|
||||
});
|
||||
|
||||
export const reset = () => ({
|
||||
type: GROUP_EDITOR_RESET,
|
||||
});
|
||||
|
||||
export const setUp = (group) => ({
|
||||
type: GROUP_EDITOR_SETUP,
|
||||
group,
|
||||
});
|
143
app/soapbox/actions/group_editor.ts
Normal file
|
@ -0,0 +1,143 @@
|
|||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { History } from 'history';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
const GROUP_CREATE_REQUEST = 'GROUP_CREATE_REQUEST';
|
||||
const GROUP_CREATE_SUCCESS = 'GROUP_CREATE_SUCCESS';
|
||||
const GROUP_CREATE_FAIL = 'GROUP_CREATE_FAIL';
|
||||
|
||||
const GROUP_UPDATE_REQUEST = 'GROUP_UPDATE_REQUEST';
|
||||
const GROUP_UPDATE_SUCCESS = 'GROUP_UPDATE_SUCCESS';
|
||||
const GROUP_UPDATE_FAIL = 'GROUP_UPDATE_FAIL';
|
||||
|
||||
const GROUP_EDITOR_VALUE_CHANGE = 'GROUP_EDITOR_VALUE_CHANGE';
|
||||
const GROUP_EDITOR_RESET = 'GROUP_EDITOR_RESET';
|
||||
const GROUP_EDITOR_SETUP = 'GROUP_EDITOR_SETUP';
|
||||
|
||||
const submit = (routerHistory: History) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const groupId = getState().group_editor.get('groupId') as string;
|
||||
const title = getState().group_editor.get('title') as string;
|
||||
const description = getState().group_editor.get('description') as string;
|
||||
const coverImage = getState().group_editor.get('coverImage') as any;
|
||||
|
||||
if (groupId === null) {
|
||||
dispatch(create(title, description, coverImage, routerHistory));
|
||||
} else {
|
||||
dispatch(update(groupId, title, description, coverImage, routerHistory));
|
||||
}
|
||||
};
|
||||
|
||||
const create = (title: string, description: string, coverImage: File, routerHistory: History) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(createRequest());
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('title', title);
|
||||
formData.append('description', description);
|
||||
|
||||
if (coverImage !== null) {
|
||||
formData.append('cover_image', coverImage);
|
||||
}
|
||||
|
||||
api(getState).post('/api/v1/groups', formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(({ data }) => {
|
||||
dispatch(createSuccess(data));
|
||||
routerHistory.push(`/groups/${data.id}`);
|
||||
}).catch(err => dispatch(createFail(err)));
|
||||
};
|
||||
|
||||
const createRequest = (id?: string) => ({
|
||||
type: GROUP_CREATE_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
const createSuccess = (group: APIEntity) => ({
|
||||
type: GROUP_CREATE_SUCCESS,
|
||||
group,
|
||||
});
|
||||
|
||||
const createFail = (error: AxiosError) => ({
|
||||
type: GROUP_CREATE_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
const update = (groupId: string, title: string, description: string, coverImage: File, routerHistory: History) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(updateRequest(groupId));
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('title', title);
|
||||
formData.append('description', description);
|
||||
|
||||
if (coverImage !== null) {
|
||||
formData.append('cover_image', coverImage);
|
||||
}
|
||||
|
||||
api(getState).put(`/api/v1/groups/${groupId}`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(({ data }) => {
|
||||
dispatch(updateSuccess(data));
|
||||
routerHistory.push(`/groups/${data.id}`);
|
||||
}).catch(err => dispatch(updateFail(err)));
|
||||
};
|
||||
|
||||
const updateRequest = (id: string) => ({
|
||||
type: GROUP_UPDATE_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
const updateSuccess = (group: APIEntity) => ({
|
||||
type: GROUP_UPDATE_SUCCESS,
|
||||
group,
|
||||
});
|
||||
|
||||
const updateFail = (error: AxiosError) => ({
|
||||
type: GROUP_UPDATE_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
const changeValue = (field: string, value: string | File) => ({
|
||||
type: GROUP_EDITOR_VALUE_CHANGE,
|
||||
field,
|
||||
value,
|
||||
});
|
||||
|
||||
const reset = () => ({
|
||||
type: GROUP_EDITOR_RESET,
|
||||
});
|
||||
|
||||
const setUp = (group: string) => ({
|
||||
type: GROUP_EDITOR_SETUP,
|
||||
group,
|
||||
});
|
||||
|
||||
export {
|
||||
GROUP_CREATE_REQUEST,
|
||||
GROUP_CREATE_SUCCESS,
|
||||
GROUP_CREATE_FAIL,
|
||||
GROUP_UPDATE_REQUEST,
|
||||
GROUP_UPDATE_SUCCESS,
|
||||
GROUP_UPDATE_FAIL,
|
||||
GROUP_EDITOR_VALUE_CHANGE,
|
||||
GROUP_EDITOR_RESET,
|
||||
GROUP_EDITOR_SETUP,
|
||||
submit,
|
||||
create,
|
||||
createRequest,
|
||||
createSuccess,
|
||||
createFail,
|
||||
update,
|
||||
updateRequest,
|
||||
updateSuccess,
|
||||
updateFail,
|
||||
changeValue,
|
||||
reset,
|
||||
setUp,
|
||||
};
|
|
@ -1,526 +0,0 @@
|
|||
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_SUCCESS = 'GROUP_FETCH_SUCCESS';
|
||||
export const GROUP_FETCH_FAIL = 'GROUP_FETCH_FAIL';
|
||||
|
||||
export const GROUP_RELATIONSHIPS_FETCH_REQUEST = 'GROUP_RELATIONSHIPS_FETCH_REQUEST';
|
||||
export const GROUP_RELATIONSHIPS_FETCH_SUCCESS = 'GROUP_RELATIONSHIPS_FETCH_SUCCESS';
|
||||
export const GROUP_RELATIONSHIPS_FETCH_FAIL = 'GROUP_RELATIONSHIPS_FETCH_FAIL';
|
||||
|
||||
export const GROUPS_FETCH_REQUEST = 'GROUPS_FETCH_REQUEST';
|
||||
export const GROUPS_FETCH_SUCCESS = 'GROUPS_FETCH_SUCCESS';
|
||||
export const GROUPS_FETCH_FAIL = 'GROUPS_FETCH_FAIL';
|
||||
|
||||
export const GROUP_JOIN_REQUEST = 'GROUP_JOIN_REQUEST';
|
||||
export const GROUP_JOIN_SUCCESS = 'GROUP_JOIN_SUCCESS';
|
||||
export const GROUP_JOIN_FAIL = 'GROUP_JOIN_FAIL';
|
||||
|
||||
export const GROUP_LEAVE_REQUEST = 'GROUP_LEAVE_REQUEST';
|
||||
export const GROUP_LEAVE_SUCCESS = 'GROUP_LEAVE_SUCCESS';
|
||||
export const GROUP_LEAVE_FAIL = 'GROUP_LEAVE_FAIL';
|
||||
|
||||
export const GROUP_MEMBERS_FETCH_REQUEST = 'GROUP_MEMBERS_FETCH_REQUEST';
|
||||
export const GROUP_MEMBERS_FETCH_SUCCESS = 'GROUP_MEMBERS_FETCH_SUCCESS';
|
||||
export const GROUP_MEMBERS_FETCH_FAIL = 'GROUP_MEMBERS_FETCH_FAIL';
|
||||
|
||||
export const GROUP_MEMBERS_EXPAND_REQUEST = 'GROUP_MEMBERS_EXPAND_REQUEST';
|
||||
export const GROUP_MEMBERS_EXPAND_SUCCESS = 'GROUP_MEMBERS_EXPAND_SUCCESS';
|
||||
export const GROUP_MEMBERS_EXPAND_FAIL = 'GROUP_MEMBERS_EXPAND_FAIL';
|
||||
|
||||
export const GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST = 'GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST';
|
||||
export const GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS';
|
||||
export const GROUP_REMOVED_ACCOUNTS_FETCH_FAIL = 'GROUP_REMOVED_ACCOUNTS_FETCH_FAIL';
|
||||
|
||||
export const GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST = 'GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST';
|
||||
export const GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS';
|
||||
export const GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL = 'GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL';
|
||||
|
||||
export const GROUP_REMOVED_ACCOUNTS_REMOVE_REQUEST = 'GROUP_REMOVED_ACCOUNTS_REMOVE_REQUEST';
|
||||
export const GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS';
|
||||
export const GROUP_REMOVED_ACCOUNTS_REMOVE_FAIL = 'GROUP_REMOVED_ACCOUNTS_REMOVE_FAIL';
|
||||
|
||||
export const GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST = 'GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST';
|
||||
export const GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS';
|
||||
export const GROUP_REMOVED_ACCOUNTS_CREATE_FAIL = 'GROUP_REMOVED_ACCOUNTS_CREATE_FAIL';
|
||||
|
||||
export const GROUP_REMOVE_STATUS_REQUEST = 'GROUP_REMOVE_STATUS_REQUEST';
|
||||
export const GROUP_REMOVE_STATUS_SUCCESS = 'GROUP_REMOVE_STATUS_SUCCESS';
|
||||
export const GROUP_REMOVE_STATUS_FAIL = 'GROUP_REMOVE_STATUS_FAIL';
|
||||
|
||||
export const fetchGroup = id => (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(fetchGroupRelationships([id]));
|
||||
|
||||
if (getState().getIn(['groups', id])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchGroupRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/groups/${id}`)
|
||||
.then(({ data }) => dispatch(fetchGroupSuccess(data)))
|
||||
.catch(err => dispatch(fetchGroupFail(id, err)));
|
||||
};
|
||||
|
||||
export const fetchGroupRequest = id => ({
|
||||
type: GROUP_FETCH_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
export const fetchGroupSuccess = group => ({
|
||||
type: GROUP_FETCH_SUCCESS,
|
||||
group,
|
||||
});
|
||||
|
||||
export const fetchGroupFail = (id, error) => ({
|
||||
type: GROUP_FETCH_FAIL,
|
||||
id,
|
||||
error,
|
||||
});
|
||||
|
||||
export function fetchGroupRelationships(groupIds) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const loadedRelationships = getState().get('group_relationships');
|
||||
const newGroupIds = groupIds.filter(id => loadedRelationships.get(id, null) === null);
|
||||
|
||||
if (newGroupIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchGroupRelationshipsRequest(newGroupIds));
|
||||
|
||||
api(getState).get(`/api/v1/groups/${newGroupIds[0]}/relationships?${newGroupIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
|
||||
dispatch(fetchGroupRelationshipsSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(fetchGroupRelationshipsFail(error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchGroupRelationshipsRequest(ids) {
|
||||
return {
|
||||
type: GROUP_RELATIONSHIPS_FETCH_REQUEST,
|
||||
ids,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchGroupRelationshipsSuccess(relationships) {
|
||||
return {
|
||||
type: GROUP_RELATIONSHIPS_FETCH_SUCCESS,
|
||||
relationships,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchGroupRelationshipsFail(error) {
|
||||
return {
|
||||
type: GROUP_RELATIONSHIPS_FETCH_FAIL,
|
||||
error,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export const fetchGroups = (tab) => (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(fetchGroupsRequest());
|
||||
|
||||
api(getState).get('/api/v1/groups?tab=' + tab)
|
||||
.then(({ data }) => {
|
||||
dispatch(fetchGroupsSuccess(data, tab));
|
||||
dispatch(fetchGroupRelationships(data.map(item => item.id)));
|
||||
})
|
||||
.catch(err => dispatch(fetchGroupsFail(err)));
|
||||
};
|
||||
|
||||
export const fetchGroupsRequest = () => ({
|
||||
type: GROUPS_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
export const fetchGroupsSuccess = (groups, tab) => ({
|
||||
type: GROUPS_FETCH_SUCCESS,
|
||||
groups,
|
||||
tab,
|
||||
});
|
||||
|
||||
export const fetchGroupsFail = error => ({
|
||||
type: GROUPS_FETCH_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export function joinGroup(id) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(joinGroupRequest(id));
|
||||
|
||||
api(getState).post(`/api/v1/groups/${id}/accounts`).then(response => {
|
||||
dispatch(joinGroupSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(joinGroupFail(id, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function leaveGroup(id) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(leaveGroupRequest(id));
|
||||
|
||||
api(getState).delete(`/api/v1/groups/${id}/accounts`).then(response => {
|
||||
dispatch(leaveGroupSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(leaveGroupFail(id, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function joinGroupRequest(id) {
|
||||
return {
|
||||
type: GROUP_JOIN_REQUEST,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function joinGroupSuccess(relationship) {
|
||||
return {
|
||||
type: GROUP_JOIN_SUCCESS,
|
||||
relationship,
|
||||
};
|
||||
}
|
||||
|
||||
export function joinGroupFail(error) {
|
||||
return {
|
||||
type: GROUP_JOIN_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function leaveGroupRequest(id) {
|
||||
return {
|
||||
type: GROUP_LEAVE_REQUEST,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function leaveGroupSuccess(relationship) {
|
||||
return {
|
||||
type: GROUP_LEAVE_SUCCESS,
|
||||
relationship,
|
||||
};
|
||||
}
|
||||
|
||||
export function leaveGroupFail(error) {
|
||||
return {
|
||||
type: GROUP_LEAVE_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchMembers(id) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(fetchMembersRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/groups/${id}/accounts`).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchMembersSuccess(id, response.data, next ? next.uri : null));
|
||||
dispatch(fetchRelationships(response.data.map(item => item.id)));
|
||||
}).catch(error => {
|
||||
dispatch(fetchMembersFail(id, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchMembersRequest(id) {
|
||||
return {
|
||||
type: GROUP_MEMBERS_FETCH_REQUEST,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchMembersSuccess(id, accounts, next) {
|
||||
return {
|
||||
type: GROUP_MEMBERS_FETCH_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchMembersFail(id, error) {
|
||||
return {
|
||||
type: GROUP_MEMBERS_FETCH_FAIL,
|
||||
id,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandMembers(id) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const url = getState().getIn(['user_lists', 'groups', id, 'next']);
|
||||
|
||||
if (url === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandMembersRequest(id));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(expandMembersSuccess(id, response.data, next ? next.uri : null));
|
||||
dispatch(fetchRelationships(response.data.map(item => item.id)));
|
||||
}).catch(error => {
|
||||
dispatch(expandMembersFail(id, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function expandMembersRequest(id) {
|
||||
return {
|
||||
type: GROUP_MEMBERS_EXPAND_REQUEST,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandMembersSuccess(id, accounts, next) {
|
||||
return {
|
||||
type: GROUP_MEMBERS_EXPAND_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandMembersFail(id, error) {
|
||||
return {
|
||||
type: GROUP_MEMBERS_EXPAND_FAIL,
|
||||
id,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchRemovedAccounts(id) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(fetchRemovedAccountsRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/groups/${id}/removed_accounts`).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchRemovedAccountsSuccess(id, response.data, next ? next.uri : null));
|
||||
dispatch(fetchRelationships(response.data.map(item => item.id)));
|
||||
}).catch(error => {
|
||||
dispatch(fetchRemovedAccountsFail(id, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchRemovedAccountsRequest(id) {
|
||||
return {
|
||||
type: GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchRemovedAccountsSuccess(id, accounts, next) {
|
||||
return {
|
||||
type: GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchRemovedAccountsFail(id, error) {
|
||||
return {
|
||||
type: GROUP_REMOVED_ACCOUNTS_FETCH_FAIL,
|
||||
id,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandRemovedAccounts(id) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const url = getState().getIn(['user_lists', 'groups_removed_accounts', id, 'next']);
|
||||
|
||||
if (url === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandRemovedAccountsRequest(id));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(expandRemovedAccountsSuccess(id, response.data, next ? next.uri : null));
|
||||
dispatch(fetchRelationships(response.data.map(item => item.id)));
|
||||
}).catch(error => {
|
||||
dispatch(expandRemovedAccountsFail(id, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function expandRemovedAccountsRequest(id) {
|
||||
return {
|
||||
type: GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandRemovedAccountsSuccess(id, accounts, next) {
|
||||
return {
|
||||
type: GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
};
|
||||
}
|
||||
|
||||
export function expandRemovedAccountsFail(id, error) {
|
||||
return {
|
||||
type: GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL,
|
||||
id,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function removeRemovedAccount(groupId, id) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(removeRemovedAccountRequest(groupId, id));
|
||||
|
||||
api(getState).delete(`/api/v1/groups/${groupId}/removed_accounts?account_id=${id}`).then(response => {
|
||||
dispatch(removeRemovedAccountSuccess(groupId, id));
|
||||
}).catch(error => {
|
||||
dispatch(removeRemovedAccountFail(groupId, id, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function removeRemovedAccountRequest(groupId, id) {
|
||||
return {
|
||||
type: GROUP_REMOVED_ACCOUNTS_REMOVE_REQUEST,
|
||||
groupId,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function removeRemovedAccountSuccess(groupId, id) {
|
||||
return {
|
||||
type: GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS,
|
||||
groupId,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function removeRemovedAccountFail(groupId, id, error) {
|
||||
return {
|
||||
type: GROUP_REMOVED_ACCOUNTS_REMOVE_FAIL,
|
||||
groupId,
|
||||
id,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function createRemovedAccount(groupId, id) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(createRemovedAccountRequest(groupId, id));
|
||||
|
||||
api(getState).post(`/api/v1/groups/${groupId}/removed_accounts?account_id=${id}`).then(response => {
|
||||
dispatch(createRemovedAccountSuccess(groupId, id));
|
||||
}).catch(error => {
|
||||
dispatch(createRemovedAccountFail(groupId, id, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function createRemovedAccountRequest(groupId, id) {
|
||||
return {
|
||||
type: GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST,
|
||||
groupId,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function createRemovedAccountSuccess(groupId, id) {
|
||||
return {
|
||||
type: GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS,
|
||||
groupId,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function createRemovedAccountFail(groupId, id, error) {
|
||||
return {
|
||||
type: GROUP_REMOVED_ACCOUNTS_CREATE_FAIL,
|
||||
groupId,
|
||||
id,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function groupRemoveStatus(groupId, id) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(groupRemoveStatusRequest(groupId, id));
|
||||
|
||||
api(getState).delete(`/api/v1/groups/${groupId}/statuses/${id}`).then(response => {
|
||||
dispatch(groupRemoveStatusSuccess(groupId, id));
|
||||
}).catch(error => {
|
||||
dispatch(groupRemoveStatusFail(groupId, id, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function groupRemoveStatusRequest(groupId, id) {
|
||||
return {
|
||||
type: GROUP_REMOVE_STATUS_REQUEST,
|
||||
groupId,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function groupRemoveStatusSuccess(groupId, id) {
|
||||
return {
|
||||
type: GROUP_REMOVE_STATUS_SUCCESS,
|
||||
groupId,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function groupRemoveStatusFail(groupId, id, error) {
|
||||
return {
|
||||
type: GROUP_REMOVE_STATUS_FAIL,
|
||||
groupId,
|
||||
id,
|
||||
error,
|
||||
};
|
||||
}
|
550
app/soapbox/actions/groups.ts
Normal file
|
@ -0,0 +1,550 @@
|
|||
import { AxiosError } from 'axios';
|
||||
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import api, { getLinks } from '../api';
|
||||
|
||||
import { fetchRelationships } from './accounts';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
const GROUP_FETCH_REQUEST = 'GROUP_FETCH_REQUEST';
|
||||
const GROUP_FETCH_SUCCESS = 'GROUP_FETCH_SUCCESS';
|
||||
const GROUP_FETCH_FAIL = 'GROUP_FETCH_FAIL';
|
||||
|
||||
const GROUP_RELATIONSHIPS_FETCH_REQUEST = 'GROUP_RELATIONSHIPS_FETCH_REQUEST';
|
||||
const GROUP_RELATIONSHIPS_FETCH_SUCCESS = 'GROUP_RELATIONSHIPS_FETCH_SUCCESS';
|
||||
const GROUP_RELATIONSHIPS_FETCH_FAIL = 'GROUP_RELATIONSHIPS_FETCH_FAIL';
|
||||
|
||||
const GROUPS_FETCH_REQUEST = 'GROUPS_FETCH_REQUEST';
|
||||
const GROUPS_FETCH_SUCCESS = 'GROUPS_FETCH_SUCCESS';
|
||||
const GROUPS_FETCH_FAIL = 'GROUPS_FETCH_FAIL';
|
||||
|
||||
const GROUP_JOIN_REQUEST = 'GROUP_JOIN_REQUEST';
|
||||
const GROUP_JOIN_SUCCESS = 'GROUP_JOIN_SUCCESS';
|
||||
const GROUP_JOIN_FAIL = 'GROUP_JOIN_FAIL';
|
||||
|
||||
const GROUP_LEAVE_REQUEST = 'GROUP_LEAVE_REQUEST';
|
||||
const GROUP_LEAVE_SUCCESS = 'GROUP_LEAVE_SUCCESS';
|
||||
const GROUP_LEAVE_FAIL = 'GROUP_LEAVE_FAIL';
|
||||
|
||||
const GROUP_MEMBERS_FETCH_REQUEST = 'GROUP_MEMBERS_FETCH_REQUEST';
|
||||
const GROUP_MEMBERS_FETCH_SUCCESS = 'GROUP_MEMBERS_FETCH_SUCCESS';
|
||||
const GROUP_MEMBERS_FETCH_FAIL = 'GROUP_MEMBERS_FETCH_FAIL';
|
||||
|
||||
const GROUP_MEMBERS_EXPAND_REQUEST = 'GROUP_MEMBERS_EXPAND_REQUEST';
|
||||
const GROUP_MEMBERS_EXPAND_SUCCESS = 'GROUP_MEMBERS_EXPAND_SUCCESS';
|
||||
const GROUP_MEMBERS_EXPAND_FAIL = 'GROUP_MEMBERS_EXPAND_FAIL';
|
||||
|
||||
const GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST = 'GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST';
|
||||
const GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS';
|
||||
const GROUP_REMOVED_ACCOUNTS_FETCH_FAIL = 'GROUP_REMOVED_ACCOUNTS_FETCH_FAIL';
|
||||
|
||||
const GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST = 'GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST';
|
||||
const GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS';
|
||||
const GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL = 'GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL';
|
||||
|
||||
const GROUP_REMOVED_ACCOUNTS_REMOVE_REQUEST = 'GROUP_REMOVED_ACCOUNTS_REMOVE_REQUEST';
|
||||
const GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS';
|
||||
const GROUP_REMOVED_ACCOUNTS_REMOVE_FAIL = 'GROUP_REMOVED_ACCOUNTS_REMOVE_FAIL';
|
||||
|
||||
const GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST = 'GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST';
|
||||
const GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS';
|
||||
const GROUP_REMOVED_ACCOUNTS_CREATE_FAIL = 'GROUP_REMOVED_ACCOUNTS_CREATE_FAIL';
|
||||
|
||||
const GROUP_REMOVE_STATUS_REQUEST = 'GROUP_REMOVE_STATUS_REQUEST';
|
||||
const GROUP_REMOVE_STATUS_SUCCESS = 'GROUP_REMOVE_STATUS_SUCCESS';
|
||||
const GROUP_REMOVE_STATUS_FAIL = 'GROUP_REMOVE_STATUS_FAIL';
|
||||
|
||||
const fetchGroup = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(fetchGroupRelationships([id]));
|
||||
|
||||
if (getState().groups.get(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchGroupRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/groups/${id}`)
|
||||
.then(({ data }) => dispatch(fetchGroupSuccess(data)))
|
||||
.catch(err => dispatch(fetchGroupFail(id, err)));
|
||||
};
|
||||
|
||||
const fetchGroupRequest = (id: string) => ({
|
||||
type: GROUP_FETCH_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
const fetchGroupSuccess = (group: APIEntity) => ({
|
||||
type: GROUP_FETCH_SUCCESS,
|
||||
group,
|
||||
});
|
||||
|
||||
const fetchGroupFail = (id: string, error: AxiosError) => ({
|
||||
type: GROUP_FETCH_FAIL,
|
||||
id,
|
||||
error,
|
||||
});
|
||||
|
||||
const fetchGroupRelationships = (groupIds: string[]) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const loadedRelationships = getState().group_relationships;
|
||||
const newGroupIds = groupIds.filter(id => loadedRelationships.get(id, null) === null);
|
||||
|
||||
if (newGroupIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchGroupRelationshipsRequest(newGroupIds));
|
||||
|
||||
api(getState).get(`/api/v1/groups/${newGroupIds[0]}/relationships?${newGroupIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
|
||||
dispatch(fetchGroupRelationshipsSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(fetchGroupRelationshipsFail(error));
|
||||
});
|
||||
};
|
||||
|
||||
const fetchGroupRelationshipsRequest = (ids: string[]) => ({
|
||||
type: GROUP_RELATIONSHIPS_FETCH_REQUEST,
|
||||
ids,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const fetchGroupRelationshipsSuccess = (relationships: APIEntity[]) => ({
|
||||
type: GROUP_RELATIONSHIPS_FETCH_SUCCESS,
|
||||
relationships,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const fetchGroupRelationshipsFail = (error: AxiosError) => ({
|
||||
type: GROUP_RELATIONSHIPS_FETCH_FAIL,
|
||||
error,
|
||||
skipLoading: true,
|
||||
});
|
||||
|
||||
const fetchGroups = (tab: string) => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(fetchGroupsRequest());
|
||||
|
||||
api(getState).get('/api/v1/groups?tab=' + tab)
|
||||
.then(({ data }) => {
|
||||
dispatch(fetchGroupsSuccess(data, tab));
|
||||
dispatch(fetchGroupRelationships(data.map((item: APIEntity) => item.id)));
|
||||
})
|
||||
.catch(err => dispatch(fetchGroupsFail(err)));
|
||||
};
|
||||
|
||||
const fetchGroupsRequest = () => ({
|
||||
type: GROUPS_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
const fetchGroupsSuccess = (groups: APIEntity[], tab: string) => ({
|
||||
type: GROUPS_FETCH_SUCCESS,
|
||||
groups,
|
||||
tab,
|
||||
});
|
||||
|
||||
const fetchGroupsFail = (error: AxiosError) => ({
|
||||
type: GROUPS_FETCH_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
const joinGroup = (id: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(joinGroupRequest(id));
|
||||
|
||||
api(getState).post(`/api/v1/groups/${id}/accounts`).then(response => {
|
||||
dispatch(joinGroupSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(joinGroupFail(id, error));
|
||||
});
|
||||
};
|
||||
|
||||
const leaveGroup = (id: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(leaveGroupRequest(id));
|
||||
|
||||
api(getState).delete(`/api/v1/groups/${id}/accounts`).then(response => {
|
||||
dispatch(leaveGroupSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(leaveGroupFail(id, error));
|
||||
});
|
||||
};
|
||||
|
||||
const joinGroupRequest = (id: string) => ({
|
||||
type: GROUP_JOIN_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
const joinGroupSuccess = (relationship: APIEntity) => ({
|
||||
type: GROUP_JOIN_SUCCESS,
|
||||
relationship,
|
||||
});
|
||||
|
||||
const joinGroupFail = (id: string, error: AxiosError) => ({
|
||||
type: GROUP_JOIN_FAIL,
|
||||
id,
|
||||
error,
|
||||
});
|
||||
|
||||
const leaveGroupRequest = (id: string) => ({
|
||||
type: GROUP_LEAVE_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
const leaveGroupSuccess = (relationship: APIEntity) => ({
|
||||
type: GROUP_LEAVE_SUCCESS,
|
||||
relationship,
|
||||
});
|
||||
|
||||
const leaveGroupFail = (id: string, error: AxiosError) => ({
|
||||
type: GROUP_LEAVE_FAIL,
|
||||
id,
|
||||
error,
|
||||
});
|
||||
|
||||
const fetchMembers = (id: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(fetchMembersRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/groups/${id}/accounts`).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchMembersSuccess(id, response.data, next ? next.uri : null));
|
||||
dispatch(fetchRelationships(response.data.map((item: APIEntity) => item.id)));
|
||||
}).catch(error => {
|
||||
dispatch(fetchMembersFail(id, error));
|
||||
});
|
||||
};
|
||||
|
||||
const fetchMembersRequest = (id: string) => ({
|
||||
type: GROUP_MEMBERS_FETCH_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
const fetchMembersSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({
|
||||
type: GROUP_MEMBERS_FETCH_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
});
|
||||
|
||||
const fetchMembersFail = (id: string, error: AxiosError) => ({
|
||||
type: GROUP_MEMBERS_FETCH_FAIL,
|
||||
id,
|
||||
error,
|
||||
});
|
||||
|
||||
const expandMembers = (id: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const url = getState().user_lists.groups.get(id)!.next;
|
||||
|
||||
if (url === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandMembersRequest(id));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(expandMembersSuccess(id, response.data, next ? next.uri : null));
|
||||
dispatch(fetchRelationships(response.data.map((item: APIEntity) => item.id)));
|
||||
}).catch(error => {
|
||||
dispatch(expandMembersFail(id, error));
|
||||
});
|
||||
};
|
||||
|
||||
const expandMembersRequest = (id: string) => ({
|
||||
type: GROUP_MEMBERS_EXPAND_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
const expandMembersSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({
|
||||
type: GROUP_MEMBERS_EXPAND_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
});
|
||||
|
||||
const expandMembersFail = (id: string, error: AxiosError) => ({
|
||||
type: GROUP_MEMBERS_EXPAND_FAIL,
|
||||
id,
|
||||
error,
|
||||
});
|
||||
|
||||
const fetchRemovedAccounts = (id: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(fetchRemovedAccountsRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/groups/${id}/removed_accounts`).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchRemovedAccountsSuccess(id, response.data, next ? next.uri : null));
|
||||
dispatch(fetchRelationships(response.data.map((item: APIEntity) => item.id)));
|
||||
}).catch(error => {
|
||||
dispatch(fetchRemovedAccountsFail(id, error));
|
||||
});
|
||||
};
|
||||
|
||||
const fetchRemovedAccountsRequest = (id: string) => ({
|
||||
type: GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
const fetchRemovedAccountsSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({
|
||||
type: GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
});
|
||||
|
||||
const fetchRemovedAccountsFail = (id: string, error: AxiosError) => ({
|
||||
type: GROUP_REMOVED_ACCOUNTS_FETCH_FAIL,
|
||||
id,
|
||||
error,
|
||||
});
|
||||
|
||||
const expandRemovedAccounts = (id: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
const url = getState().user_lists.groups_removed_accounts.get(id)!.next;
|
||||
|
||||
if (url === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandRemovedAccountsRequest(id));
|
||||
|
||||
api(getState).get(url).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(expandRemovedAccountsSuccess(id, response.data, next ? next.uri : null));
|
||||
dispatch(fetchRelationships(response.data.map((item: APIEntity) => item.id)));
|
||||
}).catch(error => {
|
||||
dispatch(expandRemovedAccountsFail(id, error));
|
||||
});
|
||||
};
|
||||
|
||||
const expandRemovedAccountsRequest = (id: string) => ({
|
||||
type: GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
const expandRemovedAccountsSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({
|
||||
type: GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS,
|
||||
id,
|
||||
accounts,
|
||||
next,
|
||||
});
|
||||
|
||||
const expandRemovedAccountsFail = (id: string, error: AxiosError) => ({
|
||||
type: GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL,
|
||||
id,
|
||||
error,
|
||||
});
|
||||
|
||||
const removeRemovedAccount = (groupId: string, id: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(removeRemovedAccountRequest(groupId, id));
|
||||
|
||||
api(getState).delete(`/api/v1/groups/${groupId}/removed_accounts?account_id=${id}`).then(response => {
|
||||
dispatch(removeRemovedAccountSuccess(groupId, id));
|
||||
}).catch(error => {
|
||||
dispatch(removeRemovedAccountFail(groupId, id, error));
|
||||
});
|
||||
};
|
||||
|
||||
const removeRemovedAccountRequest = (groupId: string, id: string) => ({
|
||||
type: GROUP_REMOVED_ACCOUNTS_REMOVE_REQUEST,
|
||||
groupId,
|
||||
id,
|
||||
});
|
||||
|
||||
const removeRemovedAccountSuccess = (groupId: string, id: string) => ({
|
||||
type: GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS,
|
||||
groupId,
|
||||
id,
|
||||
});
|
||||
|
||||
const removeRemovedAccountFail = (groupId: string, id: string, error: AxiosError) => ({
|
||||
type: GROUP_REMOVED_ACCOUNTS_REMOVE_FAIL,
|
||||
groupId,
|
||||
id,
|
||||
error,
|
||||
});
|
||||
|
||||
const createRemovedAccount = (groupId: string, id: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(createRemovedAccountRequest(groupId, id));
|
||||
|
||||
api(getState).post(`/api/v1/groups/${groupId}/removed_accounts?account_id=${id}`).then(response => {
|
||||
dispatch(createRemovedAccountSuccess(groupId, id));
|
||||
}).catch(error => {
|
||||
dispatch(createRemovedAccountFail(groupId, id, error));
|
||||
});
|
||||
};
|
||||
|
||||
const createRemovedAccountRequest = (groupId: string, id: string) => ({
|
||||
type: GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST,
|
||||
groupId,
|
||||
id,
|
||||
});
|
||||
|
||||
const createRemovedAccountSuccess = (groupId: string, id: string) => ({
|
||||
type: GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS,
|
||||
groupId,
|
||||
id,
|
||||
});
|
||||
|
||||
const createRemovedAccountFail = (groupId: string, id: string, error: AxiosError) => ({
|
||||
type: GROUP_REMOVED_ACCOUNTS_CREATE_FAIL,
|
||||
groupId,
|
||||
id,
|
||||
error,
|
||||
});
|
||||
|
||||
const groupRemoveStatus = (groupId: string, id: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(groupRemoveStatusRequest(groupId, id));
|
||||
|
||||
api(getState).delete(`/api/v1/groups/${groupId}/statuses/${id}`).then(response => {
|
||||
dispatch(groupRemoveStatusSuccess(groupId, id));
|
||||
}).catch(error => {
|
||||
dispatch(groupRemoveStatusFail(groupId, id, error));
|
||||
});
|
||||
};
|
||||
|
||||
const groupRemoveStatusRequest = (groupId: string, id: string) => ({
|
||||
type: GROUP_REMOVE_STATUS_REQUEST,
|
||||
groupId,
|
||||
id,
|
||||
});
|
||||
|
||||
const groupRemoveStatusSuccess = (groupId: string, id: string) => ({
|
||||
type: GROUP_REMOVE_STATUS_SUCCESS,
|
||||
groupId,
|
||||
id,
|
||||
});
|
||||
|
||||
const groupRemoveStatusFail = (groupId: string, id: string, error: AxiosError) => ({
|
||||
type: GROUP_REMOVE_STATUS_FAIL,
|
||||
groupId,
|
||||
id,
|
||||
error,
|
||||
});
|
||||
|
||||
export {
|
||||
GROUP_FETCH_REQUEST,
|
||||
GROUP_FETCH_SUCCESS,
|
||||
GROUP_FETCH_FAIL,
|
||||
GROUP_RELATIONSHIPS_FETCH_REQUEST,
|
||||
GROUP_RELATIONSHIPS_FETCH_SUCCESS,
|
||||
GROUP_RELATIONSHIPS_FETCH_FAIL,
|
||||
GROUPS_FETCH_REQUEST,
|
||||
GROUPS_FETCH_SUCCESS,
|
||||
GROUPS_FETCH_FAIL,
|
||||
GROUP_JOIN_REQUEST,
|
||||
GROUP_JOIN_SUCCESS,
|
||||
GROUP_JOIN_FAIL,
|
||||
GROUP_LEAVE_REQUEST,
|
||||
GROUP_LEAVE_SUCCESS,
|
||||
GROUP_LEAVE_FAIL,
|
||||
GROUP_MEMBERS_FETCH_REQUEST,
|
||||
GROUP_MEMBERS_FETCH_SUCCESS,
|
||||
GROUP_MEMBERS_FETCH_FAIL,
|
||||
GROUP_MEMBERS_EXPAND_REQUEST,
|
||||
GROUP_MEMBERS_EXPAND_SUCCESS,
|
||||
GROUP_MEMBERS_EXPAND_FAIL,
|
||||
GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST,
|
||||
GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS,
|
||||
GROUP_REMOVED_ACCOUNTS_FETCH_FAIL,
|
||||
GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST,
|
||||
GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS,
|
||||
GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL,
|
||||
GROUP_REMOVED_ACCOUNTS_REMOVE_REQUEST,
|
||||
GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS,
|
||||
GROUP_REMOVED_ACCOUNTS_REMOVE_FAIL,
|
||||
GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST,
|
||||
GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS,
|
||||
GROUP_REMOVED_ACCOUNTS_CREATE_FAIL,
|
||||
GROUP_REMOVE_STATUS_REQUEST,
|
||||
GROUP_REMOVE_STATUS_SUCCESS,
|
||||
GROUP_REMOVE_STATUS_FAIL,
|
||||
fetchGroup,
|
||||
fetchGroupRequest,
|
||||
fetchGroupSuccess,
|
||||
fetchGroupFail,
|
||||
fetchGroupRelationships,
|
||||
fetchGroupRelationshipsRequest,
|
||||
fetchGroupRelationshipsSuccess,
|
||||
fetchGroupRelationshipsFail,
|
||||
fetchGroups,
|
||||
fetchGroupsRequest,
|
||||
fetchGroupsSuccess,
|
||||
fetchGroupsFail,
|
||||
joinGroup,
|
||||
leaveGroup,
|
||||
joinGroupRequest,
|
||||
joinGroupSuccess,
|
||||
joinGroupFail,
|
||||
leaveGroupRequest,
|
||||
leaveGroupSuccess,
|
||||
leaveGroupFail,
|
||||
fetchMembers,
|
||||
fetchMembersRequest,
|
||||
fetchMembersSuccess,
|
||||
fetchMembersFail,
|
||||
expandMembers,
|
||||
expandMembersRequest,
|
||||
expandMembersSuccess,
|
||||
expandMembersFail,
|
||||
fetchRemovedAccounts,
|
||||
fetchRemovedAccountsRequest,
|
||||
fetchRemovedAccountsSuccess,
|
||||
fetchRemovedAccountsFail,
|
||||
expandRemovedAccounts,
|
||||
expandRemovedAccountsRequest,
|
||||
expandRemovedAccountsSuccess,
|
||||
expandRemovedAccountsFail,
|
||||
removeRemovedAccount,
|
||||
removeRemovedAccountRequest,
|
||||
removeRemovedAccountSuccess,
|
||||
removeRemovedAccountFail,
|
||||
createRemovedAccount,
|
||||
createRemovedAccountRequest,
|
||||
createRemovedAccountSuccess,
|
||||
createRemovedAccountFail,
|
||||
groupRemoveStatus,
|
||||
groupRemoveStatusRequest,
|
||||
groupRemoveStatusSuccess,
|
||||
groupRemoveStatusFail,
|
||||
};
|
|
@ -1,38 +0,0 @@
|
|||
import api from 'soapbox/api';
|
||||
|
||||
import { importFetchedAccounts } from './importer';
|
||||
|
||||
export const HISTORY_FETCH_REQUEST = 'HISTORY_FETCH_REQUEST';
|
||||
export const HISTORY_FETCH_SUCCESS = 'HISTORY_FETCH_SUCCESS';
|
||||
export const HISTORY_FETCH_FAIL = 'HISTORY_FETCH_FAIL';
|
||||
|
||||
export const fetchHistory = statusId => (dispatch, getState) => {
|
||||
const loading = getState().getIn(['history', statusId, 'loading']);
|
||||
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchHistoryRequest(statusId));
|
||||
|
||||
api(getState).get(`/api/v1/statuses/${statusId}/history`).then(({ data }) => {
|
||||
dispatch(importFetchedAccounts(data.map(x => x.account)));
|
||||
dispatch(fetchHistorySuccess(statusId, data));
|
||||
}).catch(error => dispatch(fetchHistoryFail(error)));
|
||||
};
|
||||
|
||||
export const fetchHistoryRequest = statusId => ({
|
||||
type: HISTORY_FETCH_REQUEST,
|
||||
statusId,
|
||||
});
|
||||
|
||||
export const fetchHistorySuccess = (statusId, history) => ({
|
||||
type: HISTORY_FETCH_SUCCESS,
|
||||
statusId,
|
||||
history,
|
||||
});
|
||||
|
||||
export const fetchHistoryFail = error => ({
|
||||
type: HISTORY_FETCH_FAIL,
|
||||
error,
|
||||
});
|
53
app/soapbox/actions/history.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import api from 'soapbox/api';
|
||||
|
||||
import { importFetchedAccounts } from './importer';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
const HISTORY_FETCH_REQUEST = 'HISTORY_FETCH_REQUEST';
|
||||
const HISTORY_FETCH_SUCCESS = 'HISTORY_FETCH_SUCCESS';
|
||||
const HISTORY_FETCH_FAIL = 'HISTORY_FETCH_FAIL';
|
||||
|
||||
const fetchHistory = (statusId: string) =>
|
||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const loading = getState().history.getIn([statusId, 'loading']);
|
||||
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchHistoryRequest(statusId));
|
||||
|
||||
api(getState).get(`/api/v1/statuses/${statusId}/history`).then(({ data }) => {
|
||||
dispatch(importFetchedAccounts(data.map((x: APIEntity) => x.account)));
|
||||
dispatch(fetchHistorySuccess(statusId, data));
|
||||
}).catch(error => dispatch(fetchHistoryFail(error)));
|
||||
};
|
||||
|
||||
const fetchHistoryRequest = (statusId: string) => ({
|
||||
type: HISTORY_FETCH_REQUEST,
|
||||
statusId,
|
||||
});
|
||||
|
||||
const fetchHistorySuccess = (statusId: String, history: APIEntity[]) => ({
|
||||
type: HISTORY_FETCH_SUCCESS,
|
||||
statusId,
|
||||
history,
|
||||
});
|
||||
|
||||
const fetchHistoryFail = (error: AxiosError) => ({
|
||||
type: HISTORY_FETCH_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export {
|
||||
HISTORY_FETCH_REQUEST,
|
||||
HISTORY_FETCH_SUCCESS,
|
||||
HISTORY_FETCH_FAIL,
|
||||
fetchHistory,
|
||||
fetchHistoryRequest,
|
||||
fetchHistorySuccess,
|
||||
fetchHistoryFail,
|
||||
};
|
|
@ -1,30 +0,0 @@
|
|||
import api from '../api';
|
||||
|
||||
export const IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST = 'IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST';
|
||||
export const IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS = 'IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS';
|
||||
export const IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL = 'IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL';
|
||||
|
||||
export const fetchAccountIdentityProofs = accountId => (dispatch, getState) => {
|
||||
dispatch(fetchAccountIdentityProofsRequest(accountId));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/${accountId}/identity_proofs`)
|
||||
.then(({ data }) => dispatch(fetchAccountIdentityProofsSuccess(accountId, data)))
|
||||
.catch(err => dispatch(fetchAccountIdentityProofsFail(accountId, err)));
|
||||
};
|
||||
|
||||
export const fetchAccountIdentityProofsRequest = id => ({
|
||||
type: IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
export const fetchAccountIdentityProofsSuccess = (accountId, identity_proofs) => ({
|
||||
type: IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS,
|
||||
accountId,
|
||||
identity_proofs,
|
||||
});
|
||||
|
||||
export const fetchAccountIdentityProofsFail = (accountId, error) => ({
|
||||
type: IDENTITY_PROOFS_ACCOUNT_FETCH_FAIL,
|
||||
accountId,
|
||||
error,
|
||||
});
|
|
@ -4,6 +4,9 @@ import snackbar from 'soapbox/actions/snackbar';
|
|||
|
||||
import api from '../api';
|
||||
|
||||
import type { SnackbarAction } from './snackbar';
|
||||
import type { RootState } from 'soapbox/store';
|
||||
|
||||
export const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST';
|
||||
export const IMPORT_FOLLOWS_SUCCESS = 'IMPORT_FOLLOWS_SUCCESS';
|
||||
export const IMPORT_FOLLOWS_FAIL = 'IMPORT_FOLLOWS_FAIL';
|
||||
|
@ -16,50 +19,61 @@ export const IMPORT_MUTES_REQUEST = 'IMPORT_MUTES_REQUEST';
|
|||
export const IMPORT_MUTES_SUCCESS = 'IMPORT_MUTES_SUCCESS';
|
||||
export const IMPORT_MUTES_FAIL = 'IMPORT_MUTES_FAIL';
|
||||
|
||||
type ImportDataActions = {
|
||||
type: typeof IMPORT_FOLLOWS_REQUEST
|
||||
| typeof IMPORT_FOLLOWS_SUCCESS
|
||||
| typeof IMPORT_FOLLOWS_FAIL
|
||||
| typeof IMPORT_BLOCKS_REQUEST
|
||||
| typeof IMPORT_BLOCKS_SUCCESS
|
||||
| typeof IMPORT_BLOCKS_FAIL
|
||||
| typeof IMPORT_MUTES_REQUEST
|
||||
| typeof IMPORT_MUTES_SUCCESS
|
||||
| typeof IMPORT_MUTES_FAIL,
|
||||
error?: any,
|
||||
config?: string
|
||||
} | SnackbarAction
|
||||
|
||||
const messages = defineMessages({
|
||||
blocksSuccess: { id: 'import_data.success.blocks', defaultMessage: 'Blocks imported successfully' },
|
||||
followersSuccess: { id: 'import_data.success.followers', defaultMessage: 'Followers imported successfully' },
|
||||
mutesSuccess: { id: 'import_data.success.mutes', defaultMessage: 'Mutes imported successfully' },
|
||||
});
|
||||
|
||||
export function importFollows(intl, params) {
|
||||
return (dispatch, getState) => {
|
||||
export const importFollows = (params: FormData) =>
|
||||
(dispatch: React.Dispatch<ImportDataActions>, getState: () => RootState) => {
|
||||
dispatch({ type: IMPORT_FOLLOWS_REQUEST });
|
||||
return api(getState)
|
||||
.post('/api/pleroma/follow_import', params)
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.followersSuccess)));
|
||||
dispatch(snackbar.success(messages.followersSuccess));
|
||||
dispatch({ type: IMPORT_FOLLOWS_SUCCESS, config: response.data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: IMPORT_FOLLOWS_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function importBlocks(intl, params) {
|
||||
return (dispatch, getState) => {
|
||||
export const importBlocks = (params: FormData) =>
|
||||
(dispatch: React.Dispatch<ImportDataActions>, getState: () => RootState) => {
|
||||
dispatch({ type: IMPORT_BLOCKS_REQUEST });
|
||||
return api(getState)
|
||||
.post('/api/pleroma/blocks_import', params)
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.blocksSuccess)));
|
||||
dispatch(snackbar.success(messages.blocksSuccess));
|
||||
dispatch({ type: IMPORT_BLOCKS_SUCCESS, config: response.data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: IMPORT_BLOCKS_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function importMutes(intl, params) {
|
||||
return (dispatch, getState) => {
|
||||
export const importMutes = (params: FormData) =>
|
||||
(dispatch: React.Dispatch<ImportDataActions>, getState: () => RootState) => {
|
||||
dispatch({ type: IMPORT_MUTES_REQUEST });
|
||||
return api(getState)
|
||||
.post('/api/pleroma/mutes_import', params)
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.mutesSuccess)));
|
||||
dispatch(snackbar.success(messages.mutesSuccess));
|
||||
dispatch({ type: IMPORT_MUTES_SUCCESS, config: response.data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: IMPORT_MUTES_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,62 +1,70 @@
|
|||
import { getSettings } from '../settings';
|
||||
|
||||
export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT';
|
||||
export const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT';
|
||||
export const STATUS_IMPORT = 'STATUS_IMPORT';
|
||||
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
||||
export const POLLS_IMPORT = 'POLLS_IMPORT';
|
||||
export const ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP = 'ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
export function importAccount(account) {
|
||||
const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT';
|
||||
const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT';
|
||||
const STATUS_IMPORT = 'STATUS_IMPORT';
|
||||
const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
||||
const POLLS_IMPORT = 'POLLS_IMPORT';
|
||||
const ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP = 'ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP';
|
||||
|
||||
export function importAccount(account: APIEntity) {
|
||||
return { type: ACCOUNT_IMPORT, account };
|
||||
}
|
||||
|
||||
export function importAccounts(accounts) {
|
||||
export function importAccounts(accounts: APIEntity[]) {
|
||||
return { type: ACCOUNTS_IMPORT, accounts };
|
||||
}
|
||||
|
||||
export function importStatus(status, idempotencyKey) {
|
||||
return (dispatch, getState) => {
|
||||
export function importStatus(status: APIEntity, idempotencyKey?: string) {
|
||||
return (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const expandSpoilers = getSettings(getState()).get('expandSpoilers');
|
||||
return dispatch({ type: STATUS_IMPORT, status, idempotencyKey, expandSpoilers });
|
||||
};
|
||||
}
|
||||
|
||||
export function importStatuses(statuses) {
|
||||
return (dispatch, getState) => {
|
||||
export function importStatuses(statuses: APIEntity[]) {
|
||||
return (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const expandSpoilers = getSettings(getState()).get('expandSpoilers');
|
||||
return dispatch({ type: STATUSES_IMPORT, statuses, expandSpoilers });
|
||||
};
|
||||
}
|
||||
|
||||
export function importPolls(polls) {
|
||||
export function importPolls(polls: APIEntity[]) {
|
||||
return { type: POLLS_IMPORT, polls };
|
||||
}
|
||||
|
||||
export function importFetchedAccount(account) {
|
||||
export function importFetchedAccount(account: APIEntity) {
|
||||
return importFetchedAccounts([account]);
|
||||
}
|
||||
|
||||
export function importFetchedAccounts(accounts) {
|
||||
const normalAccounts = [];
|
||||
export function importFetchedAccounts(accounts: APIEntity[], args = { should_refetch: false }) {
|
||||
const { should_refetch } = args;
|
||||
const normalAccounts: APIEntity[] = [];
|
||||
|
||||
function processAccount(account) {
|
||||
const processAccount = (account: APIEntity) => {
|
||||
if (!account.id) return;
|
||||
|
||||
if (should_refetch) {
|
||||
account.should_refetch = true;
|
||||
}
|
||||
|
||||
normalAccounts.push(account);
|
||||
|
||||
if (account.moved) {
|
||||
processAccount(account.moved);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
accounts.forEach(processAccount);
|
||||
|
||||
return importAccounts(normalAccounts);
|
||||
}
|
||||
|
||||
export function importFetchedStatus(status, idempotencyKey) {
|
||||
return (dispatch, getState) => {
|
||||
export function importFetchedStatus(status: APIEntity, idempotencyKey?: string) {
|
||||
return (dispatch: AppDispatch) => {
|
||||
// Skip broken statuses
|
||||
if (isBroken(status)) return;
|
||||
|
||||
|
@ -69,10 +77,21 @@ export function importFetchedStatus(status, idempotencyKey) {
|
|||
dispatch(importFetchedStatus(status.quote));
|
||||
}
|
||||
|
||||
// Pleroma quotes
|
||||
if (status.pleroma?.quote?.id) {
|
||||
dispatch(importFetchedStatus(status.pleroma.quote));
|
||||
}
|
||||
|
||||
// Fedibird quote from reblog
|
||||
if (status.reblog?.quote?.id) {
|
||||
dispatch(importFetchedStatus(status.reblog.quote));
|
||||
}
|
||||
|
||||
// Pleroma quote from reblog
|
||||
if (status.reblog?.pleroma?.quote?.id) {
|
||||
dispatch(importFetchedStatus(status.reblog.pleroma.quote));
|
||||
}
|
||||
|
||||
if (status.poll?.id) {
|
||||
dispatch(importFetchedPoll(status.poll));
|
||||
}
|
||||
|
@ -84,7 +103,7 @@ export function importFetchedStatus(status, idempotencyKey) {
|
|||
|
||||
// Sometimes Pleroma can return an empty account,
|
||||
// or a repost can appear of a deleted account. Skip these statuses.
|
||||
const isBroken = status => {
|
||||
const isBroken = (status: APIEntity) => {
|
||||
try {
|
||||
// Skip empty accounts
|
||||
// https://gitlab.com/soapbox-pub/soapbox-fe/-/issues/424
|
||||
|
@ -98,13 +117,13 @@ const isBroken = status => {
|
|||
}
|
||||
};
|
||||
|
||||
export function importFetchedStatuses(statuses) {
|
||||
return (dispatch, getState) => {
|
||||
const accounts = [];
|
||||
const normalStatuses = [];
|
||||
const polls = [];
|
||||
export function importFetchedStatuses(statuses: APIEntity[]) {
|
||||
return (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const accounts: APIEntity[] = [];
|
||||
const normalStatuses: APIEntity[] = [];
|
||||
const polls: APIEntity[] = [];
|
||||
|
||||
function processStatus(status) {
|
||||
function processStatus(status: APIEntity) {
|
||||
// Skip broken statuses
|
||||
if (isBroken(status)) return;
|
||||
|
||||
|
@ -137,12 +156,21 @@ export function importFetchedStatuses(statuses) {
|
|||
};
|
||||
}
|
||||
|
||||
export function importFetchedPoll(poll) {
|
||||
return dispatch => {
|
||||
export function importFetchedPoll(poll: APIEntity) {
|
||||
return (dispatch: AppDispatch) => {
|
||||
dispatch(importPolls([poll]));
|
||||
};
|
||||
}
|
||||
|
||||
export function importErrorWhileFetchingAccountByUsername(username) {
|
||||
export function importErrorWhileFetchingAccountByUsername(username: string) {
|
||||
return { type: ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP, username };
|
||||
}
|
||||
|
||||
export {
|
||||
ACCOUNT_IMPORT,
|
||||
ACCOUNTS_IMPORT,
|
||||
STATUS_IMPORT,
|
||||
STATUSES_IMPORT,
|
||||
POLLS_IMPORT,
|
||||
ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP,
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { get } from 'lodash';
|
||||
import get from 'lodash/get';
|
||||
|
||||
import KVStore from 'soapbox/storage/kv_store';
|
||||
import { RootState } from 'soapbox/store';
|
||||
|
@ -46,7 +46,7 @@ export const fetchInstance = createAsyncThunk<void, void, { state: RootState }>(
|
|||
dispatch(fetchNodeinfo());
|
||||
}
|
||||
return instance;
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
return rejectWithValue(e);
|
||||
}
|
||||
},
|
||||
|
|