diff --git a/src/App.js b/src/App.js index 5b1403828..19d28f843 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,6 @@ import { throttle } from 'lodash' import { mapState } from 'pinia' -import { defineAsyncComponent, toValue } from 'vue' +import { defineAsyncComponent } from 'vue' import DesktopNav from './components/desktop_nav/desktop_nav.vue' import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue' @@ -32,9 +32,6 @@ import { useSyncConfigStore } from 'src/stores/sync_config.js' import messages from 'src/i18n/messages' import localeService from 'src/services/locale/locale.service.js' -// Helper to unwrap reactive proxies -window.toValue = toValue - export default { name: 'app', components: { diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 5c2aaa6ef..fe21eb442 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -39,8 +39,8 @@ import { useUserHighlightStore } from 'src/stores/user_highlight.js' import VBodyScrollLock from 'src/directives/body_scroll_lock' import { - instanceDefaultConfigDefinitions, - instanceIdentityDefaultDefinitions, + instanceDefaultConfig, + staticOrApiConfigDefault, } from 'src/modules/default_config_state.js' let staticInitialResults = null @@ -169,21 +169,17 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { config = Object.assign({}, staticConfig, apiConfig) } - const copyInstanceOption = ({ source, definition = { required: true }, destination }) => { - const value = config[source] - let { required, type, default: defaultValue } = definition - if (type == null && defaultValue != null) type = typeof defaultValue - if (required && value == null) return - if (type != null && typeof value !== type) return - - useInstanceStore().set({ path: destination, value }) + const copyInstanceOption = ({ source, destination }) => { + if (typeof config[source] !== 'undefined') { + useInstanceStore().set({ path: destination, value: config[source] }) + } } - Object.entries(instanceIdentityDefaultDefinitions) - .map(([source, definition]) => ({ source, definition, destination: `instanceIdentity.${source}` })) + Object.keys(staticOrApiConfigDefault) + .map((k) => ({ source: k, destination: `instanceIdentity.${k}` })) .forEach(copyInstanceOption) - Object.keys(instanceDefaultConfigDefinitions) - .map(([source, definition]) => ({ source, definition, destination: `prefsStorage.${source}` })) + Object.keys(instanceDefaultConfig) + .map((k) => ({ source: k, destination: `prefsStorage.${k}` })) .forEach(copyInstanceOption) useAuthFlowStore().setInitialStrategy(config.loginMethod) diff --git a/src/modules/default_config_state.js b/src/modules/default_config_state.js index 89047f314..8634752bd 100644 --- a/src/modules/default_config_state.js +++ b/src/modules/default_config_state.js @@ -1,514 +1,163 @@ const browserLocale = (navigator.language || 'en').split('-')[0] -const convertDefinitions = definitions => Object.fromEntries( - Object.entries(definitions).map(([k, v]) => [ - k, - v.default == null ? null : v.default, - ]), -) - /// Instance config entries provided by static config or pleroma api /// Put settings here only if it does not make sense for a normal user /// to override it. -export const instanceIdentityDefaultDefinitions = { - style: { - description: 'Instance default style name', - type: 'string', - required: false, - }, - palette: { - description: 'Instance default palette name', - type: 'string', - required: false, - }, - theme: { - description: 'Instance default theme name', - type: 'string', - required: false, - }, - defaultAvatar: { - description: "Default avatar image to use when user doesn't have one set", - type: 'string', - default: '/images/avi.png', - }, - defaultBanner: { - description: "Default banner image to use when user doesn't have one set", - type: 'string', - default: '/images/banner.png', - }, - background: { - description: 'Instance background/wallpaper', - type: 'string', - default: '/static/aurora_borealis.jpg', - }, - embeddedToS: { - description: 'Whether to show Terms of Service title bar', - type: 'boolean', - default: true, - }, - logo: { - description: 'Instance logo', - type: 'string', - default: '/static/logo.svg', - }, - logoMargin: { - description: 'Margin for logo (spacing above/below)', - type: 'string', - default: '.2em', - }, - logoMask: { - description: - 'Use logo as a mask (works well for monochrome/transparent logos)', - type: 'boolean', - default: true, - }, - logoLeft: { - description: 'Show logo on the left side of navbar', - type: 'boolean', - default: false, - }, - redirectRootLogin: { - description: 'Where to redirect user after login', - type: 'string', - default: '/main/friends', - }, - redirectRootNoLogin: { - description: 'Where to redirect anonymous visitors', - type: 'string', - default: '/main/all', - }, - hideSitename: { - description: 'Hide the instance name in navbar', - type: 'boolean', - default: false, - }, - nsfwCensorImage: { - description: 'Default NSFW censor image', - type: 'string', - required: false, - }, - showFeaturesPanel: { - description: 'Show features panel to anonymous visitors', - type: 'boolean', - default: true, - }, - showInstanceSpecificPanel: { - description: 'Show instance-specific panel', - type: 'boolean', - default: false, - }, +export const staticOrApiConfigDefault = { + name: 'PleromaFE', + theme: 'pleroma-dark', + palette: null, + style: null, + themeChecksum: undefined, + defaultAvatar: '/images/avi.png', + defaultBanner: '/images/banner.png', + background: '/static/aurora_borealis.jpg', + embeddedToS: true, + logo: '/static/logo.svg', + logoMargin: '.2em', + logoMask: true, + logoLeft: false, + redirectRootLogin: '/main/friends', + redirectRootNoLogin: '/main/all', + hideSitename: false, + nsfwCensorImage: null, + showFeaturesPanel: true, + showInstanceSpecificPanel: false, // Html stuff - instanceSpecificPanelContent: { - description: 'HTML of Instance-specific panel', - type: 'string', - required: false, - }, - tos: { - description: 'HTML of Terms of Service panel', - type: 'string', - required: false, - }, + instanceSpecificPanelContent: '', + tos: '', } -export const instanceIdentityDefault = convertDefinitions(instanceIdentityDefaultDefinitions) /// This object contains setting entries that makes sense /// at the user level. The defaults can also be overriden by /// instance admins in the frontend_configuration endpoint or static config. -export const instanceDefaultConfigDefinitions = { - expertLevel: { - description: 'Used to track which settings to show and hide in settings modal', - type: 'number', // not a boolean so we could potentially make multiple levels of expert-ness - default: 0, - }, - hideISP: { - description: 'Hide Instance-specific panel', - default: false, - }, - hideInstanceWallpaper: { - description: 'Hide Instance default background', - default: false, - }, - hideShoutbox: { - description: 'Hide shoutbox if present', - default: false, - }, - hideMutedPosts: { // bad name - description: 'Hide posts of muted users entirely', - default: false, - }, - hideMutedThreads: { - description: 'Hide muted threads entirely', - default: true, - }, - hideWordFilteredPosts: { - description: 'Hide wordfiltered posts entirely', - default: false, - }, - muteBotStatuses: { - description: 'Mute posts made by bots', - default: false, - }, - muteSensitiveStatuses: { - description: 'Mute posts marked as NSFW', - default: false, - }, - collapseMessageWithSubject: { - description: 'Collapse posts with subject', - default: false, - }, - padEmoji: { - description: 'Pad emoji with spaces when using emoji picker', - default: true, - }, - hideAttachmentsInConv: { - description: 'Hide attachments', - default: false, - }, - hideScrobbles: { - description: 'Hide scrobbles', - default: false, - }, - hideScrobblesAfter: { - description: 'Hide scrobbles older than', - default: '2d', - }, - maxThumbnails: { - description: 'Maximum attachments to show', - default: 16, - }, - loopVideo: { - description: 'Loop videos', - default: true, - }, - loopVideoSilentOnly: { - description: 'Loop only videos without sound', - default: true, - }, +export const instanceDefaultConfig = { + expertLevel: 0, // used to track which settings to show and hide + hideISP: false, + hideInstanceWallpaper: false, + hideShoutbox: false, + // bad name: actually hides posts of muted USERS + hideMutedPosts: false, + hideMutedThreads: true, + hideWordFilteredPosts: false, + muteBotStatuses: false, + muteSensitiveStatuses: false, + collapseMessageWithSubject: false, + padEmoji: true, + hideAttachmentsInConv: false, + hideScrobbles: false, + hideScrobblesAfter: '2d', + maxThumbnails: 16, + loopVideo: true, + loopVideoSilentOnly: true, /// This is not the streaming API configuration, but rather an option /// for automatically loading new posts into the timeline without /// the user clicking the Show New button. - streaming: { - description: 'Automatically show new posts', - default: false, - }, - pauseOnUnfocused: { - description: 'Pause showing new posts when tab is unfocused', - default: true, - }, - emojiReactionsOnTimeline: { - description: 'Show emoji reactions on timeline', - default: true, - }, - alwaysShowNewPostButton: { - description: 'Always show mobile "new post" button, even in desktop mode', - default: false, - }, - autohideFloatingPostButton: { - description: 'Automatically hide mobile "new post" button when scrolling down', - default: false, - }, - stopGifs: { - description: 'Play animated gifs on hover only', - default: true, - }, - replyVisibility: { - description: 'Type of replies to show', - default: 'all', - }, - thirdColumnMode: { - description: 'What to display in third column', - default: 'notifications', - }, + streaming: false, + emojiReactionsOnTimeline: true, + alwaysShowNewPostButton: false, + autohideFloatingPostButton: false, + pauseOnUnfocused: true, + stopGifs: true, + replyVisibility: 'all', + thirdColumnMode: 'notifications', notificationVisibility: { - description: 'What types of notifications to show', - default: { - follows: true, - mentions: true, - statuses: true, - likes: true, - repeats: true, - moves: true, - emojiReactions: true, - followRequest: true, - reports: true, - chatMention: true, - polls: true, - }, + follows: true, + mentions: true, + statuses: true, + likes: true, + repeats: true, + moves: true, + emojiReactions: true, + followRequest: true, + reports: true, + chatMention: true, + polls: true, }, notificationNative: { - description: 'What type of notifications to show desktop notification for', - default: { - follows: true, - mentions: true, - statuses: true, - likes: false, - repeats: false, - moves: false, - emojiReactions: false, - followRequest: true, - reports: true, - chatMention: true, - polls: true, - }, - }, - webPushNotifications: { - description: 'Use WebPush', - default: false, - }, - webPushAlwaysShowNotifications: { - description: 'Ignore filter when using WebPush', - default: false, - }, - interfaceLanguage: { - description: 'UI language', - default: browserLocale, - }, - hideScopeNotice: { - description: 'Hide scope notification', - default: false, - }, - scopeCopy: { - description: 'Copy scope like mastodon does', - default: true, - }, - subjectLineBehavior: { - description: 'How to treat subject line', - default: 'email', - }, - alwaysShowSubjectInput: { - description: 'Always show subject line field', - default: true, - }, - minimalScopesMode: { - description: 'Minimize amount of options shown in scope selector', - default: false, + follows: true, + mentions: true, + statuses: true, + likes: false, + repeats: false, + moves: false, + emojiReactions: false, + followRequest: true, + reports: true, + chatMention: true, + polls: true, }, + webPushNotifications: false, + webPushAlwaysShowNotifications: false, + interfaceLanguage: browserLocale, + hideScopeNotice: false, + scopeCopy: true, + subjectLineBehavior: 'email', + alwaysShowSubjectInput: true, + minimalScopesMode: false, // This hides statuses filtered via a word filter - hideFilteredStatuses: { - description: 'Hide wordfiltered entirely', - default: false, - }, + hideFilteredStatuses: false, // Confirmations - modalOnRepeat: { - description: 'Show confirmation modal for repeat', - default: false, - }, - modalOnUnfollow: { - description: 'Show confirmation modal for unfollow', - default: false, - }, - modalOnBlock: { - description: 'Show confirmation modal for block', - default: true, - }, - modalOnMute: { - description: 'Show confirmation modal for mute', - default: false, - }, - modalOnMuteConversation: { - description: 'Show confirmation modal for mute conversation', - default: false, - }, - modalOnMuteDomain: { - description: 'Show confirmation modal for mute domain', - default: true, - }, - modalOnDelete: { - description: 'Show confirmation modal for delete', - default: true, - }, - modalOnLogout: { - description: 'Show confirmation modal for logout', - default: true, - }, - modalOnApproveFollow: { - description: 'Show confirmation modal for approve follow', - default: false, - }, - modalOnDenyFollow: { - description: 'Show confirmation modal for deny follow', - default: false, - }, - modalOnRemoveUserFromFollowers: { - description: 'Show confirmation modal for follower removal', - default: false, - }, + modalOnRepeat: false, + modalOnUnfollow: false, + modalOnBlock: true, + modalOnMute: false, + modalOnMuteConversation: false, + modalOnMuteDomain: true, + modalOnDelete: true, + modalOnLogout: true, + modalOnApproveFollow: false, + modalOnDenyFollow: false, + modalOnRemoveUserFromFollowers: false, // Expiry confirmations/default actions - onMuteDefaultAction: { - description: 'Default action when muting user', - default: 'ask', - }, - onBlockDefaultAction: { - description: 'Default action when blocking user', - default: 'ask', - }, + onMuteDefaultAction: 'ask', + onBlockDefaultAction: 'ask', - modalMobileCenter: { - description: 'Center mobile dialogs vertically', - default: false, - }, - playVideosInModal: { - description: 'Play videos in gallery view', - default: false, - }, - useContainFit: { - description: 'Use object-fit: contain for attachments', - default: true, - }, - disableStickyHeaders: { - description: 'Disable sticky headers', - default: false, - }, - showScrollbars: { - description: 'Always show scrollbars', - default: false, - }, - userPopoverAvatarAction: { - description: 'What to do when clicking popover avatar', - default: 'open', - }, - userPopoverOverlay: { - description: 'Overlay user popover with centering on avatar', - default: false, - }, - userCardLeftJustify: { - description: 'Justify user bio to the left', - default: false, - }, - userCardHidePersonalMarks: { - description: 'Hide highlight/personal note in user view', - default: false, - }, - forcedRoundness: { - description: 'Force roundness of the theme', - default: -1, - }, - greentext: { - description: 'Highlight plaintext >quotes', - default: false, - }, - mentionLinkShowTooltip: { - description: 'Show tooltips for mention links', - default: true, - }, - mentionLinkShowAvatar: { - description: 'Show avatar next to mention link', - default: false, - }, - mentionLinkFadeDomain: { - description: 'Mute (fade) domain name in mention links if configured to show it', - default: true, - }, - mentionLinkShowYous: { - description: 'Show (you)s when you are mentioned', - default: false, - }, - mentionLinkBoldenYou: { - description: 'Boldern mentionlink of you', - default: true, - }, - hidePostStats: { - description: 'Hide post stats (rt, favs)', - default: false, - }, - hideBotIndication: { - description: 'Hide bot indicator', - default: false, - }, - hideUserStats: { - description: 'Hide user stats (followers etc)', - default: false, - }, - virtualScrolling: { - description: 'Timeline virtual scrolling', - default: true, - }, - sensitiveByDefault: { - description: 'Assume attachments are NSFW by default', - default: false, - }, - conversationDisplay: { - description: 'Style of conversation display', - default: 'linear', - }, - conversationTreeAdvanced: { - description: 'Advanced features of tree view conversation', - default: false, - }, - conversationOtherRepliesButton: { - description: 'Where to show "other replies" in tree conversation view', - default: 'below', - }, - conversationTreeFadeAncestors: { - description: 'Fade ancestors in tree conversation view', - default: false, - }, - showExtraNotifications: { - description: 'Show extra notifications (chats, announcements etc) in notification panel', - default: true, - }, - showExtraNotificationsTip: { - description: 'Show tip for extra notifications (that user can remove them)', - default: true, - }, - showChatsInExtraNotifications: { - description: 'Show chat messages in notifications', - default: true, - }, - showAnnouncementsInExtraNotifications: { - description: 'Show announcements in notifications', - default: true, - }, - showFollowRequestsInExtraNotifications: { - description: 'Show follow requests in notifications', - default: true, - }, - maxDepthInThread: { - description: 'Maximum depth in tree conversation view', - default: 6, - }, - autocompleteSelect: { - description: '', - default: false, - }, - closingDrawerMarksAsSeen: { - description: 'Closing mobile notification pane marks everything as seen', - default: true, - }, - unseenAtTop: { - description: 'Show unseen notifications above others', - default: false, - }, - ignoreInactionableSeen: { - description: 'Treat inactionable (fav, rt etc) notifications as "seen"', - default: false, - }, - unsavedPostAction: { - description: 'What to do if post is aborted', - default: 'confirm', - }, - autoSaveDraft: { - description: 'Save drafts automatically', - default: false, - }, - useAbsoluteTimeFormat: { - description: 'Use absolute time format', - default: false, - }, - absoluteTimeFormatMinAge: { - description: 'Show absolute time format only after this post age', - default: '0d', - }, - absoluteTime12h: { - description: 'Use 24h time format', - default: '24h', - }, + modalMobileCenter: false, + playVideosInModal: false, + useContainFit: true, + disableStickyHeaders: false, + showScrollbars: false, + userPopoverAvatarAction: 'open', + userPopoverOverlay: false, + userCardLeftJustify: false, + userCardHidePersonalMarks: false, + forcedRoundness: -1, + greentext: false, + mentionLinkShowTooltip: true, + mentionLinkShowAvatar: false, + mentionLinkFadeDomain: true, + mentionLinkShowYous: false, + mentionLinkBoldenYou: true, + hidePostStats: false, + hideBotIndication: false, + hideUserStats: false, + virtualScrolling: true, + sensitiveByDefault: false, + conversationDisplay: 'linear', + conversationTreeAdvanced: false, + conversationOtherRepliesButton: 'below', + conversationTreeFadeAncestors: false, + showExtraNotifications: true, + showExtraNotificationsTip: true, + showChatsInExtraNotifications: true, + showAnnouncementsInExtraNotifications: true, + showFollowRequestsInExtraNotifications: true, + maxDepthInThread: 6, + autocompleteSelect: false, + closingDrawerMarksAsSeen: true, + unseenAtTop: false, + ignoreInactionableSeen: false, + unsavedPostAction: 'confirm', + autoSaveDraft: false, + useAbsoluteTimeFormat: false, + absoluteTimeFormatMinAge: '0d', + absoluteTime12h: '24h', } -export const instanceDefaultConfig = convertDefinitions(instanceDefaultConfigDefinitions) export const defaultConfigLocal = { hideAttachments: false, @@ -540,7 +189,6 @@ export const defaultConfigLocal = { themeDebug: false, // debug mode that uses computed backgrounds instead of real ones to debug contrast functions forceThemeRecompilation: false, // flag that forces recompilation on boot even if cache exists } - export const LOCAL_ONLY_KEYS = new Set(Object.keys(defaultConfigLocal)) export const makeUndefined = (c) => diff --git a/src/stores/instance.js b/src/stores/instance.js index f1f31a8d1..c893cab64 100644 --- a/src/stores/instance.js +++ b/src/stores/instance.js @@ -3,9 +3,9 @@ import { defineStore } from 'pinia' import { instanceDefaultProperties } from '../modules/config.js' import { - defaultConfigLocal, instanceDefaultConfig, - instanceIdentityDefault, + defaultConfigLocal, + staticOrApiConfigDefault, } from '../modules/default_config_state.js' import apiService from '../services/api/api.service.js' @@ -16,6 +16,7 @@ import { ensureFinalFallback } from 'src/i18n/languages.js' const REMOTE_INTERACTION_URL = '/main/ostatus' const defaultState = { + // Stuff from apiConfig name: 'Pleroma FE', registrationOpen: true, server: 'http://localhost:4040/', @@ -35,7 +36,7 @@ const defaultState = { // Instance-wide configurations that should not be changed by individual users instanceIdentity: { - ...instanceIdentityDefault, + ...staticOrApiConfigDefault, }, limits: { diff --git a/src/stores/interface.js b/src/stores/interface.js index f5a6c7d8a..32a8226b4 100644 --- a/src/stores/interface.js +++ b/src/stores/interface.js @@ -412,13 +412,15 @@ export const useInterfaceStore = defineStore('interface', { return result } - let { - theme: instanceThemeName, - style: instanceStyleName, - palette: instancePaletteName, - } = useInstanceStore().instanceIdentity + const { style: instanceStyleName, palette: instancePaletteName } = + useInstanceStore() - let { themesIndex, stylesIndex, palettesIndex } = useInstanceStore() + let { + theme: instanceThemeV2Name, + themesIndex, + stylesIndex, + palettesIndex, + } = useInstanceStore() const { style: userStyleName, @@ -445,7 +447,7 @@ export const useInterfaceStore = defineStore('interface', { console.debug( `Instance V3 palette: ${instancePaletteName}, style: ${instanceStyleName}`, ) - console.debug('Instance V2 theme: ' + instanceThemeName) + console.debug('Instance V2 theme: ' + instanceThemeV2Name) if ( userPaletteName || @@ -454,11 +456,11 @@ export const useInterfaceStore = defineStore('interface', { userStyleCustomData || // User V2 overrides instance V3 ((instancePaletteName || instanceStyleName) && - instanceThemeName == null && + instanceThemeV2Name == null && userThemeV2Name == null) ) { // Palette and/or style overrides V2 themes - instanceThemeName = null + instanceThemeV2Name = null userThemeV2Name = null userThemeV2Source = null userThemeV2Snapshot = null @@ -468,7 +470,7 @@ export const useInterfaceStore = defineStore('interface', { userThemeV2Name || userThemeV2Snapshot || userThemeV2Source || - instanceThemeName + instanceThemeV2Name ) { majorVersionUsed = 'v2' } else { @@ -586,7 +588,7 @@ export const useInterfaceStore = defineStore('interface', { 'theme', themesIndex, userThemeV2Source || userThemeV2Snapshot, - userThemeV2Name || instanceThemeName, + userThemeV2Name || instanceThemeV2Name, ) this.themeNameUsed = theme.nameUsed this.themeDataUsed = theme.dataUsed diff --git a/src/stores/sync_config.js b/src/stores/sync_config.js index d4b5ad50a..84011e2cf 100644 --- a/src/stores/sync_config.js +++ b/src/stores/sync_config.js @@ -643,9 +643,7 @@ export const useSyncConfigStore = defineStore('sync_config', { vuexState.config = vuexState.config ?? {} const migratedEntries = new Set(vuexState.config._syncMigration ?? []) - console.debug( - `Already migrated Values: ${[...migratedEntries].join() || '[none]'}`, - ) + console.debug(`Already migrated Values: ${[...migratedEntries].join() || '[none]'}`) Object.entries(oldDefaultConfigSync).forEach(([key, value]) => { const oldValue = vuexState.config[key] diff --git a/test/unit/specs/stores/sync_config.spec.js b/test/unit/specs/stores/sync_config.spec.js index eef6e39ac..8078b5949 100644 --- a/test/unit/specs/stores/sync_config.spec.js +++ b/test/unit/specs/stores/sync_config.spec.js @@ -1,8 +1,6 @@ import { cloneDeep } from 'lodash' import { createPinia, setActivePinia } from 'pinia' -import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' - import { _getAllFlags, _getRecentData, @@ -17,6 +15,7 @@ import { useSyncConfigStore, VERSION, } from 'src/stores/sync_config.js' +import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' describe('The SyncConfig store', () => { beforeEach(() => {