
Modern browsers start to tighten down on third-party access to cookies. E.g. in current Firefox, a warning about the userLanguage cookie was shown since it did not yet explicitly set the SameSite attribute and the default is about to change. The cookie name being referred to as BACKEND_LANGUAGE_COOKIE_NAME suggests it should be readable by the actual Akkoma backend, which can live at a different domain than akkoma-fe. Thus explicitly enable sharing with third-party sites. No warnings were shown for other cookies, so I assume this was the only one not yet setting SameSite.
273 lines
9.5 KiB
JavaScript
273 lines
9.5 KiB
JavaScript
import Cookies from 'js-cookie'
|
|
import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
|
|
import messages from '../i18n/messages'
|
|
import localeService from '../services/locale/locale.service.js'
|
|
|
|
const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
|
|
|
|
const browserLocale = (window.navigator.language || 'en').split('-')[0]
|
|
|
|
/* TODO this is a bit messy.
|
|
* We need to declare settings with their types and also deal with
|
|
* instance-default settings in some way, hopefully try to avoid copy-pasta
|
|
* in general.
|
|
*/
|
|
export const multiChoiceProperties = [
|
|
'postContentType',
|
|
'subjectLineBehavior',
|
|
'conversationDisplay', // tree | linear
|
|
'conversationOtherRepliesButton', // below | inside
|
|
'mentionLinkDisplay' // short | full_for_remote | full
|
|
]
|
|
|
|
export const defaultState = {
|
|
profile: 'default',
|
|
profileVersion: 0,
|
|
expertLevel: 0, // used to track which settings to show and hide
|
|
colors: {},
|
|
theme: undefined,
|
|
customTheme: undefined,
|
|
customThemeSource: undefined,
|
|
hideISP: false,
|
|
hideInstanceWallpaper: false,
|
|
// bad name: actually hides posts of muted USERS
|
|
hideMutedPosts: undefined, // instance default
|
|
hideMutedThreads: undefined, // instance default
|
|
hideThreadsWithBlockedUsers: undefined, // instance default
|
|
hideWordFilteredPosts: undefined, // instance default
|
|
muteBotStatuses: undefined, // instance default
|
|
collapseMessageWithSubject: true, // instance default
|
|
padEmoji: true,
|
|
showNavShortcuts: undefined, // instance default
|
|
showPanelNavShortcuts: undefined, // instance default
|
|
showWiderShortcuts: undefined, // instance default
|
|
hideSiteFavicon: undefined, // instance default
|
|
hideSiteName: undefined, // instance default
|
|
hideAttachments: false,
|
|
hideAttachmentsInConv: false,
|
|
maxThumbnails: 16,
|
|
hideNsfw: true,
|
|
preloadImage: true,
|
|
loopVideo: true,
|
|
loopVideoSilentOnly: true,
|
|
streaming: false,
|
|
emojiReactionsOnTimeline: true,
|
|
alwaysShowNewPostButton: false,
|
|
autohideFloatingPostButton: false,
|
|
pauseOnUnfocused: true,
|
|
stopGifs: undefined,
|
|
replyVisibility: 'all',
|
|
thirdColumnMode: 'notifications',
|
|
notificationVisibility: {
|
|
follows: true,
|
|
mentions: true,
|
|
likes: true,
|
|
repeats: true,
|
|
moves: true,
|
|
emojiReactions: true,
|
|
followRequest: true,
|
|
polls: true
|
|
},
|
|
webPushNotifications: false,
|
|
webPushHideIfCW: true,
|
|
muteWords: [],
|
|
highlight: {},
|
|
interfaceLanguage: browserLocale,
|
|
hideScopeNotice: false,
|
|
useStreamingApi: false,
|
|
sidebarRight: undefined, // instance default
|
|
subjectLineBehavior: undefined, // instance default
|
|
alwaysShowSubjectInput: undefined, // instance default
|
|
postContentType: undefined, // instance default
|
|
// This hides statuses filtered via a word filter
|
|
hideFilteredStatuses: undefined, // instance default
|
|
modalOnRepeat: undefined, // instance default
|
|
modalOnUnfollow: undefined, // instance default
|
|
modalOnBlock: undefined, // instance default
|
|
modalOnMute: undefined, // instance default
|
|
modalOnDelete: undefined, // instance default
|
|
modalOnLogout: undefined, // instance default
|
|
modalOnApproveFollow: undefined, // instance default
|
|
modalOnDenyFollow: undefined, // instance default
|
|
playVideosInModal: false,
|
|
useOneClickNsfw: false,
|
|
useContainFit: true,
|
|
disableStickyHeaders: false,
|
|
showScrollbars: false,
|
|
greentext: undefined, // instance default
|
|
useAtIcon: undefined, // instance default
|
|
mentionLinkDisplay: undefined, // instance default
|
|
mentionLinkShowTooltip: undefined, // instance default
|
|
mentionLinkShowAvatar: undefined, // instance default
|
|
mentionLinkFadeDomain: undefined, // instance default
|
|
mentionLinkShowYous: undefined, // instance default
|
|
mentionLinkBoldenYou: undefined, // instance default
|
|
hidePostStats: undefined, // instance default
|
|
hideBotIndication: undefined, // instance default
|
|
hideUserStats: undefined, // instance default
|
|
virtualScrolling: undefined, // instance default
|
|
sensitiveByDefault: undefined, // instance default
|
|
sensitiveIfSubject: undefined,
|
|
renderMisskeyMarkdown: undefined,
|
|
renderMfmOnHover: undefined, // instance default
|
|
conversationDisplay: undefined, // instance default
|
|
conversationTreeAdvanced: undefined, // instance default
|
|
conversationOtherRepliesButton: undefined, // instance default
|
|
conversationTreeFadeAncestors: undefined, // instance default
|
|
maxDepthInThread: undefined, // instance default
|
|
translationLanguage: undefined, // instance default,
|
|
supportedTranslationLanguages: {}, // instance default
|
|
userProfileDefaultTab: 'statuses',
|
|
useBlurhash: true,
|
|
}
|
|
|
|
// caching the instance default properties
|
|
export const instanceDefaultProperties = Object.entries(defaultState)
|
|
.filter(([key, value]) => value === undefined)
|
|
.map(([key, value]) => key)
|
|
|
|
const config = {
|
|
state: { ...defaultState },
|
|
getters: {
|
|
defaultConfig (state, getters, rootState, rootGetters) {
|
|
const { instance } = rootState
|
|
return {
|
|
...defaultState,
|
|
...Object.fromEntries(
|
|
instanceDefaultProperties.map(key => [key, instance[key]])
|
|
)
|
|
}
|
|
},
|
|
mergedConfig (state, getters, rootState, rootGetters) {
|
|
const { defaultConfig } = rootGetters
|
|
return {
|
|
...defaultConfig,
|
|
// Do not override with undefined
|
|
...Object.fromEntries(Object.entries(state).filter(([k, v]) => v !== undefined))
|
|
}
|
|
}
|
|
},
|
|
mutations: {
|
|
setOption (state, { name, value }) {
|
|
state[name] = value
|
|
},
|
|
setHighlight (state, { user, color, type }) {
|
|
const data = this.state.config.highlight[user]
|
|
if (color || type) {
|
|
state.highlight[user] = { color: color || data.color, type: type || data.type }
|
|
} else {
|
|
delete state.highlight[user]
|
|
}
|
|
}
|
|
},
|
|
actions: {
|
|
syncSettings: (store) => {
|
|
store.commit('setOption', { name: 'profileVersion', value: store.state.profileVersion + 1 })
|
|
const notice = {
|
|
level: 'info',
|
|
messageKey: 'settings_profile.synchronizing',
|
|
messageArgs: { profile: store.state.profile },
|
|
timeout: 5000
|
|
}
|
|
store.dispatch('pushGlobalNotice', notice)
|
|
store.rootState.api.backendInteractor.saveSettingsProfile({
|
|
settings: store.state, profileName: store.state.profile, version: store.state.profileVersion
|
|
}).then(() => {
|
|
store.dispatch('removeGlobalNotice', notice)
|
|
store.dispatch('pushGlobalNotice', {
|
|
level: 'success',
|
|
messageKey: 'settings_profile.synchronized',
|
|
messageArgs: { profile: store.state.profile },
|
|
timeout: 2000
|
|
})
|
|
store.dispatch('listSettingsProfiles')
|
|
}).catch((err) => {
|
|
store.dispatch('removeGlobalNotice', notice)
|
|
store.dispatch('pushGlobalNotice', {
|
|
level: 'error',
|
|
messageKey: 'settings_profile.synchronization_error',
|
|
messageArgs: { error: err.message },
|
|
timeout: 5000
|
|
})
|
|
console.error(err)
|
|
})
|
|
},
|
|
deleteSettingsProfile (store, name) {
|
|
store.rootState.api.backendInteractor.deleteSettingsProfile({ profileName: name }).then(() => {
|
|
store.dispatch('listSettingsProfiles')
|
|
})
|
|
},
|
|
loadSettings ({ dispatch }, data) {
|
|
const knownKeys = new Set(Object.keys(defaultState))
|
|
const presentKeys = new Set(Object.keys(data))
|
|
const intersection = new Set()
|
|
for (let elem of presentKeys) {
|
|
if (knownKeys.has(elem)) {
|
|
intersection.add(elem)
|
|
}
|
|
}
|
|
|
|
intersection.forEach(
|
|
name => dispatch('setOption', { name, value: data[name] })
|
|
)
|
|
},
|
|
setHighlight ({ commit, dispatch }, { user, color, type }) {
|
|
commit('setHighlight', { user, color, type })
|
|
},
|
|
setOption ({ commit, dispatch }, { name, value, manual }) {
|
|
commit('setOption', { name, value })
|
|
if (manual === true) {
|
|
dispatch('syncSettings')
|
|
}
|
|
switch (name) {
|
|
case 'theme':
|
|
setPreset(value)
|
|
break
|
|
case 'customTheme':
|
|
case 'customThemeSource':
|
|
applyTheme(value)
|
|
break
|
|
case 'interfaceLanguage':
|
|
messages.setLanguage(this.getters.i18n, value)
|
|
Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value), {sameSite: 'Lax'})
|
|
dispatch('setInstanceOption', { name: 'interfaceLanguage', value })
|
|
break
|
|
case 'thirdColumnMode':
|
|
dispatch('setLayoutWidth', undefined)
|
|
break
|
|
}
|
|
},
|
|
getSettingsProfile (store, forceUpdate = false) {
|
|
const profile = store.state.profile
|
|
store.rootState.api.backendInteractor.getSettingsProfile({ store, profileName: profile })
|
|
.then(({ settings, version }) => {
|
|
console.log('found settings version', version)
|
|
if (forceUpdate || (version > store.state.profileVersion)) {
|
|
store.commit('setOption', { name: 'profileVersion', value: version })
|
|
Object.entries(settings).forEach(([name, value]) => {
|
|
if (store.state[name] !== value) {
|
|
store.dispatch('setOption', { name, value })
|
|
}
|
|
})
|
|
} else {
|
|
console.log('settings are up to date')
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
console.error(`could not fetch profile ${profile}`, err)
|
|
if (err.statusCode === 404) {
|
|
// create profile
|
|
store.dispatch('pushGlobalNotice', {
|
|
level: 'warning',
|
|
messageKey: 'settings_profile.creating',
|
|
messageArgs: { profile },
|
|
timeout: 5000
|
|
})
|
|
store.dispatch('syncSettings')
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
export default config
|