Merge pull request 'API Refactor' (#3504) from api-refactor into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3504
This commit is contained in:
commit
0045cdde37
137 changed files with 5082 additions and 5364 deletions
|
|
@ -73,7 +73,7 @@ test:
|
|||
|
||||
e2e-pleroma:
|
||||
stage: test
|
||||
image: mcr.microsoft.com/playwright:v1.57.0-jammy
|
||||
image: mcr.microsoft.com/playwright:v1.61.0-jammy
|
||||
services:
|
||||
- name: postgres:15-alpine
|
||||
alias: db
|
||||
|
|
@ -175,7 +175,7 @@ e2e-pleroma:
|
|||
NOTIFY_EMAIL: $E2E_ADMIN_EMAIL
|
||||
VITE_PROXY_TARGET: http://pleroma:4000
|
||||
VITE_PROXY_ORIGIN: http://localhost:4000
|
||||
E2E_BASE_URL: http://localhost:8080
|
||||
E2E_BASE_URL: http://localhost:8099
|
||||
script:
|
||||
- npm install -g yarn@1.22.22
|
||||
- yarn --frozen-lockfile
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ variables:
|
|||
|
||||
steps:
|
||||
test:
|
||||
image: mcr.microsoft.com/playwright:v1.57.0-jammy
|
||||
image: mcr.microsoft.com/playwright:v1.61.0-jammy
|
||||
entrypoint: *script_file_entrypoint
|
||||
environment:
|
||||
APT_CACHE_DIR: apt-cache
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
E2E_BASE_URL: http://localhost:8080
|
||||
E2E_BASE_URL: http://localhost:8099
|
||||
FF_NETWORK_PER_BUILD: "true"
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
|
||||
VITE_PROXY_ORIGIN: "http://pleroma:4000"
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ variables:
|
|||
|
||||
steps:
|
||||
test:
|
||||
image: mcr.microsoft.com/playwright:v1.57.0-jammy
|
||||
image: mcr.microsoft.com/playwright:v1.61.0-jammy
|
||||
environment:
|
||||
APT_CACHE_DIR: apt-cache
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@
|
|||
"noUnusedLabels": "error",
|
||||
"noUnusedPrivateClassMembers": "error",
|
||||
"noUnusedVariables": "error",
|
||||
"noUnusedImports": "error",
|
||||
"useIsNan": "error",
|
||||
"useValidForDirection": "error",
|
||||
"useValidTypeof": "error",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { readFile } from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { exactRegex } from '@rolldown/pluginutils'
|
||||
|
|
@ -102,8 +101,12 @@ export const buildSwPlugin = ({ swSrc, swDest }) => {
|
|||
},
|
||||
})
|
||||
|
||||
const swBundle = await build(config)
|
||||
return swBundle.output[0]
|
||||
try {
|
||||
const swBundle = await build(config)
|
||||
return swBundle.output[0]
|
||||
} catch (e) {
|
||||
console.error('Error building ServiceWorker:', e)
|
||||
}
|
||||
},
|
||||
},
|
||||
closeBundle: {
|
||||
|
|
@ -112,7 +115,11 @@ export const buildSwPlugin = ({ swSrc, swDest }) => {
|
|||
async handler() {
|
||||
if (process.env.VITEST) return
|
||||
console.info('Building service worker for production')
|
||||
await build(config)
|
||||
try {
|
||||
await build(config)
|
||||
} catch (e) {
|
||||
console.error('Error building ServiceWorker:', e)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
0
changelog.d/api-refactor.skip
Normal file
0
changelog.d/api-refactor.skip
Normal file
|
|
@ -39,6 +39,8 @@ services:
|
|||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 60
|
||||
ports:
|
||||
- 4000:4000
|
||||
|
||||
e2e:
|
||||
build:
|
||||
|
|
@ -51,7 +53,7 @@ services:
|
|||
CI: "1"
|
||||
VITE_PROXY_TARGET: http://pleroma:4000
|
||||
VITE_PROXY_ORIGIN: http://localhost:4000
|
||||
E2E_BASE_URL: http://localhost:8080
|
||||
E2E_BASE_URL: http://localhost:8099
|
||||
E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin}
|
||||
E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin}
|
||||
command: ["yarn", "e2e:pw"]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM mcr.microsoft.com/playwright:v1.57.0-jammy
|
||||
FROM mcr.microsoft.com/playwright:v1.61.0-jammy
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
|
|||
11
package.json
11
package.json
|
|
@ -68,8 +68,9 @@
|
|||
"@vitejs/devtools": "^0.3.1",
|
||||
"@vitejs/plugin-vue": "^6.0.7",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.5",
|
||||
"@vitest/browser": "^3.0.7",
|
||||
"@vitest/ui": "^3.0.7",
|
||||
"@vitest/browser-playwright": "^4.1.7",
|
||||
"@vitest/browser": "^4.1.7",
|
||||
"@vitest/ui": "^4.1.7",
|
||||
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
||||
"@vue/babel-plugin-jsx": "1.5.0",
|
||||
"@vue/compiler-sfc": "3.5.22",
|
||||
|
|
@ -95,10 +96,10 @@
|
|||
"http-proxy-middleware": "3.0.5",
|
||||
"iso-639-1": "3.1.5",
|
||||
"lodash": "4.17.21",
|
||||
"msw": "2.10.5",
|
||||
"msw": "2.14.6",
|
||||
"nightwatch": "3.12.2",
|
||||
"oxc": "^1.0.1",
|
||||
"playwright": "1.57.0",
|
||||
"playwright": "1.61.0",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-scss": "^4.0.6",
|
||||
|
|
@ -118,7 +119,7 @@
|
|||
"vite": "^8.0.0",
|
||||
"vite-plugin-eslint2": "^5.1.0",
|
||||
"vite-plugin-stylelint": "^6.1.0",
|
||||
"vitest": "^3.0.7",
|
||||
"vitest": "^4.1.7",
|
||||
"vue-eslint-parser": "10.2.0"
|
||||
},
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -21,9 +21,6 @@ import { useInterfaceStore } from 'src/stores/interface.js'
|
|||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useShoutStore } from 'src/stores/shout.js'
|
||||
|
||||
import messages from 'src/i18n/messages'
|
||||
import localeService from 'src/services/locale/locale.service.js'
|
||||
|
||||
// Helper to unwrap reactive proxies
|
||||
window.toValue = (x) => JSON.parse(JSON.stringify(x))
|
||||
|
||||
|
|
|
|||
491
src/api/admin.js
Normal file
491
src/api/admin.js
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
import { promisedRequest } from './helpers.js'
|
||||
|
||||
const REPORTS = '/api/v1/pleroma/admin/reports'
|
||||
const CONFIG_URL = '/api/v1/pleroma/admin/config'
|
||||
const DESCRIPTIONS_URL = '/api/v1/pleroma/admin/config/descriptions'
|
||||
|
||||
const ANNOUNCEMENTS_URL = (id = '') =>
|
||||
`/api/v1/pleroma/admin/announcements/${id}`
|
||||
|
||||
const FRONTENDS_URL = '/api/v1/pleroma/admin/frontends'
|
||||
const FRONTENDS_INSTALL_URL = '/api/v1/pleroma/admin/frontends/install'
|
||||
|
||||
const USERS_URL = (nickname = '') => `/api/v1/pleroma/admin/users/${nickname}`
|
||||
const USERS_URL_LIST = ({
|
||||
page,
|
||||
pageSize,
|
||||
filters = {},
|
||||
query = '',
|
||||
name = '',
|
||||
email = '',
|
||||
}) => {
|
||||
const {
|
||||
local = false,
|
||||
external = false,
|
||||
active = false,
|
||||
needApproval = false,
|
||||
unconfirmed = false,
|
||||
deactivated = false,
|
||||
isAdmin = true,
|
||||
isModerator = true,
|
||||
} = filters
|
||||
const filters_str = [
|
||||
local && 'local',
|
||||
external && 'external',
|
||||
active && 'active',
|
||||
needApproval && 'need_approval',
|
||||
unconfirmed && 'unconfirmed',
|
||||
deactivated && 'deactivated',
|
||||
isAdmin && 'is_admin',
|
||||
isModerator && 'is_moderator',
|
||||
]
|
||||
.filter((x) => x)
|
||||
.join(',')
|
||||
return `/api/v1/pleroma/admin/users?page=${page}&page_size=${pageSize}&filters=${filters_str}&query=${query}&name=${name}&email=${email}`
|
||||
}
|
||||
|
||||
const TAG_USER_URL = '/api/pleroma/admin/users/tag'
|
||||
|
||||
const PERMISSION_GROUP_URL = (right) =>
|
||||
`/api/pleroma/admin/users/permission_group/${right}`
|
||||
const ACTIVATE_USERS_URL = '/api/pleroma/admin/users/activate'
|
||||
const DEACTIVATE_USERS_URL = '/api/pleroma/admin/users/deactivate'
|
||||
const SUGGEST_USERS_URL = '/api/pleroma/admin/users/suggest'
|
||||
const UNSUGGEST_USERS_URL = '/api/pleroma/admin/users/unsuggest'
|
||||
const APPROVE_USERS_URL = '/api/v1/pleroma/admin/users/approve'
|
||||
const CONFIRM_USERS_URL = '/api/v1/pleroma/admin/users/confirm_email'
|
||||
const RESEND_CONFIRMATION_EMAIL_URL =
|
||||
'/api/v1/pleroma/admin/users/resend_confirmation_email'
|
||||
const LIST_STATUSES_URL = ({ id, page, pageSize, godmode, withReblogs }) =>
|
||||
`/api/v1/pleroma/admin/users/${id}/statuses?page_size=${pageSize}&page=${page}&godmode=${godmode}&with_reblogs=${withReblogs}`
|
||||
const CHANGE_STATUS_SCOPE_URL = (id) => `/api/v1/pleroma/admin/statuses/${id}`
|
||||
const REQUIRE_PASSWORD_CHANGE_URL =
|
||||
'/api/v1/pleroma/admin/users/force_password_reset'
|
||||
|
||||
const DISABLE_MFA_URL = '/api/v1/pleroma/admin/users/disable_mfa'
|
||||
const EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji'
|
||||
const EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import'
|
||||
const EMOJI_PACK_URL = (name) => `/api/v1/pleroma/emoji/pack?name=${name}`
|
||||
const EMOJI_PACKS_DL_REMOTE_URL = '/api/v1/pleroma/emoji/packs/download'
|
||||
const EMOJI_PACKS_DL_REMOTE_ZIP_URL = '/api/v1/pleroma/emoji/packs/download_zip'
|
||||
const EMOJI_PACKS_LS_REMOTE_URL = (url, page, pageSize) =>
|
||||
`/api/v1/pleroma/emoji/packs/remote?url=${url}&page=${page}&page_size=${pageSize}`
|
||||
const EMOJI_UPDATE_FILE_URL = (name) =>
|
||||
`/api/v1/pleroma/emoji/packs/files?name=${name}`
|
||||
|
||||
//
|
||||
|
||||
export const setUsersTags = ({
|
||||
tags,
|
||||
credentials,
|
||||
value,
|
||||
screen_names: nicknames,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: TAG_USER_URL,
|
||||
method: value ? 'PUT' : 'DELETE',
|
||||
credentials,
|
||||
payload: {
|
||||
nicknames,
|
||||
tags,
|
||||
},
|
||||
})
|
||||
|
||||
export const setUsersRight = ({
|
||||
right,
|
||||
credentials,
|
||||
value,
|
||||
screen_names: nicknames,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: PERMISSION_GROUP_URL(right),
|
||||
method: value ? 'POST' : 'DELETE',
|
||||
credentials,
|
||||
payload: {
|
||||
nicknames,
|
||||
},
|
||||
})
|
||||
|
||||
export const setUsersActivationStatus = ({
|
||||
credentials,
|
||||
screen_names: nicknames,
|
||||
value,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: value ? ACTIVATE_USERS_URL : DEACTIVATE_USERS_URL,
|
||||
method: 'PATCH',
|
||||
credentials,
|
||||
payload: {
|
||||
nicknames,
|
||||
},
|
||||
}).then((response) => response.users)
|
||||
|
||||
export const setUsersApprovalStatus = ({
|
||||
credentials,
|
||||
screen_names: nicknames,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: APPROVE_USERS_URL,
|
||||
method: 'PATCH',
|
||||
credentials,
|
||||
payload: {
|
||||
nicknames,
|
||||
},
|
||||
}).then((response) => response.users)
|
||||
|
||||
export const setUsersConfirmationStatus = ({
|
||||
credentials,
|
||||
screen_names: nicknames,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: CONFIRM_USERS_URL,
|
||||
method: 'PATCH',
|
||||
credentials,
|
||||
payload: {
|
||||
nicknames,
|
||||
},
|
||||
}).then((response) => response.users)
|
||||
|
||||
export const setUsersSuggestionStatus = ({
|
||||
credentials,
|
||||
screen_names: nicknames,
|
||||
value,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: value ? SUGGEST_USERS_URL : UNSUGGEST_USERS_URL,
|
||||
method: 'PATCH',
|
||||
credentials,
|
||||
payload: {
|
||||
nicknames,
|
||||
},
|
||||
}).then((response) => response.users)
|
||||
|
||||
export const getUserData = ({ credentials, screen_name: nickname }) =>
|
||||
promisedRequest({
|
||||
url: USERS_URL(nickname),
|
||||
method: 'GET',
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const deleteAccounts = ({ credentials, screen_names: nicknames }) =>
|
||||
promisedRequest({
|
||||
url: USERS_URL(),
|
||||
method: 'DELETE',
|
||||
credentials,
|
||||
payload: {
|
||||
nicknames,
|
||||
},
|
||||
})
|
||||
|
||||
export const getAnnouncements = ({ id, credentials }) =>
|
||||
promisedRequest({ url: ANNOUNCEMENTS_URL(id), credentials })
|
||||
|
||||
// the reported list is hardly useful because standards are for dating i guess,
|
||||
// so make sure to fetchIfMissing right afterward using this call
|
||||
export const listUsers = ({ opts, credentials }) =>
|
||||
promisedRequest({
|
||||
url: USERS_URL_LIST(opts),
|
||||
credentials,
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
export const resendConfirmationEmail = ({
|
||||
screen_names: nicknames,
|
||||
credentials,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: RESEND_CONFIRMATION_EMAIL_URL,
|
||||
credentials,
|
||||
method: 'PATCH',
|
||||
payload: {
|
||||
nicknames,
|
||||
},
|
||||
})
|
||||
|
||||
export const requirePasswordChange = ({
|
||||
screen_names: nicknames,
|
||||
credentials,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: REQUIRE_PASSWORD_CHANGE_URL,
|
||||
credentials,
|
||||
method: 'PATCH',
|
||||
payload: {
|
||||
nicknames,
|
||||
},
|
||||
})
|
||||
|
||||
export const disableMFA = ({ screen_name: nickname, credentials }) =>
|
||||
promisedRequest({
|
||||
url: DISABLE_MFA_URL,
|
||||
credentials,
|
||||
method: 'PUT',
|
||||
payload: {
|
||||
nickname,
|
||||
},
|
||||
})
|
||||
|
||||
export const listStatuses = ({ opts, credentials }) =>
|
||||
promisedRequest({
|
||||
url: LIST_STATUSES_URL(opts),
|
||||
credentials,
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
export const changeStatusScope = ({
|
||||
opts: { id, sensitive, visibility },
|
||||
credentials,
|
||||
}) => {
|
||||
var payload = {}
|
||||
if (typeof sensitive !== 'undefined') {
|
||||
payload['sensitive'] = sensitive
|
||||
}
|
||||
if (typeof visibility !== 'undefined') {
|
||||
payload['visibility'] = visibility
|
||||
}
|
||||
|
||||
return promisedRequest({
|
||||
url: CHANGE_STATUS_SCOPE_URL(id),
|
||||
credentials,
|
||||
method: 'PUT',
|
||||
payload,
|
||||
})
|
||||
}
|
||||
|
||||
export const announcementToPayload = ({
|
||||
content,
|
||||
startsAt,
|
||||
endsAt,
|
||||
allDay,
|
||||
}) => {
|
||||
const payload = { content }
|
||||
|
||||
if (typeof startsAt !== 'undefined') {
|
||||
payload.starts_at = startsAt ? new Date(startsAt).toISOString() : null
|
||||
}
|
||||
|
||||
if (typeof endsAt !== 'undefined') {
|
||||
payload.ends_at = endsAt ? new Date(endsAt).toISOString() : null
|
||||
}
|
||||
|
||||
if (typeof allDay !== 'undefined') {
|
||||
payload.all_day = allDay
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
export const postAnnouncement = ({
|
||||
credentials,
|
||||
content,
|
||||
startsAt,
|
||||
endsAt,
|
||||
allDay,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: ANNOUNCEMENTS_URL(),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
payload: announcementToPayload({ content, startsAt, endsAt, allDay }),
|
||||
})
|
||||
|
||||
export const editAnnouncement = ({
|
||||
id,
|
||||
credentials,
|
||||
content,
|
||||
startsAt,
|
||||
endsAt,
|
||||
allDay,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: ANNOUNCEMENTS_URL(id),
|
||||
credentials,
|
||||
method: 'PATCH',
|
||||
payload: announcementToPayload({ content, startsAt, endsAt, allDay }),
|
||||
})
|
||||
|
||||
export const deleteAnnouncement = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: ANNOUNCEMENTS_URL(id),
|
||||
credentials,
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
export const setReportState = ({ id, state, credentials }) => {
|
||||
return promisedRequest({
|
||||
url: REPORTS,
|
||||
credentials,
|
||||
method: 'PATCH',
|
||||
payload: {
|
||||
reports: [
|
||||
{
|
||||
id,
|
||||
state,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const getInstanceDBConfig = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: CONFIG_URL,
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const getInstanceConfigDescriptions = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: DESCRIPTIONS_URL,
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const getAvailableFrontends = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: FRONTENDS_URL,
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const pushInstanceDBConfig = ({ credentials, payload }) =>
|
||||
promisedRequest({
|
||||
url: CONFIG_URL,
|
||||
method: 'POST',
|
||||
credentials,
|
||||
payload,
|
||||
})
|
||||
|
||||
export const installFrontend = ({ credentials, payload }) =>
|
||||
promisedRequest({
|
||||
url: FRONTENDS_INSTALL_URL,
|
||||
credentials,
|
||||
method: 'POST',
|
||||
payload,
|
||||
})
|
||||
|
||||
// Emoji packs
|
||||
export const deleteEmojiPack = ({ name }) =>
|
||||
promisedRequest({
|
||||
url: EMOJI_PACK_URL(name),
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
export const reloadEmoji = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: EMOJI_RELOAD_URL,
|
||||
method: 'POST',
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const importEmojiFromFS = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: EMOJI_IMPORT_FS_URL,
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const createEmojiPack = ({ name, credentials }) =>
|
||||
promisedRequest({
|
||||
url: EMOJI_PACK_URL(name),
|
||||
method: 'POST',
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const listRemoteEmojiPacks = ({
|
||||
instance,
|
||||
page,
|
||||
pageSize,
|
||||
credentials,
|
||||
}) => {
|
||||
if (!instance.startsWith('http')) {
|
||||
instance = 'https://' + instance
|
||||
}
|
||||
|
||||
return promisedRequest({
|
||||
url: EMOJI_PACKS_LS_REMOTE_URL(instance, page, pageSize),
|
||||
credentials,
|
||||
})
|
||||
}
|
||||
|
||||
export const downloadRemoteEmojiPack = ({
|
||||
instance,
|
||||
packName,
|
||||
as,
|
||||
credentials,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: EMOJI_PACKS_DL_REMOTE_URL,
|
||||
credentials,
|
||||
method: 'POST',
|
||||
payload: {
|
||||
url: instance,
|
||||
name: packName,
|
||||
as,
|
||||
},
|
||||
})
|
||||
|
||||
export const downloadRemoteEmojiPackZIP = ({
|
||||
url,
|
||||
packName,
|
||||
file,
|
||||
credentials,
|
||||
}) => {
|
||||
const data = new FormData()
|
||||
if (file) data.set('file', file)
|
||||
if (url) data.set('url', url)
|
||||
data.set('name', packName)
|
||||
|
||||
return promisedRequest({
|
||||
url: EMOJI_PACKS_DL_REMOTE_ZIP_URL,
|
||||
method: 'POST',
|
||||
payload: data,
|
||||
})
|
||||
}
|
||||
|
||||
export const saveEmojiPackMetadata = ({ name, newData, credentials }) =>
|
||||
promisedRequest({
|
||||
url: EMOJI_PACK_URL(name),
|
||||
credentials,
|
||||
method: 'PATCH',
|
||||
payload: { metadata: newData },
|
||||
})
|
||||
|
||||
export const addNewEmojiFile = ({ packName, file, shortcode, filename }) => {
|
||||
const data = new FormData()
|
||||
if (filename.trim() !== '') {
|
||||
data.set('filename', filename)
|
||||
}
|
||||
if (shortcode.trim() !== '') {
|
||||
data.set('shortcode', shortcode)
|
||||
}
|
||||
data.set('file', file)
|
||||
|
||||
return promisedRequest({
|
||||
url: EMOJI_UPDATE_FILE_URL(packName),
|
||||
method: 'POST',
|
||||
payload: data,
|
||||
})
|
||||
}
|
||||
|
||||
export const updateEmojiFile = ({
|
||||
packName,
|
||||
shortcode,
|
||||
newShortcode,
|
||||
newFilename,
|
||||
credentials,
|
||||
force,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: EMOJI_UPDATE_FILE_URL(packName),
|
||||
credentials,
|
||||
method: 'PATCH',
|
||||
payload: {
|
||||
shortcode,
|
||||
new_shortcode: newShortcode,
|
||||
new_filename: newFilename,
|
||||
force,
|
||||
},
|
||||
})
|
||||
|
||||
export const deleteEmojiFile = ({ packName, shortcode }) =>
|
||||
promisedRequest({
|
||||
url: `${EMOJI_UPDATE_FILE_URL(packName)}&shortcode=${shortcode}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
87
src/api/chats.js
Normal file
87
src/api/chats.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import { paramsString, promisedRequest } from './helpers.js'
|
||||
|
||||
import { parseChat } from 'src/services/entity_normalizer/entity_normalizer.service.js'
|
||||
|
||||
const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats'
|
||||
const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}`
|
||||
const PLEROMA_CHAT_MESSAGES_URL = (id, { maxId, sinceId, limit }) =>
|
||||
`/api/v1/pleroma/chats/${id}/messages${paramsString({ maxId, sinceId, limit })}`
|
||||
const PLEROMA_CHAT_READ_URL = (id) => `/api/v1/pleroma/chats/${id}/read`
|
||||
const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) =>
|
||||
`/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
|
||||
|
||||
export const chats = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_CHATS_URL,
|
||||
credentials,
|
||||
}).then(({ data }) => ({
|
||||
chatList: data.map(parseChat).filter((c) => c),
|
||||
}))
|
||||
|
||||
export const getOrCreateChat = ({ accountId, credentials }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_CHAT_URL(accountId),
|
||||
method: 'POST',
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const chatMessages = ({
|
||||
id,
|
||||
credentials,
|
||||
maxId,
|
||||
sinceId,
|
||||
limit = 20,
|
||||
}) => {
|
||||
return promisedRequest({
|
||||
url: PLEROMA_CHAT_MESSAGES_URL(id, { maxId, sinceId, limit }),
|
||||
method: 'GET',
|
||||
credentials,
|
||||
})
|
||||
}
|
||||
|
||||
export const sendChatMessage = ({
|
||||
id,
|
||||
content,
|
||||
mediaId = null,
|
||||
idempotencyKey,
|
||||
credentials,
|
||||
}) => {
|
||||
const payload = {
|
||||
content,
|
||||
}
|
||||
|
||||
if (mediaId) {
|
||||
payload.media_id = mediaId
|
||||
}
|
||||
|
||||
const headers = {}
|
||||
|
||||
if (idempotencyKey) {
|
||||
headers['idempotency-key'] = idempotencyKey
|
||||
}
|
||||
|
||||
return promisedRequest({
|
||||
url: PLEROMA_CHAT_MESSAGES_URL(id),
|
||||
method: 'POST',
|
||||
payload,
|
||||
credentials,
|
||||
headers,
|
||||
})
|
||||
}
|
||||
|
||||
export const readChat = ({ id, lastReadId, credentials }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_CHAT_READ_URL(id),
|
||||
method: 'POST',
|
||||
payload: {
|
||||
last_read_id: lastReadId,
|
||||
},
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const deleteChatMessage = ({ chatId, messageId, credentials }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_DELETE_CHAT_MESSAGE_URL(chatId, messageId),
|
||||
method: 'DELETE',
|
||||
credentials,
|
||||
})
|
||||
135
src/api/helpers.js
Normal file
135
src/api/helpers.js
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import { snakeCase } from 'lodash'
|
||||
|
||||
import { StatusCodeError } from 'src/services/errors/errors'
|
||||
|
||||
export const paramsString = (params = {}) => {
|
||||
if (params == null || params === undefined) return ''
|
||||
|
||||
if (typeof params !== 'object' || Array.isArray(params)) {
|
||||
throw new Error('Params are not an object!')
|
||||
}
|
||||
|
||||
const entries = (() => {
|
||||
if (params instanceof Map) {
|
||||
return params.entries()
|
||||
} else {
|
||||
return Object.entries(params)
|
||||
}
|
||||
})()
|
||||
|
||||
if (entries.length === 0) return ''
|
||||
|
||||
const arrays = []
|
||||
const nonArrays = []
|
||||
|
||||
entries.forEach(([k, v]) => {
|
||||
if (v == null) return // Drop nulls
|
||||
if (
|
||||
(typeof v === 'object' && !Array.isArray(v)) ||
|
||||
typeof v === 'function'
|
||||
) {
|
||||
throw new Error('Param cannot be non-primitive!')
|
||||
}
|
||||
if (Array.isArray(v)) {
|
||||
arrays.push([k, v])
|
||||
} else {
|
||||
nonArrays.push([k, v])
|
||||
}
|
||||
})
|
||||
|
||||
arrays.forEach(([k, array]) => {
|
||||
array.forEach((v) => {
|
||||
if (
|
||||
typeof v === 'object' ||
|
||||
typeof v === 'function' ||
|
||||
typeof v === 'undefined'
|
||||
)
|
||||
throw new Error('Array param cannot contain non-primitives!')
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
'?' +
|
||||
[
|
||||
...nonArrays.map(([k, v]) => [snakeCase(k), v]),
|
||||
// turning [a,[1,2,3]] into [[a[],1],[a[],2],[a[],3]]
|
||||
...arrays.reduce(
|
||||
(acc, [k, arrayValue]) => [
|
||||
...acc,
|
||||
...arrayValue.map((v) => [snakeCase(k) + '[]', v]),
|
||||
],
|
||||
[],
|
||||
),
|
||||
]
|
||||
.map(([k, v]) => `${k}=${window.encodeURIComponent(v)}`)
|
||||
.join('&')
|
||||
)
|
||||
}
|
||||
|
||||
export const promisedRequest = async ({
|
||||
method,
|
||||
url,
|
||||
payload,
|
||||
formData,
|
||||
credentials,
|
||||
headers = {},
|
||||
}) => {
|
||||
const options = {
|
||||
method,
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
...headers,
|
||||
},
|
||||
}
|
||||
|
||||
if (!formData) {
|
||||
options.headers['Content-Type'] = 'application/json'
|
||||
}
|
||||
|
||||
if (formData || payload) {
|
||||
options.body = formData || JSON.stringify(payload)
|
||||
}
|
||||
|
||||
if (credentials) {
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
...authHeaders(credentials),
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(url, options)
|
||||
const data = await (async () => {
|
||||
const [contentType] = response.headers
|
||||
.get('content-type')
|
||||
.split(';')
|
||||
.map((x) => x.toLowerCase().trim())
|
||||
const contentLength = parseInt(response.headers.get('content-length'))
|
||||
if (contentLength === 0) return null
|
||||
|
||||
switch (contentType) {
|
||||
case 'text/plain':
|
||||
return await response.text()
|
||||
case 'application/json':
|
||||
return await response.json()
|
||||
default:
|
||||
return await response.bytes()
|
||||
}
|
||||
})()
|
||||
|
||||
const { ok, status } = response
|
||||
|
||||
if (ok) {
|
||||
return { response, status, data }
|
||||
} else {
|
||||
throw new StatusCodeError(response.status, data, { url, options }, response)
|
||||
}
|
||||
}
|
||||
|
||||
const authHeaders = (accessToken) => {
|
||||
if (accessToken) {
|
||||
return { Authorization: `Bearer ${accessToken}` }
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
45
src/api/mfa.js
Normal file
45
src/api/mfa.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { promisedRequest } from './helpers.js'
|
||||
|
||||
export const verifyOTPCode = ({
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance,
|
||||
mfaToken,
|
||||
code,
|
||||
}) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', clientId)
|
||||
formData.append('client_secret', clientSecret)
|
||||
formData.append('mfa_token', mfaToken)
|
||||
formData.append('code', code)
|
||||
formData.append('challenge_type', 'totp')
|
||||
|
||||
return promisedRequest({
|
||||
url: '/oauth/mfa/challenge',
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
|
||||
export const verifyRecoveryCode = ({
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance,
|
||||
mfaToken,
|
||||
code,
|
||||
}) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', clientId)
|
||||
formData.append('client_secret', clientSecret)
|
||||
formData.append('mfa_token', mfaToken)
|
||||
formData.append('code', code)
|
||||
formData.append('challenge_type', 'recovery')
|
||||
|
||||
return promisedRequest({
|
||||
url: `${instance}/oauth/mfa/challenge`,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
146
src/api/oauth.js
Normal file
146
src/api/oauth.js
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
import { paramsString, promisedRequest } from './helpers.js'
|
||||
|
||||
const REDIRECT_URI = `${window.location.origin}/oauth-callback`
|
||||
|
||||
export const MASTODON_APP_VERIFY_URL = '/api/v1/apps/verify_credentials'
|
||||
export const MASTODON_APP_URL = '/api/v1/apps'
|
||||
export const OAUTH_TOKEN_URL = '/oauth/token'
|
||||
export const OAUTH_MFA_CHALLENGE_URL = '/oauth/mfa/challenge'
|
||||
export const OAUTH_REVOKE_URL = '/oauth/revoke'
|
||||
|
||||
export const createApp = () => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_name', 'PleromaFE')
|
||||
formData.append('website', 'https://pleroma.social')
|
||||
formData.append('redirect_uris', REDIRECT_URI)
|
||||
formData.append('scopes', 'read write follow push admin')
|
||||
|
||||
return promisedRequest({
|
||||
method: 'POST',
|
||||
url: MASTODON_APP_URL,
|
||||
formData,
|
||||
}).then(({ data, ...rest }) => ({
|
||||
...rest,
|
||||
data: {
|
||||
...data,
|
||||
clientId: data.client_id,
|
||||
clientSecret: data.client_secret,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
export const verifyAppToken = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_APP_VERIFY_URL,
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const getLoginUrl = ({ instance, clientId }) => {
|
||||
const data = {
|
||||
responseType: 'code',
|
||||
clientId,
|
||||
redirectUri: REDIRECT_URI,
|
||||
scope: 'read write follow push admin',
|
||||
}
|
||||
|
||||
return `${instance}/oauth/authorize${paramsString(data)}`
|
||||
}
|
||||
|
||||
export const getTokenWithCredentials = ({
|
||||
clientId,
|
||||
clientSecret,
|
||||
username,
|
||||
password,
|
||||
}) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', clientId)
|
||||
formData.append('client_secret', clientSecret)
|
||||
formData.append('grant_type', 'password')
|
||||
formData.append('username', username)
|
||||
formData.append('password', password)
|
||||
|
||||
return promisedRequest({
|
||||
url: OAUTH_TOKEN_URL,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
|
||||
export const getToken = ({ clientId, clientSecret, code }) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', clientId)
|
||||
formData.append('client_secret', clientSecret)
|
||||
formData.append('grant_type', 'authorization_code')
|
||||
formData.append('code', code)
|
||||
formData.append('redirect_uri', `${window.location.origin}/oauth-callback`)
|
||||
|
||||
return promisedRequest({
|
||||
url: OAUTH_TOKEN_URL,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
|
||||
export const getClientToken = ({ clientId, clientSecret }) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', clientId)
|
||||
formData.append('client_secret', clientSecret)
|
||||
formData.append('grant_type', 'client_credentials')
|
||||
formData.append('redirect_uri', `${window.location.origin}/oauth-callback`)
|
||||
|
||||
return promisedRequest({
|
||||
url: OAUTH_TOKEN_URL,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
|
||||
export const verifyOTPCode = ({ app, mfaToken, code }) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', app.client_id)
|
||||
formData.append('client_secret', app.client_secret)
|
||||
formData.append('mfa_token', mfaToken)
|
||||
formData.append('code', code)
|
||||
formData.append('challenge_type', 'totp')
|
||||
|
||||
return promisedRequest({
|
||||
url: OAUTH_MFA_CHALLENGE_URL,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
|
||||
export const verifyRecoveryCode = ({ app, mfaToken, code }) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', app.client_id)
|
||||
formData.append('client_secret', app.client_secret)
|
||||
formData.append('mfa_token', mfaToken)
|
||||
formData.append('code', code)
|
||||
formData.append('challenge_type', 'recovery')
|
||||
|
||||
return promisedRequest({
|
||||
url: OAUTH_MFA_CHALLENGE_URL,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
|
||||
export const revokeToken = ({ app, token }) => {
|
||||
const formData = new window.FormData()
|
||||
|
||||
formData.append('client_id', app.clientId)
|
||||
formData.append('client_secret', app.clientSecret)
|
||||
formData.append('token', token)
|
||||
|
||||
return promisedRequest({
|
||||
url: OAUTH_REVOKE_URL,
|
||||
method: 'POST',
|
||||
formData,
|
||||
})
|
||||
}
|
||||
279
src/api/public.js
Normal file
279
src/api/public.js
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
import { paramsString, promisedRequest } from './helpers.js'
|
||||
import { MASTODON_USER_TIMELINE_URL } from './timelines.js'
|
||||
|
||||
import {
|
||||
parseSource,
|
||||
parseStatus,
|
||||
parseUser,
|
||||
} from 'src/services/entity_normalizer/entity_normalizer.service.js'
|
||||
|
||||
const MASTODON_SUGGESTIONS_URL = '/api/v1/suggestions'
|
||||
const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
|
||||
const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
|
||||
const MASTODON_PASSWORD_RESET_URL = ({ email }) =>
|
||||
`/auth/password${paramsString({ email })}`
|
||||
|
||||
const MASTODON_FOLLOWING_URL = (
|
||||
id,
|
||||
{ minId, maxId, sinceId, limit, withRelationships },
|
||||
) =>
|
||||
`/api/v1/accounts/${id}/following${paramsString({ minId, maxId, sinceId, limit, withRelationships })}`
|
||||
const MASTODON_FOLLOWERS_URL = (
|
||||
id,
|
||||
{ minId, maxId, sinceId, limit, withRelationships },
|
||||
) =>
|
||||
`/api/v1/accounts/${id}/followers${paramsString({ minId, maxId, sinceId, limit, withRelationships })}`
|
||||
|
||||
export const MASTODON_STATUS_URL = (id) => `/api/v1/statuses/${id}`
|
||||
const MASTODON_STATUS_CONTEXT_URL = (id) => `/api/v1/statuses/${id}/context`
|
||||
const MASTODON_STATUS_SOURCE_URL = (id) => `/api/v1/statuses/${id}/source`
|
||||
const MASTODON_STATUS_HISTORY_URL = (id) => `/api/v1/statuses/${id}/history`
|
||||
const MASTODON_USER_URL = '/api/v1/accounts'
|
||||
const MASTODON_USER_LOOKUP_URL = ({ acct }) =>
|
||||
`/api/v1/accounts/lookup${paramsString({ acct })}`
|
||||
const MASTODON_POLL_URL = (id = '') => `/api/v1/polls/${id}`
|
||||
const MASTODON_STATUS_FAVORITEDBY_URL = (id) =>
|
||||
`/api/v1/statuses/${id}/favourited_by`
|
||||
const MASTODON_STATUS_REBLOGGEDBY_URL = (id) =>
|
||||
`/api/v1/statuses/${id}/reblogged_by`
|
||||
const MASTODON_SEARCH_2 = ({
|
||||
q,
|
||||
resolve,
|
||||
limit,
|
||||
offset,
|
||||
following,
|
||||
type,
|
||||
withRelationships,
|
||||
accountId,
|
||||
excludeUnreviewed,
|
||||
}) =>
|
||||
`/api/v2/search${paramsString({ q, resolve, limit, offset, following, type, withRelationships, accountId, excludeUnreviewed })}`
|
||||
const MASTODON_USER_SEARCH_URL = ({ q, resolve }) =>
|
||||
`/api/v1/accounts/search${paramsString({ q, resolve })}`
|
||||
const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers'
|
||||
const PLEROMA_EMOJI_REACTIONS_URL = (id) =>
|
||||
`/api/v1/pleroma/statuses/${id}/reactions`
|
||||
const PLEROMA_SCROBBLES_URL = (id, { maxId, sinceId, minId, limit, offset }) =>
|
||||
`/api/v1/pleroma/accounts/${id}/scrobbles${paramsString({ maxId, sinceId, minId, limit, offset })}`
|
||||
|
||||
const EMOJI_PACKS_URL = (page, pageSize) =>
|
||||
`/api/v1/pleroma/emoji/packs${paramsString({ page, pageSize })}`
|
||||
|
||||
// Params needed:
|
||||
// nickname
|
||||
// email
|
||||
// fullname
|
||||
// password
|
||||
// password_confirm
|
||||
//
|
||||
// Optional
|
||||
// bio
|
||||
// homepage
|
||||
// location
|
||||
// token
|
||||
// language
|
||||
export const register = ({ params, credentials }) => {
|
||||
const { nickname, ...rest } = params
|
||||
return promisedRequest({
|
||||
url: MASTODON_REGISTRATION_URL,
|
||||
method: 'POST',
|
||||
credentials,
|
||||
payload: {
|
||||
nickname,
|
||||
locale: 'en_US',
|
||||
agreement: true,
|
||||
...rest,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const getCaptcha = () =>
|
||||
promisedRequest({
|
||||
url: '/api/pleroma/captcha',
|
||||
})
|
||||
|
||||
export const fetchUser = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: `${MASTODON_USER_URL}/${id}`,
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
|
||||
|
||||
export const fetchUserByName = ({ name, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_USER_LOOKUP_URL({ acct: name }),
|
||||
credentials,
|
||||
})
|
||||
.then(({ data }) => data.id)
|
||||
.catch((error) => {
|
||||
if (error && error.statusCode === 404) {
|
||||
// Either the backend does not support lookup endpoint,
|
||||
// or there is no user with such name. Fallback and treat name as id.
|
||||
return name
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
.then((id) => fetchUser({ id, credentials }))
|
||||
|
||||
export const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_FOLLOWING_URL(id, { maxId, sinceId, limit }),
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
|
||||
|
||||
export const fetchFollowers = ({
|
||||
id,
|
||||
maxId,
|
||||
sinceId,
|
||||
limit = 20,
|
||||
credentials,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_FOLLOWERS_URL(id, {
|
||||
maxId,
|
||||
sinceId,
|
||||
limit,
|
||||
withRelationships: true,
|
||||
}),
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
|
||||
|
||||
export const fetchConversation = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_STATUS_CONTEXT_URL(id),
|
||||
credentials,
|
||||
}).then((result) => ({
|
||||
...result,
|
||||
data: {
|
||||
...result.data,
|
||||
ancestors: result.data.ancestors.map(parseStatus),
|
||||
descendants: result.data.descendants.map(parseStatus),
|
||||
},
|
||||
}))
|
||||
|
||||
export const fetchStatus = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_STATUS_URL(id),
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
||||
|
||||
export const fetchStatusSource = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_STATUS_SOURCE_URL(id),
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseSource(data) }))
|
||||
|
||||
export const fetchStatusHistory = ({ status, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_STATUS_HISTORY_URL(status.id),
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => {
|
||||
return [...data].reverse().map((item) => {
|
||||
item.originalStatus = status
|
||||
return { ...rest, data: parseStatus(item) }
|
||||
})
|
||||
})
|
||||
|
||||
export const listEmojiPacks = ({ page, pageSize, credentials }) =>
|
||||
promisedRequest({
|
||||
url: EMOJI_PACKS_URL(page, pageSize),
|
||||
})
|
||||
|
||||
export const fetchPinnedStatuses = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_USER_TIMELINE_URL(id, { pinned: true }),
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseStatus) }))
|
||||
|
||||
export const verifyCredentials = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_LOGIN_URL,
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
|
||||
|
||||
export const resetPassword = ({ email }) => {
|
||||
return promisedRequest({
|
||||
url: MASTODON_PASSWORD_RESET_URL({ email }),
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
export const suggestions = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_SUGGESTIONS_URL,
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const fetchPoll = ({ pollId, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_POLL_URL(encodeURIComponent(pollId)),
|
||||
method: 'GET',
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const fetchFavoritedByUsers = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_STATUS_FAVORITEDBY_URL(id),
|
||||
method: 'GET',
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
|
||||
|
||||
export const fetchRebloggedByUsers = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_STATUS_REBLOGGEDBY_URL(id),
|
||||
method: 'GET',
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
|
||||
|
||||
export const fetchEmojiReactions = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_EMOJI_REACTIONS_URL(id),
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({
|
||||
...rest,
|
||||
data: data.map((r) => {
|
||||
r.accounts = r.accounts.map(parseUser)
|
||||
return r
|
||||
}),
|
||||
}))
|
||||
|
||||
export const searchUsers = ({ credentials, query }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_USER_SEARCH_URL({ q: query, resolve: true }),
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
|
||||
|
||||
export const search2 = ({
|
||||
credentials,
|
||||
q,
|
||||
resolve,
|
||||
limit,
|
||||
offset,
|
||||
following,
|
||||
type,
|
||||
}) => {
|
||||
return promisedRequest({
|
||||
url: MASTODON_SEARCH_2({
|
||||
q,
|
||||
resolve,
|
||||
limit,
|
||||
offset,
|
||||
following,
|
||||
type,
|
||||
withRelationships: true,
|
||||
}),
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => {
|
||||
data.accounts = data.accounts.slice(0, limit).map((u) => parseUser(u))
|
||||
data.statuses = data.statuses.slice(0, limit).map((s) => parseStatus(s))
|
||||
return { ...rest, data }
|
||||
})
|
||||
}
|
||||
|
||||
export const fetchKnownDomains = ({ credentials }) =>
|
||||
promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials })
|
||||
|
||||
export const fetchScrobbles = ({ accountId, limit = 1 }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_SCROBBLES_URL(accountId, { limit }),
|
||||
})
|
||||
198
src/api/timelines.js
Normal file
198
src/api/timelines.js
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
import { paramsString, promisedRequest } from './helpers.js'
|
||||
|
||||
import {
|
||||
parseLinkHeaderPagination,
|
||||
parseNotification,
|
||||
parseStatus,
|
||||
} from 'src/services/entity_normalizer/entity_normalizer.service.js'
|
||||
|
||||
const MASTODON_USER_HOME_TIMELINE_URL = ({
|
||||
minId,
|
||||
sinceId,
|
||||
maxId,
|
||||
limit,
|
||||
replyVisibility,
|
||||
}) =>
|
||||
`/api/v1/timelines/home${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
||||
const MASTODON_LIST_TIMELINE_URL = (
|
||||
id,
|
||||
{ minId, sinceId, maxId, limit, replyVisibility },
|
||||
) =>
|
||||
`/api/v1/timelines/list/${id}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
||||
const MASTODON_DIRECT_MESSAGES_TIMELINE_URL = ({
|
||||
minId,
|
||||
sinceId,
|
||||
maxId,
|
||||
limit,
|
||||
replyVisibility,
|
||||
}) =>
|
||||
`/api/v1/timelines/direct${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
||||
const MASTODON_PUBLIC_TIMELINE = ({
|
||||
minId,
|
||||
sinceId,
|
||||
maxId,
|
||||
limit,
|
||||
replyVisibility,
|
||||
local,
|
||||
remote,
|
||||
onlyMedia,
|
||||
}) =>
|
||||
`/api/v1/timelines/public${paramsString({ minId, sinceId, maxId, limit, replyVisibility, local, remote, onlyMedia })}`
|
||||
const MASTODON_TAG_TIMELINE_URL = (
|
||||
tag,
|
||||
{ minId, sinceId, maxId, limit, replyVisibility },
|
||||
) =>
|
||||
`/api/v1/timelines/tag/${tag}${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
||||
export const MASTODON_USER_TIMELINE_URL = (
|
||||
id,
|
||||
{ minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia },
|
||||
) =>
|
||||
`/api/v1/accounts/${id}/statuses${paramsString({ minId, sinceId, maxId, limit, replyVisibility, pinned, onlyMedia })}`
|
||||
const MASTODON_USER_FAVORITES_TIMELINE_URL = ({
|
||||
minId,
|
||||
sinceId,
|
||||
maxId,
|
||||
limit,
|
||||
replyVisibility,
|
||||
}) =>
|
||||
`/api/v1/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
||||
const MASTODON_BOOKMARK_TIMELINE_URL = ({
|
||||
minId,
|
||||
sinceId,
|
||||
maxId,
|
||||
limit,
|
||||
replyVisibility,
|
||||
folderId,
|
||||
}) =>
|
||||
`/api/v1/bookmarks${paramsString({ minId, sinceId, maxId, limit, replyVisibility, folderId })}`
|
||||
const PLEROMA_STATUS_QUOTES_URL = (
|
||||
id,
|
||||
{ minId, sinceId, maxId, limit, replyVisibility },
|
||||
) =>
|
||||
`/api/v1/pleroma/statuses/${id}/quotes${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
||||
const PLEROMA_USER_FAVORITES_TIMELINE_URL = (
|
||||
id,
|
||||
{ minId, sinceId, maxId, limit, replyVisibility },
|
||||
) =>
|
||||
`/api/v1/pleroma/accounts/${id}/favourites${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
||||
const AKKOMA_BUBBLE_TIMELINE_URL = ({
|
||||
minId,
|
||||
sinceId,
|
||||
maxId,
|
||||
limit,
|
||||
replyVisibility,
|
||||
}) =>
|
||||
`/api/v1/timelines/bubble${paramsString({ minId, sinceId, maxId, limit, replyVisibility })}`
|
||||
|
||||
const MASTODON_USER_NOTIFICATIONS_URL = ({
|
||||
minId,
|
||||
sinceId,
|
||||
maxId,
|
||||
limit,
|
||||
includeTypes,
|
||||
replyVisibility,
|
||||
}) =>
|
||||
`/api/v1/notifications${paramsString({ minId, sinceId, maxId, limit, includeTypes, replyVisibility })}`
|
||||
|
||||
export const fetchTimeline = ({
|
||||
timeline,
|
||||
credentials,
|
||||
sinceId,
|
||||
minId,
|
||||
maxId,
|
||||
userId,
|
||||
listId,
|
||||
statusId,
|
||||
tag,
|
||||
withMuted,
|
||||
replyVisibility = 'all',
|
||||
includeTypes = [],
|
||||
bookmarkFolderId,
|
||||
}) => {
|
||||
const timelineUrls = {
|
||||
friends: MASTODON_USER_HOME_TIMELINE_URL,
|
||||
public: MASTODON_PUBLIC_TIMELINE,
|
||||
publicAndExternal: MASTODON_PUBLIC_TIMELINE,
|
||||
dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
|
||||
user: MASTODON_USER_TIMELINE_URL,
|
||||
media: MASTODON_USER_TIMELINE_URL,
|
||||
list: MASTODON_LIST_TIMELINE_URL,
|
||||
favorites: MASTODON_USER_FAVORITES_TIMELINE_URL,
|
||||
publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL,
|
||||
bookmarks: MASTODON_BOOKMARK_TIMELINE_URL,
|
||||
bubble: AKKOMA_BUBBLE_TIMELINE_URL,
|
||||
tag: MASTODON_TAG_TIMELINE_URL,
|
||||
quotes: PLEROMA_STATUS_QUOTES_URL,
|
||||
|
||||
notifications: MASTODON_USER_NOTIFICATIONS_URL,
|
||||
}
|
||||
const urlFunc = timelineUrls[timeline]
|
||||
|
||||
const twoArgs = new Set([
|
||||
'user',
|
||||
'media',
|
||||
'list',
|
||||
'publicFavorites',
|
||||
'tag',
|
||||
'quotes',
|
||||
])
|
||||
|
||||
const params = {
|
||||
minId,
|
||||
sinceId,
|
||||
maxId,
|
||||
limit: 20,
|
||||
}
|
||||
|
||||
const id = (() => {
|
||||
switch (timeline) {
|
||||
case 'user':
|
||||
case 'media':
|
||||
return userId
|
||||
case 'list':
|
||||
return listId
|
||||
case 'quotes':
|
||||
return statusId
|
||||
case 'tag':
|
||||
return tag
|
||||
}
|
||||
})()
|
||||
|
||||
const isNotifications = timeline === 'notifications'
|
||||
|
||||
if (timeline === 'media') {
|
||||
params.onlyMedia = true
|
||||
}
|
||||
if (timeline === 'public') {
|
||||
params.local = true
|
||||
}
|
||||
if (timeline !== 'favorites' && timeline !== 'bookmarks') {
|
||||
params.withMuted = withMuted
|
||||
}
|
||||
if (replyVisibility !== 'all') {
|
||||
params.replyVisibility = replyVisibility
|
||||
}
|
||||
if (timeline === 'bookmarks' && bookmarkFolderId) {
|
||||
params.folderId = bookmarkFolderId
|
||||
}
|
||||
|
||||
if (isNotifications && includeTypes.length > 0) {
|
||||
params.includeTypes = includeTypes
|
||||
}
|
||||
|
||||
const url = twoArgs.has(timeline) ? urlFunc(id, params) : urlFunc(params)
|
||||
return promisedRequest({ url, credentials }).then((result) => {
|
||||
const pagination = parseLinkHeaderPagination(
|
||||
result.response.headers.get('Link'),
|
||||
{
|
||||
flakeId: timeline !== 'bookmarks' && timeline !== 'notifications',
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
...result,
|
||||
data: result.data.map(isNotifications ? parseNotification : parseStatus),
|
||||
pagination,
|
||||
}
|
||||
})
|
||||
}
|
||||
924
src/api/user.js
Normal file
924
src/api/user.js
Normal file
|
|
@ -0,0 +1,924 @@
|
|||
import { concat, last } from 'lodash'
|
||||
|
||||
import { paramsString, promisedRequest } from './helpers.js'
|
||||
import { fetchFriends, MASTODON_STATUS_URL } from './public.js'
|
||||
|
||||
import {
|
||||
parseAttachment,
|
||||
parseStatus,
|
||||
parseUser,
|
||||
} from 'src/services/entity_normalizer/entity_normalizer.service.js'
|
||||
|
||||
const MUTES_IMPORT_URL = '/api/pleroma/mutes_import'
|
||||
const BLOCKS_IMPORT_URL = '/api/pleroma/blocks_import'
|
||||
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
|
||||
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
|
||||
const CHANGE_EMAIL_URL = '/api/pleroma/change_email'
|
||||
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
|
||||
const MOVE_ACCOUNT_URL = '/api/pleroma/move_account'
|
||||
const ALIASES_URL = '/api/pleroma/aliases'
|
||||
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
|
||||
const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
|
||||
|
||||
const MFA_SETTINGS_URL = '/api/pleroma/accounts/mfa'
|
||||
const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
|
||||
|
||||
const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp'
|
||||
const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp'
|
||||
const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/totp'
|
||||
|
||||
const MASTODON_DISMISS_NOTIFICATION_URL = (id) =>
|
||||
`/api/v1/notifications/${id}/dismiss`
|
||||
const MASTODON_FAVORITE_URL = (id) => `/api/v1/statuses/${id}/favourite`
|
||||
const MASTODON_UNFAVORITE_URL = (id) => `/api/v1/statuses/${id}/unfavourite`
|
||||
const MASTODON_RETWEET_URL = (id) => `/api/v1/statuses/${id}/reblog`
|
||||
const MASTODON_UNRETWEET_URL = (id) => `/api/v1/statuses/${id}/unreblog`
|
||||
const MASTODON_DELETE_URL = (id) => `/api/v1/statuses/${id}`
|
||||
const MASTODON_FOLLOW_URL = (id) => `/api/v1/accounts/${id}/follow`
|
||||
const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow`
|
||||
|
||||
const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests'
|
||||
const MASTODON_APPROVE_USER_URL = (id) =>
|
||||
`/api/v1/follow_requests/${id}/authorize`
|
||||
const MASTODON_DENY_USER_URL = (id) => `/api/v1/follow_requests/${id}/reject`
|
||||
const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) =>
|
||||
`/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}`
|
||||
const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists`
|
||||
export const MASTODON_LIST_URL = (id = '') => `/api/v1/lists/${id}`
|
||||
export const MASTODON_LIST_ACCOUNTS_URL = (id) => `/api/v1/lists/${id}/accounts`
|
||||
const MASTODON_USER_BLOCKS_URL = ({
|
||||
maxId,
|
||||
sinceId,
|
||||
limit,
|
||||
withRelationships,
|
||||
}) =>
|
||||
`/api/v1/blocks/${paramsString({ maxId, sinceId, limit, withRelationships })}`
|
||||
const MASTODON_USER_MUTES_URL = ({
|
||||
maxId,
|
||||
sinceId,
|
||||
limit,
|
||||
withRelationships,
|
||||
}) =>
|
||||
`/api/v1/mutes/${paramsString({ maxId, sinceId, limit, withRelationships })}`
|
||||
const MASTODON_BLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/block`
|
||||
const MASTODON_UNBLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/unblock`
|
||||
const MASTODON_MUTE_USER_URL = (id) => `/api/v1/accounts/${id}/mute`
|
||||
const MASTODON_UNMUTE_USER_URL = (id) => `/api/v1/accounts/${id}/unmute`
|
||||
const MASTODON_REMOVE_USER_FROM_FOLLOWERS = (id) =>
|
||||
`/api/v1/accounts/${id}/remove_from_followers`
|
||||
const MASTODON_USER_NOTE_URL = (id) => `/api/v1/accounts/${id}/note`
|
||||
const MASTODON_BOOKMARK_STATUS_URL = (id) => `/api/v1/statuses/${id}/bookmark`
|
||||
const MASTODON_UNBOOKMARK_STATUS_URL = (id) =>
|
||||
`/api/v1/statuses/${id}/unbookmark`
|
||||
const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
|
||||
const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
|
||||
const MASTODON_VOTE_URL = (id) => `/api/v1/polls/${id}/votes`
|
||||
const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials'
|
||||
const MASTODON_REPORT_USER_URL = '/api/v1/reports'
|
||||
const MASTODON_PIN_OWN_STATUS = (id) => `/api/v1/statuses/${id}/pin`
|
||||
const MASTODON_UNPIN_OWN_STATUS = (id) => `/api/v1/statuses/${id}/unpin`
|
||||
const MASTODON_MUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/mute`
|
||||
const MASTODON_UNMUTE_CONVERSATION = (id) => `/api/v1/statuses/${id}/unmute`
|
||||
const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
|
||||
const MASTODON_ANNOUNCEMENTS_URL = '/api/v1/announcements'
|
||||
const MASTODON_ANNOUNCEMENTS_DISMISS_URL = (id) =>
|
||||
`/api/v1/announcements/${id}/dismiss`
|
||||
const PLEROMA_EMOJI_REACT_URL = (id, emoji) =>
|
||||
`/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
|
||||
const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) =>
|
||||
`/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
|
||||
const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
|
||||
const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders'
|
||||
const PLEROMA_BOOKMARK_FOLDER_URL = (id) =>
|
||||
`/api/v1/pleroma/bookmark_folders/${id}`
|
||||
|
||||
// #Posts
|
||||
export const favorite = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_FAVORITE_URL(id),
|
||||
method: 'POST',
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
||||
|
||||
export const unfavorite = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_UNFAVORITE_URL(id),
|
||||
method: 'POST',
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
||||
|
||||
export const retweet = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_RETWEET_URL(id),
|
||||
method: 'POST',
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
||||
|
||||
export const unretweet = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_UNRETWEET_URL(id),
|
||||
method: 'POST',
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
||||
|
||||
export const reactWithEmoji = ({ id, emoji, credentials }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_EMOJI_REACT_URL(id, emoji),
|
||||
method: 'PUT',
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
||||
|
||||
export const unreactWithEmoji = ({ id, emoji, credentials }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_EMOJI_UNREACT_URL(id, emoji),
|
||||
method: 'DELETE',
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
||||
|
||||
export const bookmarkStatus = ({ id, credentials, ...options }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_BOOKMARK_STATUS_URL(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
payload: {
|
||||
folder_id: options.folder_id,
|
||||
},
|
||||
})
|
||||
|
||||
export const unbookmarkStatus = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_UNBOOKMARK_STATUS_URL(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
export const pinOwnStatus = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_PIN_OWN_STATUS(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
||||
|
||||
export const unpinOwnStatus = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_UNPIN_OWN_STATUS(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
||||
|
||||
export const muteConversation = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_MUTE_CONVERSATION(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
||||
|
||||
export const unmuteConversation = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_UNMUTE_CONVERSATION(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
||||
|
||||
export const vote = ({ pollId, choices, credentials }) => {
|
||||
const form = new FormData()
|
||||
form.append('choices', choices)
|
||||
|
||||
return promisedRequest({
|
||||
url: MASTODON_VOTE_URL(encodeURIComponent(pollId)),
|
||||
method: 'POST',
|
||||
credentials,
|
||||
payload: {
|
||||
choices,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// #Posting
|
||||
export const postStatus = ({
|
||||
credentials,
|
||||
status,
|
||||
spoilerText,
|
||||
visibility,
|
||||
sensitive,
|
||||
poll,
|
||||
mediaIds = [],
|
||||
inReplyToStatusId,
|
||||
quoteId,
|
||||
contentType,
|
||||
preview,
|
||||
idempotencyKey,
|
||||
}) => {
|
||||
const form = new FormData()
|
||||
const pollOptions = poll.options || []
|
||||
|
||||
form.append('status', status)
|
||||
form.append('source', 'Pleroma FE')
|
||||
if (spoilerText) form.append('spoiler_text', spoilerText)
|
||||
if (visibility) form.append('visibility', visibility)
|
||||
if (sensitive) form.append('sensitive', sensitive)
|
||||
if (contentType) form.append('content_type', contentType)
|
||||
mediaIds.forEach((val) => {
|
||||
form.append('media_ids[]', val)
|
||||
})
|
||||
if (pollOptions.some((option) => option !== '')) {
|
||||
const normalizedPoll = {
|
||||
expires_in: parseInt(poll.expiresIn, 10),
|
||||
multiple: poll.multiple,
|
||||
}
|
||||
Object.keys(normalizedPoll).forEach((key) => {
|
||||
form.append(`poll[${key}]`, normalizedPoll[key])
|
||||
})
|
||||
|
||||
pollOptions.forEach((option) => {
|
||||
form.append('poll[options][]', option)
|
||||
})
|
||||
}
|
||||
if (inReplyToStatusId) {
|
||||
form.append('in_reply_to_id', inReplyToStatusId)
|
||||
}
|
||||
if (quoteId) {
|
||||
form.append('quote_id', quoteId)
|
||||
}
|
||||
if (preview) {
|
||||
form.append('preview', 'true')
|
||||
}
|
||||
|
||||
const headers = {}
|
||||
if (idempotencyKey) {
|
||||
headers['idempotency-key'] = idempotencyKey
|
||||
}
|
||||
|
||||
return promisedRequest({
|
||||
url: MASTODON_POST_STATUS_URL,
|
||||
formData: form,
|
||||
method: 'POST',
|
||||
credentials,
|
||||
headers,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
||||
}
|
||||
|
||||
export const editStatus = ({
|
||||
id,
|
||||
credentials,
|
||||
status,
|
||||
spoilerText,
|
||||
sensitive,
|
||||
poll,
|
||||
mediaIds = [],
|
||||
contentType,
|
||||
}) => {
|
||||
const form = new FormData()
|
||||
const pollOptions = poll.options || []
|
||||
|
||||
form.append('status', status)
|
||||
if (spoilerText) form.append('spoiler_text', spoilerText)
|
||||
if (sensitive) form.append('sensitive', sensitive)
|
||||
if (contentType) form.append('content_type', contentType)
|
||||
mediaIds.forEach((val) => {
|
||||
form.append('media_ids[]', val)
|
||||
})
|
||||
|
||||
if (pollOptions.some((option) => option !== '')) {
|
||||
const normalizedPoll = {
|
||||
expires_in: parseInt(poll.expiresIn, 10),
|
||||
multiple: poll.multiple,
|
||||
}
|
||||
Object.keys(normalizedPoll).forEach((key) => {
|
||||
form.append(`poll[${key}]`, normalizedPoll[key])
|
||||
})
|
||||
|
||||
pollOptions.forEach((option) => {
|
||||
form.append('poll[options][]', option)
|
||||
})
|
||||
}
|
||||
|
||||
return promisedRequest({
|
||||
url: MASTODON_STATUS_URL(id),
|
||||
formData: form,
|
||||
method: 'PUT',
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseStatus(data) }))
|
||||
}
|
||||
|
||||
export const deleteStatus = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_DELETE_URL(id),
|
||||
credentials,
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
export const uploadMedia = ({ formData, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_MEDIA_UPLOAD_URL,
|
||||
formData,
|
||||
method: 'POST',
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseAttachment(data) }))
|
||||
|
||||
export const setMediaDescription = ({ id, description, credentials }) =>
|
||||
promisedRequest({
|
||||
url: `${MASTODON_MEDIA_UPLOAD_URL}/${id}`,
|
||||
method: 'PUT',
|
||||
credentials,
|
||||
payload: {
|
||||
description,
|
||||
},
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseAttachment(data) }))
|
||||
|
||||
// #Notifications
|
||||
export const dismissNotification = ({ credentials, id }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_DISMISS_NOTIFICATION_URL(id),
|
||||
method: 'POST',
|
||||
payload: { id },
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const markNotificationsAsSeen = ({
|
||||
id,
|
||||
credentials,
|
||||
single = false,
|
||||
}) => {
|
||||
const formData = new FormData()
|
||||
|
||||
if (single) {
|
||||
formData.append('id', id)
|
||||
} else {
|
||||
formData.append('max_id', id)
|
||||
}
|
||||
|
||||
return promisedRequest({
|
||||
url: NOTIFICATION_READ_URL,
|
||||
formData,
|
||||
credentials,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
// #Announcements
|
||||
export const getAnnouncements = ({ credentials }) =>
|
||||
promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials })
|
||||
|
||||
export const dismissAnnouncement = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_ANNOUNCEMENTS_DISMISS_URL(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
// #Imports
|
||||
export const importMutes = ({ file, credentials }) => {
|
||||
const formData = new FormData()
|
||||
formData.append('list', file)
|
||||
return promisedRequest({
|
||||
url: MUTES_IMPORT_URL,
|
||||
formData,
|
||||
method: 'POST',
|
||||
credentials,
|
||||
}).then((response) => response.ok)
|
||||
}
|
||||
|
||||
export const importBlocks = ({ file, credentials }) => {
|
||||
const formData = new FormData()
|
||||
formData.append('list', file)
|
||||
return promisedRequest({
|
||||
url: BLOCKS_IMPORT_URL,
|
||||
formData,
|
||||
method: 'POST',
|
||||
credentials,
|
||||
}).then((response) => response.ok)
|
||||
}
|
||||
|
||||
export const importFollows = ({ file, credentials }) => {
|
||||
const formData = new FormData()
|
||||
formData.append('list', file)
|
||||
return promisedRequest({
|
||||
url: FOLLOW_IMPORT_URL,
|
||||
formData,
|
||||
method: 'POST',
|
||||
credentials,
|
||||
}).then((response) => response.ok)
|
||||
}
|
||||
|
||||
export const exportFriends = ({ id, credentials }) => {
|
||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO refactor this
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
let friends = []
|
||||
let more = true
|
||||
while (more) {
|
||||
const maxId = friends.length > 0 ? last(friends).id : undefined
|
||||
const users = await fetchFriends({
|
||||
id,
|
||||
maxId,
|
||||
credentials,
|
||||
withRelationships: true,
|
||||
})
|
||||
friends = concat(friends, users)
|
||||
if (users.length === 0) {
|
||||
more = false
|
||||
}
|
||||
}
|
||||
resolve(friends)
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// #Profile settings
|
||||
export const updateNotificationSettings = ({ credentials, settings }) => {
|
||||
return promisedRequest({
|
||||
url: NOTIFICATION_SETTINGS_URL,
|
||||
credentials,
|
||||
method: 'PUT',
|
||||
payload: settings,
|
||||
})
|
||||
}
|
||||
|
||||
export const updateProfileImages = ({
|
||||
credentials,
|
||||
avatar = null,
|
||||
avatarName = null,
|
||||
banner = null,
|
||||
background = null,
|
||||
}) => {
|
||||
const form = new FormData()
|
||||
if (avatar !== null) {
|
||||
if (avatarName !== null) {
|
||||
form.append('avatar', avatar, avatarName)
|
||||
} else {
|
||||
form.append('avatar', avatar)
|
||||
}
|
||||
}
|
||||
if (banner !== null) form.append('header', banner)
|
||||
if (background !== null) form.append('pleroma_background_image', background)
|
||||
return promisedRequest({
|
||||
url: MASTODON_PROFILE_UPDATE_URL,
|
||||
credentials,
|
||||
method: 'PATCH',
|
||||
formData: form,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
|
||||
}
|
||||
|
||||
export const updateProfile = ({ credentials, params }) => {
|
||||
const formData = new FormData()
|
||||
|
||||
for (const name in params) {
|
||||
if (name === 'fields_attributes') {
|
||||
params[name].forEach((param, i) => {
|
||||
formData.append(name + `[${i}][name]`, param.name)
|
||||
formData.append(name + `[${i}][value]`, param.value)
|
||||
})
|
||||
} else {
|
||||
if (typeof params[name] === 'object') {
|
||||
console.warn(
|
||||
'Object detected in updateProfile API call. This will not work, use updateProfileJSON instead.',
|
||||
)
|
||||
console.warn('Object:\n' + JSON.stringify(params[name], null, 2))
|
||||
}
|
||||
formData.append(name, params[name])
|
||||
}
|
||||
}
|
||||
|
||||
return promisedRequest({
|
||||
url: MASTODON_PROFILE_UPDATE_URL,
|
||||
credentials,
|
||||
method: 'PATCH',
|
||||
formData,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
|
||||
}
|
||||
|
||||
export const updateProfileJSON = ({ credentials, params }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_PROFILE_UPDATE_URL,
|
||||
credentials,
|
||||
payload: params,
|
||||
method: 'PATCH',
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: parseUser(data) }))
|
||||
|
||||
export const changeEmail = ({ credentials, email, password }) => {
|
||||
const form = new FormData()
|
||||
|
||||
form.append('email', email)
|
||||
form.append('password', password)
|
||||
|
||||
return promisedRequest({
|
||||
url: CHANGE_EMAIL_URL,
|
||||
formData: form,
|
||||
method: 'POST',
|
||||
credentials,
|
||||
})
|
||||
}
|
||||
|
||||
export const moveAccount = ({ credentials, password, targetAccount }) => {
|
||||
const form = new FormData()
|
||||
|
||||
form.append('password', password)
|
||||
form.append('target_account', targetAccount)
|
||||
|
||||
return promisedRequest({
|
||||
url: MOVE_ACCOUNT_URL,
|
||||
formData: form,
|
||||
method: 'POST',
|
||||
credentials,
|
||||
})
|
||||
}
|
||||
|
||||
export const changePassword = ({
|
||||
credentials,
|
||||
password,
|
||||
newPassword,
|
||||
newPasswordConfirmation,
|
||||
}) => {
|
||||
const form = new FormData()
|
||||
|
||||
form.append('password', password)
|
||||
form.append('new_password', newPassword)
|
||||
form.append('new_password_confirmation', newPasswordConfirmation)
|
||||
|
||||
return promisedRequest({
|
||||
url: CHANGE_PASSWORD_URL,
|
||||
formData: form,
|
||||
method: 'POST',
|
||||
credentials,
|
||||
})
|
||||
}
|
||||
|
||||
// #MFA
|
||||
export const settingsMFA = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: MFA_SETTINGS_URL,
|
||||
credentials,
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
export const mfaDisableOTP = ({ credentials, password }) => {
|
||||
const form = new FormData()
|
||||
|
||||
form.append('password', password)
|
||||
|
||||
return promisedRequest({
|
||||
url: MFA_DISABLE_OTP_URL,
|
||||
formData: form,
|
||||
method: 'DELETE',
|
||||
credentials,
|
||||
})
|
||||
}
|
||||
|
||||
export const mfaConfirmOTP = ({ credentials, password, token }) => {
|
||||
const form = new FormData()
|
||||
|
||||
form.append('password', password)
|
||||
form.append('code', token)
|
||||
|
||||
return promisedRequest({
|
||||
url: MFA_CONFIRM_OTP_URL,
|
||||
formData: form,
|
||||
credentials,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
export const mfaSetupOTP = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: MFA_SETUP_OTP_URL,
|
||||
credentials,
|
||||
method: 'GET',
|
||||
})
|
||||
export const generateMfaBackupCodes = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: MFA_BACKUP_CODES_URL,
|
||||
credentials,
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
// #Aliases
|
||||
export const addAlias = ({ credentials, alias }) =>
|
||||
promisedRequest({
|
||||
url: ALIASES_URL,
|
||||
method: 'PUT',
|
||||
credentials,
|
||||
payload: { alias },
|
||||
})
|
||||
|
||||
export const deleteAlias = ({ credentials, alias }) =>
|
||||
promisedRequest({
|
||||
url: ALIASES_URL,
|
||||
method: 'DELETE',
|
||||
credentials,
|
||||
payload: { alias },
|
||||
})
|
||||
|
||||
export const listAliases = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: ALIASES_URL,
|
||||
method: 'GET',
|
||||
credentials,
|
||||
params: {
|
||||
_cacheBooster: new Date().getTime(),
|
||||
},
|
||||
})
|
||||
|
||||
// User manipulation
|
||||
export const fetchUserRelationship = ({ id, withSuspended, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_USER_RELATIONSHIPS_URL({ id, withSuspended }),
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const followUser = ({ id, credentials, ...options }) => {
|
||||
const payload = {}
|
||||
|
||||
if (options.reblogs !== undefined) {
|
||||
payload.reblogs = options.reblogs
|
||||
}
|
||||
|
||||
if (options.notify !== undefined) {
|
||||
payload.notify = options.notify
|
||||
}
|
||||
|
||||
return promisedRequest({
|
||||
url: MASTODON_FOLLOW_URL(id),
|
||||
payload,
|
||||
credentials,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
export const unfollowUser = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_UNFOLLOW_URL(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
})
|
||||
export const fetchUserInLists = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_USER_IN_LISTS(id),
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const removeUserFromFollowers = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_REMOVE_USER_FROM_FOLLOWERS(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
export const fetchFollowRequests = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_FOLLOW_REQUESTS_URL,
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
|
||||
|
||||
export const approveUser = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_APPROVE_USER_URL(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
export const denyUser = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_DENY_USER_URL(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
export const editUserNote = ({ id, credentials, comment }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_USER_NOTE_URL(id),
|
||||
credentials,
|
||||
payload: {
|
||||
comment,
|
||||
},
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
export const fetchMutes = ({ maxId, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_USER_MUTES_URL({ maxId, withRelationships: true }),
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
|
||||
|
||||
export const muteUser = ({ id, expiresIn, credentials }) => {
|
||||
const payload = {}
|
||||
if (expiresIn) {
|
||||
payload.expires_in = expiresIn
|
||||
}
|
||||
|
||||
return promisedRequest({
|
||||
url: MASTODON_MUTE_USER_URL(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
payload,
|
||||
})
|
||||
}
|
||||
|
||||
export const unmuteUser = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_UNMUTE_USER_URL(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
export const fetchBlocks = ({ maxId, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_USER_BLOCKS_URL({ maxId, withRelationships: true }),
|
||||
credentials,
|
||||
}).then(({ data, ...rest }) => ({ ...rest, data: data.map(parseUser) }))
|
||||
|
||||
export const blockUser = ({ id, expiresIn, credentials }) => {
|
||||
const payload = {}
|
||||
if (expiresIn) {
|
||||
payload.duration = expiresIn
|
||||
}
|
||||
|
||||
return promisedRequest({
|
||||
url: MASTODON_BLOCK_USER_URL(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
payload,
|
||||
})
|
||||
}
|
||||
|
||||
export const unblockUser = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_UNBLOCK_USER_URL(id),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
export const reportUser = ({
|
||||
credentials,
|
||||
userId,
|
||||
statusIds,
|
||||
comment,
|
||||
forward,
|
||||
}) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_REPORT_USER_URL,
|
||||
method: 'POST',
|
||||
payload: {
|
||||
account_id: userId,
|
||||
status_ids: statusIds,
|
||||
comment,
|
||||
forward,
|
||||
},
|
||||
credentials,
|
||||
})
|
||||
|
||||
// #Domain mutes
|
||||
export const fetchDomainMutes = ({ credentials }) =>
|
||||
promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials })
|
||||
|
||||
export const muteDomain = ({ domain, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_DOMAIN_BLOCKS_URL,
|
||||
method: 'POST',
|
||||
payload: { domain },
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const unmuteDomain = ({ domain, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_DOMAIN_BLOCKS_URL,
|
||||
method: 'DELETE',
|
||||
payload: { domain },
|
||||
credentials,
|
||||
})
|
||||
|
||||
// #Backups
|
||||
export const addBackup = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_BACKUP_URL,
|
||||
method: 'POST',
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const listBackups = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_BACKUP_URL,
|
||||
method: 'GET',
|
||||
credentials,
|
||||
params: {
|
||||
_cacheBooster: new Date().getTime(),
|
||||
},
|
||||
})
|
||||
|
||||
// #OAuth
|
||||
export const fetchOAuthTokens = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: '/api/oauth_tokens.json',
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const revokeOAuthToken = ({ id, credentials }) =>
|
||||
promisedRequest({
|
||||
url: `/api/oauth_tokens/${id}`,
|
||||
credentials,
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
// #Lists
|
||||
export const fetchLists = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_LIST_URL(),
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const createList = ({ title, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_LIST_URL(),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
payload: { title },
|
||||
})
|
||||
|
||||
export const getList = ({ listId, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_LIST_URL(listId),
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const updateList = ({ listId, title, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_LIST_URL(listId),
|
||||
|
||||
credentials,
|
||||
method: 'PUT',
|
||||
payload: { title },
|
||||
})
|
||||
|
||||
export const getListAccounts = ({ listId, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_LIST_ACCOUNTS_URL(listId),
|
||||
credentials,
|
||||
}).then((data) => data.map(({ id }) => id))
|
||||
|
||||
export const addAccountsToList = ({ listId, accountIds, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_LIST_ACCOUNTS_URL(listId),
|
||||
credentials,
|
||||
method: 'POST',
|
||||
payload: { account_ids: accountIds },
|
||||
})
|
||||
|
||||
export const removeAccountsFromList = ({ listId, accountIds, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_LIST_ACCOUNTS_URL(listId),
|
||||
credentials,
|
||||
method: 'DELETE',
|
||||
payload: { account_ids: accountIds },
|
||||
})
|
||||
|
||||
export const deleteList = ({ listId, credentials }) =>
|
||||
promisedRequest({
|
||||
url: MASTODON_LIST_URL(listId),
|
||||
method: 'DELETE',
|
||||
credentials,
|
||||
})
|
||||
|
||||
// #Bookmarks
|
||||
export const fetchBookmarkFolders = ({ credentials }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_BOOKMARK_FOLDERS_URL,
|
||||
credentials,
|
||||
})
|
||||
|
||||
export const createBookmarkFolder = ({ name, emoji, credentials }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_BOOKMARK_FOLDERS_URL,
|
||||
credentials,
|
||||
method: 'POST',
|
||||
payload: { name, emoji },
|
||||
})
|
||||
|
||||
export const updateBookmarkFolder = ({ folderId, name, emoji, credentials }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_BOOKMARK_FOLDER_URL(folderId),
|
||||
credentials,
|
||||
method: 'PATCH',
|
||||
payload: { name, emoji },
|
||||
})
|
||||
|
||||
export const deleteBookmarkFolder = ({ folderId, credentials }) =>
|
||||
promisedRequest({
|
||||
url: PLEROMA_BOOKMARK_FOLDER_URL(folderId),
|
||||
method: 'DELETE',
|
||||
credentials,
|
||||
})
|
||||
|
||||
// #So long and thanks for all the fish
|
||||
export const deleteAccount = ({ credentials, password }) => {
|
||||
const formData = new FormData()
|
||||
|
||||
formData.append('password', password)
|
||||
|
||||
return promisedRequest({
|
||||
url: DELETE_ACCOUNT_URL,
|
||||
formData,
|
||||
method: 'POST',
|
||||
credentials,
|
||||
})
|
||||
}
|
||||
176
src/api/websocket.js
Normal file
176
src/api/websocket.js
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
import { paramsString } from './helpers.js'
|
||||
|
||||
import {
|
||||
parseChat,
|
||||
parseNotification,
|
||||
parseStatus,
|
||||
} from 'src/services/entity_normalizer/entity_normalizer.service.js'
|
||||
|
||||
const MASTODON_STREAMING = ({ accessToken, stream }) =>
|
||||
`/api/v1/streaming${paramsString({ accessToken, stream })}`
|
||||
|
||||
export const getMastodonSocketURI = ({ credentials, stream }) => {
|
||||
return MASTODON_STREAMING({ accessToken: credentials, stream })
|
||||
}
|
||||
|
||||
const MASTODON_STREAMING_EVENTS = new Set([
|
||||
'update',
|
||||
'notification',
|
||||
'delete',
|
||||
'filters_changed',
|
||||
'status.update',
|
||||
])
|
||||
|
||||
const PLEROMA_STREAMING_EVENTS = new Set([
|
||||
'pleroma:chat_update',
|
||||
'pleroma:respond',
|
||||
])
|
||||
|
||||
// A thin wrapper around WebSocket API that allows adding a pre-processor to it
|
||||
// Uses EventTarget and a CustomEvent to proxy events
|
||||
export const ProcessedWS = ({
|
||||
url,
|
||||
preprocessor = handleMastoWS,
|
||||
id = 'Unknown',
|
||||
credentials,
|
||||
}) => {
|
||||
const eventTarget = new EventTarget()
|
||||
const socket = new WebSocket(url)
|
||||
if (!socket) throw new Error(`Failed to create socket ${id}`)
|
||||
const proxy = (original, eventName, processor = (a) => a) => {
|
||||
original.addEventListener(eventName, (eventData) => {
|
||||
eventTarget.dispatchEvent(
|
||||
new CustomEvent(eventName, { detail: processor(eventData) }),
|
||||
)
|
||||
})
|
||||
}
|
||||
socket.addEventListener('open', (wsEvent) => {
|
||||
console.debug(`[WS][${id}] Socket connected`, wsEvent)
|
||||
if (credentials) {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: 'pleroma:authenticate',
|
||||
token: credentials,
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
socket.addEventListener('error', (wsEvent) => {
|
||||
console.debug(`[WS][${id}] Socket errored`, wsEvent)
|
||||
})
|
||||
socket.addEventListener('close', (wsEvent) => {
|
||||
console.debug(
|
||||
`[WS][${id}] Socket disconnected with code ${wsEvent.code}`,
|
||||
wsEvent,
|
||||
)
|
||||
})
|
||||
// Commented code reason: very spammy, uncomment to enable message debug logging
|
||||
/*
|
||||
socket.addEventListener('message', (wsEvent) => {
|
||||
console.debug(
|
||||
`[WS][${id}] Message received`,
|
||||
wsEvent
|
||||
)
|
||||
})
|
||||
/**/
|
||||
|
||||
const onAuthenticated = () => {
|
||||
eventTarget.dispatchEvent(new CustomEvent('pleroma:authenticated'))
|
||||
}
|
||||
|
||||
proxy(socket, 'open')
|
||||
proxy(socket, 'close')
|
||||
proxy(socket, 'message', (event) => preprocessor(event, { onAuthenticated }))
|
||||
proxy(socket, 'error')
|
||||
|
||||
// 1000 = Normal Closure
|
||||
eventTarget.close = () => {
|
||||
socket.close(1000, 'Shutting down socket')
|
||||
}
|
||||
eventTarget.getState = () => socket.readyState
|
||||
eventTarget.subscribe = (stream, args = {}) => {
|
||||
console.debug(`[WS][${id}] Subscribing to stream ${stream} with args`, args)
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: 'subscribe',
|
||||
stream,
|
||||
...args,
|
||||
}),
|
||||
)
|
||||
}
|
||||
eventTarget.unsubscribe = (stream, args = {}) => {
|
||||
console.debug(
|
||||
`[WS][${id}] Unsubscribing from stream ${stream} with args`,
|
||||
args,
|
||||
)
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: 'unsubscribe',
|
||||
stream,
|
||||
...args,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return eventTarget
|
||||
}
|
||||
|
||||
export const handleMastoWS = (
|
||||
wsEvent,
|
||||
{
|
||||
onAuthenticated = () => {
|
||||
/* no-op */
|
||||
},
|
||||
} = {},
|
||||
) => {
|
||||
const { data } = wsEvent
|
||||
if (!data) return
|
||||
const parsedEvent = JSON.parse(data)
|
||||
const { event, payload } = parsedEvent
|
||||
if (
|
||||
MASTODON_STREAMING_EVENTS.has(event) ||
|
||||
PLEROMA_STREAMING_EVENTS.has(event)
|
||||
) {
|
||||
// MastoBE and PleromaBE both send payload for delete as a PLAIN string
|
||||
if (event === 'delete') {
|
||||
return { event, id: payload }
|
||||
}
|
||||
const data = payload ? JSON.parse(payload) : null
|
||||
if (event === 'pleroma:respond') {
|
||||
if (data.type === 'pleroma:authenticate') {
|
||||
if (data.result === 'success') {
|
||||
console.debug('[WS] Successfully authenticated')
|
||||
onAuthenticated()
|
||||
} else {
|
||||
if (data.error === 'already_authenticated') {
|
||||
onAuthenticated()
|
||||
} else {
|
||||
console.error('[WS] Unable to authenticate:', data.error)
|
||||
wsEvent.target.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
} else if (event === 'update') {
|
||||
return { event, status: parseStatus(data) }
|
||||
} else if (event === 'status.update') {
|
||||
return { event, status: parseStatus(data) }
|
||||
} else if (event === 'notification') {
|
||||
return { event, notification: parseNotification(data) }
|
||||
} else if (event === 'pleroma:chat_update') {
|
||||
return { event, chatUpdate: parseChat(data) }
|
||||
}
|
||||
} else {
|
||||
console.warn('Unknown event', wsEvent)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const WSConnectionStatus = Object.freeze({
|
||||
JOINED: 1,
|
||||
CLOSED: 2,
|
||||
ERROR: 3,
|
||||
DISABLED: 4,
|
||||
STARTING: 5,
|
||||
STARTING_INITIAL: 6,
|
||||
})
|
||||
|
|
@ -19,7 +19,6 @@ import {
|
|||
config.autoAddCss = false
|
||||
|
||||
import App from '../App.vue'
|
||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||
import FaviconService from '../services/favicon_service/favicon_service.js'
|
||||
import { applyStyleConfig } from '../services/style_setter/style_setter.js'
|
||||
import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
|
||||
|
|
@ -29,7 +28,6 @@ import {
|
|||
} from '../services/window_utils/window_utils'
|
||||
import routes from './routes'
|
||||
|
||||
import { useAnnouncementsStore } from 'src/stores/announcements'
|
||||
import { useAuthFlowStore } from 'src/stores/auth_flow'
|
||||
import { useEmojiStore } from 'src/stores/emoji.js'
|
||||
import { useI18nStore } from 'src/stores/i18n'
|
||||
|
|
@ -38,7 +36,7 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j
|
|||
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||
import { useLocalConfigStore } from 'src/stores/local_config.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
|
||||
|
||||
|
|
@ -261,16 +259,6 @@ const getStickers = async ({ store }) => {
|
|||
}
|
||||
}
|
||||
|
||||
const getAppSecret = async ({ store }) => {
|
||||
const oauth = useOAuthStore()
|
||||
if (oauth.userToken) {
|
||||
store.commit(
|
||||
'setBackendInteractor',
|
||||
backendInteractorService(oauth.getToken),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const resolveStaffAccounts = ({ store, accounts }) => {
|
||||
const nicknames = accounts.map((uri) => uri.split('/').pop())
|
||||
useInstanceStore().set({
|
||||
|
|
@ -461,14 +449,13 @@ const setConfig = async ({ store }) => {
|
|||
const apiConfig = configInfos[0]
|
||||
const staticConfig = configInfos[1]
|
||||
|
||||
getAppSecret({ store })
|
||||
await setSettings({ store, apiConfig, staticConfig })
|
||||
}
|
||||
|
||||
const checkOAuthToken = async ({ store }) => {
|
||||
const oauth = useOAuthStore()
|
||||
if (oauth.getUserToken) {
|
||||
return store.dispatch('loginUser', oauth.getUserToken)
|
||||
if (oauth.userToken) {
|
||||
return store.dispatch('loginUser', oauth.userToken)
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
|
@ -578,10 +565,6 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
|
|||
getInstanceConfig({ store }),
|
||||
]).catch((e) => Promise.reject(e))
|
||||
|
||||
// Start fetching things that don't need to block the UI
|
||||
store.dispatch('fetchMutes')
|
||||
store.dispatch('loadDrafts')
|
||||
useAnnouncementsStore().startFetchingAnnouncements()
|
||||
getTOS({ store })
|
||||
getStickers({ store })
|
||||
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ const AnnouncementsPage = {
|
|||
canPostAnnouncement() {
|
||||
return (
|
||||
this.currentUser &&
|
||||
this.currentUser.privileges.includes(
|
||||
'announcements_manage_announcements',
|
||||
)
|
||||
this.currentUser.privileges.has('announcements_manage_announcements')
|
||||
)
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { mapState } from 'pinia'
|
|||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import Popover from 'src/components/popover/popover.vue'
|
||||
import VideoAttachment from 'src/components/video_attachment/video_attachment.vue'
|
||||
import nsfwImage from '../../assets/nsfw.png'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(faEllipsisH)
|
||||
|
||||
const BookmarkFolderCard = {
|
||||
props: ['folder', 'allBookmarks'],
|
||||
computed: {
|
||||
firstLetter() {
|
||||
return this.folder ? this.folder.name[0] : null
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default BookmarkFolderCard
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="allBookmarks"
|
||||
class="bookmark-folder-card"
|
||||
>
|
||||
<router-link
|
||||
:to="{ name: 'bookmarks' }"
|
||||
class="bookmark-folder-name"
|
||||
>
|
||||
<span class="icon">
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 menu-icon"
|
||||
icon="bookmark"
|
||||
/>
|
||||
</span>{{ $t('nav.all_bookmarks') }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="bookmark-folder-card"
|
||||
>
|
||||
<router-link
|
||||
:to="{ name: 'bookmark-folder', params: { id: folder.id } }"
|
||||
class="bookmark-folder-name"
|
||||
>
|
||||
<img
|
||||
v-if="folder.emoji_url"
|
||||
class="iconEmoji iconEmoji-image"
|
||||
:src="folder.emoji_url"
|
||||
:alt="folder.emoji"
|
||||
:title="folder.emoji"
|
||||
>
|
||||
<span
|
||||
v-else-if="folder.emoji"
|
||||
class="iconEmoji"
|
||||
>
|
||||
<span>
|
||||
{{ folder.emoji }}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
v-else-if="firstLetter"
|
||||
class="icon iconLetter fa-scale-110"
|
||||
>{{ firstLetter }}</span>{{ folder.name }}
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{ name: 'bookmark-folder-edit', params: { id: folder.id } }"
|
||||
class="button-folder-edit"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="ellipsis-h"
|
||||
/>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./bookmark_folder_card.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.bookmark-folder-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
a.bookmark-folder-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
|
||||
.icon,
|
||||
.iconLetter,
|
||||
.iconEmoji {
|
||||
display: inline-block;
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.icon,
|
||||
.iconLetter {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.iconEmoji {
|
||||
text-align: center;
|
||||
object-fit: contain;
|
||||
vertical-align: middle;
|
||||
|
||||
> span {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
img.iconEmoji {
|
||||
padding: 0.25em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.bookmark-folder-name,
|
||||
.button-folder-edit {
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
color: var(--link);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
import EmojiPicker from 'src/components/emoji_picker/emoji_picker.vue'
|
||||
import apiService from '../../services/api/api.service'
|
||||
|
||||
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
|
||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { fetchBookmarkFolders } from 'src/api/user.js'
|
||||
|
||||
const BookmarkFolderEdit = {
|
||||
data() {
|
||||
|
|
@ -22,8 +24,10 @@ const BookmarkFolderEdit = {
|
|||
},
|
||||
created() {
|
||||
if (!this.id) return
|
||||
const credentials = this.$store.state.users.currentUser.credentials
|
||||
apiService.fetchBookmarkFolders({ credentials }).then((folders) => {
|
||||
|
||||
fetchBookmarkFolders({
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: folders }) => {
|
||||
const folder = folders.find((folder) => folder.id === this.id)
|
||||
if (!folder) return
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import BookmarkFolderCard from 'src/components/bookmark_folder_card/bookmark_folder_card.vue'
|
||||
import FolderCard from 'src/components/folder_card/folder_card.vue'
|
||||
|
||||
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ const BookmarkFolders = {
|
|||
}
|
||||
},
|
||||
components: {
|
||||
BookmarkFolderCard,
|
||||
FolderCard,
|
||||
},
|
||||
computed: {
|
||||
bookmarkFolders() {
|
||||
|
|
|
|||
|
|
@ -12,14 +12,28 @@
|
|||
</router-link>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<BookmarkFolderCard
|
||||
:all-bookmarks="true"
|
||||
class="list-item"
|
||||
/>
|
||||
<BookmarkFolderCard
|
||||
<div class="list-item FolderCard">
|
||||
<router-link
|
||||
:to="{ name: 'bookmarks' }"
|
||||
class="folder-name"
|
||||
>
|
||||
<span class="icon">
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 menu-icon"
|
||||
icon="bookmark"
|
||||
/>
|
||||
</span>{{ $t('nav.all_bookmarks') }}
|
||||
</router-link>
|
||||
</div>
|
||||
<FolderCard
|
||||
v-for="folder in bookmarkFolders.slice().reverse()"
|
||||
:key="folder"
|
||||
:folder="folder"
|
||||
:name="folder.name"
|
||||
:emoji="folder.emoji"
|
||||
:emoji-url="folder.emoji_url"
|
||||
:link="{ name: 'bookmark-folder', params: { id: folder.id } }"
|
||||
:link-edit="{ name: 'bookmark-folder-edit', params: { id: folder.id } }"
|
||||
class="list-item"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { mapGetters, mapState } from 'vuex'
|
|||
import ChatMessage from 'src/components/chat_message/chat_message.vue'
|
||||
import ChatTitle from 'src/components/chat_title/chat_title.vue'
|
||||
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
|
||||
import { WSConnectionStatus } from '../../services/api/api.service.js'
|
||||
import chatService from '../../services/chat_service/chat_service.js'
|
||||
import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js'
|
||||
import { promiseInterval } from '../../services/promise_interval/promise_interval.js'
|
||||
|
|
@ -17,6 +16,14 @@ import {
|
|||
} from './chat_layout_utils.js'
|
||||
|
||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import {
|
||||
chatMessages,
|
||||
getOrCreateChat,
|
||||
sendChatMessage,
|
||||
} from 'src/api/chats.js'
|
||||
import { WSConnectionStatus } from 'src/api/websocket.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons'
|
||||
|
|
@ -115,7 +122,6 @@ const Chat = {
|
|||
mobileLayout: (store) => store.layoutType === 'mobile',
|
||||
}),
|
||||
...mapState({
|
||||
backendInteractor: (state) => state.api.backendInteractor,
|
||||
mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus,
|
||||
currentUser: (state) => state.users.currentUser,
|
||||
}),
|
||||
|
|
@ -267,43 +273,48 @@ const Chat = {
|
|||
const fetchOlderMessages = !!maxId
|
||||
const sinceId = fetchLatest && chatMessageService.maxId
|
||||
|
||||
return this.backendInteractor
|
||||
.chatMessages({ id: chatId, maxId, sinceId })
|
||||
.then((messages) => {
|
||||
// Clear the current chat in case we're recovering from a ws connection loss.
|
||||
if (isFirstFetch) {
|
||||
chatService.clear(chatMessageService)
|
||||
}
|
||||
return chatMessages({
|
||||
id: chatId,
|
||||
maxId,
|
||||
sinceId,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: messages }) => {
|
||||
// Clear the current chat in case we're recovering from a ws connection loss.
|
||||
if (isFirstFetch) {
|
||||
chatService.clear(chatMessageService)
|
||||
}
|
||||
|
||||
const positionBeforeUpdate = getScrollPosition()
|
||||
this.$store
|
||||
.dispatch('addChatMessages', { chatId, messages })
|
||||
.then(() => {
|
||||
this.$nextTick(() => {
|
||||
if (fetchOlderMessages) {
|
||||
this.handleScrollUp(positionBeforeUpdate)
|
||||
}
|
||||
const positionBeforeUpdate = getScrollPosition()
|
||||
this.$store
|
||||
.dispatch('addChatMessages', { chatId, messages })
|
||||
.then(() => {
|
||||
this.$nextTick(() => {
|
||||
if (fetchOlderMessages) {
|
||||
this.handleScrollUp(positionBeforeUpdate)
|
||||
}
|
||||
|
||||
// In vertical screens, the first batch of fetched messages may not always take the
|
||||
// full height of the scrollable container.
|
||||
// If this is the case, we want to fetch the messages until the scrollable container
|
||||
// is fully populated so that the user has the ability to scroll up and load the history.
|
||||
if (!isScrollable() && messages.length > 0) {
|
||||
this.fetchChat({
|
||||
maxId: this.currentChatMessageService.minId,
|
||||
})
|
||||
}
|
||||
})
|
||||
// In vertical screens, the first batch of fetched messages may not always take the
|
||||
// full height of the scrollable container.
|
||||
// If this is the case, we want to fetch the messages until the scrollable container
|
||||
// is fully populated so that the user has the ability to scroll up and load the history.
|
||||
if (!isScrollable() && messages.length > 0) {
|
||||
this.fetchChat({
|
||||
maxId: this.currentChatMessageService.minId,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
async startFetching() {
|
||||
let chat = this.findOpenedChatByRecipientId(this.recipientId)
|
||||
if (!chat) {
|
||||
try {
|
||||
chat = await this.backendInteractor.getOrCreateChat({
|
||||
const { data } = await getOrCreateChat({
|
||||
accountId: this.recipientId,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
chat = data
|
||||
} catch (e) {
|
||||
console.error('Error creating or getting a chat', e)
|
||||
this.errorLoadingChat = true
|
||||
|
|
@ -369,9 +380,11 @@ const Chat = {
|
|||
doSendMessage({ params, fakeMessage, retriesLeft = MAX_RETRIES }) {
|
||||
if (retriesLeft <= 0) return
|
||||
|
||||
this.backendInteractor
|
||||
.sendChatMessage(params)
|
||||
.then((data) => {
|
||||
sendChatMessage({
|
||||
params,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
this.$store.dispatch('addChatMessages', {
|
||||
chatId: this.currentChat.id,
|
||||
updateMaxId: false,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { mapState as mapPiniaState } from 'pinia'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import Attachment from 'src/components/attachment/attachment.vue'
|
||||
import ChatMessageDate from 'src/components/chat_message_date/chat_message_date.vue'
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { mapGetters, mapState } from 'vuex'
|
|||
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
|
||||
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
|
||||
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
|
|
@ -22,7 +24,9 @@ const chatNew = {
|
|||
}
|
||||
},
|
||||
async created() {
|
||||
const { chats } = await this.backendInteractor.chats()
|
||||
const { chats } = await chats({
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
chats.forEach((chat) => this.suggestions.push(chat.account))
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -38,7 +42,6 @@ const chatNew = {
|
|||
},
|
||||
...mapState({
|
||||
currentUser: (state) => state.users.currentUser,
|
||||
backendInteractor: (state) => state.api.backendInteractor,
|
||||
}),
|
||||
...mapGetters(['findUser']),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
|
||||
import UserPopover from 'src/components/user_popover/user_popover.vue'
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,13 @@ import { mapState } from 'vuex'
|
|||
import QuickFilterSettings from 'src/components/quick_filter_settings/quick_filter_settings.vue'
|
||||
import QuickViewSettings from 'src/components/quick_view_settings/quick_view_settings.vue'
|
||||
import ThreadTree from 'src/components/thread_tree/thread_tree.vue'
|
||||
import { WSConnectionStatus } from '../../services/api/api.service.js'
|
||||
|
||||
import { useInterfaceStore } from 'src/stores/interface'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { fetchConversation, fetchStatus } from 'src/api/public.js'
|
||||
import { WSConnectionStatus } from 'src/api/websocket.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
|
|
@ -436,22 +439,26 @@ const conversation = {
|
|||
methods: {
|
||||
fetchConversation() {
|
||||
if (this.status) {
|
||||
this.$store.state.api.backendInteractor
|
||||
.fetchConversation({ id: this.statusId })
|
||||
.then(({ ancestors, descendants }) => {
|
||||
this.$store.dispatch('addNewStatuses', { statuses: ancestors })
|
||||
this.$store.dispatch('addNewStatuses', { statuses: descendants })
|
||||
this.setHighlight(this.originalStatusId)
|
||||
})
|
||||
fetchConversation({
|
||||
id: this.statusId,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: { ancestors, descendants } }) => {
|
||||
this.$store.dispatch('addNewStatuses', { statuses: ancestors })
|
||||
this.$store.dispatch('addNewStatuses', { statuses: descendants })
|
||||
this.setHighlight(this.originalStatusId)
|
||||
})
|
||||
} else {
|
||||
this.loadStatusError = null
|
||||
this.$store.state.api.backendInteractor
|
||||
.fetchStatus({ id: this.statusId })
|
||||
.then((status) => {
|
||||
fetchStatus({
|
||||
id: this.statusId,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(({ data: status }) => {
|
||||
this.$store.dispatch('addNewStatuses', { statuses: [status] })
|
||||
this.fetchConversation()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.loadStatusError = error
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { useEmojiStore } from 'src/stores/emoji.js'
|
||||
|
||||
/**
|
||||
* suggest - generates a suggestor function to be used by emoji-input
|
||||
* data: object providing source information for specific types of suggestions:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import Popover from 'src/components/popover/popover.vue'
|
|||
import { ensureFinalFallback } from '../../i18n/languages.js'
|
||||
|
||||
import { useEmojiStore } from 'src/stores/emoji.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
|
|
|
|||
38
src/components/folder_card/folder_card.js
Normal file
38
src/components/folder_card/folder_card.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(faEllipsisH)
|
||||
|
||||
const FolderCard = {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
emoji: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
emojiUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
link: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
linkEdit: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
firstLetter() {
|
||||
return this.name[0]
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default FolderCard
|
||||
84
src/components/folder_card/folder_card.vue
Normal file
84
src/components/folder_card/folder_card.vue
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<div class="FolderCard">
|
||||
<router-link
|
||||
:to="link"
|
||||
class="folder-name"
|
||||
>
|
||||
<img
|
||||
v-if="emojiUrl"
|
||||
class="iconEmoji iconEmoji-image"
|
||||
:src="emojiUrl"
|
||||
:alt="emoji"
|
||||
:title="emoji"
|
||||
>
|
||||
<span
|
||||
v-else-if="emoji"
|
||||
class="iconEmoji"
|
||||
>
|
||||
<span>
|
||||
{{ emoji }}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
v-else-if="firstLetter"
|
||||
class="icon iconLetter fa-scale-110"
|
||||
>{{ firstLetter }}</span>{{ name }}
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="linkEdit"
|
||||
class="button-folder-edit"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="ellipsis-h"
|
||||
/>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./folder_card.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.FolderCard {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.folder-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
|
||||
.icon,
|
||||
.iconLetter,
|
||||
.iconEmoji {
|
||||
display: inline-block;
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.icon,
|
||||
.iconLetter {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.iconEmoji {
|
||||
text-align: center;
|
||||
object-fit: contain;
|
||||
vertical-align: middle;
|
||||
|
||||
> span {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
img.iconEmoji {
|
||||
padding: 0.25em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -4,6 +4,9 @@ import { notificationsFromStore } from '../../services/notification_utils/notifi
|
|||
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { approveUser, denyUser } from 'src/api/user.js'
|
||||
|
||||
const FollowRequestCard = {
|
||||
props: ['user'],
|
||||
|
|
@ -48,7 +51,10 @@ const FollowRequestCard = {
|
|||
}
|
||||
},
|
||||
doApprove() {
|
||||
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
|
||||
approveUser({
|
||||
id: this.user.id,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
|
||||
const notifId = this.findFollowRequestNotificationId()
|
||||
|
|
@ -70,12 +76,14 @@ const FollowRequestCard = {
|
|||
},
|
||||
doDeny() {
|
||||
const notifId = this.findFollowRequestNotificationId()
|
||||
this.$store.state.api.backendInteractor
|
||||
.denyUser({ id: this.user.id })
|
||||
.then(() => {
|
||||
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
})
|
||||
|
||||
denyUser({
|
||||
id: this.user.id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(() => {
|
||||
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
})
|
||||
this.hideDenyConfirmDialog()
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import ListsCard from 'src/components/lists_card/lists_card.vue'
|
||||
import FolderCard from 'src/components/folder_card/folder_card.vue'
|
||||
|
||||
import { useListsStore } from 'src/stores/lists.js'
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ const Lists = {
|
|||
}
|
||||
},
|
||||
components: {
|
||||
ListsCard,
|
||||
FolderCard,
|
||||
},
|
||||
computed: {
|
||||
lists() {
|
||||
|
|
|
|||
|
|
@ -14,10 +14,12 @@
|
|||
</router-link>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ListsCard
|
||||
<FolderCard
|
||||
v-for="list in lists.slice().reverse()"
|
||||
:key="list"
|
||||
:list="list"
|
||||
:name="list.title"
|
||||
:link="{ name: 'lists-timeline', params: { id: list.id } }"
|
||||
:link-edit="{ name: 'lists-edit', params: { id: list.id } }"
|
||||
class="list-item"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(faEllipsisH)
|
||||
|
||||
const ListsCard = {
|
||||
props: ['list'],
|
||||
}
|
||||
|
||||
export default ListsCard
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
<template>
|
||||
<div class="list-card">
|
||||
<router-link
|
||||
:to="{ name: 'lists-timeline', params: { id: list.id } }"
|
||||
class="list-name"
|
||||
>
|
||||
{{ list.title }}
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{ name: 'lists-edit', params: { id: list.id } }"
|
||||
class="button-list-edit"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="ellipsis-h"
|
||||
/>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./lists_card.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.list-card {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.list-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.list-name,
|
||||
.button-list-edit {
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
color: var(--link);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -4,7 +4,6 @@ import { mapState } from 'vuex'
|
|||
import { getListEntries } from 'src/components/navigation/filter.js'
|
||||
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useListsStore } from 'src/stores/lists.js'
|
||||
|
||||
export const ListsMenuContent = {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { mapActions, mapState as mapPiniaState, mapStores } from 'pinia'
|
||||
import { mapActions, mapState as mapPiniaState } from 'pinia'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import oauthApi from '../../services/new_api/oauth.js'
|
||||
|
||||
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { getLoginUrl, getTokenWithCredentials } from 'src/api/oauth.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
|
|
@ -35,19 +35,13 @@ const LoginForm = {
|
|||
this.isTokenAuth ? this.submitToken() : this.submitPassword()
|
||||
},
|
||||
submitToken() {
|
||||
const data = {
|
||||
instance: this.server,
|
||||
commit: this.$store.commit,
|
||||
}
|
||||
|
||||
// NOTE: we do not really need the app token, but obtaining a token and
|
||||
// calling verify_credentials is the only way to ensure the app still works.
|
||||
this.ensureAppToken().then(() => {
|
||||
const app = {
|
||||
window.location.href = getLoginUrl({
|
||||
clientId: this.clientId,
|
||||
clientSecret: this.clientSecret,
|
||||
}
|
||||
oauthApi.login({ ...app, ...data })
|
||||
instance: this.server,
|
||||
})
|
||||
})
|
||||
},
|
||||
submitPassword() {
|
||||
|
|
@ -56,37 +50,32 @@ const LoginForm = {
|
|||
// NOTE: we do not really need the app token, but obtaining a token and
|
||||
// calling verify_credentials is the only way to ensure the app still works.
|
||||
this.ensureAppToken().then(() => {
|
||||
const app = {
|
||||
getTokenWithCredentials({
|
||||
clientId: this.clientId,
|
||||
clientSecret: this.clientSecret,
|
||||
}
|
||||
|
||||
oauthApi
|
||||
.getTokenWithCredentials({
|
||||
...app,
|
||||
instance: this.server,
|
||||
username: this.user.username,
|
||||
password: this.user.password,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.error) {
|
||||
if (result.error === 'mfa_required') {
|
||||
this.requireMFA({ settings: result })
|
||||
} else if (result.identifier === 'password_reset_required') {
|
||||
this.$router.push({
|
||||
name: 'password-reset',
|
||||
params: { passwordResetRequested: true },
|
||||
})
|
||||
} else {
|
||||
this.error = result.error
|
||||
this.focusOnPasswordInput()
|
||||
}
|
||||
return
|
||||
}
|
||||
instance: this.server,
|
||||
username: this.user.username,
|
||||
password: this.user.password,
|
||||
})
|
||||
.then(({ data: result }) => {
|
||||
this.login(result).then(() => {
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.errorData?.error === 'mfa_required') {
|
||||
this.requireMFA({ settings: error })
|
||||
} else if (error.identifier === 'password_reset_required') {
|
||||
this.$router.push({
|
||||
name: 'password-reset',
|
||||
params: { passwordResetRequested: true },
|
||||
})
|
||||
} else {
|
||||
this.error = error
|
||||
this.focusOnPasswordInput()
|
||||
}
|
||||
return
|
||||
})
|
||||
})
|
||||
},
|
||||
clearError() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import Modal from 'src/components/modal/modal.vue'
|
||||
import StillImage from 'src/components/still-image/still-image.vue'
|
||||
import GestureService from '../../services/gesture_service/gesture_service'
|
||||
|
||||
import { useMediaViewerStore } from 'src/stores/media_viewer.js'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { mapState as mapPiniaState } from 'pinia'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import UnicodeDomainIndicator from 'src/components/unicode_domain_indicator/unicode_domain_indicator.vue'
|
||||
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { mapActions, mapState, mapStores } from 'pinia'
|
||||
|
||||
import mfaApi from '../../services/new_api/mfa.js'
|
||||
|
||||
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { verifyRecoveryCode } from 'src/api/mfa.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
|
|
@ -43,18 +43,18 @@ export default {
|
|||
code: this.code,
|
||||
}
|
||||
|
||||
mfaApi.verifyRecoveryCode(data).then((result) => {
|
||||
if (result.error) {
|
||||
this.error = result.error
|
||||
verifyRecoveryCode(data)
|
||||
.then((result) => {
|
||||
this.login(result).then(() => {
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.error = error
|
||||
this.code = null
|
||||
this.focusOnCodeInput()
|
||||
return
|
||||
}
|
||||
|
||||
this.login(result).then(() => {
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { mapActions, mapState, mapStores } from 'pinia'
|
||||
|
||||
import mfaApi from '../../services/new_api/mfa.js'
|
||||
|
||||
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { verifyOTPCode } from 'src/api/mfa.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
|
|
@ -46,18 +46,18 @@ export default {
|
|||
code: this.code,
|
||||
}
|
||||
|
||||
mfaApi.verifyOTPCode(data).then((result) => {
|
||||
if (result.error) {
|
||||
this.error = result.error
|
||||
verifyOTPCode(data)
|
||||
.then(({ data: result }) => {
|
||||
this.login(result).then(() => {
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.error = error
|
||||
this.code = null
|
||||
this.focusOnCodeInput()
|
||||
return
|
||||
}
|
||||
|
||||
this.login(result).then(() => {
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
|
|||
import Popover from 'src/components/popover/popover.vue'
|
||||
|
||||
import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ import {
|
|||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
|
||||
|
||||
import { approveUser, denyUser } from 'src/api/user.js'
|
||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
|
|
@ -142,7 +144,10 @@ const Notification = {
|
|||
}
|
||||
},
|
||||
doApprove() {
|
||||
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
|
||||
approveUser({
|
||||
id: this.user.id,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
this.$store.dispatch('markSingleNotificationAsSeen', {
|
||||
id: this.notification.id,
|
||||
|
|
@ -163,14 +168,15 @@ const Notification = {
|
|||
}
|
||||
},
|
||||
doDeny() {
|
||||
this.$store.state.api.backendInteractor
|
||||
.denyUser({ id: this.user.id })
|
||||
.then(() => {
|
||||
this.$store.dispatch('dismissNotificationLocal', {
|
||||
id: this.notification.id,
|
||||
})
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
denyUser({
|
||||
id: this.user.id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(() => {
|
||||
this.$store.dispatch('dismissNotificationLocal', {
|
||||
id: this.notification.id,
|
||||
})
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
})
|
||||
this.hideDenyConfirmDialog()
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import oauth from '../../services/new_api/oauth.js'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { getToken } from 'src/api/oauth.js'
|
||||
|
||||
const oac = {
|
||||
props: ['code'],
|
||||
mounted() {
|
||||
|
|
@ -10,18 +10,16 @@ const oac = {
|
|||
const oauthStore = useOAuthStore()
|
||||
const { clientId, clientSecret } = oauthStore
|
||||
|
||||
oauth
|
||||
.getToken({
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance: useInstanceStore().server,
|
||||
code: this.code,
|
||||
})
|
||||
.then((result) => {
|
||||
oauthStore.setToken(result.access_token)
|
||||
this.$store.dispatch('loginUser', result.access_token)
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
getToken({
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance: useInstanceStore().server,
|
||||
code: this.code,
|
||||
}).then(({ data: result }) => {
|
||||
oauthStore.setToken(result.access_token)
|
||||
this.$store.dispatch('loginUser', result.access_token)
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { mapState as mapPiniaState } from 'pinia'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import passwordResetApi from '../../services/new_api/password_reset.js'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
|
||||
import { resetPassword } from 'src/api/public.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ const passwordReset = {
|
|||
...mapState({
|
||||
signedIn: (state) => !!state.users.currentUser,
|
||||
}),
|
||||
...mapPiniaState(useInstanceStore, ['server', 'mailerEnabled']),
|
||||
...mapPiniaState(useInstanceStore, ['mailerEnabled']),
|
||||
},
|
||||
created() {
|
||||
if (this.signedIn) {
|
||||
|
|
@ -44,9 +44,8 @@ const passwordReset = {
|
|||
submit() {
|
||||
this.isPending = true
|
||||
const email = this.user.email
|
||||
const server = this.server
|
||||
|
||||
passwordResetApi({ server, email })
|
||||
resetPassword({ email })
|
||||
.then(({ status }) => {
|
||||
this.isPending = false
|
||||
this.user.email = ''
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { debounce, map, reject, uniqBy } from 'lodash'
|
||||
import { mapActions, mapState } from 'pinia'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import Attachment from 'src/components/attachment/attachment.vue'
|
||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||
|
|
@ -635,11 +634,7 @@ const PostStatusForm = {
|
|||
// Don't apply preview if not loading, because it means
|
||||
// user has closed the preview manually.
|
||||
if (!this.previewLoading) return
|
||||
if (!data.error) {
|
||||
this.preview = data
|
||||
} else {
|
||||
this.preview = { error: data.error }
|
||||
}
|
||||
this.preview = data
|
||||
})
|
||||
.catch((error) => {
|
||||
this.preview = { error }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { get } from 'lodash'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import Modal from 'src/components/modal/modal.vue'
|
||||
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { fetchUser } from 'src/api/public.js'
|
||||
|
||||
const RemoteUserResolver = {
|
||||
data: () => ({
|
||||
error: false,
|
||||
|
|
@ -7,11 +11,12 @@ const RemoteUserResolver = {
|
|||
},
|
||||
methods: {
|
||||
redirect() {
|
||||
const acct =
|
||||
this.$route.params.username + '@' + this.$route.params.hostname
|
||||
this.$store.state.api.backendInteractor
|
||||
.fetchUser({ id: acct })
|
||||
.then((externalUser) => {
|
||||
const id = this.$route.params.username + '@' + this.$route.params.hostname
|
||||
fetchUser({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(({ data: externalUser }) => {
|
||||
if (externalUser.error) {
|
||||
this.error = true
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
|
||||
import ModerationTools from 'src/components/moderation_tools/moderation_tools.vue'
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import Checkbox from 'components/checkbox/checkbox.vue'
|
|||
import Popover from 'components/popover/popover.vue'
|
||||
import Select from 'components/select/select.vue'
|
||||
import StillImage from 'components/still-image/still-image.vue'
|
||||
import { assign, clone } from 'lodash'
|
||||
import { clone } from 'lodash'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
||||
|
|
@ -11,6 +11,7 @@ import ModifiedIndicator from '../helpers/modified_indicator.vue'
|
|||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import StringSetting from '../helpers/string_setting.vue'
|
||||
|
||||
import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
|
||||
import { useEmojiStore } from 'src/stores/emoji.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||
|
|
@ -98,10 +99,10 @@ const EmojiTab = {
|
|||
|
||||
methods: {
|
||||
reloadEmoji() {
|
||||
this.$store.state.api.backendInteractor.reloadEmoji()
|
||||
useAdminSettingsStore().reloadEmoji()
|
||||
},
|
||||
importFromFS() {
|
||||
this.$store.state.api.backendInteractor.importEmojiFromFS()
|
||||
useAdminSettingsStore().importEmojiFromFS()
|
||||
},
|
||||
emojiAddr(name) {
|
||||
if (this.pack.remote !== undefined) {
|
||||
|
|
@ -113,7 +114,7 @@ const EmojiTab = {
|
|||
},
|
||||
|
||||
createEmojiPack() {
|
||||
this.$store.state.api.backendInteractor
|
||||
useAdminSettingsStore()
|
||||
.createEmojiPack({ name: this.newPackName })
|
||||
.then((resp) => resp.json())
|
||||
.then((resp) => {
|
||||
|
|
@ -130,7 +131,7 @@ const EmojiTab = {
|
|||
})
|
||||
},
|
||||
deleteEmojiPack() {
|
||||
this.$store.state.api.backendInteractor
|
||||
useAdminSettingsStore()
|
||||
.deleteEmojiPack({ name: this.packName })
|
||||
.then((resp) => resp.json())
|
||||
.then((resp) => {
|
||||
|
|
@ -157,7 +158,7 @@ const EmojiTab = {
|
|||
return edited !== def
|
||||
},
|
||||
savePackMetadata() {
|
||||
this.$store.state.api.backendInteractor
|
||||
useAdminSettingsStore()
|
||||
.saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta })
|
||||
.then((resp) => resp.json())
|
||||
.then((resp) => {
|
||||
|
|
@ -182,7 +183,7 @@ const EmojiTab = {
|
|||
useEmojiStore()
|
||||
.getAdminPacks(
|
||||
this.remotePackInstance,
|
||||
this.$store.state.api.backendInteractor.listEmojiPacks,
|
||||
useAdminSettingsStore().listEmojiPacks,
|
||||
)
|
||||
.then((allPacks) => {
|
||||
this.knownLocalPacks = allPacks
|
||||
|
|
@ -195,7 +196,7 @@ const EmojiTab = {
|
|||
useEmojiStore()
|
||||
.getAdminPacks(
|
||||
this.remotePackInstance,
|
||||
this.$store.state.api.backendInteractor.listRemoteEmojiPacks,
|
||||
useAdminSettingsStore().listRemoteEmojiPacks,
|
||||
)
|
||||
.then((allPacks) => {
|
||||
let inst = this.remotePackInstance
|
||||
|
|
@ -226,7 +227,7 @@ const EmojiTab = {
|
|||
this.remotePackDownloadAs = this.pack.remote.baseName
|
||||
}
|
||||
|
||||
this.$store.state.api.backendInteractor
|
||||
useAdminSettingsStore()
|
||||
.downloadRemoteEmojiPack({
|
||||
instance: this.pack.remote.instance,
|
||||
packName: this.pack.remote.baseName,
|
||||
|
|
@ -247,7 +248,7 @@ const EmojiTab = {
|
|||
})
|
||||
},
|
||||
downloadRemoteURLPack() {
|
||||
this.$store.state.api.backendInteractor
|
||||
useAdminSettingsStore()
|
||||
.downloadRemoteEmojiPackZIP({
|
||||
url: this.remotePackURL,
|
||||
packName: this.newPackName,
|
||||
|
|
@ -268,7 +269,7 @@ const EmojiTab = {
|
|||
})
|
||||
},
|
||||
downloadRemoteFilePack() {
|
||||
this.$store.state.api.backendInteractor
|
||||
useAdminSettingsStore()
|
||||
.downloadRemoteEmojiPackZIP({
|
||||
file: this.remotePackFile[0],
|
||||
packName: this.newPackName,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ const FrontendsTab = {
|
|||
const payload = { name, ref }
|
||||
|
||||
this.working = true
|
||||
this.$store.state.api.backendInteractor
|
||||
useAdminSettingsStore()
|
||||
.installFrontend({ payload })
|
||||
.finally(() => {
|
||||
this.working = false
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { isEmpty } from 'lodash'
|
||||
|
||||
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
|
||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||
import List from 'src/components/list/list.vue'
|
||||
|
|
|
|||
|
|
@ -153,6 +153,14 @@ import Popover from 'components/popover/popover.vue'
|
|||
import SelectComponent from 'components/select/select.vue'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import {
|
||||
addNewEmojiFile,
|
||||
deleteEmojiFile,
|
||||
updateEmojiFile,
|
||||
} from 'src/api/admin.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Popover,
|
||||
|
|
@ -243,14 +251,14 @@ export default {
|
|||
saveEditedEmoji() {
|
||||
if (!this.isEdited) return
|
||||
|
||||
this.$store.state.api.backendInteractor
|
||||
.updateEmojiFile({
|
||||
packName: this.packName,
|
||||
shortcode: this.shortcode,
|
||||
newShortcode: this.editedShortcode,
|
||||
newFilename: this.editedFile,
|
||||
force: false,
|
||||
})
|
||||
updateEmojiFile({
|
||||
packName: this.packName,
|
||||
shortcode: this.shortcode,
|
||||
newShortcode: this.editedShortcode,
|
||||
newFilename: this.editedFile,
|
||||
force: false,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then((resp) => {
|
||||
if (resp.error !== undefined) {
|
||||
this.$emit('displayError', resp.error)
|
||||
|
|
@ -263,18 +271,18 @@ export default {
|
|||
},
|
||||
uploadEmoji() {
|
||||
let packName = this.remote === undefined ? this.packName : this.copyToPack
|
||||
this.$store.state.api.backendInteractor
|
||||
.addNewEmojiFile({
|
||||
packName: packName,
|
||||
file:
|
||||
this.remote === undefined
|
||||
? this.uploadURL !== ''
|
||||
? this.uploadURL
|
||||
: this.uploadFile[0]
|
||||
: this.emojiAddr(this.file),
|
||||
shortcode: this.editedShortcode,
|
||||
filename: this.editedFile,
|
||||
})
|
||||
addNewEmojiFile({
|
||||
packName: packName,
|
||||
file:
|
||||
this.remote === undefined
|
||||
? this.uploadURL !== ''
|
||||
? this.uploadURL
|
||||
: this.uploadFile[0]
|
||||
: this.emojiAddr(this.file),
|
||||
shortcode: this.editedShortcode,
|
||||
filename: this.editedFile,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then((resp) => resp.json())
|
||||
.then((resp) => {
|
||||
if (resp.error !== undefined) {
|
||||
|
|
@ -297,8 +305,11 @@ export default {
|
|||
deleteEmoji() {
|
||||
this.deleteModalVisible = false
|
||||
|
||||
this.$store.state.api.backendInteractor
|
||||
.deleteEmojiFile({ packName: this.packName, shortcode: this.shortcode })
|
||||
deleteEmojiFile({
|
||||
packName: this.packName,
|
||||
shortcode: this.shortcode,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then((resp) => resp.json())
|
||||
.then((resp) => {
|
||||
if (resp.error !== undefined) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import LocalSettingIndicator from './local_setting_indicator.vue'
|
|||
import ModifiedIndicator from './modified_indicator.vue'
|
||||
|
||||
import { useAdminSettingsStore } from 'src/stores/admin_settings.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||
import { useLocalConfigStore } from 'src/stores/local_config.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// eslint-disable-next-line no-unused
|
||||
|
||||
import { throttle } from 'lodash'
|
||||
import { mapState as mapPiniaState, mapState } from 'pinia'
|
||||
import { Fragment, h } from 'vue'
|
||||
import { mapState as mapPiniaState } from 'pinia'
|
||||
import { Fragment } from 'vue'
|
||||
|
||||
import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
||||
import AuthTab from './admin_tabs/auth_tab.vue'
|
||||
import EmojiTab from './admin_tabs/emoji_tab.vue'
|
||||
import FederationTab from './admin_tabs/federation_tab.vue'
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ import Preview from './old_theme_tab/theme_preview.vue'
|
|||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { normalizeThemeData, useInterfaceStore } from 'src/stores/interface.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { updateProfileImages } from 'src/api/user.js'
|
||||
import { newImporter } from 'src/services/export_import/export_import.js'
|
||||
import {
|
||||
adoptStyleSheets,
|
||||
|
|
@ -484,9 +486,11 @@ const AppearanceTab = {
|
|||
}
|
||||
|
||||
this.backgroundUploading = true
|
||||
this.$store.state.api.backendInteractor
|
||||
.updateProfileImages({ background })
|
||||
.then((data) => {
|
||||
updateProfileImages({
|
||||
background,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
this.$store.commit('addNewUsers', [data])
|
||||
this.$store.commit('setCurrentUser', data)
|
||||
this.backgroundPreview = null
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@ import UnitSetting from '../helpers/unit_setting.vue'
|
|||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
|
||||
import { updateProfile } from 'src/api/user.js'
|
||||
import localeService from 'src/services/locale/locale.service.js'
|
||||
import { cacheKey, clearCache, emojiCacheKey } from 'src/services/sw/sw.js'
|
||||
|
||||
|
|
@ -164,12 +166,13 @@ const ComposingTab = {
|
|||
),
|
||||
}
|
||||
|
||||
this.$store.state.api.backendInteractor
|
||||
.updateProfile({ params })
|
||||
.then((user) => {
|
||||
this.$store.commit('addNewUsers', [user])
|
||||
this.$store.commit('setCurrentUser', user)
|
||||
})
|
||||
updateProfile({
|
||||
params,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: user }) => {
|
||||
this.$store.commit('addNewUsers', [user])
|
||||
this.$store.commit('setCurrentUser', user)
|
||||
})
|
||||
},
|
||||
updateFont(key, value) {
|
||||
useSyncConfigStore().setSimplePrefAndSave({
|
||||
|
|
|
|||
|
|
@ -4,8 +4,20 @@ import Checkbox from 'src/components/checkbox/checkbox.vue'
|
|||
import Exporter from 'src/components/exporter/exporter.vue'
|
||||
import Importer from 'src/components/importer/importer.vue'
|
||||
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useOAuthTokensStore } from 'src/stores/oauth_tokens.js'
|
||||
|
||||
import {
|
||||
addBackup,
|
||||
exportFriends,
|
||||
fetchBlocks,
|
||||
fetchMutes,
|
||||
importBlocks,
|
||||
importFollows,
|
||||
importMutes,
|
||||
listBackups,
|
||||
} from 'src/api/user.js'
|
||||
|
||||
const DataImportExportTab = {
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -28,48 +40,57 @@ const DataImportExportTab = {
|
|||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
backendInteractor: (state) => state.api.backendInteractor,
|
||||
user: (state) => state.users.currentUser,
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
getFollowsContent() {
|
||||
return this.backendInteractor
|
||||
.exportFriends({ id: this.user.id })
|
||||
.then(this.generateExportableUsersContent)
|
||||
return exportFriends({
|
||||
id: this.user.id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(this.generateExportableUsersContent)
|
||||
},
|
||||
getBlocksContent() {
|
||||
return this.backendInteractor
|
||||
.fetchBlocks()
|
||||
.then(this.generateExportableUsersContent)
|
||||
return fetchBlocks({
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(this.generateExportableUsersContent)
|
||||
},
|
||||
getMutesContent() {
|
||||
return this.backendInteractor
|
||||
.fetchMutes()
|
||||
.then(this.generateExportableUsersContent)
|
||||
return fetchMutes({
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(this.generateExportableUsersContent)
|
||||
},
|
||||
importFollows(file) {
|
||||
return this.backendInteractor.importFollows({ file }).then((status) => {
|
||||
return importFollows({
|
||||
file,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) => {
|
||||
if (!status) {
|
||||
throw new Error('failed')
|
||||
}
|
||||
})
|
||||
},
|
||||
importBlocks(file) {
|
||||
return this.backendInteractor.importBlocks({ file }).then((status) => {
|
||||
return importBlocks({
|
||||
file,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) => {
|
||||
if (!status) {
|
||||
throw new Error('failed')
|
||||
}
|
||||
})
|
||||
},
|
||||
importMutes(file) {
|
||||
return this.backendInteractor.importMutes({ file }).then((status) => {
|
||||
return importMutes({
|
||||
file,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) => {
|
||||
if (!status) {
|
||||
throw new Error('failed')
|
||||
}
|
||||
})
|
||||
},
|
||||
generateExportableUsersContent(users) {
|
||||
generateExportableUsersContent({ data: users }) {
|
||||
// Get addresses
|
||||
return users
|
||||
.map((user) => {
|
||||
|
|
@ -83,8 +104,9 @@ const DataImportExportTab = {
|
|||
.join('\n')
|
||||
},
|
||||
addBackup() {
|
||||
this.$store.state.api.backendInteractor
|
||||
.addBackup()
|
||||
addBackup({
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(() => {
|
||||
this.addedBackup = true
|
||||
this.addBackupError = false
|
||||
|
|
@ -96,9 +118,10 @@ const DataImportExportTab = {
|
|||
.then(() => this.fetchBackups())
|
||||
},
|
||||
fetchBackups() {
|
||||
this.$store.state.api.backendInteractor
|
||||
.listBackups()
|
||||
.then((res) => {
|
||||
listBackups({
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(({ data: res }) => {
|
||||
this.backups = res
|
||||
this.listBackupsError = false
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,12 +8,13 @@ import FloatSetting from '../helpers/float_setting.vue'
|
|||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import UnitSetting from '../helpers/unit_setting.vue'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
import { useLocalConfigStore } from 'src/stores/local_config.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
|
||||
import { updateProfile } from 'src/api/user.js'
|
||||
import localeService from 'src/services/locale/locale.service.js'
|
||||
|
||||
const GeneralTab = {
|
||||
|
|
@ -58,12 +59,13 @@ const GeneralTab = {
|
|||
),
|
||||
}
|
||||
|
||||
this.$store.state.api.backendInteractor
|
||||
.updateProfile({ params })
|
||||
.then((user) => {
|
||||
this.$store.commit('addNewUsers', [user])
|
||||
this.$store.commit('setCurrentUser', user)
|
||||
})
|
||||
updateProfile({
|
||||
params,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: user }) => {
|
||||
this.$store.commit('addNewUsers', [user])
|
||||
this.$store.commit('setCurrentUser', user)
|
||||
})
|
||||
},
|
||||
updateFont(path, value) {
|
||||
useLocalConfigStore().set({ path, value })
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { get, isEmpty, map, reject } from 'lodash'
|
||||
import { get, map, reject } from 'lodash'
|
||||
|
||||
import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
|
||||
import BlockCard from 'src/components/block_card/block_card.vue'
|
||||
|
|
@ -10,8 +10,11 @@ import ProgressButton from 'src/components/progress_button/progress_button.vue'
|
|||
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useOAuthTokensStore } from 'src/stores/oauth_tokens.js'
|
||||
|
||||
import { importBlocks, importFollows } from 'src/api/user.js'
|
||||
|
||||
const MutesAndBlocks = {
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -54,22 +57,24 @@ const MutesAndBlocks = {
|
|||
return () => this.$store.dispatch('fetch' + group, this.userId)
|
||||
},
|
||||
importFollows(file) {
|
||||
return this.$store.state.api.backendInteractor
|
||||
.importFollows({ file })
|
||||
.then((status) => {
|
||||
if (!status) {
|
||||
throw new Error('failed')
|
||||
}
|
||||
})
|
||||
return importFollows({
|
||||
file,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) => {
|
||||
if (!status) {
|
||||
throw new Error('failed')
|
||||
}
|
||||
})
|
||||
},
|
||||
importBlocks(file) {
|
||||
return this.$store.state.api.backendInteractor
|
||||
.importBlocks({ file })
|
||||
.then((status) => {
|
||||
if (!status) {
|
||||
throw new Error('failed')
|
||||
}
|
||||
})
|
||||
return importBlocks({
|
||||
file,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) => {
|
||||
if (!status) {
|
||||
throw new Error('failed')
|
||||
}
|
||||
})
|
||||
},
|
||||
generateExportableUsersContent(users) {
|
||||
// Get addresses
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { updateNotificationSettings } from 'src/api/user.js'
|
||||
|
||||
const NotificationsTab = {
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -27,7 +31,8 @@ const NotificationsTab = {
|
|||
},
|
||||
methods: {
|
||||
updateNotificationSettings() {
|
||||
this.$store.state.api.backendInteractor.updateNotificationSettings({
|
||||
updateNotificationSettings({
|
||||
credentials: useOAuthStore().token,
|
||||
settings: this.notificationSettings,
|
||||
})
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import SharedComputedObject from '../helpers/shared_computed_object.js'
|
|||
import UnitSetting from '../helpers/unit_setting.vue'
|
||||
|
||||
import { useLocalConfigStore } from 'src/stores/local_config.js'
|
||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
|
||||
const PostsTab = {
|
||||
data() {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ import UserCard from 'src/components/user_card/user_card.vue'
|
|||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { updateProfile } from 'src/api/user.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faCircleNotch,
|
||||
|
|
@ -35,10 +39,11 @@ const ProfileTab = {
|
|||
const params = {
|
||||
locked: this.locked,
|
||||
}
|
||||
|
||||
this.$store.state.api.backendInteractor
|
||||
.updateProfile({ params })
|
||||
.then((user) => {
|
||||
updateProfile({
|
||||
params,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(({ data: user }) => {
|
||||
this.$store.commit('addNewUsers', [user])
|
||||
this.$store.commit('setCurrentUser', user)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
import VueQrcode from '@chenfengyuan/vue-qrcode'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import Confirm from './confirm.vue'
|
||||
import RecoveryCodes from './mfa_backup_codes.vue'
|
||||
import TOTP from './mfa_totp.vue'
|
||||
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import {
|
||||
generateMfaBackupCodes,
|
||||
mfaConfirmOTP,
|
||||
mfaSetupOTP,
|
||||
settingsMFA,
|
||||
} from 'src/api/user.js'
|
||||
|
||||
const Mfa = {
|
||||
data: () => ({
|
||||
settings: {
|
||||
|
|
@ -71,9 +79,6 @@ const Mfa = {
|
|||
confirmNewBackupCodes() {
|
||||
return this.backupCodes.getNewCodes
|
||||
},
|
||||
...mapState({
|
||||
backendInteractor: (state) => state.api.backendInteractor,
|
||||
}),
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
@ -87,7 +92,9 @@ const Mfa = {
|
|||
this.backupCodes.inProgress = true
|
||||
this.backupCodes.codes = []
|
||||
|
||||
return this.backendInteractor.generateMfaBackupCodes().then((res) => {
|
||||
return generateMfaBackupCodes({
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: res }) => {
|
||||
this.backupCodes.codes = res.codes
|
||||
this.backupCodes.inProgress = false
|
||||
})
|
||||
|
|
@ -112,7 +119,9 @@ const Mfa = {
|
|||
// prepare setup OTP
|
||||
this.setupState.state = 'setupOTP'
|
||||
this.setupState.setupOTPState = 'prepare'
|
||||
this.backendInteractor.mfaSetupOTP().then((res) => {
|
||||
mfaSetupOTP({
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: res }) => {
|
||||
this.otpSettings = res
|
||||
this.setupState.setupOTPState = 'confirm'
|
||||
})
|
||||
|
|
@ -120,18 +129,17 @@ const Mfa = {
|
|||
doConfirmOTP() {
|
||||
// handler confirm enable OTP
|
||||
this.error = null
|
||||
this.backendInteractor
|
||||
.mfaConfirmOTP({
|
||||
token: this.otpConfirmToken,
|
||||
password: this.currentPassword,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.error) {
|
||||
this.error = res.error
|
||||
return
|
||||
}
|
||||
mfaConfirmOTP({
|
||||
token: this.otpConfirmToken,
|
||||
password: this.currentPassword,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(() => {
|
||||
this.completeSetup()
|
||||
})
|
||||
.catch((error) => {
|
||||
this.error = error
|
||||
})
|
||||
},
|
||||
|
||||
completeSetup() {
|
||||
|
|
@ -152,7 +160,9 @@ const Mfa = {
|
|||
|
||||
// fetch settings from server
|
||||
async fetchSettings() {
|
||||
const result = await this.backendInteractor.settingsMFA()
|
||||
const { data: result } = await settingsMFA({
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
if (result.error) return
|
||||
this.settings = result.settings
|
||||
this.settings.available = true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { mapState } from 'vuex'
|
||||
|
||||
import Confirm from './confirm.vue'
|
||||
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { mfaDisableOTP } from 'src/api/user.js'
|
||||
|
||||
export default {
|
||||
props: ['settings'],
|
||||
data: () => ({
|
||||
|
|
@ -17,9 +19,6 @@ export default {
|
|||
isActivated() {
|
||||
return this.settings.totp
|
||||
},
|
||||
...mapState({
|
||||
backendInteractor: (state) => state.api.backendInteractor,
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
doActivate() {
|
||||
|
|
@ -36,19 +35,18 @@ export default {
|
|||
// confirm deactivate TOTP method
|
||||
this.error = null
|
||||
this.inProgress = true
|
||||
this.backendInteractor
|
||||
.mfaDisableOTP({
|
||||
password: this.currentPassword,
|
||||
})
|
||||
.then((res) => {
|
||||
this.inProgress = false
|
||||
if (res.error) {
|
||||
this.error = res.error
|
||||
return
|
||||
}
|
||||
this.deactivate = false
|
||||
this.$emit('deactivate')
|
||||
})
|
||||
mfaDisableOTP({
|
||||
password: this.currentPassword,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: res }) => {
|
||||
this.inProgress = false
|
||||
if (res.error) {
|
||||
this.error = res.error
|
||||
return
|
||||
}
|
||||
this.deactivate = false
|
||||
this.$emit('deactivate')
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,19 @@ import Checkbox from 'src/components/checkbox/checkbox.vue'
|
|||
import ProgressButton from 'src/components/progress_button/progress_button.vue'
|
||||
import Mfa from './mfa.vue'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useOAuthTokensStore } from 'src/stores/oauth_tokens'
|
||||
|
||||
import {
|
||||
addAlias,
|
||||
changeEmail,
|
||||
changePassword,
|
||||
deleteAccount,
|
||||
deleteAlias,
|
||||
listAliases,
|
||||
moveAccount,
|
||||
} from 'src/api/user.js'
|
||||
import localeService from 'src/services/locale/locale.service.js'
|
||||
|
||||
const SecurityTab = {
|
||||
|
|
@ -65,78 +74,79 @@ const SecurityTab = {
|
|||
this.deletingAccount = true
|
||||
},
|
||||
deleteAccount() {
|
||||
this.$store.state.api.backendInteractor
|
||||
.deleteAccount({ password: this.deleteAccountConfirmPasswordInput })
|
||||
.then((res) => {
|
||||
if (res.status === 'success') {
|
||||
this.$store.dispatch('logout')
|
||||
this.$router.push({ name: 'root' })
|
||||
} else {
|
||||
this.deleteAccountError = res.error
|
||||
}
|
||||
})
|
||||
deleteAccount({
|
||||
credentials: useOAuthStore().token,
|
||||
password: this.deleteAccountConfirmPasswordInput,
|
||||
}).then(({ data: res }) => {
|
||||
if (res.status === 'success') {
|
||||
this.$store.dispatch('logout')
|
||||
this.$router.push({ name: 'root' })
|
||||
} else {
|
||||
this.deleteAccountError = res.error
|
||||
}
|
||||
})
|
||||
},
|
||||
changePassword() {
|
||||
const params = {
|
||||
password: this.changePasswordInputs[0],
|
||||
newPassword: this.changePasswordInputs[1],
|
||||
newPasswordConfirmation: this.changePasswordInputs[2],
|
||||
credentials: useOAuthStore().token,
|
||||
}
|
||||
this.$store.state.api.backendInteractor
|
||||
.changePassword(params)
|
||||
.then((res) => {
|
||||
if (res.status === 'success') {
|
||||
this.changedPassword = true
|
||||
this.changePasswordError = false
|
||||
this.logout()
|
||||
} else {
|
||||
this.changedPassword = false
|
||||
this.changePasswordError = res.error
|
||||
}
|
||||
})
|
||||
changePassword(params).then(({ data: res }) => {
|
||||
if (res.status === 'success') {
|
||||
this.changedPassword = true
|
||||
this.changePasswordError = false
|
||||
this.logout()
|
||||
} else {
|
||||
this.changedPassword = false
|
||||
this.changePasswordError = res.error
|
||||
}
|
||||
})
|
||||
},
|
||||
changeEmail() {
|
||||
const params = {
|
||||
email: this.newEmail,
|
||||
password: this.changeEmailPassword,
|
||||
credentials: useOAuthStore().token,
|
||||
}
|
||||
this.$store.state.api.backendInteractor
|
||||
.changeEmail(params)
|
||||
.then((res) => {
|
||||
if (res.status === 'success') {
|
||||
this.changedEmail = true
|
||||
this.changeEmailError = false
|
||||
} else {
|
||||
this.changedEmail = false
|
||||
this.changeEmailError = res.error
|
||||
}
|
||||
})
|
||||
changeEmail(params).then(({ data: res }) => {
|
||||
if (res.status === 'success') {
|
||||
this.changedEmail = true
|
||||
this.changeEmailError = false
|
||||
} else {
|
||||
this.changedEmail = false
|
||||
this.changeEmailError = res.error
|
||||
}
|
||||
})
|
||||
},
|
||||
moveAccount() {
|
||||
const params = {
|
||||
targetAccount: this.moveAccountTarget,
|
||||
password: this.moveAccountPassword,
|
||||
credentials: useOAuthStore().token,
|
||||
}
|
||||
this.$store.state.api.backendInteractor
|
||||
.moveAccount(params)
|
||||
.then((res) => {
|
||||
if (res.status === 'success') {
|
||||
this.movedAccount = true
|
||||
this.moveAccountError = false
|
||||
} else {
|
||||
this.movedAccount = false
|
||||
this.moveAccountError = res.error
|
||||
}
|
||||
})
|
||||
moveAccount(params).then(({ data: res }) => {
|
||||
if (res.status === 'success') {
|
||||
this.movedAccount = true
|
||||
this.moveAccountError = false
|
||||
} else {
|
||||
this.movedAccount = false
|
||||
this.moveAccountError = res.error
|
||||
}
|
||||
})
|
||||
},
|
||||
removeAlias(alias) {
|
||||
this.$store.state.api.backendInteractor
|
||||
.deleteAlias({ alias })
|
||||
.then(() => this.fetchAliases())
|
||||
deleteAlias({
|
||||
alias,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(() => this.fetchAliases())
|
||||
},
|
||||
addAlias() {
|
||||
this.$store.state.api.backendInteractor
|
||||
.addAlias({ alias: this.addAliasTarget })
|
||||
addAlias({
|
||||
alias: this.addAliasTarget,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(() => {
|
||||
this.addedAlias = true
|
||||
this.addAliasError = false
|
||||
|
|
@ -149,9 +159,10 @@ const SecurityTab = {
|
|||
.then(() => this.fetchAliases())
|
||||
},
|
||||
fetchAliases() {
|
||||
this.$store.state.api.backendInteractor
|
||||
.listAliases()
|
||||
.then((res) => {
|
||||
listAliases({
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(({ data: res }) => {
|
||||
this.aliases = res.aliases
|
||||
this.listAliasesError = false
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { mapActions, mapState } from 'pinia'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import { USERNAME_ROUTES } from 'src/components/navigation/navigation.js'
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ const StatusActionButtons = {
|
|||
button
|
||||
.action?.(this.funcArg)
|
||||
.then(() => this.$emit('onSuccess'))
|
||||
.catch((err) => this.$emit('onError', err.error.error))
|
||||
.catch((err) => this.$emit('onError', err))
|
||||
},
|
||||
onExtraClose() {
|
||||
this.showPin = false
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { mapState as mapPiniaState } from 'pinia'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import Attachment from 'src/components/attachment/attachment.vue'
|
||||
import Gallery from 'src/components/gallery/gallery.vue'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { find } from 'lodash'
|
||||
|
||||
import Popover from 'src/components/popover/popover.vue'
|
||||
import Status from 'src/components/status/status.vue'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import statusPosterService from '../../services/status_poster/status_poster.serv
|
|||
import TabSwitcher from '../tab_switcher/tab_switcher.jsx'
|
||||
|
||||
import { useEmojiStore } from 'src/stores/emoji.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
|
||||
const StickerPicker = {
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import Popover from 'components/popover/popover.vue'
|
|||
import SelectComponent from 'components/select/select.vue'
|
||||
import { mapState } from 'pinia'
|
||||
|
||||
import { useAdminSettingsStore } from 'src/stores/admin_settings'
|
||||
import { useEmojiStore } from 'src/stores/emoji'
|
||||
import { useInterfaceStore } from 'src/stores/interface'
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ export default {
|
|||
})
|
||||
},
|
||||
copyToLocalPack() {
|
||||
this.$store.state.api.backendInteractor
|
||||
useAdminSettingsStore()
|
||||
.addNewEmojiFile({
|
||||
packName: this.packName,
|
||||
file: this.$attrs.src,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
// eslint-disable-next-line no-unused
|
||||
|
||||
import { mapState } from 'pinia'
|
||||
import { Fragment, h } from 'vue'
|
||||
import { Fragment } from 'vue'
|
||||
|
||||
import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
|
||||
|
||||
import './tab_switcher.scss'
|
||||
|
||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||
|
||||
const findFirstUsable = (slots) => slots.findIndex((_) => _.props)
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
|||
import { usePostStatusStore } from 'src/stores/post_status'
|
||||
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
|
||||
|
||||
import { updateProfile } from 'src/api/user.js'
|
||||
import { propsToNative } from 'src/services/attributes_helper/attributes_helper.service.js'
|
||||
import localeService from 'src/services/locale/locale.service.js'
|
||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
|
|
@ -307,9 +308,9 @@ export default {
|
|||
const privileges = this.loggedIn.privileges
|
||||
return (
|
||||
this.loggedIn.role === 'admin' ||
|
||||
privileges.includes('users_manage_activation_state') ||
|
||||
privileges.includes('users_delete') ||
|
||||
privileges.includes('users_manage_tags')
|
||||
privileges.has('users_manage_activation_state') ||
|
||||
privileges.has('users_delete') ||
|
||||
privileges.has('users_manage_tags')
|
||||
)
|
||||
},
|
||||
hasNote() {
|
||||
|
|
@ -597,9 +598,8 @@ export default {
|
|||
params.header = this.newBannerFile
|
||||
}
|
||||
|
||||
this.$store.state.api.backendInteractor
|
||||
.updateProfile({ params })
|
||||
.then((user) => {
|
||||
updateProfile({ params })
|
||||
.then(({ data: user }) => {
|
||||
this.newFields.splice(this.newFields.length)
|
||||
merge(this.newFields, user.fields)
|
||||
this.$store.commit('addNewUsers', [user])
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import Popover from 'src/components/popover/popover.vue'
|
||||
import UnicodeDomainIndicator from 'src/components/unicode_domain_indicator/unicode_domain_indicator.vue'
|
||||
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { mapState } from 'pinia'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import Popover from 'src/components/popover/popover.vue'
|
||||
import UserCard from 'src/components/user_card/user_card.vue'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { get } from 'lodash'
|
||||
import { mapState } from 'pinia'
|
||||
|
||||
import FollowCard from 'src/components/follow_card/follow_card.vue'
|
||||
import List from 'src/components/list/list.vue'
|
||||
|
|
@ -7,7 +6,6 @@ import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
|||
import Timeline from 'src/components/timeline/timeline.vue'
|
||||
import UserCard from 'src/components/user_card/user_card.vue'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
import { get } from 'lodash'
|
||||
import { mapState } from 'pinia'
|
||||
|
||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||
import List from 'src/components/list/list.vue'
|
||||
import Status from 'src/components/status/status.vue'
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ import List from 'src/components/list/list.vue'
|
|||
import Modal from 'src/components/modal/modal.vue'
|
||||
import UserLink from 'src/components/user_link/user_link.vue'
|
||||
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useReportsStore } from 'src/stores/reports.js'
|
||||
|
||||
import { reportUser } from 'src/api/user.js'
|
||||
|
||||
const UserReportingModal = {
|
||||
components: {
|
||||
List,
|
||||
|
|
@ -70,9 +73,9 @@ const UserReportingModal = {
|
|||
comment: this.comment,
|
||||
forward: this.forward,
|
||||
statusIds: [...this.statusIdsToReport],
|
||||
credentials: useOAuthStore().token,
|
||||
}
|
||||
this.$store.state.api.backendInteractor
|
||||
.reportUser({ ...params })
|
||||
reportUser({ ...params })
|
||||
.then(() => {
|
||||
this.processing = false
|
||||
this.resetState()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import FollowCard from 'src/components/follow_card/follow_card.vue'
|
||||
import apiService from '../../services/api/api.service.js'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { fetchUser, suggestions } from 'src/api/public.js'
|
||||
|
||||
const WhoToFollow = {
|
||||
components: {
|
||||
|
|
@ -17,21 +18,22 @@ const WhoToFollow = {
|
|||
},
|
||||
methods: {
|
||||
showWhoToFollow(reply) {
|
||||
reply.forEach((i) => {
|
||||
this.$store.state.api.backendInteractor
|
||||
.fetchUser({ id: i.acct })
|
||||
.then((externalUser) => {
|
||||
if (!externalUser.error) {
|
||||
this.$store.commit('addNewUsers', [externalUser])
|
||||
this.users.push(externalUser)
|
||||
}
|
||||
})
|
||||
reply.forEach(({ id }) => {
|
||||
fetchUser({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: externalUser }) => {
|
||||
if (!externalUser.error) {
|
||||
this.$store.commit('addNewUsers', [externalUser])
|
||||
this.users.push(externalUser)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
getWhoToFollow() {
|
||||
const credentials = this.$store.state.users.currentUser.credentials
|
||||
const credentials = useOAuthStore().token
|
||||
if (credentials) {
|
||||
apiService.suggestions({ credentials }).then((reply) => {
|
||||
suggestions({ credentials }).then(({ data: reply }) => {
|
||||
this.showWhoToFollow(reply)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { shuffle } from 'lodash'
|
||||
|
||||
import apiService from '../../services/api/api.service.js'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { fetchUser, suggestions } from 'src/api/public.js'
|
||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
|
||||
function showWhoToFollow(panel, reply) {
|
||||
|
|
@ -18,14 +18,15 @@ function showWhoToFollow(panel, reply) {
|
|||
toFollow.img = img
|
||||
toFollow.name = name
|
||||
|
||||
panel.$store.state.api.backendInteractor
|
||||
.fetchUser({ id: name })
|
||||
.then((externalUser) => {
|
||||
if (!externalUser.error) {
|
||||
panel.$store.commit('addNewUsers', [externalUser])
|
||||
toFollow.id = externalUser.id
|
||||
}
|
||||
})
|
||||
fetchUser({
|
||||
id: name,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: externalUser }) => {
|
||||
if (!externalUser.error) {
|
||||
panel.$store.commit('addNewUsers', [externalUser])
|
||||
toFollow.id = externalUser.id
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +36,7 @@ function getWhoToFollow(panel) {
|
|||
panel.usersToFollow.forEach((toFollow) => {
|
||||
toFollow.name = 'Loading...'
|
||||
})
|
||||
apiService.suggestions({ credentials }).then((reply) => {
|
||||
suggestions({ credentials }).then(({ data: reply }) => {
|
||||
showWhoToFollow(panel, reply)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import Cookies from 'js-cookie'
|
||||
|
||||
import { useEmojiStore } from 'src/stores/emoji.js'
|
||||
import { useI18nStore } from 'src/stores/i18n.js'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,27 @@
|
|||
import { Socket } from 'phoenix'
|
||||
|
||||
import { WSConnectionStatus } from '../services/api/api.service.js'
|
||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||
import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useShoutStore } from 'src/stores/shout.js'
|
||||
|
||||
import { fetchTimeline } from 'src/api/timelines.js'
|
||||
import {
|
||||
getMastodonSocketURI,
|
||||
ProcessedWS,
|
||||
WSConnectionStatus,
|
||||
} from 'src/api/websocket.js'
|
||||
import followRequestFetcher from 'src/services/follow_request_fetcher/follow_request_fetcher.service'
|
||||
import notificationsFetcher from 'src/services/notifications_fetcher/notifications_fetcher.service.js'
|
||||
import timelineFetcher from 'src/services/timeline_fetcher/timeline_fetcher.service.js'
|
||||
|
||||
const retryTimeout = (multiplier) => 1000 * multiplier
|
||||
|
||||
const api = {
|
||||
state: {
|
||||
retryMultiplier: 1,
|
||||
backendInteractor: backendInteractorService(),
|
||||
fetchers: {},
|
||||
socket: null,
|
||||
mastoUserSocket: null,
|
||||
|
|
@ -25,9 +32,6 @@ const api = {
|
|||
followRequestCount: (state) => state.followRequests.length,
|
||||
},
|
||||
mutations: {
|
||||
setBackendInteractor(state, backendInteractor) {
|
||||
state.backendInteractor = backendInteractor
|
||||
},
|
||||
addFetcher(state, { fetcherName, fetcher }) {
|
||||
state.fetchers[fetcherName] = fetcher
|
||||
},
|
||||
|
|
@ -91,9 +95,16 @@ const api = {
|
|||
try {
|
||||
const { state, commit, dispatch, rootState } = store
|
||||
const timelineData = rootState.statuses.timelines.friends
|
||||
state.mastoUserSocket = state.backendInteractor.startUserSocket({
|
||||
store,
|
||||
|
||||
const credentials = useOAuthStore().token
|
||||
const url = getMastodonSocketURI({ credentials })
|
||||
|
||||
state.mastoUserSocket = ProcessedWS({
|
||||
url,
|
||||
id: 'Unified',
|
||||
credentials,
|
||||
})
|
||||
|
||||
state.mastoUserSocket.addEventListener(
|
||||
'pleroma:authenticated',
|
||||
() => {
|
||||
|
|
@ -245,7 +256,7 @@ const api = {
|
|||
return
|
||||
if (store.state.fetchers[timeline]) return
|
||||
|
||||
const fetcher = store.state.backendInteractor.startFetchingTimeline({
|
||||
const fetcher = timelineFetcher.startFetching({
|
||||
timeline,
|
||||
store,
|
||||
userId,
|
||||
|
|
@ -253,7 +264,9 @@ const api = {
|
|||
statusId,
|
||||
bookmarkFolderId,
|
||||
tag,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
|
||||
store.commit('addFetcher', { fetcherName: timeline, fetcher })
|
||||
},
|
||||
stopFetchingTimeline(store, timeline) {
|
||||
|
|
@ -261,19 +274,22 @@ const api = {
|
|||
if (!fetcher) return
|
||||
store.commit('removeFetcher', { fetcherName: timeline, fetcher })
|
||||
},
|
||||
|
||||
fetchTimeline(store, { timeline, ...rest }) {
|
||||
store.state.backendInteractor.fetchTimeline({
|
||||
fetchTimeline({
|
||||
store,
|
||||
timeline,
|
||||
...rest,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
},
|
||||
|
||||
// Notifications
|
||||
startFetchingNotifications(store) {
|
||||
if (store.state.fetchers.notifications) return
|
||||
const fetcher = store.state.backendInteractor.startFetchingNotifications({
|
||||
const fetcher = notificationsFetcher.startFetching({
|
||||
store,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
|
||||
},
|
||||
|
|
@ -282,19 +298,14 @@ const api = {
|
|||
if (!fetcher) return
|
||||
store.commit('removeFetcher', { fetcherName: 'notifications', fetcher })
|
||||
},
|
||||
fetchNotifications(store, { ...rest }) {
|
||||
store.state.backendInteractor.fetchNotifications({
|
||||
store,
|
||||
...rest,
|
||||
})
|
||||
},
|
||||
|
||||
// Follow requests
|
||||
startFetchingFollowRequests(store) {
|
||||
if (store.state.fetchers.followRequests) return
|
||||
const fetcher = store.state.backendInteractor.startFetchingFollowRequests(
|
||||
{ store },
|
||||
)
|
||||
const fetcher = followRequestFetcher.startFetchingFollowRequests({
|
||||
store,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
|
||||
store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
|
||||
},
|
||||
|
|
@ -303,39 +314,6 @@ const api = {
|
|||
if (!fetcher) return
|
||||
store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher })
|
||||
},
|
||||
removeFollowRequest(store, request) {
|
||||
const requests = store.state.followRequests.filter((it) => it !== request)
|
||||
store.commit('setFollowRequests', requests)
|
||||
},
|
||||
|
||||
// Lists
|
||||
startFetchingLists(store) {
|
||||
if (store.state.fetchers.lists) return
|
||||
const fetcher = store.state.backendInteractor.startFetchingLists({
|
||||
store,
|
||||
})
|
||||
store.commit('addFetcher', { fetcherName: 'lists', fetcher })
|
||||
},
|
||||
stopFetchingLists(store) {
|
||||
const fetcher = store.state.fetchers.lists
|
||||
if (!fetcher) return
|
||||
store.commit('removeFetcher', { fetcherName: 'lists', fetcher })
|
||||
},
|
||||
|
||||
// Bookmark folders
|
||||
startFetchingBookmarkFolders(store) {
|
||||
if (store.state.fetchers.bookmarkFolders) return
|
||||
if (!useInstanceCapabilitiesStore().pleromaBookmarkFoldersAvailable)
|
||||
return
|
||||
const fetcher =
|
||||
store.state.backendInteractor.startFetchingBookmarkFolders({ store })
|
||||
store.commit('addFetcher', { fetcherName: 'bookmarkFolders', fetcher })
|
||||
},
|
||||
stopFetchingBookmarkFolders(store) {
|
||||
const fetcher = store.state.fetchers.bookmarkFolders
|
||||
if (!fetcher) return
|
||||
store.commit('removeFetcher', { fetcherName: 'bookmarkFolders', fetcher })
|
||||
},
|
||||
|
||||
// Pleroma websocket
|
||||
setWsToken(store, token) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ import {
|
|||
} from '../services/entity_normalizer/entity_normalizer.service.js'
|
||||
import { promiseInterval } from '../services/promise_interval/promise_interval.js'
|
||||
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { chats, deleteChatMessage, readChat } from 'src/api/chats.js'
|
||||
|
||||
const emptyChatList = () => ({
|
||||
data: [],
|
||||
idStore: {},
|
||||
|
|
@ -36,7 +40,7 @@ const unreadChatCount = (state) => {
|
|||
return sumBy(state.chatList.data, 'unread')
|
||||
}
|
||||
|
||||
const chats = {
|
||||
const chatsModule = {
|
||||
state: { ...defaultState },
|
||||
getters: {
|
||||
currentChat: (state) => state.openedChats[state.currentChatId],
|
||||
|
|
@ -51,7 +55,6 @@ const chats = {
|
|||
// Chat list
|
||||
startFetchingChats({ dispatch, commit }) {
|
||||
const fetcher = () => dispatch('fetchChats', { latest: true })
|
||||
fetcher()
|
||||
commit('setChatListFetcher', {
|
||||
fetcher: () => promiseInterval(fetcher, 5000),
|
||||
})
|
||||
|
|
@ -60,8 +63,10 @@ const chats = {
|
|||
commit('setChatListFetcher', { fetcher: undefined })
|
||||
},
|
||||
fetchChats({ dispatch, rootState }) {
|
||||
return rootState.api.backendInteractor.chats().then(({ chats }) => {
|
||||
dispatch('addNewChats', { chats })
|
||||
return chats({
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ chatList }) => {
|
||||
dispatch('addNewChats', { chats: chatList })
|
||||
return chats
|
||||
})
|
||||
},
|
||||
|
|
@ -113,11 +118,18 @@ const chats = {
|
|||
commit('readChat', { id, lastReadId })
|
||||
|
||||
if (isNewMessage) {
|
||||
rootState.api.backendInteractor.readChat({ id, lastReadId })
|
||||
readChat({
|
||||
id,
|
||||
lastReadId,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
}
|
||||
},
|
||||
deleteChatMessage({ rootState, commit }, value) {
|
||||
rootState.api.backendInteractor.deleteChatMessage(value)
|
||||
deleteChatMessage({
|
||||
...value,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
commit('deleteChatMessage', { commit, ...value })
|
||||
},
|
||||
resetChats({ commit, dispatch }) {
|
||||
|
|
@ -262,4 +274,4 @@ const chats = {
|
|||
},
|
||||
}
|
||||
|
||||
export default chats
|
||||
export default chatsModule
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { get, set } from 'lodash'
|
||||
import { get } from 'lodash'
|
||||
|
||||
const browserLocale = (navigator.language || 'en').split('-')[0]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import apiService from '../services/api/api.service.js'
|
||||
import {
|
||||
closeAllDesktopNotifications,
|
||||
closeDesktopNotification,
|
||||
|
|
@ -11,9 +10,12 @@ import {
|
|||
|
||||
import { useI18nStore } from 'src/stores/i18n.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useReportsStore } from 'src/stores/reports.js'
|
||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
|
||||
import { dismissNotification, markNotificationsAsSeen } from 'src/api/user.js'
|
||||
|
||||
const emptyNotifications = () => ({
|
||||
desktopNotificationSilence: true,
|
||||
maxId: 0,
|
||||
|
|
@ -154,33 +156,32 @@ export const notifications = {
|
|||
},
|
||||
markNotificationsAsSeen({ rootState, state, commit }) {
|
||||
commit('markNotificationsAsSeen')
|
||||
apiService
|
||||
.markNotificationsAsSeen({
|
||||
id: state.maxId,
|
||||
credentials: rootState.users.currentUser.credentials,
|
||||
})
|
||||
.then(() => {
|
||||
closeAllDesktopNotifications(rootState)
|
||||
})
|
||||
markNotificationsAsSeen({
|
||||
id: state.maxId,
|
||||
credentials: rootState.users.currentUser.credentials,
|
||||
}).then(() => {
|
||||
closeAllDesktopNotifications(rootState)
|
||||
})
|
||||
},
|
||||
markSingleNotificationAsSeen({ rootState, commit }, { id }) {
|
||||
commit('markSingleNotificationAsSeen', { id })
|
||||
apiService
|
||||
.markNotificationsAsSeen({
|
||||
single: true,
|
||||
id,
|
||||
credentials: rootState.users.currentUser.credentials,
|
||||
})
|
||||
.then(() => {
|
||||
closeDesktopNotification(rootState, { id })
|
||||
})
|
||||
markNotificationsAsSeen({
|
||||
single: true,
|
||||
id,
|
||||
credentials: rootState.users.currentUser.credentials,
|
||||
}).then(() => {
|
||||
closeDesktopNotification(rootState, { id })
|
||||
})
|
||||
},
|
||||
dismissNotificationLocal({ commit }, { id }) {
|
||||
commit('dismissNotification', { id })
|
||||
},
|
||||
dismissNotification({ rootState, commit }, { id }) {
|
||||
commit('dismissNotification', { id })
|
||||
rootState.api.backendInteractor.dismissNotification({ id })
|
||||
dismissNotification({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
},
|
||||
updateNotification({ commit }, { id, updater }) {
|
||||
commit('updateNotification', { id, updater })
|
||||
|
|
|
|||
|
|
@ -1,28 +1,34 @@
|
|||
import { get, set } from 'lodash'
|
||||
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import { updateNotificationSettings, updateProfile } from 'src/api/user.js'
|
||||
|
||||
const defaultApi = ({ rootState, commit }, { path, value }) => {
|
||||
const params = {}
|
||||
set(params, path, value)
|
||||
return rootState.api.backendInteractor
|
||||
.updateProfile({ params })
|
||||
.then((result) => {
|
||||
commit('addNewUsers', [result])
|
||||
commit('setCurrentUser', result)
|
||||
})
|
||||
return updateProfile({
|
||||
params,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: result }) => {
|
||||
commit('addNewUsers', [result])
|
||||
commit('setCurrentUser', result)
|
||||
})
|
||||
}
|
||||
|
||||
const notificationsApi = ({ rootState, commit }, { path, value, oldValue }) => {
|
||||
const settings = {}
|
||||
set(settings, path, value)
|
||||
return rootState.api.backendInteractor
|
||||
.updateNotificationSettings({ settings })
|
||||
.then((result) => {
|
||||
if (result.status === 'success') {
|
||||
commit('confirmProfileOption', { name, value })
|
||||
} else {
|
||||
commit('confirmProfileOption', { name, value: oldValue })
|
||||
}
|
||||
})
|
||||
return updateNotificationSettings({
|
||||
settings,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: result }) => {
|
||||
if (result.status === 'success') {
|
||||
commit('confirmProfileOption', { name, value })
|
||||
} else {
|
||||
commit('confirmProfileOption', { name, value: oldValue })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,10 +13,36 @@ import {
|
|||
slice,
|
||||
} from 'lodash'
|
||||
|
||||
import apiService from '../services/api/api.service.js'
|
||||
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
|
||||
import {
|
||||
fetchEmojiReactions,
|
||||
fetchFavoritedByUsers,
|
||||
fetchPinnedStatuses,
|
||||
fetchRebloggedByUsers,
|
||||
fetchScrobbles,
|
||||
fetchStatus,
|
||||
fetchStatusHistory,
|
||||
fetchStatusSource,
|
||||
search2,
|
||||
} from 'src/api/public.js'
|
||||
import {
|
||||
bookmarkStatus,
|
||||
deleteStatus,
|
||||
favorite,
|
||||
muteConversation,
|
||||
pinOwnStatus,
|
||||
reactWithEmoji,
|
||||
retweet,
|
||||
unbookmarkStatus,
|
||||
unfavorite,
|
||||
unmuteConversation,
|
||||
unpinOwnStatus,
|
||||
unreactWithEmoji,
|
||||
unretweet,
|
||||
} from 'src/api/user.js'
|
||||
|
||||
const emptyTl = (userId = 0) => ({
|
||||
statuses: [],
|
||||
|
|
@ -131,9 +157,8 @@ const getLatestScrobble = (state, user) => {
|
|||
|
||||
state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000
|
||||
if (!scrobblesSupport) return
|
||||
apiService
|
||||
.fetchScrobbles({ accountId: user.id })
|
||||
.then((scrobbles) => {
|
||||
fetchScrobbles({ accountId: user.id })
|
||||
.then(({ data: scrobbles }) => {
|
||||
if (scrobbles?.error) {
|
||||
useInstanceCapabilitiesStore().set('pleromaScrobblesAvailable', false)
|
||||
return
|
||||
|
|
@ -602,25 +627,24 @@ const statuses = {
|
|||
})
|
||||
},
|
||||
fetchStatus({ rootState, dispatch }, id) {
|
||||
return rootState.api.backendInteractor
|
||||
.fetchStatus({ id })
|
||||
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
|
||||
return fetchStatus({ id }).then(({ data: status }) =>
|
||||
dispatch('addNewStatuses', { statuses: [status] }),
|
||||
)
|
||||
},
|
||||
fetchStatusSource({ rootState }, status) {
|
||||
return apiService.fetchStatusSource({
|
||||
return fetchStatusSource({
|
||||
id: status.id,
|
||||
credentials: rootState.users.currentUser.credentials,
|
||||
})
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data }) => data)
|
||||
},
|
||||
fetchStatusHistory(_, status) {
|
||||
return apiService.fetchStatusHistory({ status })
|
||||
return fetchStatusHistory({ status }).then(({ data }) => data)
|
||||
},
|
||||
deleteStatus({ rootState, commit }, status) {
|
||||
apiService
|
||||
.deleteStatus({
|
||||
id: status.id,
|
||||
credentials: rootState.users.currentUser.credentials,
|
||||
})
|
||||
deleteStatus({
|
||||
id: status.id,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(() => {
|
||||
commit('setDeleted', { status })
|
||||
})
|
||||
|
|
@ -643,99 +667,115 @@ const statuses = {
|
|||
favorite({ rootState, commit }, status) {
|
||||
// Optimistic favoriting...
|
||||
commit('setFavorited', { status, value: true })
|
||||
rootState.api.backendInteractor
|
||||
.favorite({ id: status.id })
|
||||
.then((status) =>
|
||||
commit('setFavoritedConfirm', {
|
||||
status,
|
||||
user: rootState.users.currentUser,
|
||||
}),
|
||||
)
|
||||
favorite({
|
||||
id: status.id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) =>
|
||||
commit('setFavoritedConfirm', {
|
||||
status,
|
||||
user: rootState.users.currentUser,
|
||||
}),
|
||||
)
|
||||
},
|
||||
unfavorite({ rootState, commit }, status) {
|
||||
// Optimistic unfavoriting...
|
||||
commit('setFavorited', { status, value: false })
|
||||
rootState.api.backendInteractor
|
||||
.unfavorite({ id: status.id })
|
||||
.then((status) =>
|
||||
commit('setFavoritedConfirm', {
|
||||
status,
|
||||
user: rootState.users.currentUser,
|
||||
}),
|
||||
)
|
||||
unfavorite({
|
||||
id: status.id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) =>
|
||||
commit('setFavoritedConfirm', {
|
||||
status,
|
||||
user: rootState.users.currentUser,
|
||||
}),
|
||||
)
|
||||
},
|
||||
fetchPinnedStatuses({ rootState, dispatch }, userId) {
|
||||
rootState.api.backendInteractor
|
||||
.fetchPinnedStatuses({ id: userId })
|
||||
.then((statuses) =>
|
||||
dispatch('addNewStatuses', {
|
||||
statuses,
|
||||
timeline: 'user',
|
||||
userId,
|
||||
showImmediately: true,
|
||||
noIdUpdate: true,
|
||||
}),
|
||||
)
|
||||
fetchPinnedStatuses({
|
||||
id: userId,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: statuses }) =>
|
||||
dispatch('addNewStatuses', {
|
||||
statuses,
|
||||
timeline: 'user',
|
||||
userId,
|
||||
showImmediately: true,
|
||||
noIdUpdate: true,
|
||||
}),
|
||||
)
|
||||
},
|
||||
pinStatus({ rootState, dispatch }, statusId) {
|
||||
return rootState.api.backendInteractor
|
||||
.pinOwnStatus({ id: statusId })
|
||||
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
|
||||
return pinOwnStatus({
|
||||
id: statusId,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) =>
|
||||
dispatch('addNewStatuses', { statuses: [status] }),
|
||||
)
|
||||
},
|
||||
unpinStatus({ rootState, dispatch }, statusId) {
|
||||
return rootState.api.backendInteractor
|
||||
.unpinOwnStatus({ id: statusId })
|
||||
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
|
||||
return unpinOwnStatus({
|
||||
id: statusId,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) =>
|
||||
dispatch('addNewStatuses', { statuses: [status] }),
|
||||
)
|
||||
},
|
||||
muteConversation({ rootState, commit }, { id: statusId }) {
|
||||
return rootState.api.backendInteractor
|
||||
.muteConversation({ id: statusId })
|
||||
.then((status) => commit('setMutedStatus', status))
|
||||
return muteConversation({
|
||||
id: statusId,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) => commit('setMutedStatus', status))
|
||||
},
|
||||
unmuteConversation({ rootState, commit }, { id: statusId }) {
|
||||
return rootState.api.backendInteractor
|
||||
.unmuteConversation({ id: statusId })
|
||||
.then((status) => commit('setMutedStatus', status))
|
||||
return unmuteConversation({
|
||||
id: statusId,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) => commit('setMutedStatus', status))
|
||||
},
|
||||
retweet({ rootState, commit }, status) {
|
||||
// Optimistic retweeting...
|
||||
commit('setRetweeted', { status, value: true })
|
||||
rootState.api.backendInteractor
|
||||
.retweet({ id: status.id })
|
||||
.then((status) =>
|
||||
commit('setRetweetedConfirm', {
|
||||
status: status.retweeted_status,
|
||||
user: rootState.users.currentUser,
|
||||
}),
|
||||
)
|
||||
retweet({
|
||||
id: status.id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) =>
|
||||
commit('setRetweetedConfirm', {
|
||||
status: status.retweeted_status,
|
||||
user: rootState.users.currentUser,
|
||||
}),
|
||||
)
|
||||
},
|
||||
unretweet({ rootState, commit }, status) {
|
||||
// Optimistic unretweeting...
|
||||
commit('setRetweeted', { status, value: false })
|
||||
rootState.api.backendInteractor
|
||||
.unretweet({ id: status.id })
|
||||
.then((status) =>
|
||||
commit('setRetweetedConfirm', {
|
||||
status,
|
||||
user: rootState.users.currentUser,
|
||||
}),
|
||||
)
|
||||
unretweet({
|
||||
id: status.id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) =>
|
||||
commit('setRetweetedConfirm', {
|
||||
status,
|
||||
user: rootState.users.currentUser,
|
||||
}),
|
||||
)
|
||||
},
|
||||
bookmark({ rootState, commit }, status) {
|
||||
commit('setBookmarked', { status, value: true })
|
||||
rootState.api.backendInteractor
|
||||
.bookmarkStatus({ id: status.id, folder_id: status.bookmark_folder_id })
|
||||
.then((status) => {
|
||||
commit('setBookmarkedConfirm', { status })
|
||||
})
|
||||
bookmarkStatus({
|
||||
id: status.id,
|
||||
folder_id: status.bookmark_folder_id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) => {
|
||||
commit('setBookmarkedConfirm', { status })
|
||||
})
|
||||
},
|
||||
unbookmark({ rootState, commit }, status) {
|
||||
commit('setBookmarked', { status, value: false })
|
||||
rootState.api.backendInteractor
|
||||
.unbookmarkStatus({ id: status.id })
|
||||
.then((status) => {
|
||||
commit('setBookmarkedConfirm', { status })
|
||||
})
|
||||
unbookmarkStatus({
|
||||
id: status.id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: status }) => {
|
||||
commit('setBookmarkedConfirm', { status })
|
||||
})
|
||||
},
|
||||
queueFlush({ commit }, { timeline, id }) {
|
||||
commit('queueFlush', { timeline, id })
|
||||
|
|
@ -745,8 +785,14 @@ const statuses = {
|
|||
},
|
||||
fetchFavsAndRepeats({ rootState, commit }, id) {
|
||||
Promise.all([
|
||||
rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),
|
||||
rootState.api.backendInteractor.fetchRebloggedByUsers({ id }),
|
||||
fetchFavoritedByUsers({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data }) => data),
|
||||
fetchRebloggedByUsers({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data }) => data),
|
||||
]).then(([favoritedByUsers, rebloggedByUsers]) => {
|
||||
commit('addFavs', {
|
||||
id,
|
||||
|
|
@ -765,7 +811,11 @@ const statuses = {
|
|||
if (!currentUser) return
|
||||
|
||||
commit('addOwnReaction', { id, emoji, currentUser })
|
||||
rootState.api.backendInteractor.reactWithEmoji({ id, emoji }).then(() => {
|
||||
reactWithEmoji({
|
||||
id,
|
||||
emoji,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(() => {
|
||||
dispatch('fetchEmojiReactionsBy', id)
|
||||
})
|
||||
},
|
||||
|
|
@ -774,59 +824,70 @@ const statuses = {
|
|||
if (!currentUser) return
|
||||
|
||||
commit('removeOwnReaction', { id, emoji, currentUser })
|
||||
rootState.api.backendInteractor
|
||||
.unreactWithEmoji({ id, emoji })
|
||||
.then(() => {
|
||||
dispatch('fetchEmojiReactionsBy', id)
|
||||
})
|
||||
unreactWithEmoji({
|
||||
id,
|
||||
emoji,
|
||||
currentUser: rootState.users.currentUser,
|
||||
}).then(() => {
|
||||
dispatch('fetchEmojiReactionsBy', id)
|
||||
})
|
||||
},
|
||||
fetchEmojiReactionsBy({ rootState, commit }, id) {
|
||||
return rootState.api.backendInteractor
|
||||
.fetchEmojiReactions({ id })
|
||||
.then((emojiReactions) => {
|
||||
commit('addEmojiReactionsBy', {
|
||||
id,
|
||||
emojiReactions,
|
||||
currentUser: rootState.users.currentUser,
|
||||
})
|
||||
return fetchEmojiReactions({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: emojiReactions }) => {
|
||||
commit('addEmojiReactionsBy', {
|
||||
id,
|
||||
emojiReactions,
|
||||
currentUser: rootState.users.currentUser,
|
||||
})
|
||||
})
|
||||
},
|
||||
fetchFavs({ rootState, commit }, id) {
|
||||
rootState.api.backendInteractor
|
||||
.fetchFavoritedByUsers({ id })
|
||||
.then((favoritedByUsers) =>
|
||||
commit('addFavs', {
|
||||
id,
|
||||
favoritedByUsers,
|
||||
currentUser: rootState.users.currentUser,
|
||||
}),
|
||||
)
|
||||
fetchFavoritedByUsers({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: favoritedByUsers }) =>
|
||||
commit('addFavs', {
|
||||
id,
|
||||
favoritedByUsers,
|
||||
currentUser: rootState.users.currentUser,
|
||||
}),
|
||||
)
|
||||
},
|
||||
fetchRepeats({ rootState, commit }, id) {
|
||||
rootState.api.backendInteractor
|
||||
.fetchRebloggedByUsers({ id })
|
||||
.then((rebloggedByUsers) =>
|
||||
commit('addRepeats', {
|
||||
id,
|
||||
rebloggedByUsers,
|
||||
currentUser: rootState.users.currentUser,
|
||||
}),
|
||||
)
|
||||
fetchRebloggedByUsers({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: rebloggedByUsers }) =>
|
||||
commit('addRepeats', {
|
||||
id,
|
||||
rebloggedByUsers,
|
||||
currentUser: rootState.users.currentUser,
|
||||
}),
|
||||
)
|
||||
},
|
||||
search(store, { q, resolve, limit, offset, following, type }) {
|
||||
return store.rootState.api.backendInteractor
|
||||
.search2({ q, resolve, limit, offset, following, type })
|
||||
.then((data) => {
|
||||
store.commit('addNewUsers', data.accounts)
|
||||
store.commit(
|
||||
'addNewUsers',
|
||||
data.statuses.map((s) => s.user).filter((u) => u),
|
||||
)
|
||||
data.statuses = store.commit('addNewStatuses', {
|
||||
statuses: data.statuses,
|
||||
})
|
||||
return data
|
||||
return search2({
|
||||
q,
|
||||
resolve,
|
||||
limit,
|
||||
offset,
|
||||
following,
|
||||
type,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data }) => {
|
||||
store.commit('addNewUsers', data.accounts)
|
||||
store.commit(
|
||||
'addNewUsers',
|
||||
data.statuses.map((s) => s.user).filter((u) => u),
|
||||
)
|
||||
data.statuses = store.commit('addNewStatuses', {
|
||||
statuses: data.statuses,
|
||||
})
|
||||
return data
|
||||
})
|
||||
},
|
||||
setVirtualHeight({ commit }, { statusId, height }) {
|
||||
commit('setVirtualHeight', { statusId, height })
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import Cookies from 'js-cookie'
|
||||
import {
|
||||
compact,
|
||||
concat,
|
||||
|
|
@ -9,9 +10,6 @@ import {
|
|||
uniq,
|
||||
} from 'lodash'
|
||||
|
||||
import apiService from '../services/api/api.service.js'
|
||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||
import oauthApi from '../services/new_api/oauth.js'
|
||||
import {
|
||||
registerPushNotifications,
|
||||
unregisterPushNotifications,
|
||||
|
|
@ -21,15 +19,42 @@ import {
|
|||
windowWidth,
|
||||
} from '../services/window_utils/window_utils'
|
||||
|
||||
import { useAnnouncementsStore } from 'src/stores/announcements.js'
|
||||
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
|
||||
import { useEmojiStore } from 'src/stores/emoji.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||
import { useListsStore } from 'src/stores/lists.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
|
||||
|
||||
import { revokeToken } from 'src/api/oauth.js'
|
||||
import {
|
||||
fetchFollowers,
|
||||
fetchFriends,
|
||||
fetchUser,
|
||||
fetchUserByName,
|
||||
getCaptcha,
|
||||
register,
|
||||
searchUsers,
|
||||
verifyCredentials,
|
||||
} from 'src/api/public.js'
|
||||
import {
|
||||
blockUser as apiBlockUser,
|
||||
muteUser as apiMuteUser,
|
||||
unblockUser as apiUnblockUser,
|
||||
unmuteUser as apiUnmuteUser,
|
||||
fetchBlocks,
|
||||
fetchDomainMutes,
|
||||
fetchMutes,
|
||||
fetchUserInLists,
|
||||
fetchUserRelationship,
|
||||
followUser,
|
||||
} from 'src/api/user.js'
|
||||
|
||||
// TODO: Unify with mergeOrAdd in statuses.js
|
||||
export const mergeOrAdd = (arr, obj, item) => {
|
||||
if (!item) {
|
||||
|
|
@ -72,43 +97,35 @@ const blockUser = (store, args) => {
|
|||
store.commit('updateUserRelationship', [predictedRelationship])
|
||||
store.commit('addBlockId', id)
|
||||
|
||||
return store.rootState.api.backendInteractor
|
||||
.blockUser({ id, expiresIn })
|
||||
.then((relationship) => {
|
||||
store.commit('updateUserRelationship', [relationship])
|
||||
store.commit('addBlockId', id)
|
||||
return apiBlockUser({ id, expiresIn }).then(({ data: relationship }) => {
|
||||
store.commit('updateUserRelationship', [relationship])
|
||||
store.commit('addBlockId', id)
|
||||
|
||||
store.commit('removeStatus', { timeline: 'friends', userId: id })
|
||||
store.commit('removeStatus', { timeline: 'public', userId: id })
|
||||
store.commit('removeStatus', {
|
||||
timeline: 'publicAndExternal',
|
||||
userId: id,
|
||||
})
|
||||
store.commit('removeStatus', { timeline: 'friends', userId: id })
|
||||
store.commit('removeStatus', { timeline: 'public', userId: id })
|
||||
store.commit('removeStatus', {
|
||||
timeline: 'publicAndExternal',
|
||||
userId: id,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const unblockUser = (store, id) => {
|
||||
return store.rootState.api.backendInteractor
|
||||
.unblockUser({ id })
|
||||
.then((relationship) =>
|
||||
store.commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
return apiUnblockUser({ id }).then(({ data: relationship }) =>
|
||||
store.commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
}
|
||||
|
||||
const removeUserFromFollowers = (store, id) => {
|
||||
return store.rootState.api.backendInteractor
|
||||
.removeUserFromFollowers({ id })
|
||||
.then((relationship) =>
|
||||
store.commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
return removeUserFromFollowers({ id }).then((relationship) =>
|
||||
store.commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
}
|
||||
|
||||
const editUserNote = (store, { id, comment }) => {
|
||||
return store.rootState.api.backendInteractor
|
||||
.editUserNote({ id, comment })
|
||||
.then((relationship) =>
|
||||
store.commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
return editUserNote({ id, comment }).then((relationship) =>
|
||||
store.commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
}
|
||||
|
||||
const muteUser = (store, args) => {
|
||||
|
|
@ -119,12 +136,14 @@ const muteUser = (store, args) => {
|
|||
store.commit('updateUserRelationship', [predictedRelationship])
|
||||
store.commit('addMuteId', id)
|
||||
|
||||
return store.rootState.api.backendInteractor
|
||||
.muteUser({ id, expiresIn })
|
||||
.then((relationship) => {
|
||||
store.commit('updateUserRelationship', [relationship])
|
||||
store.commit('addMuteId', id)
|
||||
})
|
||||
return apiMuteUser({
|
||||
id,
|
||||
expiresIn,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: relationship }) => {
|
||||
store.commit('updateUserRelationship', [relationship])
|
||||
store.commit('addMuteId', id)
|
||||
})
|
||||
}
|
||||
|
||||
const unmuteUser = (store, id) => {
|
||||
|
|
@ -132,39 +151,43 @@ const unmuteUser = (store, id) => {
|
|||
predictedRelationship.muting = false
|
||||
store.commit('updateUserRelationship', [predictedRelationship])
|
||||
|
||||
return store.rootState.api.backendInteractor
|
||||
.unmuteUser({ id })
|
||||
.then((relationship) =>
|
||||
store.commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
return apiUnmuteUser({ id }).then(({ data: relationship }) =>
|
||||
store.commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
}
|
||||
|
||||
const hideReblogs = (store, userId) => {
|
||||
return store.rootState.api.backendInteractor
|
||||
.followUser({ id: userId, reblogs: false })
|
||||
.then((relationship) => {
|
||||
store.commit('updateUserRelationship', [relationship])
|
||||
})
|
||||
return followUser({
|
||||
id: userId,
|
||||
reblogs: false,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: relationship }) =>
|
||||
store.commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
}
|
||||
|
||||
const showReblogs = (store, userId) => {
|
||||
return store.rootState.api.backendInteractor
|
||||
.followUser({ id: userId, reblogs: true })
|
||||
.then((relationship) =>
|
||||
store.commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
return followUser({
|
||||
id: userId,
|
||||
reblogs: true,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: relationship }) =>
|
||||
store.commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
}
|
||||
|
||||
const muteDomain = (store, domain) => {
|
||||
return store.rootState.api.backendInteractor
|
||||
.muteDomain({ domain })
|
||||
.then(() => store.commit('addDomainMute', domain))
|
||||
return muteDomain({
|
||||
domain,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(() => store.commit('addDomainMute', domain))
|
||||
}
|
||||
|
||||
const unmuteDomain = (store, domain) => {
|
||||
return store.rootState.api.backendInteractor
|
||||
.unmuteDomain({ domain })
|
||||
.then(() => store.commit('removeDomainMute', domain))
|
||||
return unmuteDomain({
|
||||
domain,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(() => store.commit('removeDomainMute', domain))
|
||||
}
|
||||
|
||||
export const mutations = {
|
||||
|
|
@ -385,55 +408,70 @@ const users = {
|
|||
})
|
||||
},
|
||||
fetchUser(store, id) {
|
||||
return store.rootState.api.backendInteractor
|
||||
.fetchUser({ id })
|
||||
.then((user) => {
|
||||
return fetchUser({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(({ data: user }) => {
|
||||
store.commit('addNewUsers', [user])
|
||||
return user
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.statusCode === 404) {
|
||||
console.warn(`User ${id} not found`)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchUserByName(store, name) {
|
||||
return store.rootState.api.backendInteractor
|
||||
.fetchUserByName({ name })
|
||||
.then((user) => {
|
||||
store.commit('addNewUsers', [user])
|
||||
return user
|
||||
})
|
||||
return fetchUserByName({
|
||||
name,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: user }) => {
|
||||
store.commit('addNewUsers', [user])
|
||||
return user
|
||||
})
|
||||
},
|
||||
fetchUserRelationship(store, id) {
|
||||
if (store.state.currentUser) {
|
||||
store.rootState.api.backendInteractor
|
||||
.fetchUserRelationship({ id })
|
||||
.then((relationships) =>
|
||||
store.commit('updateUserRelationship', relationships),
|
||||
)
|
||||
fetchUserRelationship({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: relationships }) =>
|
||||
store.commit('updateUserRelationship', relationships),
|
||||
)
|
||||
}
|
||||
},
|
||||
fetchUserInLists(store, id) {
|
||||
if (store.state.currentUser) {
|
||||
store.rootState.api.backendInteractor
|
||||
.fetchUserInLists({ id })
|
||||
.then((inLists) => store.commit('updateUserInLists', { id, inLists }))
|
||||
fetchUserInLists({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: inLists }) =>
|
||||
store.commit('updateUserInLists', { id, inLists }),
|
||||
)
|
||||
}
|
||||
},
|
||||
fetchBlocks(store, args) {
|
||||
const { reset } = args || {}
|
||||
|
||||
const maxId = store.state.currentUser.blockIdsMaxId
|
||||
return store.rootState.api.backendInteractor
|
||||
.fetchBlocks({ maxId })
|
||||
.then((blocks) => {
|
||||
if (reset) {
|
||||
store.commit('saveBlockIds', map(blocks, 'id'))
|
||||
} else {
|
||||
map(blocks, 'id').map((id) => store.commit('addBlockId', id))
|
||||
}
|
||||
if (blocks.length) {
|
||||
store.commit('setBlockIdsMaxId', last(blocks).id)
|
||||
}
|
||||
store.commit('addNewUsers', blocks)
|
||||
return blocks
|
||||
})
|
||||
return fetchBlocks({
|
||||
maxId,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: blocks }) => {
|
||||
if (reset) {
|
||||
store.commit('saveBlockIds', map(blocks, 'id'))
|
||||
} else {
|
||||
map(blocks, 'id').map((id) => store.commit('addBlockId', id))
|
||||
}
|
||||
if (blocks.length) {
|
||||
store.commit('setBlockIdsMaxId', last(blocks).id)
|
||||
}
|
||||
store.commit('addNewUsers', blocks)
|
||||
return blocks
|
||||
})
|
||||
},
|
||||
blockUser(store, data) {
|
||||
return blockUser(store, data)
|
||||
|
|
@ -457,20 +495,21 @@ const users = {
|
|||
const { reset } = args || {}
|
||||
|
||||
const maxId = store.state.currentUser.muteIdsMaxId
|
||||
return store.rootState.api.backendInteractor
|
||||
.fetchMutes({ maxId })
|
||||
.then((mutes) => {
|
||||
if (reset) {
|
||||
store.commit('saveMuteIds', map(mutes, 'id'))
|
||||
} else {
|
||||
map(mutes, 'id').map((id) => store.commit('addMuteId', id))
|
||||
}
|
||||
if (mutes.length) {
|
||||
store.commit('setMuteIdsMaxId', last(mutes).id)
|
||||
}
|
||||
store.commit('addNewUsers', mutes)
|
||||
return mutes
|
||||
})
|
||||
return fetchMutes({
|
||||
maxId,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: mutes }) => {
|
||||
if (reset) {
|
||||
store.commit('saveMuteIds', map(mutes, 'id'))
|
||||
} else {
|
||||
map(mutes, 'id').map((id) => store.commit('addMuteId', id))
|
||||
}
|
||||
if (mutes.length) {
|
||||
store.commit('setMuteIdsMaxId', last(mutes).id)
|
||||
}
|
||||
store.commit('addNewUsers', mutes)
|
||||
return mutes
|
||||
})
|
||||
},
|
||||
muteUser(store, data) {
|
||||
return muteUser(store, data)
|
||||
|
|
@ -491,12 +530,12 @@ const users = {
|
|||
return Promise.all(ids.map((d) => unmuteUser(store, d)))
|
||||
},
|
||||
fetchDomainMutes(store) {
|
||||
return store.rootState.api.backendInteractor
|
||||
.fetchDomainMutes()
|
||||
.then((domainMutes) => {
|
||||
store.commit('saveDomainMutes', domainMutes)
|
||||
return domainMutes
|
||||
})
|
||||
return fetchDomainMutes({
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: domainMutes }) => {
|
||||
store.commit('saveDomainMutes', domainMutes)
|
||||
return domainMutes
|
||||
})
|
||||
},
|
||||
muteDomain(store, domain) {
|
||||
return muteDomain(store, domain)
|
||||
|
|
@ -513,24 +552,28 @@ const users = {
|
|||
fetchFriends({ rootState, commit }, id) {
|
||||
const user = rootState.users.usersObject[id]
|
||||
const maxId = last(user.friendIds)
|
||||
return rootState.api.backendInteractor
|
||||
.fetchFriends({ id, maxId })
|
||||
.then((friends) => {
|
||||
commit('addNewUsers', friends)
|
||||
commit('saveFriendIds', { id, friendIds: map(friends, 'id') })
|
||||
return friends
|
||||
})
|
||||
return fetchFriends({
|
||||
id,
|
||||
maxId,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: friends }) => {
|
||||
commit('addNewUsers', friends)
|
||||
commit('saveFriendIds', { id, friendIds: map(friends, 'id') })
|
||||
return friends
|
||||
})
|
||||
},
|
||||
fetchFollowers({ rootState, commit }, id) {
|
||||
const user = rootState.users.usersObject[id]
|
||||
const maxId = last(user.followerIds)
|
||||
return rootState.api.backendInteractor
|
||||
.fetchFollowers({ id, maxId })
|
||||
.then((followers) => {
|
||||
commit('addNewUsers', followers)
|
||||
commit('saveFollowerIds', { id, followerIds: map(followers, 'id') })
|
||||
return followers
|
||||
})
|
||||
return fetchFollowers({
|
||||
id,
|
||||
maxId,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: followers }) => {
|
||||
commit('addNewUsers', followers)
|
||||
commit('saveFollowerIds', { id, followerIds: map(followers, 'id') })
|
||||
return followers
|
||||
})
|
||||
},
|
||||
clearFriends({ commit }, userId) {
|
||||
commit('clearFriends', userId)
|
||||
|
|
@ -539,18 +582,22 @@ const users = {
|
|||
commit('clearFollowers', userId)
|
||||
},
|
||||
subscribeUser({ rootState, commit }, id) {
|
||||
return rootState.api.backendInteractor
|
||||
.followUser({ id, notify: true })
|
||||
.then((relationship) =>
|
||||
commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
return followUser({
|
||||
id,
|
||||
notify: true,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: relationship }) =>
|
||||
commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
},
|
||||
unsubscribeUser({ rootState, commit }, id) {
|
||||
return rootState.api.backendInteractor
|
||||
.followUser({ id, notify: false })
|
||||
.then((relationship) =>
|
||||
commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
return followUser({
|
||||
id,
|
||||
notify: false,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: relationship }) =>
|
||||
commit('updateUserRelationship', [relationship]),
|
||||
)
|
||||
},
|
||||
registerPushNotifications(store) {
|
||||
const token = store.state.currentUser.credentials
|
||||
|
|
@ -611,12 +658,13 @@ const users = {
|
|||
})
|
||||
},
|
||||
searchUsers({ rootState, commit }, { query }) {
|
||||
return rootState.api.backendInteractor
|
||||
.searchUsers({ query })
|
||||
.then((users) => {
|
||||
commit('addNewUsers', users)
|
||||
return users
|
||||
})
|
||||
return searchUsers({
|
||||
query,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: users }) => {
|
||||
commit('addNewUsers', users)
|
||||
return users
|
||||
})
|
||||
},
|
||||
async signUp(store, userInfo) {
|
||||
const oauthStore = useOAuthStore()
|
||||
|
|
@ -624,7 +672,7 @@ const users = {
|
|||
|
||||
try {
|
||||
const token = await oauthStore.ensureAppToken()
|
||||
const data = await apiService.register({
|
||||
const { data } = await register({
|
||||
credentials: token,
|
||||
params: { ...userInfo },
|
||||
})
|
||||
|
|
@ -645,8 +693,10 @@ const users = {
|
|||
throw e
|
||||
}
|
||||
},
|
||||
async getCaptcha(store) {
|
||||
return store.rootState.api.backendInteractor.getCaptcha()
|
||||
getCaptcha(store) {
|
||||
return getCaptcha({
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data }) => data)
|
||||
},
|
||||
|
||||
logout(store) {
|
||||
|
|
@ -663,24 +713,21 @@ const users = {
|
|||
token: oauth.userToken,
|
||||
}
|
||||
|
||||
return oauthApi.revokeToken(params)
|
||||
return revokeToken(params)
|
||||
})
|
||||
.then(() => {
|
||||
store.commit('clearCurrentUser')
|
||||
store.dispatch('disconnectFromSocket')
|
||||
oauth.clearToken()
|
||||
store.dispatch('stopFetchingTimeline', 'friends')
|
||||
store.commit(
|
||||
'setBackendInteractor',
|
||||
backendInteractorService(oauth.getToken),
|
||||
)
|
||||
store.dispatch('stopFetchingNotifications')
|
||||
store.dispatch('stopFetchingLists')
|
||||
store.dispatch('stopFetchingBookmarkFolders')
|
||||
useListsStore().stopFetching()
|
||||
useBookmarkFoldersStore().stopFetching()
|
||||
store.dispatch('stopFetchingFollowRequests')
|
||||
store.commit('clearNotifications')
|
||||
store.commit('resetStatuses')
|
||||
store.dispatch('resetChats')
|
||||
oauth.clearToken()
|
||||
Cookies.remove('__Host-pleroma_key', { path: '/' })
|
||||
useInterfaceStore().setLastTimeline('public-timeline')
|
||||
useInterfaceStore().setLayoutWidth(windowWidth())
|
||||
useInterfaceStore().setLayoutHeight(windowHeight())
|
||||
|
|
@ -690,141 +737,137 @@ const users = {
|
|||
return new Promise((resolve, reject) => {
|
||||
const commit = store.commit
|
||||
const dispatch = store.dispatch
|
||||
|
||||
commit('beginLogin')
|
||||
store.rootState.api.backendInteractor
|
||||
.verifyCredentials(accessToken)
|
||||
.then((data) => {
|
||||
if (!data.error) {
|
||||
const user = data
|
||||
// user.credentials = userCredentials
|
||||
user.credentials = accessToken
|
||||
user.blockIds = []
|
||||
user.muteIds = []
|
||||
user.domainMutes = []
|
||||
commit('setCurrentUser', user)
|
||||
|
||||
useSyncConfigStore()
|
||||
.initSyncConfig(user)
|
||||
.then(() => {
|
||||
useInterfaceStore()
|
||||
.applyTheme()
|
||||
.catch((e) => {
|
||||
console.error('Error setting theme', e)
|
||||
})
|
||||
})
|
||||
useUserHighlightStore().initUserHighlight(user)
|
||||
commit('addNewUsers', [user])
|
||||
verifyCredentials({
|
||||
credentials: useOAuthStore().token,
|
||||
})
|
||||
.then(({ data: user }) => {
|
||||
// user.credentials = userCredentials
|
||||
user.credentials = accessToken
|
||||
user.blockIds = []
|
||||
user.muteIds = []
|
||||
user.domainMutes = []
|
||||
commit('setCurrentUser', user)
|
||||
|
||||
useEmojiStore().fetchEmoji()
|
||||
|
||||
getNotificationPermission().then((permission) =>
|
||||
useInterfaceStore().setNotificationPermission(permission),
|
||||
)
|
||||
|
||||
// Set our new backend interactor
|
||||
commit(
|
||||
'setBackendInteractor',
|
||||
backendInteractorService(accessToken),
|
||||
)
|
||||
|
||||
// Do server-side storage migrations
|
||||
|
||||
// Debug snippet to clean up storage and reset migrations
|
||||
/*
|
||||
// Reset wordfilter
|
||||
Object.keys(
|
||||
useSyncConfigStore().prefsStorage.simple.muteFilters
|
||||
).forEach(key => {
|
||||
useSyncConfigStore().unsetSimplePrefAndSave({ path: 'muteFilters.' + key, value: null })
|
||||
useSyncConfigStore()
|
||||
.initSyncConfig(user)
|
||||
.then(() => {
|
||||
useInterfaceStore()
|
||||
.applyTheme()
|
||||
.catch((e) => {
|
||||
console.error('Error setting theme', e)
|
||||
})
|
||||
})
|
||||
useUserHighlightStore().initUserHighlight(user)
|
||||
commit('addNewUsers', [user])
|
||||
|
||||
// Reset flag to 0 to re-run migrations
|
||||
useSyncConfigStore().setFlag({ flag: 'configMigration', value: 0 })
|
||||
/**/
|
||||
useEmojiStore().fetchEmoji()
|
||||
|
||||
if (user.token) {
|
||||
dispatch('setWsToken', user.token)
|
||||
getNotificationPermission().then((permission) =>
|
||||
useInterfaceStore().setNotificationPermission(permission),
|
||||
)
|
||||
|
||||
// Initialize the shout socket.
|
||||
dispatch('initializeSocket')
|
||||
}
|
||||
// Do server-side storage migrations
|
||||
|
||||
const startPolling = () => {
|
||||
// Start getting fresh posts.
|
||||
dispatch('startFetchingTimeline', { timeline: 'friends' })
|
||||
// Debug snippet to clean up storage and reset migrations
|
||||
/*
|
||||
// Reset wordfilter
|
||||
Object.keys(
|
||||
useSyncConfigStore().prefsStorage.simple.muteFilters
|
||||
).forEach(key => {
|
||||
useSyncConfigStore().unsetSimplePrefAndSave({ path: 'muteFilters.' + key, value: null })
|
||||
})
|
||||
|
||||
// Start fetching notifications
|
||||
dispatch('startFetchingNotifications')
|
||||
// Reset flag to 0 to re-run migrations
|
||||
useSyncConfigStore().setFlag({ flag: 'configMigration', value: 0 })
|
||||
/**/
|
||||
|
||||
if (
|
||||
useInstanceCapabilitiesStore().pleromaChatMessagesAvailable
|
||||
) {
|
||||
// Start fetching chats
|
||||
dispatch('startFetchingChats')
|
||||
}
|
||||
}
|
||||
if (user.token) {
|
||||
dispatch('setWsToken', user.token)
|
||||
|
||||
dispatch('startFetchingLists')
|
||||
dispatch('startFetchingBookmarkFolders')
|
||||
// Initialize the shout socket.
|
||||
dispatch('initializeSocket')
|
||||
}
|
||||
|
||||
if (user.locked) {
|
||||
dispatch('startFetchingFollowRequests')
|
||||
}
|
||||
const startPolling = () => {
|
||||
// Start getting fresh posts.
|
||||
dispatch('startFetchingTimeline', { timeline: 'friends' })
|
||||
|
||||
if (useMergedConfigStore().mergedConfig.useStreamingApi) {
|
||||
dispatch('fetchTimeline', { timeline: 'friends', since: null })
|
||||
dispatch('fetchNotifications', { since: null })
|
||||
dispatch('enableMastoSockets', true)
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
'Failed initializing MastoAPI Streaming socket',
|
||||
error,
|
||||
)
|
||||
})
|
||||
.then(() => {
|
||||
dispatch('fetchChats', { latest: true })
|
||||
setTimeout(
|
||||
() => dispatch('setNotificationsSilence', false),
|
||||
10000,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
startPolling()
|
||||
}
|
||||
// Start fetching notifications
|
||||
dispatch('startFetchingNotifications')
|
||||
|
||||
// Get user mutes
|
||||
dispatch('fetchMutes')
|
||||
|
||||
useInterfaceStore().setLayoutWidth(windowWidth())
|
||||
useInterfaceStore().setLayoutHeight(windowHeight())
|
||||
|
||||
// Fetch our friends
|
||||
store.rootState.api.backendInteractor
|
||||
.fetchFriends({ id: user.id })
|
||||
.then((friends) => commit('addNewUsers', friends))
|
||||
} else {
|
||||
const response = data.error
|
||||
// Authentication failed
|
||||
commit('endLogin')
|
||||
|
||||
// remove authentication token on client/authentication errors
|
||||
if ([400, 401, 403, 422].includes(response.status)) {
|
||||
useOAuthStore().clearToken()
|
||||
}
|
||||
|
||||
if (response.status === 401) {
|
||||
reject(new Error('Wrong username or password'))
|
||||
} else {
|
||||
reject(new Error('An error occurred, please try again'))
|
||||
if (useInstanceCapabilitiesStore().pleromaChatMessagesAvailable) {
|
||||
// Start fetching chats
|
||||
dispatch('startFetchingChats')
|
||||
}
|
||||
}
|
||||
|
||||
useListsStore().startFetching()
|
||||
useBookmarkFoldersStore().startFetching()
|
||||
|
||||
if (user.locked) {
|
||||
dispatch('startFetchingFollowRequests')
|
||||
}
|
||||
|
||||
if (useMergedConfigStore().mergedConfig.useStreamingApi) {
|
||||
dispatch('fetchTimeline', {
|
||||
timeline: 'friends',
|
||||
sinceId: null,
|
||||
})
|
||||
dispatch('fetchNotifications', { sinceId: null })
|
||||
dispatch('enableMastoSockets', true)
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
'Failed initializing MastoAPI Streaming socket',
|
||||
error,
|
||||
)
|
||||
})
|
||||
.then(() => {
|
||||
dispatch('fetchChats', { latest: true })
|
||||
setTimeout(
|
||||
() => dispatch('setNotificationsSilence', false),
|
||||
10000,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
startPolling()
|
||||
}
|
||||
|
||||
// Start fetching things that don't need to block the UI
|
||||
useAnnouncementsStore().startFetchingAnnouncements()
|
||||
|
||||
dispatch('fetchMutes')
|
||||
dispatch('loadDrafts')
|
||||
|
||||
useInterfaceStore().setLayoutWidth(windowWidth())
|
||||
useInterfaceStore().setLayoutHeight(windowHeight())
|
||||
|
||||
// Fetch our friends
|
||||
fetchFriends({ id: user.id }).then(({ data: friends }) =>
|
||||
commit('addNewUsers', friends),
|
||||
)
|
||||
commit('endLogin')
|
||||
resolve()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
|
||||
// Authentication failed
|
||||
commit('endLogin')
|
||||
reject(new Error('Failed to connect to server, try again'))
|
||||
|
||||
// remove authentication token on client/authentication errors
|
||||
if ([400, 401, 403, 422].includes(error.statusCode)) {
|
||||
useOAuthStore().clearToken()
|
||||
}
|
||||
|
||||
commit('endLogin')
|
||||
if (error.tatusCode === 401) {
|
||||
throw new Error('Wrong username or password', error)
|
||||
} else {
|
||||
throw new Error('An error occurred, please try again', error)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,75 +0,0 @@
|
|||
import bookmarkFoldersFetcher from '../../services/bookmark_folders_fetcher/bookmark_folders_fetcher.service.js'
|
||||
import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
|
||||
import listsFetcher from '../../services/lists_fetcher/lists_fetcher.service.js'
|
||||
import apiService, {
|
||||
getMastodonSocketURI,
|
||||
ProcessedWS,
|
||||
} from '../api/api.service.js'
|
||||
import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
|
||||
import timelineFetcher from '../timeline_fetcher/timeline_fetcher.service.js'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
|
||||
const backendInteractorService = (credentials) => ({
|
||||
startFetchingTimeline({
|
||||
timeline,
|
||||
store,
|
||||
userId = false,
|
||||
listId = false,
|
||||
statusId = false,
|
||||
bookmarkFolderId = false,
|
||||
tag,
|
||||
}) {
|
||||
return timelineFetcher.startFetching({
|
||||
timeline,
|
||||
store,
|
||||
credentials,
|
||||
userId,
|
||||
listId,
|
||||
statusId,
|
||||
bookmarkFolderId,
|
||||
tag,
|
||||
})
|
||||
},
|
||||
|
||||
fetchTimeline(args) {
|
||||
return timelineFetcher.fetchAndUpdate({ ...args, credentials })
|
||||
},
|
||||
|
||||
startFetchingNotifications({ store }) {
|
||||
return notificationsFetcher.startFetching({ store, credentials })
|
||||
},
|
||||
|
||||
fetchNotifications(args) {
|
||||
return notificationsFetcher.fetchAndUpdate({ ...args, credentials })
|
||||
},
|
||||
|
||||
startFetchingFollowRequests({ store }) {
|
||||
return followRequestFetcher.startFetching({ store, credentials })
|
||||
},
|
||||
|
||||
startFetchingLists({ store }) {
|
||||
return listsFetcher.startFetching({ store, credentials })
|
||||
},
|
||||
|
||||
startFetchingBookmarkFolders({ store }) {
|
||||
return bookmarkFoldersFetcher.startFetching({ store, credentials })
|
||||
},
|
||||
|
||||
startUserSocket({ store }) {
|
||||
const serv = useInstanceStore().server.replace('http', 'ws')
|
||||
const url = getMastodonSocketURI({}, serv)
|
||||
return ProcessedWS({ url, id: 'Unified', credentials })
|
||||
},
|
||||
|
||||
...Object.entries(apiService).reduce((acc, [key, func]) => {
|
||||
return {
|
||||
...acc,
|
||||
[key]: (args) => func({ credentials, ...args }),
|
||||
}
|
||||
}, {}),
|
||||
|
||||
verifyCredentials: apiService.verifyCredentials,
|
||||
})
|
||||
|
||||
export default backendInteractorService
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import apiService from '../api/api.service.js'
|
||||
import { promiseInterval } from '../promise_interval/promise_interval.js'
|
||||
|
||||
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
|
||||
|
||||
const fetchAndUpdate = ({ credentials }) => {
|
||||
return apiService
|
||||
.fetchBookmarkFolders({ credentials })
|
||||
.then(
|
||||
(bookmarkFolders) => {
|
||||
useBookmarkFoldersStore().setBookmarkFolders(bookmarkFolders)
|
||||
},
|
||||
(rej) => {
|
||||
console.error(rej)
|
||||
},
|
||||
)
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
|
||||
const startFetching = ({ credentials, store }) => {
|
||||
const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
|
||||
boundFetchAndUpdate()
|
||||
return promiseInterval(boundFetchAndUpdate, 240000)
|
||||
}
|
||||
|
||||
const bookmarkFoldersFetcher = {
|
||||
startFetching,
|
||||
}
|
||||
|
||||
export default bookmarkFoldersFetcher
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue