biome format --write

This commit is contained in:
Henry Jameson 2026-01-06 16:22:52 +02:00
commit 9262e803ec
415 changed files with 54076 additions and 17419 deletions

View file

@ -6,41 +6,48 @@ export const useAnnouncementsStore = defineStore('announcements', {
state: () => ({
announcements: [],
supportsAnnouncements: true,
fetchAnnouncementsTimer: undefined
fetchAnnouncementsTimer: undefined,
}),
getters: {
unreadAnnouncementCount () {
unreadAnnouncementCount() {
if (!window.vuex.state.users.currentUser) {
return 0
}
const unread = this.announcements.filter(announcement => !(announcement.inactive || announcement.read))
const unread = this.announcements.filter(
(announcement) => !(announcement.inactive || announcement.read),
)
return unread.length
}
},
},
actions: {
fetchAnnouncements () {
fetchAnnouncements() {
if (!this.supportsAnnouncements) {
return Promise.resolve()
}
const currentUser = window.vuex.state.users.currentUser
const isAdmin = currentUser && currentUser.privileges.includes('announcements_manage_announcements')
const isAdmin =
currentUser &&
currentUser.privileges.includes('announcements_manage_announcements')
const getAnnouncements = async () => {
if (!isAdmin) {
return window.vuex.state.api.backendInteractor.fetchAnnouncements()
}
const all = await window.vuex.state.api.backendInteractor.adminFetchAnnouncements()
const visible = await window.vuex.state.api.backendInteractor.fetchAnnouncements()
const all =
await window.vuex.state.api.backendInteractor.adminFetchAnnouncements()
const visible =
await window.vuex.state.api.backendInteractor.fetchAnnouncements()
const visibleObject = visible.reduce((a, c) => {
a[c.id] = c
return a
}, {})
const getWithinVisible = announcement => visibleObject[announcement.id]
const getWithinVisible = (announcement) =>
visibleObject[announcement.id]
all.forEach(announcement => {
all.forEach((announcement) => {
const visibleAnnouncement = getWithinVisible(announcement)
if (!visibleAnnouncement) {
announcement.inactive = true
@ -53,10 +60,10 @@ export const useAnnouncementsStore = defineStore('announcements', {
}
return getAnnouncements()
.then(announcements => {
.then((announcements) => {
this.announcements = announcements
})
.catch(error => {
.catch((error) => {
// If and only if backend does not support announcements, it would return 404.
// In this case, silently ignores it.
if (error && error.statusCode === 404) {
@ -66,10 +73,11 @@ export const useAnnouncementsStore = defineStore('announcements', {
}
})
},
markAnnouncementAsRead (id) {
return window.vuex.state.api.backendInteractor.dismissAnnouncement({ id })
markAnnouncementAsRead(id) {
return window.vuex.state.api.backendInteractor
.dismissAnnouncement({ id })
.then(() => {
const index = this.announcements.findIndex(a => a.id === id)
const index = this.announcements.findIndex((a) => a.id === id)
if (index < 0) {
return
@ -78,38 +86,44 @@ export const useAnnouncementsStore = defineStore('announcements', {
this.announcements[index].read = true
})
},
startFetchingAnnouncements () {
startFetchingAnnouncements() {
if (this.fetchAnnouncementsTimer) {
return
}
const interval = setInterval(() => this.fetchAnnouncements(), FETCH_ANNOUNCEMENT_INTERVAL_MS)
const interval = setInterval(
() => this.fetchAnnouncements(),
FETCH_ANNOUNCEMENT_INTERVAL_MS,
)
this.fetchAnnouncementsTimer = interval
return this.fetchAnnouncements()
},
stopFetchingAnnouncements () {
stopFetchingAnnouncements() {
const interval = this.fetchAnnouncementsTimer
this.fetchAnnouncementsTimer = undefined
clearInterval(interval)
},
postAnnouncement ({ content, startsAt, endsAt, allDay }) {
return window.vuex.state.api.backendInteractor.postAnnouncement({ content, startsAt, endsAt, allDay })
postAnnouncement({ content, startsAt, endsAt, allDay }) {
return window.vuex.state.api.backendInteractor
.postAnnouncement({ content, startsAt, endsAt, allDay })
.then(() => {
return this.fetchAnnouncements()
})
},
editAnnouncement ({ id, content, startsAt, endsAt, allDay }) {
return window.vuex.state.api.backendInteractor.editAnnouncement({ id, content, startsAt, endsAt, allDay })
editAnnouncement({ id, content, startsAt, endsAt, allDay }) {
return window.vuex.state.api.backendInteractor
.editAnnouncement({ id, content, startsAt, endsAt, allDay })
.then(() => {
return this.fetchAnnouncements()
})
},
deleteAnnouncement (id) {
return window.vuex.state.api.backendInteractor.deleteAnnouncement({ id })
deleteAnnouncement(id) {
return window.vuex.state.api.backendInteractor
.deleteAnnouncement({ id })
.then(() => {
return this.fetchAnnouncements()
})
}
}
},
},
})

View file

@ -13,7 +13,7 @@ export const useAuthFlowStore = defineStore('authFlow', {
state: () => ({
settings: {},
strategy: PASSWORD_STRATEGY,
initStrategy: PASSWORD_STRATEGY // default strategy from config
initStrategy: PASSWORD_STRATEGY, // default strategy from config
}),
// getters
getters: {
@ -31,39 +31,39 @@ export const useAuthFlowStore = defineStore('authFlow', {
},
},
actions: {
setInitialStrategy (strategy) {
setInitialStrategy(strategy) {
if (strategy) {
this.initStrategy = strategy
this.strategy = strategy
}
},
requirePassword () {
requirePassword() {
this.strategy = PASSWORD_STRATEGY
},
requireToken () {
requireToken() {
this.strategy = TOKEN_STRATEGY
},
requireMFA ({ settings }) {
requireMFA({ settings }) {
this.settings = settings
this.strategy = TOTP_STRATEGY // default strategy of MFA
},
requireRecovery () {
requireRecovery() {
this.strategy = RECOVERY_STRATEGY
},
requireTOTP () {
requireTOTP() {
this.strategy = TOTP_STRATEGY
},
abortMFA () {
abortMFA() {
this.resetState()
},
resetState () {
resetState() {
this.strategy = this.initStrategy
this.settings = {}
},
async login ({ access_token: accessToken }) {
async login({ access_token: accessToken }) {
useOAuthStore().setToken(accessToken)
await window.vuex.dispatch('loginUser', accessToken, { root: true })
this.resetState()
}
}
})
},
},
})

View file

@ -3,23 +3,23 @@ import { defineStore } from 'pinia'
export const useBookmarkFoldersStore = defineStore('bookmarkFolders', {
state: () => ({
allFolders: []
allFolders: [],
}),
getters: {
findBookmarkFolderName () {
findBookmarkFolderName() {
return (id) => {
const folder = this.allFolders.find(folder => folder.id === id)
const folder = this.allFolders.find((folder) => folder.id === id)
if (!folder) return
return folder.name
}
}
},
},
actions: {
setBookmarkFolders (value) {
setBookmarkFolders(value) {
this.allFolders = value
},
setBookmarkFolder ({ id, name, emoji, emoji_url: emojiUrl }) {
setBookmarkFolder({ id, name, emoji, emoji_url: emojiUrl }) {
const entry = find(this.allFolders, { id })
if (!entry) {
this.allFolders.push({ id, name, emoji, emoji_url: emojiUrl })
@ -29,23 +29,25 @@ export const useBookmarkFoldersStore = defineStore('bookmarkFolders', {
entry.emoji_url = emojiUrl
}
},
createBookmarkFolder ({ name, emoji }) {
return window.vuex.state.api.backendInteractor.createBookmarkFolder({ name, emoji })
createBookmarkFolder({ name, emoji }) {
return window.vuex.state.api.backendInteractor
.createBookmarkFolder({ name, emoji })
.then((folder) => {
this.setBookmarkFolder(folder)
return folder
})
},
updateBookmarkFolder ({ folderId, name, emoji }) {
return window.vuex.state.api.backendInteractor.updateBookmarkFolder({ folderId, name, emoji })
updateBookmarkFolder({ folderId, name, emoji }) {
return window.vuex.state.api.backendInteractor
.updateBookmarkFolder({ folderId, name, emoji })
.then((folder) => {
this.setBookmarkFolder(folder)
return folder
})
},
deleteBookmarkFolder ({ folderId }) {
deleteBookmarkFolder({ folderId }) {
window.vuex.state.api.backendInteractor.deleteBookmarkFolder({ folderId })
remove(this.allFolders, folder => folder.id === folderId)
}
}
remove(this.allFolders, (folder) => folder.id === folderId)
},
},
})

View file

@ -3,15 +3,15 @@ import { defineStore } from 'pinia'
export const useEditStatusStore = defineStore('editStatus', {
state: () => ({
params: null,
modalActivated: false
modalActivated: false,
}),
actions: {
openEditStatusModal (params) {
openEditStatusModal(params) {
this.params = params
this.modalActivated = true
},
closeEditStatusModal () {
closeEditStatusModal() {
this.modalActivated = false
}
}
},
},
})

View file

@ -2,13 +2,13 @@ import { defineStore } from 'pinia'
export const useI18nStore = defineStore('i18n', {
state: () => ({
i18n: null
i18n: null,
}),
actions: {
setI18n (newI18n) {
setI18n(newI18n) {
this.$patch({
i18n: newI18n.global
i18n: newI18n.global,
})
}
}
},
},
})

View file

@ -1,7 +1,14 @@
import { defineStore } from 'pinia'
import { CURRENT_VERSION, generatePreset } from 'src/services/theme_data/theme_data.service.js'
import { getResourcesIndex, applyTheme, tryLoadCache } from '../services/style_setter/style_setter.js'
import {
CURRENT_VERSION,
generatePreset,
} from 'src/services/theme_data/theme_data.service.js'
import {
getResourcesIndex,
applyTheme,
tryLoadCache,
} from '../services/style_setter/style_setter.js'
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
import { deserialize } from '../services/theme_data/iss_deserializer.js'
@ -19,9 +26,9 @@ export const useInterfaceStore = defineStore('interface', {
themeNameUsed: null,
themeDataUsed: null,
temporaryChangesTimeoutId: null,
temporaryChangesCountdown: -1, // used for temporary options that revert after a timeout
temporaryChangesCountdown: -1, // used for temporary options that revert after a timeout
temporaryChangesConfirm: () => {}, // used for applying temporary options
temporaryChangesRevert: () => {}, // used for reverting temporary options
temporaryChangesRevert: () => {}, // used for reverting temporary options
settingsModalState: 'hidden',
settingsModalLoadedUser: false,
settingsModalLoadedAdmin: false,
@ -30,22 +37,23 @@ export const useInterfaceStore = defineStore('interface', {
settings: {
currentSaveStateNotice: null,
noticeClearTimeout: null,
notificationPermission: null
notificationPermission: null,
},
browserSupport: {
cssFilter: window.CSS && window.CSS.supports && (
window.CSS.supports('filter', 'drop-shadow(0 0)') ||
window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
),
localFonts: typeof window.queryLocalFonts === 'function'
cssFilter:
window.CSS &&
window.CSS.supports &&
(window.CSS.supports('filter', 'drop-shadow(0 0)') ||
window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')),
localFonts: typeof window.queryLocalFonts === 'function',
},
layoutType: 'normal',
globalNotices: [],
layoutHeight: 0,
lastTimeline: null
lastTimeline: null,
}),
actions: {
setTemporaryChanges ({ confirm, revert }) {
setTemporaryChanges({ confirm, revert }) {
this.temporaryChangesCountdown = 10
this.temporaryChangesConfirm = confirm
this.temporaryChangesRevert = revert
@ -60,38 +68,42 @@ export const useInterfaceStore = defineStore('interface', {
}
this.temporaryChangesTimeoutId = setTimeout(countdownFunc, 1000)
},
clearTemporaryChanges () {
this.temporaryChangesTimeoutId ?? clearTimeout(this.temporaryChangesTimeoutId)
clearTemporaryChanges() {
this.temporaryChangesTimeoutId ??
clearTimeout(this.temporaryChangesTimeoutId)
this.temporaryChangesTimeoutId = null
this.temporaryChangesCountdown = -1
this.temporaryChangesConfirm = () => {}
this.temporaryChangesRevert = () => {}
},
setPageTitle (option = '') {
setPageTitle(option = '') {
try {
document.title = `${option} ${window.vuex.state.instance.name}`
} catch (error) {
console.error(`${error}`)
}
},
settingsSaved ({ success, error }) {
settingsSaved({ success, error }) {
if (success) {
if (this.noticeClearTimeout) {
clearTimeout(this.noticeClearTimeout)
}
this.settings.currentSaveStateNotice = { error: false, data: success }
this.settings.noticeClearTimeout = setTimeout(() => delete this.settings.currentSaveStateNotice, 2000)
this.settings.noticeClearTimeout = setTimeout(
() => delete this.settings.currentSaveStateNotice,
2000,
)
} else {
this.settings.currentSaveStateNotice = { error: true, errorData: error }
}
},
setNotificationPermission (permission) {
setNotificationPermission(permission) {
this.notificationPermission = permission
},
closeSettingsModal () {
closeSettingsModal() {
this.settingsModalState = 'hidden'
},
openSettingsModal (value) {
openSettingsModal(value) {
this.settingsModalMode = value
this.settingsModalState = 'visible'
if (value === 'user') {
@ -104,7 +116,7 @@ export const useInterfaceStore = defineStore('interface', {
}
}
},
togglePeekSettingsModal () {
togglePeekSettingsModal() {
switch (this.settingsModalState) {
case 'minimized':
this.settingsModalState = 'visible'
@ -116,27 +128,26 @@ export const useInterfaceStore = defineStore('interface', {
throw new Error('Illegal minimization state of settings modal')
}
},
clearSettingsModalTargetTab () {
clearSettingsModalTargetTab() {
this.settingsModalTargetTab = null
},
openSettingsModalTab (value, mode = 'user') {
openSettingsModalTab(value, mode = 'user') {
this.settingsModalTargetTab = value
this.openSettingsModal(mode)
},
removeGlobalNotice (notice) {
this.globalNotices = this.globalNotices.filter(n => n !== notice)
removeGlobalNotice(notice) {
this.globalNotices = this.globalNotices.filter((n) => n !== notice)
},
pushGlobalNotice (
{
messageKey,
messageArgs = {},
level = 'error',
timeout = 0
}) {
pushGlobalNotice({
messageKey,
messageArgs = {},
level = 'error',
timeout = 0,
}) {
const notice = {
messageKey,
messageArgs,
level
level,
}
this.globalNotices.push(notice)
@ -150,10 +161,10 @@ export const useInterfaceStore = defineStore('interface', {
return newNotice
},
setLayoutHeight (value) {
setLayoutHeight(value) {
this.layoutHeight = value
},
setLayoutWidth (value) {
setLayoutWidth(value) {
let width = value
if (value !== undefined) {
this.layoutWidth = value
@ -171,10 +182,10 @@ export const useInterfaceStore = defineStore('interface', {
this.layoutType = wideLayout ? 'wide' : normalOrMobile
}
},
setFontsList (value) {
this.localFonts = [...(new Set(value.map(font => font.family))).values()]
setFontsList(value) {
this.localFonts = [...new Set(value.map((font) => font.family)).values()]
},
queryLocalFonts () {
queryLocalFonts() {
if (this.localFonts !== null) return
this.setFontsList([])
@ -190,27 +201,33 @@ export const useInterfaceStore = defineStore('interface', {
this.pushGlobalNotice({
messageKey: 'settings.style.themes3.font.font_list_unavailable',
messageArgs: {
error: e
error: e,
},
level: 'error'
level: 'error',
})
})
},
setLastTimeline (value) {
setLastTimeline(value) {
this.lastTimeline = value
},
async fetchPalettesIndex () {
async fetchPalettesIndex() {
try {
const value = await getResourcesIndex('/static/palettes/index.json')
window.vuex.commit('setInstanceOption', { name: 'palettesIndex', value })
window.vuex.commit('setInstanceOption', {
name: 'palettesIndex',
value,
})
return value
} catch (e) {
console.error('Could not fetch palettes index', e)
window.vuex.commit('setInstanceOption', { name: 'palettesIndex', value: { _error: e } })
window.vuex.commit('setInstanceOption', {
name: 'palettesIndex',
value: { _error: e },
})
return Promise.resolve({})
}
},
setPalette (value) {
setPalette(value) {
this.resetThemeV3Palette()
this.resetThemeV2()
@ -218,7 +235,7 @@ export const useInterfaceStore = defineStore('interface', {
this.applyTheme({ recompile: true })
},
setPaletteCustom (value) {
setPaletteCustom(value) {
this.resetThemeV3Palette()
this.resetThemeV2()
@ -226,21 +243,24 @@ export const useInterfaceStore = defineStore('interface', {
this.applyTheme({ recompile: true })
},
async fetchStylesIndex () {
async fetchStylesIndex() {
try {
const value = await getResourcesIndex(
'/static/styles/index.json',
deserialize
deserialize,
)
window.vuex.commit('setInstanceOption', { name: 'stylesIndex', value })
return value
} catch (e) {
console.error('Could not fetch styles index', e)
window.vuex.commit('setInstanceOption', { name: 'stylesIndex', value: { _error: e } })
window.vuex.commit('setInstanceOption', {
name: 'stylesIndex',
value: { _error: e },
})
return Promise.resolve({})
}
},
setStyle (value) {
setStyle(value) {
this.resetThemeV3()
this.resetThemeV2()
this.resetThemeV3Palette()
@ -252,7 +272,7 @@ export const useInterfaceStore = defineStore('interface', {
this.useStylePalette = false
})
},
setStyleCustom (value) {
setStyleCustom(value) {
this.resetThemeV3()
this.resetThemeV2()
this.resetThemeV3Palette()
@ -264,18 +284,21 @@ export const useInterfaceStore = defineStore('interface', {
this.useStylePalette = false
})
},
async fetchThemesIndex () {
async fetchThemesIndex() {
try {
const value = await getResourcesIndex('/static/styles.json')
window.vuex.commit('setInstanceOption', { name: 'themesIndex', value })
return value
} catch (e) {
console.error('Could not fetch themes index', e)
window.vuex.commit('setInstanceOption', { name: 'themesIndex', value: { _error: e } })
window.vuex.commit('setInstanceOption', {
name: 'themesIndex',
value: { _error: e },
})
return Promise.resolve({})
}
},
setTheme (value) {
setTheme(value) {
this.resetThemeV3()
this.resetThemeV3Palette()
this.resetThemeV2()
@ -284,7 +307,7 @@ export const useInterfaceStore = defineStore('interface', {
this.applyTheme({ recompile: true })
},
setThemeCustom (value) {
setThemeCustom(value) {
this.resetThemeV3()
this.resetThemeV3Palette()
this.resetThemeV2()
@ -294,22 +317,29 @@ export const useInterfaceStore = defineStore('interface', {
this.applyTheme({ recompile: true })
},
resetThemeV3 () {
resetThemeV3() {
window.vuex.commit('setOption', { name: 'style', value: null })
window.vuex.commit('setOption', { name: 'styleCustomData', value: null })
},
resetThemeV3Palette () {
resetThemeV3Palette() {
window.vuex.commit('setOption', { name: 'palette', value: null })
window.vuex.commit('setOption', { name: 'paletteCustomData', value: null })
window.vuex.commit('setOption', {
name: 'paletteCustomData',
value: null,
})
},
resetThemeV2 () {
resetThemeV2() {
window.vuex.commit('setOption', { name: 'theme', value: null })
window.vuex.commit('setOption', { name: 'customTheme', value: null })
window.vuex.commit('setOption', { name: 'customThemeSource', value: null })
window.vuex.commit('setOption', {
name: 'customThemeSource',
value: null,
})
},
async getThemeData () {
async getThemeData() {
const getData = async (resource, index, customData, name) => {
const capitalizedResource = resource[0].toUpperCase() + resource.slice(1)
const capitalizedResource =
resource[0].toUpperCase() + resource.slice(1)
const result = {}
if (customData) {
@ -331,9 +361,13 @@ export const useInterfaceStore = defineStore('interface', {
}
const newName = Object.keys(index)[0]
fetchFunc = index[newName]
console.warn(`${capitalizedResource} with id '${this.styleNameUsed}' not found, trying back to '${newName}'`)
console.warn(
`${capitalizedResource} with id '${this.styleNameUsed}' not found, trying back to '${newName}'`,
)
if (!fetchFunc) {
console.warn(`${capitalizedResource} doesn't have a fallback, defaulting to stock.`)
console.warn(
`${capitalizedResource} doesn't have a fallback, defaulting to stock.`,
)
fetchFunc = () => Promise.resolve(null)
}
}
@ -342,52 +376,52 @@ export const useInterfaceStore = defineStore('interface', {
return result
}
const {
style: instanceStyleName,
palette: instancePaletteName
} = window.vuex.state.instance
const { style: instanceStyleName, palette: instancePaletteName } =
window.vuex.state.instance
let {
theme: instanceThemeV2Name,
themesIndex,
stylesIndex,
palettesIndex
palettesIndex,
} = window.vuex.state.instance
const {
style: userStyleName,
styleCustomData: userStyleCustomData,
palette: userPaletteName,
paletteCustomData: userPaletteCustomData
paletteCustomData: userPaletteCustomData,
} = window.vuex.state.config
let {
theme: userThemeV2Name,
customTheme: userThemeV2Snapshot,
customThemeSource: userThemeV2Source
customThemeSource: userThemeV2Source,
} = window.vuex.state.config
let majorVersionUsed
console.debug(
`User V3 palette: ${userPaletteName}, style: ${userStyleName} , custom: ${!!userStyleCustomData}`
`User V3 palette: ${userPaletteName}, style: ${userStyleName} , custom: ${!!userStyleCustomData}`,
)
console.debug(
`User V2 name: ${userThemeV2Name}, source: ${!!userThemeV2Source}, snapshot: ${!!userThemeV2Snapshot}`
`User V2 name: ${userThemeV2Name}, source: ${!!userThemeV2Source}, snapshot: ${!!userThemeV2Snapshot}`,
)
console.debug(`Instance V3 palette: ${instancePaletteName}, style: ${instanceStyleName}`)
console.debug(
`Instance V3 palette: ${instancePaletteName}, style: ${instanceStyleName}`,
)
console.debug('Instance V2 theme: ' + instanceThemeV2Name)
if (userPaletteName || userPaletteCustomData ||
userStyleName || userStyleCustomData ||
(
// User V2 overrides instance V3
(instancePaletteName ||
instanceStyleName) &&
instanceThemeV2Name == null &&
userThemeV2Name == null
)
if (
userPaletteName ||
userPaletteCustomData ||
userStyleName ||
userStyleCustomData ||
// User V2 overrides instance V3
((instancePaletteName || instanceStyleName) &&
instanceThemeV2Name == null &&
userThemeV2Name == null)
) {
// Palette and/or style overrides V2 themes
instanceThemeV2Name = null
@ -397,10 +431,10 @@ export const useInterfaceStore = defineStore('interface', {
majorVersionUsed = 'v3'
} else if (
(userThemeV2Name ||
userThemeV2Snapshot ||
userThemeV2Source ||
instanceThemeV2Name)
userThemeV2Name ||
userThemeV2Snapshot ||
userThemeV2Source ||
instanceThemeV2Name
) {
majorVersionUsed = 'v2'
} else {
@ -411,16 +445,14 @@ export const useInterfaceStore = defineStore('interface', {
if (majorVersionUsed === 'v3') {
const result = await Promise.all([
this.fetchPalettesIndex(),
this.fetchStylesIndex()
this.fetchStylesIndex(),
])
palettesIndex = result[0]
stylesIndex = result[1]
} else {
// Promise.all just to be uniform with v3
const result = await Promise.all([
this.fetchThemesIndex()
])
const result = await Promise.all([this.fetchThemesIndex()])
themesIndex = result[0]
}
@ -437,25 +469,22 @@ export const useInterfaceStore = defineStore('interface', {
'style',
stylesIndex,
userStyleCustomData,
userStyleName || instanceStyleName
userStyleName || instanceStyleName,
)
this.styleNameUsed = style.nameUsed
this.styleDataUsed = style.dataUsed
let firstStylePaletteName = null
style
.dataUsed
?.filter(x => x.component === '@palette')
.map(x => {
style.dataUsed
?.filter((x) => x.component === '@palette')
.map((x) => {
const cleanDirectives = Object.fromEntries(
Object
.entries(x.directives)
.filter(([k]) => k)
Object.entries(x.directives).filter(([k]) => k),
)
return { name: x.variant, ...cleanDirectives }
})
.forEach(palette => {
.forEach((palette) => {
const key = 'style.' + palette.name.toLowerCase().replace(/ /g, '_')
if (!firstStylePaletteName) firstStylePaletteName = key
palettesIndex[key] = () => Promise.resolve(palette)
@ -465,19 +494,26 @@ export const useInterfaceStore = defineStore('interface', {
'palette',
palettesIndex,
userPaletteCustomData,
this.useStylePalette ? firstStylePaletteName : (userPaletteName || instancePaletteName)
this.useStylePalette
? firstStylePaletteName
: userPaletteName || instancePaletteName,
)
if (this.useStylePalette) {
window.vuex.commit('setOption', { name: 'palette', value: firstStylePaletteName })
window.vuex.commit('setOption', {
name: 'palette',
value: firstStylePaletteName,
})
}
this.paletteNameUsed = palette.nameUsed
this.paletteDataUsed = palette.dataUsed
if (this.paletteDataUsed) {
this.paletteDataUsed.link = this.paletteDataUsed.link || this.paletteDataUsed.accent
this.paletteDataUsed.accent = this.paletteDataUsed.accent || this.paletteDataUsed.link
this.paletteDataUsed.link =
this.paletteDataUsed.link || this.paletteDataUsed.accent
this.paletteDataUsed.accent =
this.paletteDataUsed.accent || this.paletteDataUsed.link
}
if (Array.isArray(this.paletteDataUsed)) {
const [
@ -489,7 +525,7 @@ export const useInterfaceStore = defineStore('interface', {
cRed = '#FF0000',
cGreen = '#00FF00',
cBlue = '#0000FF',
cOrange = '#E3FF00'
cOrange = '#E3FF00',
] = palette.dataUsed
this.paletteDataUsed = {
name,
@ -501,7 +537,7 @@ export const useInterfaceStore = defineStore('interface', {
cRed,
cBlue,
cGreen,
cOrange
cOrange,
}
}
console.debug('Palette data used', palette.dataUsed)
@ -515,23 +551,18 @@ export const useInterfaceStore = defineStore('interface', {
'theme',
themesIndex,
userThemeV2Source || userThemeV2Snapshot,
userThemeV2Name || instanceThemeV2Name
userThemeV2Name || instanceThemeV2Name,
)
this.themeNameUsed = theme.nameUsed
this.themeDataUsed = theme.dataUsed
}
},
async setThemeApplied () {
async setThemeApplied() {
this.themeApplied = true
},
async applyTheme (
{ recompile = false } = {}
) {
const {
forceThemeRecompilation,
themeDebug,
theme3hacks
} = window.vuex.state.config
async applyTheme({ recompile = false } = {}) {
const { forceThemeRecompilation, themeDebug, theme3hacks } =
window.vuex.state.config
this.themeChangeInProgress = true
// If we're not forced to recompile try using
// cache (tryLoadCache return true if load successful)
@ -540,7 +571,7 @@ export const useInterfaceStore = defineStore('interface', {
await this.getThemeData()
if (!forceRecompile && !themeDebug && await tryLoadCache()) {
if (!forceRecompile && !themeDebug && (await tryLoadCache())) {
this.themeChangeInProgress = false
return this.setThemeApplied()
}
@ -551,11 +582,10 @@ export const useInterfaceStore = defineStore('interface', {
if (!this.paletteDataUsed) return null
const result = {
component: 'Root',
directives: {}
directives: {},
}
Object
.entries(this.paletteDataUsed)
Object.entries(this.paletteDataUsed)
.filter(([k]) => k !== 'name')
.forEach(([k, v]) => {
let issRootDirectiveName
@ -574,7 +604,9 @@ export const useInterfaceStore = defineStore('interface', {
return result
})()
const theme2ruleset = this.themeDataUsed && convertTheme2To3(normalizeThemeData(this.themeDataUsed))
const theme2ruleset =
this.themeDataUsed &&
convertTheme2To3(normalizeThemeData(this.themeDataUsed))
const hacks = []
Object.entries(theme3hacks).forEach(([key, value]) => {
@ -587,32 +619,32 @@ export const useInterfaceStore = defineStore('interface', {
hacks.push({
component: 'Root',
directives: {
'--font': 'generic | ' + font.family
}
'--font': 'generic | ' + font.family,
},
})
break
case 'input':
hacks.push({
component: 'Input',
directives: {
'--font': 'generic | ' + font.family
}
'--font': 'generic | ' + font.family,
},
})
break
case 'post':
hacks.push({
component: 'RichContent',
directives: {
'--font': 'generic | ' + font.family
}
'--font': 'generic | ' + font.family,
},
})
break
case 'monospace':
hacks.push({
component: 'Root',
directives: {
'--monoFont': 'generic | ' + font.family
}
'--monoFont': 'generic | ' + font.family,
},
})
break
}
@ -623,7 +655,7 @@ export const useInterfaceStore = defineStore('interface', {
if (value !== 'none') {
const newRule = {
component: 'Underlay',
directives: {}
directives: {},
}
if (value === 'opaque') {
newRule.directives.opacity = 1
@ -643,8 +675,8 @@ export const useInterfaceStore = defineStore('interface', {
theme2ruleset,
this.styleDataUsed,
paletteIss,
hacks
].filter(x => x)
hacks,
].filter((x) => x)
return applyTheme(
rulesetArray.flat(),
@ -652,13 +684,13 @@ export const useInterfaceStore = defineStore('interface', {
() => {
this.themeChangeInProgress = false
},
themeDebug
themeDebug,
)
} catch (e) {
window.splashError(e)
}
}
}
},
},
})
export const normalizeThemeData = (input) => {
@ -669,15 +701,15 @@ export const normalizeThemeData = (input) => {
return generatePreset(input).theme
} else if (
Object.hasOwn(input, '_pleroma_theme_version') ||
Object.hasOwn(input, 'source') ||
Object.hasOwn(input, 'theme')
Object.hasOwn(input, 'source') ||
Object.hasOwn(input, 'theme')
) {
// We got passed a full theme file
themeData = input.theme
themeSource = input.source
} else if (
Object.hasOwn(input, 'themeEngineVersion') ||
Object.hasOwn(input, 'colors')
Object.hasOwn(input, 'colors')
) {
// We got passed a source/snapshot
themeData = input

View file

@ -5,36 +5,39 @@ import { remove, find } from 'lodash'
export const useListsStore = defineStore('lists', {
state: () => ({
allLists: [],
allListsObject: {}
allListsObject: {},
}),
getters: {
findListTitle () {
findListTitle() {
return (id) => {
if (!this.allListsObject[id]) return
return this.allListsObject[id].title
}
},
findListAccounts () {
findListAccounts() {
return (id) => [...this.allListsObject[id].accountIds]
}
},
},
actions: {
setLists (value) {
setLists(value) {
this.allLists = value
},
createList ({ title }) {
return window.vuex.state.api.backendInteractor.createList({ title })
createList({ title }) {
return window.vuex.state.api.backendInteractor
.createList({ title })
.then((list) => {
this.setList({ listId: list.id, title })
return list
})
},
fetchList ({ listId }) {
return window.vuex.state.api.backendInteractor.getList({ listId })
fetchList({ listId }) {
return window.vuex.state.api.backendInteractor
.getList({ listId })
.then((list) => this.setList({ listId: list.id, title: list.title }))
},
fetchListAccounts ({ listId }) {
return window.vuex.state.api.backendInteractor.getListAccounts({ listId })
fetchListAccounts({ listId }) {
return window.vuex.state.api.backendInteractor
.getListAccounts({ listId })
.then((accountIds) => {
if (!this.allListsObject[listId]) {
this.allListsObject[listId] = { accountIds: [] }
@ -42,7 +45,7 @@ export const useListsStore = defineStore('lists', {
this.allListsObject[listId].accountIds = accountIds
})
},
setList ({ listId, title }) {
setList({ listId, title }) {
window.vuex.state.api.backendInteractor.updateList({ listId, title })
if (!this.allListsObject[listId]) {
@ -57,25 +60,29 @@ export const useListsStore = defineStore('lists', {
entry.title = title
}
},
setListAccounts ({ listId, accountIds }) {
setListAccounts({ listId, accountIds }) {
const saved = this.allListsObject[listId]?.accountIds || []
const added = accountIds.filter(id => !saved.includes(id))
const removed = saved.filter(id => !accountIds.includes(id))
const added = accountIds.filter((id) => !saved.includes(id))
const removed = saved.filter((id) => !accountIds.includes(id))
if (!this.allListsObject[listId]) {
this.allListsObject[listId] = { accountIds: [] }
}
this.allListsObject[listId].accountIds = accountIds
if (added.length > 0) {
window.vuex.state.api.backendInteractor.addAccountsToList({ listId, accountIds: added })
window.vuex.state.api.backendInteractor.addAccountsToList({
listId,
accountIds: added,
})
}
if (removed.length > 0) {
window.vuex.state.api.backendInteractor.removeAccountsFromList({ listId, accountIds: removed })
window.vuex.state.api.backendInteractor.removeAccountsFromList({
listId,
accountIds: removed,
})
}
},
addListAccount ({ listId, accountId }) {
return window.vuex.state
.api
.backendInteractor
addListAccount({ listId, accountId }) {
return window.vuex.state.api.backendInteractor
.addAccountsToList({ listId, accountIds: [accountId] })
.then((result) => {
if (!this.allListsObject[listId]) {
@ -85,10 +92,8 @@ export const useListsStore = defineStore('lists', {
return result
})
},
removeListAccount ({ listId, accountId }) {
return window.vuex.state
.api
.backendInteractor
removeListAccount({ listId, accountId }) {
return window.vuex.state.api.backendInteractor
.removeAccountsFromList({ listId, accountIds: [accountId] })
.then((result) => {
if (!this.allListsObject[listId]) {
@ -102,11 +107,11 @@ export const useListsStore = defineStore('lists', {
return result
})
},
deleteList ({ listId }) {
deleteList({ listId }) {
window.vuex.state.api.backendInteractor.deleteList({ listId })
delete this.allListsObject[listId]
remove(this.allLists, list => list.id === listId)
}
}
remove(this.allLists, (list) => list.id === listId)
},
},
})

View file

@ -7,24 +7,24 @@ export const useMediaViewerStore = defineStore('mediaViewer', {
state: () => ({
media: [],
currentIndex: 0,
activated: false
activated: false,
}),
actions: {
setMedia (attachments) {
const media = attachments.filter(attachment => {
setMedia(attachments) {
const media = attachments.filter((attachment) => {
const type = fileTypeService.fileType(attachment.mimetype)
return supportedTypes.has(type)
})
this.media = media
},
setCurrentMedia (current) {
setCurrentMedia(current) {
const index = this.media.indexOf(current)
this.activated = true
this.currentIndex = index
},
closeMediaViewer () {
closeMediaViewer() {
this.activated = false
}
}
},
},
})

View file

@ -1,8 +1,12 @@
import { defineStore } from 'pinia'
import { createApp, getClientToken, verifyAppToken } from 'src/services/new_api/oauth.js'
import {
createApp,
getClientToken,
verifyAppToken,
} from 'src/services/new_api/oauth.js'
// status codes about verifyAppToken (GET /api/v1/apps/verify_credentials)
const isAppTokenRejected = error => (
const isAppTokenRejected = (error) =>
// Pleroma API docs say it returns 422 when unauthorized
error.statusCode === 422 ||
// but it actually returns 400 (as of 2.9.0)
@ -10,16 +14,14 @@ const isAppTokenRejected = error => (
error.statusCode === 400 ||
// and Mastodon docs say it returns 401
error.statusCode === 401
)
// status codes about getAppToken (GET /oauth/token)
const isClientDataRejected = error => (
const isClientDataRejected = (error) =>
// Mastodon docs say it returns 401
error.statusCode === 401 ||
// but Pleroma actually returns 400 (as of 2.9.0)
// NOTE: don't try to match against the error message, it is translatable
error.statusCode === 400
)
export const useOAuthStore = defineStore('oauth', {
state: () => ({
@ -33,31 +35,31 @@ export const useOAuthStore = defineStore('oauth', {
/* User token is authentication for app with user, this is for every calls
* that need authorized user to be successful (i.e. posting, liking etc)
*/
userToken: false
userToken: false,
}),
getters: {
getToken () {
getToken() {
return this.userToken || this.appToken
},
getUserToken () {
getUserToken() {
return this.userToken
}
},
},
actions: {
setClientData ({ clientId, clientSecret }) {
setClientData({ clientId, clientSecret }) {
this.clientId = clientId
this.clientSecret = clientSecret
},
setAppToken (token) {
setAppToken(token) {
this.appToken = token
},
setToken (token) {
setToken(token) {
this.userToken = token
},
clearToken () {
clearToken() {
this.userToken = false
},
async createApp () {
async createApp() {
const { state } = window.vuex
const instance = state.instance.server
const app = await createApp(instance)
@ -67,36 +69,36 @@ export const useOAuthStore = defineStore('oauth', {
/// Use this if you want to get the client id and secret but are not interested
/// in whether they are valid.
/// @return {{ clientId: string, clientSecret: string }} An object representing the app
async ensureApp () {
async ensureApp() {
if (this.clientId && this.clientSecret) {
return {
clientId: this.clientId,
clientSecret: this.clientSecret
clientSecret: this.clientSecret,
}
} else {
return this.createApp()
}
},
async getAppToken () {
async getAppToken() {
const { state } = window.vuex
const instance = state.instance.server
const res = await getClientToken({
clientId: this.clientId,
clientSecret: this.clientSecret,
instance
instance,
})
this.setAppToken(res.access_token)
return res.access_token
},
/// Use this if you want to ensure the app is still valid to use.
/// @return {string} The access token to the app (not attached to any user)
async ensureAppToken () {
async ensureAppToken() {
const { state } = window.vuex
if (this.appToken) {
try {
await verifyAppToken({
instance: state.instance.server,
appToken: this.appToken
appToken: this.appToken,
})
return this.appToken
} catch (e) {
@ -133,17 +135,17 @@ export const useOAuthStore = defineStore('oauth', {
// and re-create our app
this.setClientData({
clientId: false,
clientSecret: false
clientSecret: false,
})
await this.createApp()
// try once again to get the token
return await this.getAppToken()
}
}
}
},
},
persist: {
afterLoad (state) {
afterLoad(state) {
// state.token is userToken with older name, coming from persistent state
if (state.token && !state.userToken) {
state.userToken = state.token
@ -152,6 +154,6 @@ export const useOAuthStore = defineStore('oauth', {
delete state.token
}
return state
}
}
},
},
})

View file

@ -2,23 +2,27 @@ import { defineStore } from 'pinia'
export const useOAuthTokensStore = defineStore('oauthTokens', {
state: () => ({
tokens: []
tokens: [],
}),
actions: {
fetchTokens () {
window.vuex.state.api.backendInteractor.fetchOAuthTokens().then((tokens) => {
this.swapTokens(tokens)
})
fetchTokens() {
window.vuex.state.api.backendInteractor
.fetchOAuthTokens()
.then((tokens) => {
this.swapTokens(tokens)
})
},
revokeToken (id) {
window.vuex.state.api.backendInteractor.revokeOAuthToken({ id }).then((response) => {
if (response.status === 201) {
this.swapTokens(this.tokens.filter(token => token.id !== id))
}
})
revokeToken(id) {
window.vuex.state.api.backendInteractor
.revokeOAuthToken({ id })
.then((response) => {
if (response.status === 201) {
this.swapTokens(this.tokens.filter((token) => token.id !== id))
}
})
},
swapTokens (tokens) {
swapTokens(tokens) {
this.tokens = tokens
}
}
});
},
},
})

View file

@ -5,10 +5,10 @@ export const usePollsStore = defineStore('polls', {
state: () => ({
// Contains key = id, value = number of trackers for this poll
trackedPolls: {},
pollsObject: {}
pollsObject: {},
}),
actions: {
mergeOrAddPoll (poll) {
mergeOrAddPoll(poll) {
const existingPoll = this.pollsObject[poll.id]
// Make expired-state change trigger re-renders properly
poll.expired = Date.now() > Date.parse(poll.expires_at)
@ -18,17 +18,19 @@ export const usePollsStore = defineStore('polls', {
this.pollsObject[poll.id] = poll
}
},
updateTrackedPoll (pollId) {
window.vuex.state.api.backendInteractor.fetchPoll({ pollId }).then(poll => {
setTimeout(() => {
if (this.trackedPolls[pollId]) {
this.updateTrackedPoll(pollId)
}
}, 30 * 1000)
this.mergeOrAddPoll(poll)
})
updateTrackedPoll(pollId) {
window.vuex.state.api.backendInteractor
.fetchPoll({ pollId })
.then((poll) => {
setTimeout(() => {
if (this.trackedPolls[pollId]) {
this.updateTrackedPoll(pollId)
}
}, 30 * 1000)
this.mergeOrAddPoll(poll)
})
},
trackPoll (pollId) {
trackPoll(pollId) {
if (!this.trackedPolls[pollId]) {
setTimeout(() => this.updateTrackedPoll(pollId), 30 * 1000)
}
@ -39,7 +41,7 @@ export const usePollsStore = defineStore('polls', {
this.trackedPolls[pollId] = 1
}
},
untrackPoll (pollId) {
untrackPoll(pollId) {
const currentValue = this.trackedPolls[pollId]
if (currentValue) {
this.trackedPolls[pollId] = currentValue - 1
@ -47,11 +49,13 @@ export const usePollsStore = defineStore('polls', {
this.trackedPolls[pollId] = 0
}
},
votePoll ({ pollId, choices }) {
return window.vuex.state.api.backendInteractor.vote({ pollId, choices }).then(poll => {
this.mergeOrAddPoll(poll)
return poll
})
}
}
votePoll({ pollId, choices }) {
return window.vuex.state.api.backendInteractor
.vote({ pollId, choices })
.then((poll) => {
this.mergeOrAddPoll(poll)
return poll
})
},
},
})

View file

@ -3,18 +3,18 @@ import { defineStore } from 'pinia'
export const usePostStatusStore = defineStore('postStatus', {
state: () => ({
params: null,
modalActivated: false
modalActivated: false,
}),
actions: {
openPostStatusModal (params) {
openPostStatusModal(params) {
this.params = params
this.modalActivated = true
},
closePostStatusModal () {
closePostStatusModal() {
this.modalActivated = false
},
resetPostStatusModal () {
resetPostStatusModal() {
this.params = null
}
}
},
},
})

View file

@ -9,18 +9,22 @@ export const useReportsStore = defineStore('reports', {
userId: null,
statuses: [],
preTickedIds: [],
activated: false
activated: false,
},
reports: {}
reports: {},
}),
actions: {
openUserReportingModal ({ userId, statusIds = [] }) {
const preTickedStatuses = statusIds.map(id => window.vuex.state.statuses.allStatusesObject[id])
openUserReportingModal({ userId, statusIds = [] }) {
const preTickedStatuses = statusIds.map(
(id) => window.vuex.state.statuses.allStatusesObject[id],
)
const preTickedIds = statusIds
const statuses = preTickedStatuses.concat(
filter(window.vuex.state.statuses.allStatuses,
status => status.user.id === userId && !preTickedIds.includes(status.id)
)
filter(
window.vuex.state.statuses.allStatuses,
(status) =>
status.user.id === userId && !preTickedIds.includes(status.id),
),
)
this.reportModal.userId = userId
@ -28,25 +32,27 @@ export const useReportsStore = defineStore('reports', {
this.reportModal.preTickedIds = preTickedIds
this.reportModal.activated = true
},
closeUserReportingModal () {
closeUserReportingModal() {
this.reportModal.activated = false
},
setReportState ({ id, state }) {
setReportState({ id, state }) {
const oldState = this.reports[id].state
this.reports[id].state = state
window.vuex.state.api.backendInteractor.setReportState({ id, state }).catch(e => {
console.error('Failed to set report state', e)
useInterfaceStore().pushGlobalNotice({
level: 'error',
messageKey: 'general.generic_error_message',
messageArgs: [e.message],
timeout: 5000
window.vuex.state.api.backendInteractor
.setReportState({ id, state })
.catch((e) => {
console.error('Failed to set report state', e)
useInterfaceStore().pushGlobalNotice({
level: 'error',
messageKey: 'general.generic_error_message',
messageArgs: [e.message],
timeout: 5000,
})
this.reports[id].state = oldState
})
this.reports[id].state = oldState
})
},
addReport (report) {
addReport(report) {
this.reports[report.id] = report
}
}
},
},
})

View file

@ -12,7 +12,7 @@ import {
findLastIndex,
takeRight,
uniqWith,
merge as _merge
merge as _merge,
} from 'lodash'
import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js'
@ -29,7 +29,7 @@ export const defaultState = {
flagStorage: {
updateCounter: 0, // Counter for most recent update notification seen
configMigration: 0, // Counter for config -> server-side migrations
reset: 0 // special flag that can be used to force-reset all flags, debug purposes only
reset: 0, // special flag that can be used to force-reset all flags, debug purposes only
// special reset codes:
// 1000: trim keys to those known by currently running FE
// 1001: same as above + reset everything to 0
@ -39,22 +39,22 @@ export const defaultState = {
simple: {
dontShowUpdateNotifs: false,
collapseNav: false,
muteFilters: {}
muteFilters: {},
},
collections: {
pinnedStatusActions: ['reply', 'retweet', 'favorite', 'emoji'],
pinnedNavItems: ['home', 'dms', 'chats']
}
pinnedNavItems: ['home', 'dms', 'chats'],
},
},
// raw data
raw: null,
// local cache
cache: null
cache: null,
}
export const newUserFlags = {
...defaultState.flagStorage,
updateCounter: CURRENT_UPDATE_COUNTER // new users don't need to see update notification
updateCounter: CURRENT_UPDATE_COUNTER, // new users don't need to see update notification
}
export const _moveItemInArray = (array, value, movement) => {
@ -72,7 +72,7 @@ const _wrapData = (data, userName) => ({
...data,
_user: userName,
_timestamp: Date.now(),
_version: VERSION
_version: VERSION,
})
const _checkValidity = (data) => data._timestamp > 0 && data._version > 0
@ -80,7 +80,7 @@ const _checkValidity = (data) => data._timestamp > 0 && data._version > 0
const _verifyPrefs = (state) => {
state.prefsStorage = state.prefsStorage || {
simple: {},
collections: {}
collections: {},
}
// Simple
@ -95,7 +95,11 @@ const _verifyPrefs = (state) => {
Object.entries(defaultState.prefsStorage.collections).forEach(([k, v]) => {
if (Array.isArray(v)) return
console.warn(`Preference collections.${k} as invalid type, reinitializing`)
set(state.prefsStorage.collections, k, defaultState.prefsStorage.collections[k])
set(
state.prefsStorage.collections,
k,
defaultState.prefsStorage.collections[k],
)
})
}
@ -105,21 +109,32 @@ export const _getRecentData = (cache, live, isTest) => {
const liveValid = _checkValidity(live || {})
if (!liveValid && cacheValid) {
result.needUpload = true
console.debug('Nothing valid stored on server, assuming cache to be source of truth')
console.debug(
'Nothing valid stored on server, assuming cache to be source of truth',
)
result.recent = cache
result.stale = live
} else if (!cacheValid && liveValid) {
console.debug('Valid storage on server found, no local cache found, using live as source of truth')
console.debug(
'Valid storage on server found, no local cache found, using live as source of truth',
)
result.recent = live
result.stale = cache
} else if (cacheValid && liveValid) {
console.debug('Both sources have valid data, figuring things out...')
if (live._timestamp === cache._timestamp && live._version === cache._version) {
console.debug('Same version/timestamp on both source, source of truth irrelevant')
if (
live._timestamp === cache._timestamp &&
live._version === cache._version
) {
console.debug(
'Same version/timestamp on both source, source of truth irrelevant',
)
result.recent = cache
result.stale = live
} else {
console.debug('Different timestamp, figuring out which one is more recent')
console.debug(
'Different timestamp, figuring out which one is more recent',
)
if (live._timestamp < cache._timestamp) {
result.recent = cache
result.stale = live
@ -139,49 +154,64 @@ export const _getRecentData = (cache, live, isTest) => {
_timestamp: a._timestamp ?? b._timestamp,
needUpload: b.needUpload ?? a.needUpload,
prefsStorage: _merge(a.prefsStorage, b.prefsStorage),
flagStorage: _merge(a.flagStorage, b.flagStorage)
flagStorage: _merge(a.flagStorage, b.flagStorage),
})
result.recent = isTest ? result.recent : (result.recent && merge(defaultState, result.recent))
result.stale = isTest ? result.stale : (result.stale && merge(defaultState, result.stale))
result.recent = isTest
? result.recent
: result.recent && merge(defaultState, result.recent)
result.stale = isTest
? result.stale
: result.stale && merge(defaultState, result.stale)
return result
}
export const _getAllFlags = (recent, stale) => {
return Array.from(new Set([
...Object.keys(toRaw((recent || {}).flagStorage || {})),
...Object.keys(toRaw((stale || {}).flagStorage || {}))
]))
return Array.from(
new Set([
...Object.keys(toRaw((recent || {}).flagStorage || {})),
...Object.keys(toRaw((stale || {}).flagStorage || {})),
]),
)
}
export const _mergeFlags = (recent, stale, allFlagKeys) => {
if (!stale.flagStorage) return recent.flagStorage
if (!recent.flagStorage) return stale.flagStorage
return Object.fromEntries(allFlagKeys.map(flag => {
const recentFlag = recent.flagStorage[flag]
const staleFlag = stale.flagStorage[flag]
// use flag that is of higher value
return [flag, Number((recentFlag > staleFlag ? recentFlag : staleFlag) || 0)]
}))
return Object.fromEntries(
allFlagKeys.map((flag) => {
const recentFlag = recent.flagStorage[flag]
const staleFlag = stale.flagStorage[flag]
// use flag that is of higher value
return [
flag,
Number((recentFlag > staleFlag ? recentFlag : staleFlag) || 0),
]
}),
)
}
const _mergeJournal = (...journals) => {
// Ignore invalid journal entries
const allJournals = flatten(
journals.map(j => Array.isArray(j) ? j : [])
).filter(entry =>
Object.hasOwn(entry, 'path') &&
Object.hasOwn(entry, 'operation') &&
Object.hasOwn(entry, 'args') &&
Object.hasOwn(entry, 'timestamp')
journals.map((j) => (Array.isArray(j) ? j : [])),
).filter(
(entry) =>
Object.hasOwn(entry, 'path') &&
Object.hasOwn(entry, 'operation') &&
Object.hasOwn(entry, 'args') &&
Object.hasOwn(entry, 'timestamp'),
)
const grouped = groupBy(allJournals, 'path')
const trimmedGrouped = Object.entries(grouped).map(([path, journal]) => {
// side effect
journal.sort((a, b) => a.timestamp > b.timestamp ? 1 : -1)
journal.sort((a, b) => (a.timestamp > b.timestamp ? 1 : -1))
if (path.startsWith('collections')) {
const lastRemoveIndex = findLastIndex(journal, ({ operation }) => operation === 'removeFromCollection')
const lastRemoveIndex = findLastIndex(
journal,
({ operation }) => operation === 'removeFromCollection',
)
// everything before last remove is unimportant
let remainder
if (lastRemoveIndex > 0) {
@ -191,8 +221,12 @@ const _mergeJournal = (...journals) => {
remainder = journal
}
return uniqWith(remainder, (a, b) => {
if (a.path !== b.path) { return false }
if (a.operation !== b.operation) { return false }
if (a.path !== b.path) {
return false
}
if (a.operation !== b.operation) {
return false
}
if (a.operation === 'addToCollection') {
return a.args[0] === b.args[0]
}
@ -205,8 +239,9 @@ const _mergeJournal = (...journals) => {
return journal
}
})
return flatten(trimmedGrouped)
.sort((a, b) => a.timestamp > b.timestamp ? 1 : -1)
return flatten(trimmedGrouped).sort((a, b) =>
a.timestamp > b.timestamp ? 1 : -1,
)
}
export const _mergePrefs = (recent, stale) => {
@ -228,29 +263,45 @@ export const _mergePrefs = (recent, stale) => {
const totalJournal = _mergeJournal(staleJournal, recentJournal)
totalJournal.forEach(({ path, operation, args }) => {
if (path.startsWith('_')) {
throw new Error(`journal contains entry to edit internal (starts with _) field '${path}', something is incorrect here, ignoring.`)
throw new Error(
`journal contains entry to edit internal (starts with _) field '${path}', something is incorrect here, ignoring.`,
)
}
switch (operation) {
case 'set':
if (path.startsWith('collections') || path.startsWith('objectCollections')) {
if (
path.startsWith('collections') ||
path.startsWith('objectCollections')
) {
throw new Error('Illegal operation "set" on a collection')
}
if (path.split(/\./g).length <= 1) {
throw new Error(`Calling set on depth <= 1 (path: ${path}) is not allowed`)
throw new Error(
`Calling set on depth <= 1 (path: ${path}) is not allowed`,
)
}
set(resultOutput, path, args[0])
break
case 'unset':
if (path.startsWith('collections') || path.startsWith('objectCollections')) {
if (
path.startsWith('collections') ||
path.startsWith('objectCollections')
) {
throw new Error('Illegal operation "unset" on a collection')
}
if (path.split(/\./g).length <= 2) {
throw new Error(`Calling unset on depth <= 2 (path: ${path}) is not allowed`)
throw new Error(
`Calling unset on depth <= 2 (path: ${path}) is not allowed`,
)
}
unset(resultOutput, path)
break
case 'addToCollection':
set(resultOutput, path, Array.from(new Set(get(resultOutput, path)).add(args[0])))
set(
resultOutput,
path,
Array.from(new Set(get(resultOutput, path)).add(args[0])),
)
break
case 'removeFromCollection': {
const newSet = new Set(get(resultOutput, path))
@ -260,27 +311,39 @@ export const _mergePrefs = (recent, stale) => {
}
case 'reorderCollection': {
const [value, movement] = args
set(resultOutput, path, _moveItemInArray(get(resultOutput, path), value, movement))
set(
resultOutput,
path,
_moveItemInArray(get(resultOutput, path), value, movement),
)
break
}
default:
throw new Error(`Unknown journal operation: '${operation}', did we forget to run reverse migrations beforehand?`)
throw new Error(
`Unknown journal operation: '${operation}', did we forget to run reverse migrations beforehand?`,
)
}
})
return { ...resultOutput, _journal: totalJournal }
}
export const _resetFlags = (totalFlags, knownKeys = defaultState.flagStorage) => {
export const _resetFlags = (
totalFlags,
knownKeys = defaultState.flagStorage,
) => {
let result = { ...totalFlags }
const allFlagKeys = Object.keys(totalFlags)
// flag reset functionality
if (totalFlags.reset >= COMMAND_TRIM_FLAGS && totalFlags.reset <= COMMAND_TRIM_FLAGS_AND_RESET) {
if (
totalFlags.reset >= COMMAND_TRIM_FLAGS &&
totalFlags.reset <= COMMAND_TRIM_FLAGS_AND_RESET
) {
console.debug('Received command to trim the flags')
const knownKeysSet = new Set(Object.keys(knownKeys))
// Trim
result = {}
allFlagKeys.forEach(flag => {
allFlagKeys.forEach((flag) => {
if (knownKeysSet.has(flag)) {
result[flag] = totalFlags[flag]
}
@ -290,11 +353,15 @@ export const _resetFlags = (totalFlags, knownKeys = defaultState.flagStorage) =>
if (totalFlags.reset === COMMAND_TRIM_FLAGS_AND_RESET) {
// 1001 - and reset everything to 0
console.debug('Received command to reset the flags')
Object.keys(knownKeys).forEach(flag => { result[flag] = 0 })
Object.keys(knownKeys).forEach((flag) => {
result[flag] = 0
})
}
} else if (totalFlags.reset > 0 && totalFlags.reset < 9000) {
console.debug('Received command to reset the flags')
allFlagKeys.forEach(flag => { result[flag] = 0 })
allFlagKeys.forEach((flag) => {
result[flag] = 0
})
}
result.reset = 0
return result
@ -304,20 +371,29 @@ export const _doMigrations = (cache) => {
if (!cache) return cache
if (cache._version < VERSION) {
console.debug('Local cached data has older version, seeing if there any migrations that can be applied')
console.debug(
'Local cached data has older version, seeing if there any migrations that can be applied',
)
// no migrations right now since we only have one version
console.debug('No migrations found')
}
if (cache._version > VERSION) {
console.debug('Local cached data has newer version, seeing if there any reverse migrations that can be applied')
console.debug(
'Local cached data has newer version, seeing if there any reverse migrations that can be applied',
)
// no reverse migrations right now but we leave a possibility of loading a hotpatch if need be
if (window._PLEROMA_HOTPATCH) {
if (window._PLEROMA_HOTPATCH.reverseMigrations) {
console.debug('Found hotpatch migration, applying')
return window._PLEROMA_HOTPATCH.reverseMigrations.call({}, 'serverSideStorage', { from: cache._version, to: VERSION }, cache)
return window._PLEROMA_HOTPATCH.reverseMigrations.call(
{},
'serverSideStorage',
{ from: cache._version, to: VERSION },
cache,
)
}
}
}
@ -330,53 +406,77 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', {
return cloneDeep(defaultState)
},
actions: {
setFlag ({ flag, value }) {
setFlag({ flag, value }) {
this.flagStorage[flag] = value
this.dirty = true
},
setPreference ({ path, value }) {
setPreference({ path, value }) {
if (path.startsWith('_')) {
throw new Error(`Tried to edit internal (starts with _) field '${path}', ignoring.`)
throw new Error(
`Tried to edit internal (starts with _) field '${path}', ignoring.`,
)
}
if (path.startsWith('collections') || path.startsWith('objectCollections')) {
throw new Error(`Invalid operation 'set' for collection field '${path}', ignoring.`)
if (
path.startsWith('collections') ||
path.startsWith('objectCollections')
) {
throw new Error(
`Invalid operation 'set' for collection field '${path}', ignoring.`,
)
}
if (path.split(/\./g).length <= 1) {
throw new Error(`Calling set on depth <= 1 (path: ${path}) is not allowed`)
throw new Error(
`Calling set on depth <= 1 (path: ${path}) is not allowed`,
)
}
if (path.split(/\./g).length > 3) {
throw new Error(`Calling set on depth > 3 (path: ${path}) is not allowed`)
throw new Error(
`Calling set on depth > 3 (path: ${path}) is not allowed`,
)
}
set(this.prefsStorage, path, value)
this.prefsStorage._journal = [
...this.prefsStorage._journal,
{ operation: 'set', path, args: [value], timestamp: Date.now() }
{ operation: 'set', path, args: [value], timestamp: Date.now() },
]
this.dirty = true
},
unsetPreference ({ path, value }) {
unsetPreference({ path, value }) {
if (path.startsWith('_')) {
throw new Error(`Tried to edit internal (starts with _) field '${path}', ignoring.`)
throw new Error(
`Tried to edit internal (starts with _) field '${path}', ignoring.`,
)
}
if (path.startsWith('collections') || path.startsWith('objectCollections')) {
throw new Error(`Invalid operation 'unset' for collection field '${path}', ignoring.`)
if (
path.startsWith('collections') ||
path.startsWith('objectCollections')
) {
throw new Error(
`Invalid operation 'unset' for collection field '${path}', ignoring.`,
)
}
if (path.split(/\./g).length <= 2) {
throw new Error(`Calling unset on depth <= 2 (path: ${path}) is not allowed`)
throw new Error(
`Calling unset on depth <= 2 (path: ${path}) is not allowed`,
)
}
if (path.split(/\./g).length > 3) {
throw new Error(`Calling unset on depth > 3 (path: ${path}) is not allowed`)
throw new Error(
`Calling unset on depth > 3 (path: ${path}) is not allowed`,
)
}
unset(this.prefsStorage, path, value)
this.prefsStorage._journal = [
...this.prefsStorage._journal,
{ operation: 'unset', path, args: [], timestamp: Date.now() }
{ operation: 'unset', path, args: [], timestamp: Date.now() },
]
this.dirty = true
},
addCollectionPreference ({ path, value }) {
addCollectionPreference({ path, value }) {
if (path.startsWith('_')) {
throw new Error(`tried to edit internal (starts with _) field '${path}'`)
throw new Error(
`tried to edit internal (starts with _) field '${path}'`,
)
}
if (path.startsWith('collections')) {
const collection = new Set(get(this.prefsStorage, path))
@ -394,55 +494,79 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', {
}
this.prefsStorage._journal = [
...this.prefsStorage._journal,
{ operation: 'addToCollection', path, args: [value], timestamp: Date.now() }
{
operation: 'addToCollection',
path,
args: [value],
timestamp: Date.now(),
},
]
this.dirty = true
},
removeCollectionPreference ({ path, value }) {
removeCollectionPreference({ path, value }) {
if (path.startsWith('_')) {
throw new Error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
throw new Error(
`tried to edit internal (starts with _) field '${path}', ignoring.`,
)
}
const collection = new Set(get(this.prefsStorage, path))
collection.delete(value)
set(this.prefsStorage, path, [...collection])
this.prefsStorage._journal = [
...this.prefsStorage._journal,
{ operation: 'removeFromCollection', path, args: [value], timestamp: Date.now() }
{
operation: 'removeFromCollection',
path,
args: [value],
timestamp: Date.now(),
},
]
this.dirty = true
},
reorderCollectionPreference ({ path, value, movement }) {
reorderCollectionPreference({ path, value, movement }) {
if (path.startsWith('_')) {
throw new Error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
throw new Error(
`tried to edit internal (starts with _) field '${path}', ignoring.`,
)
}
const collection = get(this.prefsStorage, path)
const newCollection = _moveItemInArray(collection, value, movement)
set(this.prefsStorage, path, newCollection)
this.prefsStorage._journal = [
...this.prefsStorage._journal,
{ operation: 'arrangeCollection', path, args: [value], timestamp: Date.now() }
{
operation: 'arrangeCollection',
path,
args: [value],
timestamp: Date.now(),
},
]
this.dirty = true
},
updateCache ({ username }) {
updateCache({ username }) {
this.prefsStorage._journal = _mergeJournal(this.prefsStorage._journal)
this.cache = _wrapData({
flagStorage: toRaw(this.flagStorage),
prefsStorage: toRaw(this.prefsStorage)
}, username)
this.cache = _wrapData(
{
flagStorage: toRaw(this.flagStorage),
prefsStorage: toRaw(this.prefsStorage),
},
username,
)
},
clearServerSideStorage () {
clearServerSideStorage() {
const blankState = { ...cloneDeep(defaultState) }
Object.keys(this).forEach(k => {
Object.keys(this).forEach((k) => {
this[k] = blankState[k]
})
},
setServerSideStorage (userData) {
setServerSideStorage(userData) {
const live = userData.storage
this.raw = live
let cache = this.cache
if (cache && cache._user !== userData.fqn) {
console.warn('Cache belongs to another user! reinitializing local cache!')
console.warn(
'Cache belongs to another user! reinitializing local cache!',
)
cache = null
}
@ -455,10 +579,12 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', {
let dirty = false
if (recent === null) {
console.debug(`Data is empty, initializing for ${userNew ? 'new' : 'existing'} user`)
console.debug(
`Data is empty, initializing for ${userNew ? 'new' : 'existing'} user`,
)
recent = _wrapData({
flagStorage: { ...flagsTemplate },
prefsStorage: { ...defaultState.prefsStorage }
prefsStorage: { ...defaultState.prefsStorage },
})
}
@ -470,7 +596,7 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', {
const { _timestamp: _2, _version: _3, ...staleData } = stale
/* eslint-enable no-unused-vars */
dirty = !isEqual(recentData, staleData)
console.debug(`Data ${dirty ? 'needs' : 'doesn\'t need'} merging`)
console.debug(`Data ${dirty ? 'needs' : "doesn't need"} merging`)
}
const allFlagKeys = _getAllFlags(recent, stale)
@ -502,7 +628,7 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', {
this.flagStorage = this.cache.flagStorage
this.prefsStorage = this.cache.prefsStorage
},
pushServerSideStorage ({ force = false } = {}) {
pushServerSideStorage({ force = false } = {}) {
const needPush = this.dirty || force
if (!needPush) return
this.updateCache({ username: window.vuex.state.users.currentUser.fqn })
@ -513,6 +639,6 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', {
this.setServerSideStorage(user)
this.dirty = false
})
}
}
},
},
})

View file

@ -4,10 +4,10 @@ export const useShoutStore = defineStore('shout', {
state: () => ({
messages: [],
channel: { state: '' },
joined: false
joined: false,
}),
actions: {
initializeShout (socket) {
initializeShout(socket) {
const channel = socket.channel('chat:public')
channel.joinPush.receive('ok', () => {
this.joined = true
@ -27,6 +27,6 @@ export const useShoutStore = defineStore('shout', {
})
channel.join()
this.channel = channel
}
}
},
},
})

View file

@ -3,15 +3,15 @@ import { defineStore } from 'pinia'
export const useStatusHistoryStore = defineStore('statusHistory', {
state: () => ({
params: {},
modalActivated: false
modalActivated: false,
}),
actions: {
openStatusHistoryModal (params) {
openStatusHistoryModal(params) {
this.params = params
this.modalActivated = true
},
closeStatusHistoryModal () {
closeStatusHistoryModal() {
this.modalActivated = false
}
}
},
},
})