Merge branch 'akkoma' into 'develop'

add Akkoma compatibility

See merge request pleroma/pleroma-fe!2176
This commit is contained in:
HJ 2025-06-24 14:20:30 +00:00
commit 168430fff2
23 changed files with 159 additions and 38 deletions

View file

@ -0,0 +1 @@
Added support for Akkoma and Sharkey.NET backend (tested on Sharkey)

View file

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
<link rel="preload" href="/static/config.json" as="fetch" crossorigin /> <link rel="preload" href="/static/config.json" as="fetch" crossorigin />
<link rel="preload" href="/api/pleroma/frontend_configurations" as="fetch" crossorigin /> <link rel="preload" href="/api/pleroma/frontend_configurations" as="fetch" crossorigin />
<link rel="preload" href="/nodeinfo/2.0.json" as="fetch" crossorigin />
<link rel="preload" href="/nodeinfo/2.1.json" as="fetch" crossorigin /> <link rel="preload" href="/nodeinfo/2.1.json" as="fetch" crossorigin />
<link rel="preload" href="/api/v1/instance" as="fetch" crossorigin /> <link rel="preload" href="/api/v1/instance" as="fetch" crossorigin />
<link rel="preload" href="/static/pleromatan_apology_fox_small.webp" as="image" /> <link rel="preload" href="/static/pleromatan_apology_fox_small.webp" as="image" />

View file

@ -63,10 +63,11 @@ const getInstanceConfig = async ({ store }) => {
const textlimit = data.max_toot_chars const textlimit = data.max_toot_chars
const vapidPublicKey = data.pleroma.vapid_public_key const vapidPublicKey = data.pleroma.vapid_public_key
store.dispatch('setInstanceOption', { name: 'pleromaExtensionsAvailable', value: data.pleroma })
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit }) store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required }) store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required }) store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma?.metadata.birthday_required })
store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 }) store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma?.metadata.birthday_min_age || 0 })
if (vapidPublicKey) { if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
@ -78,6 +79,8 @@ const getInstanceConfig = async ({ store }) => {
console.error('Could not load instance config, potentially fatal') console.error('Could not load instance config, potentially fatal')
console.error(error) console.error(error)
} }
// We should check for scrobbles support here but it requires userId
// so instead we check for it where it's fetched (statuses.js)
} }
const getBackendProvidedConfig = async () => { const getBackendProvidedConfig = async () => {
@ -242,7 +245,8 @@ const resolveStaffAccounts = ({ store, accounts }) => {
const getNodeInfo = async ({ store }) => { const getNodeInfo = async ({ store }) => {
try { try {
const res = await preloadFetch('/nodeinfo/2.1.json') let res = await preloadFetch('/nodeinfo/2.1.json')
if (!res.ok) res = await preloadFetch('/nodeinfo/2.0.json')
if (res.ok) { if (res.ok) {
const data = await res.json() const data = await res.json()
const metadata = data.metadata const metadata = data.metadata
@ -262,6 +266,7 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') }) store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') }) store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
store.dispatch('setInstanceOption', { name: 'localBubbleInstances', value: metadata.localBubbleInstances ?? [] })
const uploadLimits = metadata.uploadLimits const uploadLimits = metadata.uploadLimits
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) }) store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
@ -280,7 +285,6 @@ const getNodeInfo = async ({ store }) => {
const software = data.software const software = data.software
store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version }) store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository }) store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository })
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
const priv = metadata.private const priv = metadata.private
store.dispatch('setInstanceOption', { name: 'private', value: priv }) store.dispatch('setInstanceOption', { name: 'private', value: priv })

View file

