aboutsummaryrefslogtreecommitdiff
path: root/src/services
diff options
context:
space:
mode:
Diffstat (limited to 'src/services')
-rw-r--r--src/services/api/api.service.js169
-rw-r--r--src/services/attributes_helper/attributes_helper.service.js8
-rw-r--r--src/services/date_utils/date_utils.js16
-rw-r--r--src/services/entity_normalizer/entity_normalizer.service.js8
-rw-r--r--src/services/file_type/file_type.service.js18
-rw-r--r--src/services/html_converter/utility.service.js2
-rw-r--r--src/services/locale/locale.service.js6
-rw-r--r--src/services/matcher/matcher.service.js7
-rw-r--r--src/services/status_poster/status_poster.service.js2
-rw-r--r--src/services/style_setter/style_setter.js4
10 files changed, 204 insertions, 36 deletions
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 7174cc5d..c6bca10b 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -108,6 +108,11 @@ const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements'
const PLEROMA_EDIT_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}`
const PLEROMA_DELETE_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}`
+const PLEROMA_ADMIN_CONFIG_URL = '/api/pleroma/admin/config'
+const PLEROMA_ADMIN_DESCRIPTIONS_URL = '/api/pleroma/admin/config/descriptions'
+const PLEROMA_ADMIN_FRONTENDS_URL = '/api/pleroma/admin/frontends'
+const PLEROMA_ADMIN_FRONTENDS_INSTALL_URL = '/api/pleroma/admin/frontends/install'
+
const oldfetch = window.fetch
const fetch = (url, options) => {
@@ -164,7 +169,7 @@ const updateNotificationSettings = ({ credentials, settings }) => {
form.append(key, value)
})
- return fetch(NOTIFICATION_SETTINGS_URL, {
+ return fetch(`${NOTIFICATION_SETTINGS_URL}?${new URLSearchParams(settings)}`, {
headers: authHeaders(credentials),
method: 'PUT',
body: form
@@ -734,26 +739,22 @@ const fetchTimeline = ({
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
url += `?${queryString}`
- let status = ''
- let statusText = ''
-
- let pagination = {}
return fetch(url, { headers: authHeaders(credentials) })
- .then((data) => {
- status = data.status
- statusText = data.statusText
- pagination = parseLinkHeaderPagination(data.headers.get('Link'), {
- flakeId: timeline !== 'bookmarks' && timeline !== 'notifications'
- })
- return data
- })
- .then((data) => data.json())
- .then((data) => {
- if (!data.errors) {
+ .then(async (response) => {
+ const success = response.ok
+
+ const data = await response.json()
+
+ if (success && !data.errors) {
+ const pagination = parseLinkHeaderPagination(response.headers.get('Link'), {
+ flakeId: timeline !== 'bookmarks' && timeline !== 'notifications'
+ })
+
return { data: data.map(isNotifications ? parseNotification : parseStatus), pagination }
} else {
- data.status = status
- data.statusText = statusText
+ data.errors ||= []
+ data.status = response.status
+ data.statusText = response.statusText
return data
}
})
@@ -826,6 +827,7 @@ const postStatus = ({
poll,
mediaIds = [],
inReplyToStatusId,
+ quoteId,
contentType,
preview,
idempotencyKey
@@ -844,7 +846,7 @@ const postStatus = ({
})
if (pollOptions.some(option => option !== '')) {
const normalizedPoll = {
- expires_in: poll.expiresIn,
+ expires_in: parseInt(poll.expiresIn, 10),
multiple: poll.multiple
}
Object.keys(normalizedPoll).forEach(key => {
@@ -858,6 +860,9 @@ const postStatus = ({
if (inReplyToStatusId) {
form.append('in_reply_to_id', inReplyToStatusId)
}
+ if (quoteId) {
+ form.append('quote_id', quoteId)
+ }
if (preview) {
form.append('preview', 'true')
}
@@ -901,7 +906,7 @@ const editStatus = ({
if (pollOptions.some(option => option !== '')) {
const normalizedPoll = {
- expires_in: poll.expiresIn,
+ expires_in: parseInt(poll.expiresIn, 10),
multiple: poll.multiple
}
Object.keys(normalizedPoll).forEach(key => {
@@ -927,8 +932,9 @@ const editStatus = ({
}
const deleteStatus = ({ id, credentials }) => {
- return fetch(MASTODON_DELETE_URL(id), {
- headers: authHeaders(credentials),
+ return promisedRequest({
+ url: MASTODON_DELETE_URL(id),
+ credentials,
method: 'DELETE'
})
}
@@ -1117,13 +1123,21 @@ const generateMfaBackupCodes = ({ credentials }) => {
}).then((data) => data.json())
}
-const fetchMutes = ({ credentials }) => {
- return promisedRequest({ url: MASTODON_USER_MUTES_URL, credentials })
+const fetchMutes = ({ maxId, credentials }) => {
+ const query = new URLSearchParams({ with_relationships: true })
+ if (maxId) {
+ query.append('max_id', maxId)
+ }
+ return promisedRequest({ url: `${MASTODON_USER_MUTES_URL}?${query.toString()}`, credentials })
.then((users) => users.map(parseUser))
}
-const muteUser = ({ id, credentials }) => {
- return promisedRequest({ url: MASTODON_MUTE_USER_URL(id), credentials, method: 'POST' })
+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 })
}
const unmuteUser = ({ id, credentials }) => {
@@ -1138,8 +1152,12 @@ const unsubscribeUser = ({ id, credentials }) => {
return promisedRequest({ url: MASTODON_UNSUBSCRIBE_USER(id), credentials, method: 'POST' })
}
-const fetchBlocks = ({ credentials }) => {
- return promisedRequest({ url: MASTODON_USER_BLOCKS_URL, credentials })
+const fetchBlocks = ({ maxId, credentials }) => {
+ const query = new URLSearchParams({ with_relationships: true })
+ if (maxId) {
+ query.append('max_id', maxId)
+ }
+ return promisedRequest({ url: `${MASTODON_USER_BLOCKS_URL}?${query.toString()}`, credentials })
.then((users) => users.map(parseUser))
}
@@ -1659,6 +1677,94 @@ const setReportState = ({ id, state, credentials }) => {
})
}
+// ADMIN STUFF // EXPERIMENTAL
+const fetchInstanceDBConfig = ({ credentials }) => {
+ return fetch(PLEROMA_ADMIN_CONFIG_URL, {
+ headers: authHeaders(credentials)
+ })
+ .then((response) => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ return {
+ error: response
+ }
+ }
+ })
+}
+
+const fetchInstanceConfigDescriptions = ({ credentials }) => {
+ return fetch(PLEROMA_ADMIN_DESCRIPTIONS_URL, {
+ headers: authHeaders(credentials)
+ })
+ .then((response) => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ return {
+ error: response
+ }
+ }
+ })
+}
+
+const fetchAvailableFrontends = ({ credentials }) => {
+ return fetch(PLEROMA_ADMIN_FRONTENDS_URL, {
+ headers: authHeaders(credentials)
+ })
+ .then((response) => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ return {
+ error: response
+ }
+ }
+ })
+}
+
+const pushInstanceDBConfig = ({ credentials, payload }) => {
+ return fetch(PLEROMA_ADMIN_CONFIG_URL, {
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ ...authHeaders(credentials)
+ },
+ method: 'POST',
+ body: JSON.stringify(payload)
+ })
+ .then((response) => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ return {
+ error: response
+ }
+ }
+ })
+}
+
+const installFrontend = ({ credentials, payload }) => {
+ return fetch(PLEROMA_ADMIN_FRONTENDS_INSTALL_URL, {
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ ...authHeaders(credentials)
+ },
+ method: 'POST',
+ body: JSON.stringify(payload)
+ })
+ .then((response) => {
+ if (response.ok) {
+ return response.json()
+ } else {
+ return {
+ error: response
+ }
+ }
+ })
+}
+
const apiService = {
verifyCredentials,
fetchTimeline,
@@ -1772,7 +1878,12 @@ const apiService = {
postAnnouncement,
editAnnouncement,
deleteAnnouncement,
- adminFetchAnnouncements
+ adminFetchAnnouncements,
+ fetchInstanceDBConfig,
+ fetchInstanceConfigDescriptions,
+ fetchAvailableFrontends,
+ pushInstanceDBConfig,
+ installFrontend
}
export default apiService
diff --git a/src/services/attributes_helper/attributes_helper.service.js b/src/services/attributes_helper/attributes_helper.service.js
new file mode 100644
index 00000000..74d3323c
--- /dev/null
+++ b/src/services/attributes_helper/attributes_helper.service.js
@@ -0,0 +1,8 @@
+import { kebabCase } from 'lodash'
+
+const propsToNative = props => Object.keys(props).reduce((acc, cur) => {
+ acc[kebabCase(cur)] = props[cur]
+ return acc
+}, {})
+
+export { propsToNative }
diff --git a/src/services/date_utils/date_utils.js b/src/services/date_utils/date_utils.js
index c93d2176..ed8e1417 100644
--- a/src/services/date_utils/date_utils.js
+++ b/src/services/date_utils/date_utils.js
@@ -41,3 +41,19 @@ export const relativeTimeShort = (date, nowThreshold = 1) => {
r.key += '_short'
return r
}
+
+export const unitToSeconds = (unit, amount) => {
+ switch (unit) {
+ case 'minutes': return 0.001 * amount * MINUTE
+ case 'hours': return 0.001 * amount * HOUR
+ case 'days': return 0.001 * amount * DAY
+ }
+}
+
+export const secondsToUnit = (unit, amount) => {
+ switch (unit) {
+ case 'minutes': return (1000 * amount) / MINUTE
+ case 'hours': return (1000 * amount) / HOUR
+ case 'days': return (1000 * amount) / DAY
+ }
+}
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index ea138177..610ba1ab 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -125,6 +125,8 @@ export const parseUser = (data) => {
output.role = 'member'
}
+ output.birthday = data.pleroma.birthday
+
if (data.pleroma.privileges) {
output.privileges = data.pleroma.privileges
} else if (data.pleroma.is_admin) {
@@ -162,6 +164,7 @@ export const parseUser = (data) => {
output.no_rich_text = data.source.pleroma.no_rich_text
output.show_role = data.source.pleroma.show_role
output.discoverable = data.source.pleroma.discoverable
+ output.show_birthday = data.pleroma.show_birthday
}
}
@@ -322,6 +325,10 @@ export const parseStatus = (data) => {
output.thread_muted = pleroma.thread_muted
output.emoji_reactions = pleroma.emoji_reactions
output.parent_visible = pleroma.parent_visible === undefined ? true : pleroma.parent_visible
+ output.quote = pleroma.quote ? parseStatus(pleroma.quote) : undefined
+ output.quote_id = pleroma.quote_id ? pleroma.quote_id : (output.quote ? output.quote.id : undefined)
+ output.quote_url = pleroma.quote_url
+ output.quote_visible = pleroma.quote_visible
} else {
output.text = data.content
output.summary = data.spoiler_text
@@ -438,6 +445,7 @@ export const parseNotification = (data) => {
: parseUser(data.target)
output.from_profile = parseUser(data.account)
output.emoji = data.emoji
+ output.emoji_url = data.emoji_url
if (data.report) {
output.report = data.report
output.report.content = data.report.content
diff --git a/src/services/file_type/file_type.service.js b/src/services/file_type/file_type.service.js
index 5182ecd1..b92c6c64 100644
--- a/src/services/file_type/file_type.service.js
+++ b/src/services/file_type/file_type.service.js
@@ -1,7 +1,7 @@
// TODO this func might as well take the entire file and use its mimetype
// or the entire service could be just mimetype service that only operates
// on mimetypes and not files. Currently the naming is confusing.
-const fileType = mimetype => {
+export const fileType = mimetype => {
if (mimetype.match(/flash/)) {
return 'flash'
}
@@ -25,11 +25,25 @@ const fileType = mimetype => {
return 'unknown'
}
-const fileMatchesSomeType = (types, file) =>
+export const fileTypeExt = url => {
+ if (url.match(/\.(png|jpe?g|gif|webp|avif)$/)) {
+ return 'image'
+ }
+ if (url.match(/\.(ogv|mp4|webm|mov)$/)) {
+ return 'video'
+ }
+ if (url.match(/\.(it|s3m|mod|umx|mp3|aac|m4a|flac|alac|ogg|oga|opus|wav|ape|midi?)$/)) {
+ return 'audio'
+ }
+ return 'unknown'
+}
+
+export const fileMatchesSomeType = (types, file) =>
types.some(type => fileType(file.mimetype) === type)
const fileTypeService = {
fileType,
+ fileTypeExt,
fileMatchesSomeType
}
diff --git a/src/services/html_converter/utility.service.js b/src/services/html_converter/utility.service.js
index a1301353..f8e62dfe 100644
--- a/src/services/html_converter/utility.service.js
+++ b/src/services/html_converter/utility.service.js
@@ -22,7 +22,7 @@ export const getAttrs = (tag, filter) => {
.replace(new RegExp('^' + getTagName(tag)), '')
.replace(/\/?$/, '')
.trim()
- const attrs = Array.from(innertag.matchAll(/([a-z0-9-]+)(?:=("[^"]+?"|'[^']+?'))?/gi))
+ const attrs = Array.from(innertag.matchAll(/([a-z]+[a-z0-9-]*)(?:=("[^"]+?"|'[^']+?'))?/gi))
.map(([trash, key, value]) => [key, value])
.map(([k, v]) => {
if (!v) return [k, true]
diff --git a/src/services/locale/locale.service.js b/src/services/locale/locale.service.js
index d3389785..24ed3cdb 100644
--- a/src/services/locale/locale.service.js
+++ b/src/services/locale/locale.service.js
@@ -11,10 +11,15 @@ const specialLanguageCodes = {
const internalToBrowserLocale = code => specialLanguageCodes[code] || code
const internalToBackendLocale = code => internalToBrowserLocale(code).replace('_', '-')
+const internalToBackendLocaleMulti = codes => {
+ const langs = Array.isArray(codes) ? codes : [codes]
+ return langs.map(internalToBackendLocale).join(',')
+}
const getLanguageName = (code) => {
const specialLanguageNames = {
ja_easy: 'やさしいにほんご',
+ 'nan-TW': '臺語(閩南語)',
zh: '简体中文',
zh_Hant: '繁體中文'
}
@@ -28,6 +33,7 @@ const languages = _.map(languagesObject.languages, (code) => ({ code, name: getL
const localeService = {
internalToBrowserLocale,
internalToBackendLocale,
+ internalToBackendLocaleMulti,
languages,
getLanguageName
}
diff --git a/src/services/matcher/matcher.service.js b/src/services/matcher/matcher.service.js
index b6c4e909..54f02d31 100644
--- a/src/services/matcher/matcher.service.js
+++ b/src/services/matcher/matcher.service.js
@@ -14,8 +14,11 @@ export const mentionMatchesUrl = (attention, url) => {
* @param {string} url
*/
export const extractTagFromUrl = (url) => {
- const regex = /tag[s]*\/(\w+)$/g
- const result = regex.exec(url)
+ const decoded = decodeURI(url)
+ // https://git.pleroma.social/pleroma/elixir-libraries/linkify/-/blob/master/lib/linkify/parser.ex
+ // https://www.pcre.org/original/doc/html/pcrepattern.html
+ const regex = /tag[s]*\/([\p{L}\p{N}_]*[\p{Alphabetic}_·\u{200c}][\p{L}\p{N}_·\p{M}\u{200c}]*)$/ug
+ const result = regex.exec(decoded)
if (!result) {
return false
}
diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js
index 1eb10bb6..aaef5a7a 100644
--- a/src/services/status_poster/status_poster.service.js
+++ b/src/services/status_poster/status_poster.service.js
@@ -10,6 +10,7 @@ const postStatus = ({
poll,
media = [],
inReplyToStatusId = undefined,
+ quoteId = undefined,
contentType = 'text/plain',
preview = false,
idempotencyKey = ''
@@ -24,6 +25,7 @@ const postStatus = ({
sensitive,
mediaIds,
inReplyToStatusId,
+ quoteId,
contentType,
poll,
preview,
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index d6e973a1..43fe3c73 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -21,8 +21,8 @@ export const applyTheme = (input) => {
body.classList.remove('hidden')
}
-const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth }) =>
- ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth })
+const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale }) =>
+ ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale })
const defaultConfigColumns = configColumns(defaultState)