@ -1,4 +1,5 @@
import PublicTimeline from 'components/public_timeline/public_timeline.vue' import PublicTimeline from 'components/public_timeline/public_timeline.vue'
import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue'
import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue' import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue'
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue' import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
import TagTimeline from 'components/tag_timeline/tag_timeline.vue' import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
@ -54,6 +55,7 @@ export default (store) => {
{ name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute }, { name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline }, { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline }, { name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'bubble', path: '/bubble', component: BubbleTimeline },
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
{ name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline }, { name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
{ {

View file

@ -0,0 +1,18 @@
import Timeline from '../timeline/timeline.vue'
const BubbleTimeline = {
components: {
Timeline
},
computed: {
timeline () { return this.$store.state.statuses.timelines.bubble }
},
created () {
this.$store.dispatch('startFetchingTimeline', { timeline: 'bubble' })
},
unmounted () {
this.$store.dispatch('stopFetchingTimeline', 'bubble')
}
}
export default BubbleTimeline

View file

@ -0,0 +1,9 @@
<template>
<Timeline
:title="$t('nav.bubble')"
:timeline="timeline"
:timeline-name="'bubble'"
/>
</template>
<script src="./bubble_timeline.js"></script>

View file

@ -15,6 +15,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faUsers, faUsers,
faGlobe, faGlobe,
faCity,
faBookmark, faBookmark,
faEnvelope, faEnvelope,
faChevronDown, faChevronDown,
@ -31,6 +32,7 @@ import {
library.add( library.add(
faUsers, faUsers,
faGlobe, faGlobe,
faCity,
faBookmark, faBookmark,
faEnvelope, faEnvelope,
faChevronDown, faChevronDown,
@ -108,12 +110,15 @@ const NavPanel = {
privateMode: state => state.instance.private, privateMode: state => state.instance.private,
federating: state => state.instance.federating, federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable,
bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
}), }),
timelinesItems () { timelinesItems () {
return filterNavigation( return filterNavigation(
Object Object
.entries({ ...TIMELINES }) .entries({ ...TIMELINES })
// do not show in timeliens list since it's in a better place now
.filter(([key]) => key !== 'bookmarks')
.map(([k, v]) => ({ ...v, name: k })), .map(([k, v]) => ({ ...v, name: k })),
{ {
hasChats: this.pleromaChatMessagesAvailable, hasChats: this.pleromaChatMessagesAvailable,
@ -121,6 +126,7 @@ const NavPanel = {
isFederating: this.federating, isFederating: this.federating,
isPrivate: this.privateMode, isPrivate: this.privateMode,
currentUser: this.currentUser, currentUser: this.currentUser,
supportsBubbleTimeline: this.bubbleTimeline,
supportsBookmarkFolders: this.bookmarkFolders supportsBookmarkFolders: this.bookmarkFolders
} }
) )
@ -136,6 +142,7 @@ const NavPanel = {
isFederating: this.federating, isFederating: this.federating,
isPrivate: this.privateMode, isPrivate: this.privateMode,
currentUser: this.currentUser, currentUser: this.currentUser,
supportsBubbleTimeline: this.bubbleTimeline,
supportsBookmarkFolders: this.bookmarkFolders supportsBookmarkFolders: this.bookmarkFolders
} }
) )

View file

@ -1,4 +1,12 @@
export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFederating, isPrivate, currentUser, supportsBookmarkFolders }) => { export const filterNavigation = (list = [], {
hasChats,
hasAnnouncements,
isFederating,
isPrivate,
currentUser,
supportsBookmarkFolders,
supportsBubbleTimeline
}) => {
return list.filter(({ criteria, anon, anonRoute }) => { return list.filter(({ criteria, anon, anonRoute }) => {
const set = new Set(criteria || []) const set = new Set(criteria || [])
if (!isFederating && set.has('federating')) return false if (!isFederating && set.has('federating')) return false
@ -7,6 +15,8 @@ export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFede
if ((!currentUser || !currentUser.locked) && set.has('lockedUser')) return false if ((!currentUser || !currentUser.locked) && set.has('lockedUser')) return false
if (!hasChats && set.has('chats')) return false if (!hasChats && set.has('chats')) return false
if (!hasAnnouncements && set.has('announcements')) return false if (!hasAnnouncements && set.has('announcements')) return false
if (!supportsBubbleTimeline && set.has('supportsBubbleTimeline')) return false
if (!supportsBookmarkFolders && set.has('supportsBookmarkFolders')) return false
if (supportsBookmarkFolders && set.has('!supportsBookmarkFolders')) return false if (supportsBookmarkFolders && set.has('!supportsBookmarkFolders')) return false
return true return true
}) })
@ -19,11 +29,11 @@ export const getListEntries = store => store.allLists.map(list => ({
iconLetter: list.title[0] iconLetter: list.title[0]
})) }))
export const getBookmarkFolderEntries = store => store.allFolders.map(folder => ({ export const getBookmarkFolderEntries = store => store.allFolders ? store.allFolders.map(folder => ({
name: 'bookmark-folder-' + folder.id, name: 'bookmark-folder-' + folder.id,
routeObject: { name: 'bookmark-folder', params: { id: folder.id } }, routeObject: { name: 'bookmark-folder', params: { id: folder.id } },
labelRaw: folder.name, labelRaw: folder.name,
iconEmoji: folder.emoji, iconEmoji: folder.emoji,
iconEmojiUrl: folder.emoji_url, iconEmojiUrl: folder.emoji_url,
iconLetter: folder.name[0] iconLetter: folder.name[0]
})) })) : []

View file

@ -27,6 +27,13 @@ export const TIMELINES = {
label: 'nav.public_tl', label: 'nav.public_tl',
criteria: ['!private'] criteria: ['!private']
}, },
bubble: {
route: 'bubble',
anon: true,
icon: 'city',
label: 'nav.bubble',
criteria: ['!private', 'federating', 'supportsBubbleTimeline']
},
twkn: { twkn: {
route: 'public-external-timeline', route: 'public-external-timeline',
anon: true, anon: true,
@ -34,11 +41,11 @@ export const TIMELINES = {
label: 'nav.twkn', label: 'nav.twkn',
criteria: ['!private', 'federating'] criteria: ['!private', 'federating']
}, },
// bookmarks are still technically a timeline so we should show it in the dropdown
bookmarks: { bookmarks: {
route: 'bookmarks', route: 'bookmarks',
icon: 'bookmark', icon: 'bookmark',
label: 'nav.bookmarks', label: 'nav.bookmarks',
criteria: ['!supportsBookmarkFolders']
}, },
favorites: { favorites: {
routeObject: { name: 'user-profile', query: { tab: 'favorites' } }, routeObject: { name: 'user-profile', query: { tab: 'favorites' } },
@ -53,6 +60,15 @@ export const TIMELINES = {
} }
export const ROOT_ITEMS = { export const ROOT_ITEMS = {
bookmarks: {
route: 'bookmarks',
icon: 'bookmark',
label: 'nav.bookmarks',
// shows bookmarks entry in a better suited location
// hides it when bookmark folders are supported since
// we show custom component instead of it
criteria: ['!supportsBookmarkFolders']
},
interactions: { interactions: {
route: 'interactions', route: 'interactions',
icon: 'bell', icon: 'bell',

View file

@ -9,6 +9,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faUsers, faUsers,
faGlobe, faGlobe,
faCity,
faBookmark, faBookmark,
faEnvelope, faEnvelope,
faComments, faComments,
@ -25,6 +26,7 @@ import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
library.add( library.add(
faUsers, faUsers,
faGlobe, faGlobe,
faCity,
faBookmark, faBookmark,
faEnvelope, faEnvelope,
faComments, faComments,
@ -65,7 +67,8 @@ const NavPanel = {
followRequestCount: state => state.api.followRequests.length, followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private, privateMode: state => state.instance.private,
federating: state => state.instance.federating, federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
}), }),
pinnedList () { pinnedList () {
if (!this.currentUser) { if (!this.currentUser) {
@ -79,7 +82,9 @@ const NavPanel = {
hasAnnouncements: this.supportsAnnouncements, hasAnnouncements: this.supportsAnnouncements,
isFederating: this.federating, isFederating: this.federating,
isPrivate: this.privateMode, isPrivate: this.privateMode,
currentUser: this.currentUser currentUser: this.currentUser,
supportsBubbleTimeline: this.bubbleTimeline,
supportsBookmarkFolders: this.bookmarks
}) })
} }
return filterNavigation( return filterNavigation(
@ -98,6 +103,8 @@ const NavPanel = {
{ {
hasChats: this.pleromaChatMessagesAvailable, hasChats: this.pleromaChatMessagesAvailable,
hasAnnouncements: this.supportsAnnouncements, hasAnnouncements: this.supportsAnnouncements,
supportsBubbleTimeline: this.bubbleTimeline,
supportsBookmarkFolders: this.bookmarks,
isFederating: this.federating, isFederating: this.federating,
isPrivate: this.privateMode, isPrivate: this.privateMode,
currentUser: this.currentUser currentUser: this.currentUser

View file

@ -41,8 +41,8 @@ const SecurityTab = {
user () { user () {
return this.$store.state.users.currentUser return this.$store.state.users.currentUser
}, },
pleromaBackend () { pleromaExtensionsAvailable () {
return this.$store.state.instance.pleromaBackend return this.$store.state.instance.pleromaExtensionsAvailable
}, },
oauthTokens () { oauthTokens () {
return useOAuthTokensStore().tokens.map(oauthToken => { return useOAuthTokensStore().tokens.map(oauthToken => {

View file

@ -24,6 +24,7 @@ import {
faLock, faLock,
faLockOpen, faLockOpen,
faGlobe, faGlobe,
faIgloo,
faTimes, faTimes,
faRetweet, faRetweet,
faReply, faReply,
@ -43,6 +44,7 @@ import {
library.add( library.add(
faEnvelope, faEnvelope,
faGlobe, faGlobe,
faIgloo,
faLock, faLock,
faLockOpen, faLockOpen,
faTimes, faTimes,
@ -484,6 +486,8 @@ const Status = {
return 'lock-open' return 'lock-open'
case 'direct': case 'direct':
return 'envelope' return 'envelope'
case 'local':
return 'igloo'
default: default:
return 'globe' return 'globe'
} }

View file

@ -24,7 +24,8 @@ export const timelineNames = (supportsBookmarkFolders) => {
dms: 'nav.dms', dms: 'nav.dms',
'public-timeline': 'nav.public_tl', 'public-timeline': 'nav.public_tl',
'public-external-timeline': 'nav.twkn', 'public-external-timeline': 'nav.twkn',
quotes: 'nav.quotes' quotes: 'nav.quotes',
bubble: 'nav.bubble'
} }
} }
@ -58,7 +59,8 @@ const TimelineMenu = {
currentUser: state => state.users.currentUser, currentUser: state => state.users.currentUser,
privateMode: state => state.instance.private, privateMode: state => state.instance.private,
federating: state => state.instance.federating, federating: state => state.instance.federating,
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable,
bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
}), }),
timelinesList () { timelinesList () {
return filterNavigation( return filterNavigation(
@ -68,7 +70,8 @@ const TimelineMenu = {
isFederating: this.federating, isFederating: this.federating,
isPrivate: this.privateMode, isPrivate: this.privateMode,
currentUser: this.currentUser, currentUser: this.currentUser,
supportsBookmarkFolders: this.bookmarkFolders supportsBookmarkFolders: this.bookmarkFolders,
supportsBubbleTimeline: this.bubbleTimeline
} }
) )
} }

View file

@ -134,7 +134,10 @@ export default {
}, },
showModerationMenu () { showModerationMenu () {
const privileges = this.loggedIn.privileges 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') return this.loggedIn.role === 'admin' ||
privileges.includes('users_manage_activation_state') ||
privileges.includes('users_delete') ||
privileges.includes('users_manage_tags')
}, },
hasNote () { hasNote () {
return this.relationship.note return this.relationship.note

View file

@ -81,7 +81,7 @@ const UserProfile = {
return this.isUs || !this.user.hide_followers return this.isUs || !this.user.hide_followers
}, },
favoritesTabVisible () { favoritesTabVisible () {
return this.isUs || !this.user.hide_favorites return this.isUs || (this.$store.state.instance.pleromaPublicFavouritesAvailable && !this.user.hide_favorites)
}, },
formattedBirthday () { formattedBirthday () {
const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale) const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)

View file

@ -117,6 +117,7 @@
"flash_security": "Note that this can be potentially dangerous since Flash content is still arbitrary code.", "flash_security": "Note that this can be potentially dangerous since Flash content is still arbitrary code.",
"flash_fail": "Failed to load flash content, see console for details.", "flash_fail": "Failed to load flash content, see console for details.",
"scope_in_timeline": { "scope_in_timeline": {
"local": "Non-federated",
"direct": "Direct", "direct": "Direct",
"private": "Followers-only", "private": "Followers-only",
"public": "Public", "public": "Public",
@ -171,6 +172,7 @@
"interactions": "Interactions", "interactions": "Interactions",
"dms": "Direct messages", "dms": "Direct messages",
"public_tl": "Public timeline", "public_tl": "Public timeline",
"bubble": "Bubble timeline",
"timeline": "Timeline", "timeline": "Timeline",
"home_timeline": "Home timeline", "home_timeline": "Home timeline",
"twkn": "Known Network", "twkn": "Known Network",
@ -289,7 +291,8 @@
"text/plain": "Plain text", "text/plain": "Plain text",
"text/html": "HTML", "text/html": "HTML",
"text/markdown": "Markdown", "text/markdown": "Markdown",
"text/bbcode": "BBCode" "text/bbcode": "BBCode",
"text/x.misskeymarkdown": "MFM"
}, },
"content_type_selection": "Post format", "content_type_selection": "Post format",
"content_warning": "Subject (optional)", "content_warning": "Subject (optional)",

View file

@ -211,6 +211,7 @@ const api = {
statusId = false, statusId = false,
bookmarkFolderId = false bookmarkFolderId = false
}) { }) {
if (timeline === 'favourites' && !store.rootState.instance.pleromaPublicFavouritesAvailable) return
if (store.state.fetchers[timeline]) return if (store.state.fetchers[timeline]) return
const fetcher = store.state.backendInteractor.startFetchingTimeline({ const fetcher = store.state.backendInteractor.startFetchingTimeline({
@ -281,6 +282,7 @@ const api = {
// Bookmark folders // Bookmark folders
startFetchingBookmarkFolders (store) { startFetchingBookmarkFolders (store) {
if (store.state.fetchers.bookmarkFolders) return if (store.state.fetchers.bookmarkFolders) return
if (!store.rootState.instance.pleromaBookmarkFoldersAvailable) return
const fetcher = store.state.backendInteractor.startFetchingBookmarkFolders({ store }) const fetcher = store.state.backendInteractor.startFetchingBookmarkFolders({ store })
store.commit('addFetcher', { fetcherName: 'bookmarkFolders', fetcher }) store.commit('addFetcher', { fetcherName: 'bookmarkFolders', fetcher })
}, },

View file

@ -143,7 +143,7 @@ const defaultState = {
emoji: {}, emoji: {},
emojiFetched: false, emojiFetched: false,
unicodeEmojiAnnotations: {}, unicodeEmojiAnnotations: {},
pleromaBackend: true, pleromaExtensionsAvailable: true,
postFormats: [], postFormats: [],
restrictedNicknames: [], restrictedNicknames: [],
safeDM: true, safeDM: true,
@ -156,12 +156,14 @@ const defaultState = {
pleromaChatMessagesAvailable: false, pleromaChatMessagesAvailable: false,
pleromaCustomEmojiReactionsAvailable: false, pleromaCustomEmojiReactionsAvailable: false,
pleromaBookmarkFoldersAvailable: false, pleromaBookmarkFoldersAvailable: false,
pleromaPublicFavouritesAvailable: true,
gopherAvailable: false, gopherAvailable: false,
mediaProxyAvailable: false, mediaProxyAvailable: false,
suggestionsEnabled: false, suggestionsEnabled: false,
suggestionsWeb: '', suggestionsWeb: '',
quotingAvailable: false, quotingAvailable: false,
groupActorAvailable: false, groupActorAvailable: false,
localBubbleInstances: [], // Akkoma
// Html stuff // Html stuff
instanceSpecificPanelContent: '', instanceSpecificPanelContent: '',
@ -340,7 +342,10 @@ const instance = {
async getCustomEmoji ({ commit, state }) { async getCustomEmoji ({ commit, state }) {
try { try {
const res = await window.fetch('/api/pleroma/emoji.json') let res = await window.fetch('/api/v1/pleroma/emoji')
if (!res.ok) {
res = await window.fetch('/api/pleroma/emoji.json')
}
if (res.ok) { if (res.ok) {
const result = await res.json() const result = await res.json()
const values = Array.isArray(result) ? Object.assign({}, ...result) : result const values = Array.isArray(result) ? Object.assign({}, ...result) : result

View file

@ -39,6 +39,7 @@ export const defaultState = () => ({
conversationsObject: {}, conversationsObject: {},
maxId: 0, maxId: 0,
favorites: new Set(), favorites: new Set(),
pleromaScrobblesAvailable: true, // not reported in nodeinfo
timelines: { timelines: {
mentions: emptyTl(), mentions: emptyTl(),
public: emptyTl(), public: emptyTl(),
@ -50,7 +51,8 @@ export const defaultState = () => ({
tag: emptyTl(), tag: emptyTl(),
dms: emptyTl(), dms: emptyTl(),
bookmarks: emptyTl(), bookmarks: emptyTl(),
list: emptyTl() list: emptyTl(),
bubble: emptyTl()
} }
}) })
@ -108,12 +110,21 @@ const sortTimeline = (timeline) => {
} }
const getLatestScrobble = (state, user) => { const getLatestScrobble = (state, user) => {
const scrobbles = state.pleromaScrobblesAvailable
if (!scrobbles) return
if (state.scrobblesNextFetch[user.id] && state.scrobblesNextFetch[user.id] > Date.now()) { if (state.scrobblesNextFetch[user.id] && state.scrobblesNextFetch[user.id] > Date.now()) {
return return
} }
state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000 state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000
if (!scrobbles) return
apiService.fetchScrobbles({ accountId: user.id }).then((scrobbles) => { apiService.fetchScrobbles({ accountId: user.id }).then((scrobbles) => {
if (scrobbles?.error?.status === 501) {
state.pleromaScrobblesAvailable = false
return
}
if (scrobbles.length > 0) { if (scrobbles.length > 0) {
user.latestScrobble = scrobbles[0] user.latestScrobble = scrobbles[0]

View file

@ -599,6 +599,7 @@ const users = {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const commit = store.commit const commit = store.commit
const dispatch = store.dispatch const dispatch = store.dispatch
const rootState = store.rootState
commit('beginLogin') commit('beginLogin')
store.rootState.api.backendInteractor.verifyCredentials(accessToken) store.rootState.api.backendInteractor.verifyCredentials(accessToken)
.then((data) => { .then((data) => {
@ -665,8 +666,10 @@ const users = {
// Start fetching notifications // Start fetching notifications
dispatch('startFetchingNotifications') dispatch('startFetchingNotifications')
// Start fetching chats if (rootState.instance.pleromaChatMessagesAvailable) {
dispatch('startFetchingChats') // Start fetching chats
dispatch('startFetchingChats')
}
} }
dispatch('startFetchingLists') dispatch('startFetchingLists')

View file

@ -15,7 +15,7 @@ const TAG_USER_URL = '/api/pleroma/admin/users/tag'
const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}` const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate' const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate'
const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate' const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate'
const ADMIN_USERS_URL = '/api/pleroma/admin/users' const ADMIN_USERS_URL = '/api/v1/pleroma/admin/users'
const SUGGESTIONS_URL = '/api/v1/suggestions' const SUGGESTIONS_URL = '/api/v1/suggestions'
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings' const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read' const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
@ -61,6 +61,7 @@ const MASTODON_LIST_TIMELINE_URL = id => `/api/v1/timelines/list/${id}`
const MASTODON_LIST_ACCOUNTS_URL = id => `/api/v1/lists/${id}/accounts` const MASTODON_LIST_ACCOUNTS_URL = id => `/api/v1/lists/${id}/accounts`
const MASTODON_TAG_TIMELINE_URL = tag => `/api/v1/timelines/tag/${tag}` const MASTODON_TAG_TIMELINE_URL = tag => `/api/v1/timelines/tag/${tag}`
const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks' const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks'
const AKKOMA_BUBBLE_TIMELINE_URL = '/api/v1/timelines/bubble'
const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/' const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/'
const MASTODON_USER_MUTES_URL = '/api/v1/mutes/' const MASTODON_USER_MUTES_URL = '/api/v1/mutes/'
const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block` const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block`
@ -99,7 +100,7 @@ const PLEROMA_CHAT_URL = id => `/api/v1/pleroma/chats/by-account-id/${id}`
const PLEROMA_CHAT_MESSAGES_URL = id => `/api/v1/pleroma/chats/${id}/messages` const PLEROMA_CHAT_MESSAGES_URL = id => `/api/v1/pleroma/chats/${id}/messages`
const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read` 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}` const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
const PLEROMA_ADMIN_REPORTS = '/api/pleroma/admin/reports' const PLEROMA_ADMIN_REPORTS = '/api/v1/pleroma/admin/reports'
const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups' const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements' const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements'
const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements' const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements'
@ -111,10 +112,10 @@ const PLEROMA_USER_FAVORITES_TIMELINE_URL = id => `/api/v1/pleroma/accounts/${id
const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders' const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders'
const PLEROMA_BOOKMARK_FOLDER_URL = id => `/api/v1/pleroma/bookmark_folders/${id}` const PLEROMA_BOOKMARK_FOLDER_URL = id => `/api/v1/pleroma/bookmark_folders/${id}`
const PLEROMA_ADMIN_CONFIG_URL = '/api/pleroma/admin/config' const PLEROMA_ADMIN_CONFIG_URL = '/api/v1/pleroma/admin/config'
const PLEROMA_ADMIN_DESCRIPTIONS_URL = '/api/pleroma/admin/config/descriptions' const PLEROMA_ADMIN_DESCRIPTIONS_URL = '/api/v1/pleroma/admin/config/descriptions'
const PLEROMA_ADMIN_FRONTENDS_URL = '/api/pleroma/admin/frontends' const PLEROMA_ADMIN_FRONTENDS_URL = '/api/v1/pleroma/admin/frontends'
const PLEROMA_ADMIN_FRONTENDS_INSTALL_URL = '/api/pleroma/admin/frontends/install' const PLEROMA_ADMIN_FRONTENDS_INSTALL_URL = '/api/v1/pleroma/admin/frontends/install'
const PLEROMA_EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji' const PLEROMA_EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji'
const PLEROMA_EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import' const PLEROMA_EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import'
@ -707,7 +708,8 @@ const fetchTimeline = ({
publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL, publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL,
tag: MASTODON_TAG_TIMELINE_URL, tag: MASTODON_TAG_TIMELINE_URL,
bookmarks: MASTODON_BOOKMARK_TIMELINE_URL, bookmarks: MASTODON_BOOKMARK_TIMELINE_URL,
quotes: PLEROMA_STATUS_QUOTES_URL quotes: PLEROMA_STATUS_QUOTES_URL,
bubble: AKKOMA_BUBBLE_TIMELINE_URL
} }
const isNotifications = timeline === 'notifications' const isNotifications = timeline === 'notifications'
const params = [] const params = []

View file

@ -91,6 +91,8 @@ export const parseUser = (data) => {
output.bot = data.bot output.bot = data.bot
output.privileges = []
if (data.pleroma) { if (data.pleroma) {
if (data.pleroma.settings_store) { if (data.pleroma.settings_store) {
output.storage = data.pleroma.settings_store['pleroma-fe'] output.storage = data.pleroma.settings_store['pleroma-fe']
@ -317,20 +319,18 @@ export const parseStatus = (data) => {
output.edited_at = data.edited_at output.edited_at = data.edited_at
const { pleroma } = data
if (data.pleroma) { if (data.pleroma) {
const { pleroma } = data
output.text = pleroma.content ? data.pleroma.content['text/plain'] : data.content output.text = pleroma.content ? data.pleroma.content['text/plain'] : data.content
output.summary = pleroma.spoiler_text ? data.pleroma.spoiler_text['text/plain'] : data.spoiler_text output.summary = pleroma.spoiler_text ? data.pleroma.spoiler_text['text/plain'] : data.spoiler_text
output.statusnet_conversation_id = data.pleroma.conversation_id output.statusnet_conversation_id = data.pleroma.conversation_id
output.is_local = pleroma.local output.is_local = pleroma.local
output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct output.in_reply_to_screen_name = pleroma.in_reply_to_account_acct
output.thread_muted = pleroma.thread_muted output.thread_muted = pleroma.thread_muted
output.emoji_reactions = pleroma.emoji_reactions output.emoji_reactions = pleroma.emoji_reactions
output.parent_visible = pleroma.parent_visible === undefined ? true : pleroma.parent_visible output.parent_visible = pleroma.parent_visible === undefined ? true : pleroma.parent_visible
output.quote = pleroma.quote ? parseStatus(pleroma.quote) : undefined output.quote_visible = pleroma.quote_visible || true
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
output.quotes_count = pleroma.quotes_count output.quotes_count = pleroma.quotes_count
output.bookmark_folder_id = pleroma.bookmark_folder output.bookmark_folder_id = pleroma.bookmark_folder
} else { } else {
@ -338,6 +338,12 @@ export const parseStatus = (data) => {
output.summary = data.spoiler_text output.summary = data.spoiler_text
} }
const quoteRaw = pleroma?.quote || data.quote
const quoteData = quoteRaw ? parseStatus(quoteRaw) : undefined
output.quote = quoteData
output.quote_id = data.quote?.id ?? data.quote_id ?? quoteData?.id ?? pleroma.quote_id
output.quote_url = data.quote?.url ?? quoteData?.url ?? pleroma.quote_url
output.in_reply_to_status_id = data.in_reply_to_id output.in_reply_to_status_id = data.in_reply_to_id
output.in_reply_to_user_id = data.in_reply_to_account_id output.in_reply_to_user_id = data.in_reply_to_account_id
output.replies_count = data.replies_count output.replies_count = data.replies_count

View file

@ -54,7 +54,7 @@ const fetchAndUpdate = ({
args.bookmarkFolderId = bookmarkFolderId args.bookmarkFolderId = bookmarkFolderId
args.tag = tag args.tag = tag
args.withMuted = !hideMutedPosts args.withMuted = !hideMutedPosts
if (loggedIn && ['friends', 'public', 'publicAndExternal'].includes(timeline)) { if (loggedIn && ['friends', 'public', 'publicAndExternal', 'bubble'].includes(timeline)) {
args.replyVisibility = replyVisibility args.replyVisibility = replyVisibility
} }
@ -63,6 +63,10 @@ const fetchAndUpdate = ({
return apiService.fetchTimeline(args) return apiService.fetchTimeline(args)
.then(response => { .then(response => {
if (response.errors) { if (response.errors) {
if (timeline === 'favorites') {
rootState.instance.pleromaPublicFavouritesAvailable = false
return
}
throw new Error(`${response.status} ${response.statusText}`) throw new Error(`${response.status} ${response.statusText}`)
} }