refactor: update preferences management and enhance PreferenceService documentation

- Updated preferences types in preferences.ts to use PreferenceTypes for better type safety.
- Added new preference keys related to notes features in preferences.ts.
- Enhanced documentation in PreferenceService.ts and usePreference.ts to clarify usage and update strategies.
- Improved caching and subscription mechanisms in PreferenceService for better performance and reliability.
This commit is contained in:
fullex 2025-09-04 12:47:22 +08:00
parent 7f114ade4d
commit c2a1178dff
3 changed files with 142 additions and 86 deletions

View File

@ -1,6 +1,6 @@
/**
* Auto-generated preferences configuration
* Generated at: 2025-09-03T04:46:03.708Z
* Generated at: 2025-09-03T13:39:01.110Z
*
* This file is automatically generated from classification.json
* To update this file, modify classification.json and run:
@ -10,24 +10,7 @@
*/
import { TRANSLATE_PROMPT } from '@shared/config/prompts'
import type {
AssistantIconType,
AssistantTabSortType,
ChatMessageNavigationMode,
ChatMessageStyle,
LanguageVarious,
MultiModelFoldDisplayMode,
MultiModelGridPopoverTrigger,
MultiModelMessageStyle,
ProxyMode,
SelectionActionItem,
SelectionFilterMode,
SelectionTriggerMode,
SendMessageShortcut,
SidebarIcon,
WindowStyle
} from '@shared/data/preferenceTypes'
import { ThemeMode, UpgradeChannel } from '@shared/data/preferenceTypes'
import * as PreferenceTypes from '@shared/data/preferenceTypes'
/* eslint @typescript-eslint/member-ordering: ["error", {
"interfaces": { "order": "alphabetically" },
@ -43,11 +26,11 @@ export interface PreferencesType {
// redux/settings/autoCheckUpdate
'app.dist.auto_update.enabled': boolean
// redux/settings/testChannel
'app.dist.test_plan.channel': UpgradeChannel
'app.dist.test_plan.channel': PreferenceTypes.UpgradeChannel
// redux/settings/testPlan
'app.dist.test_plan.enabled': boolean
// redux/settings/language
'app.language': LanguageVarious | null
'app.language': PreferenceTypes.LanguageVarious | null
// redux/settings/launchOnBoot
'app.launch_on_boot': boolean
// redux/settings/notification.assistant
@ -61,7 +44,7 @@ export interface PreferencesType {
// redux/settings/proxyBypassRules
'app.proxy.bypass_rules': string
// redux/settings/proxyMode
'app.proxy.mode': ProxyMode
'app.proxy.mode': PreferenceTypes.ProxyMode
// redux/settings/proxyUrl
'app.proxy.url': string
// redux/settings/enableSpellCheck
@ -83,11 +66,11 @@ export interface PreferencesType {
// redux/settings/clickAssistantToShowTopic
'assistant.click_to_show_topic': boolean
// redux/settings/assistantIconType
'assistant.icon_type': AssistantIconType
'assistant.icon_type': PreferenceTypes.AssistantIconType
// redux/settings/showAssistants
'assistant.tab.show': boolean
// redux/settings/assistantsTabSortType
'assistant.tab.sort_type': AssistantTabSortType
'assistant.tab.sort_type': PreferenceTypes.AssistantTabSortType
// redux/settings/codeCollapsible
'chat.code.collapsible': boolean
// redux/settings/codeEditor.autocompletion
@ -129,7 +112,7 @@ export interface PreferencesType {
// redux/settings/enableQuickPanelTriggers
'chat.input.quick_panel.triggers_enabled': boolean
// redux/settings/sendMessageShortcut
'chat.input.send_message_shortcut': SendMessageShortcut
'chat.input.send_message_shortcut': PreferenceTypes.SendMessageShortcut
// redux/settings/showInputEstimatedTokens
'chat.input.show_estimated_tokens': boolean
// redux/settings/autoTranslateWithSpace
@ -149,15 +132,15 @@ export interface PreferencesType {
// redux/settings/mathEnableSingleDollar
'chat.message.math.single_dollar': boolean
// redux/settings/foldDisplayMode
'chat.message.multi_model.fold_display_mode': MultiModelFoldDisplayMode
'chat.message.multi_model.fold_display_mode': PreferenceTypes.MultiModelFoldDisplayMode
// redux/settings/gridColumns
'chat.message.multi_model.grid_columns': number
// redux/settings/gridPopoverTrigger
'chat.message.multi_model.grid_popover_trigger': MultiModelGridPopoverTrigger
'chat.message.multi_model.grid_popover_trigger': PreferenceTypes.MultiModelGridPopoverTrigger
// redux/settings/multiModelMessageStyle
'chat.message.multi_model.style': MultiModelMessageStyle
'chat.message.multi_model.style': PreferenceTypes.MultiModelMessageStyle
// redux/settings/messageNavigation
'chat.message.navigation_mode': ChatMessageNavigationMode
'chat.message.navigation_mode': PreferenceTypes.ChatMessageNavigationMode
// redux/settings/renderInputMessageAsMarkdown
'chat.message.render_as_markdown': boolean
// redux/settings/showMessageDivider
@ -167,7 +150,7 @@ export interface PreferencesType {
// redux/settings/showPrompt
'chat.message.show_prompt': boolean
// redux/settings/messageStyle
'chat.message.style': ChatMessageStyle
'chat.message.style': PreferenceTypes.ChatMessageStyle
// redux/settings/thoughtAutoCollapse
'chat.message.thought.auto_collapse': boolean
// redux/settings/narrowMode
@ -312,7 +295,17 @@ export interface PreferencesType {
'feature.minapp.open_link_external': boolean
// redux/settings/showOpenedMinappsInSidebar
'feature.minapp.show_opened_in_sidebar': boolean
// redux/settings/showWorkspace
// redux/note/settings.defaultEditMode
'feature.notes.default_edit_mode': string
// redux/note/settings.fontFamily
'feature.notes.default_font_family': string
// redux/note/settings.defaultViewMode
'feature.notes.default_view_mode': string
// redux/note/settings.isFullWidth
'feature.notes.full_width': boolean
// redux/note/settings.showTabStatus
'feature.notes.show_tab_status': boolean
// redux/note/settings.showWorkspace
'feature.notes.show_workspace': boolean
// redux/settings/clickTrayToShowQuickAssistant
'feature.quick_assistant.click_tray_to_show': boolean
@ -321,7 +314,7 @@ export interface PreferencesType {
// redux/settings/readClipboardAtStartup
'feature.quick_assistant.read_clipboard_at_startup': boolean
// redux/selectionStore/actionItems
'feature.selection.action_items': SelectionActionItem[]
'feature.selection.action_items': PreferenceTypes.SelectionActionItem[]
// redux/selectionStore/actionWindowOpacity
'feature.selection.action_window_opacity': number
// redux/selectionStore/isAutoClose
@ -335,13 +328,13 @@ export interface PreferencesType {
// redux/selectionStore/filterList
'feature.selection.filter_list': string[]
// redux/selectionStore/filterMode
'feature.selection.filter_mode': SelectionFilterMode
'feature.selection.filter_mode': PreferenceTypes.SelectionFilterMode
// redux/selectionStore/isFollowToolbar
'feature.selection.follow_toolbar': boolean
// redux/selectionStore/isRemeberWinSize
'feature.selection.remember_win_size': boolean
// redux/selectionStore/triggerMode
'feature.selection.trigger_mode': SelectionTriggerMode
'feature.selection.trigger_mode': PreferenceTypes.SelectionTriggerMode
// redux/settings/translateModelPrompt
'feature.translate.model_prompt': string
// redux/settings/targetLanguage
@ -395,15 +388,15 @@ export interface PreferencesType {
// redux/settings/navbarPosition
'ui.navbar.position': 'left' | 'top'
// redux/settings/sidebarIcons.disabled
'ui.sidebar.icons.invisible': SidebarIcon[]
'ui.sidebar.icons.invisible': PreferenceTypes.SidebarIcon[]
// redux/settings/sidebarIcons.visible
'ui.sidebar.icons.visible': SidebarIcon[]
'ui.sidebar.icons.visible': PreferenceTypes.SidebarIcon[]
// redux/settings/theme
'ui.theme_mode': ThemeMode
'ui.theme_mode': PreferenceTypes.ThemeMode
// redux/settings/userTheme.colorPrimary
'ui.theme_user.color_primary': string
// redux/settings/windowStyle
'ui.window_style': WindowStyle
'ui.window_style': PreferenceTypes.WindowStyle
}
}
@ -413,7 +406,7 @@ export const DefaultPreferences: PreferencesType = {
'app.developer_mode.enabled': false,
'app.disable_hardware_acceleration': false,
'app.dist.auto_update.enabled': true,
'app.dist.test_plan.channel': UpgradeChannel.LATEST,
'app.dist.test_plan.channel': PreferenceTypes.UpgradeChannel.LATEST,
'app.dist.test_plan.enabled': false,
'app.language': null,
'app.launch_on_boot': false,
@ -548,6 +541,11 @@ export const DefaultPreferences: PreferencesType = {
'feature.minapp.max_keep_alive': 3,
'feature.minapp.open_link_external': false,
'feature.minapp.show_opened_in_sidebar': true,
'feature.notes.default_edit_mode': 'preview',
'feature.notes.default_font_family': 'default',
'feature.notes.default_view_mode': 'edit',
'feature.notes.full_width': true,
'feature.notes.show_tab_status': true,
'feature.notes.show_workspace': true,
'feature.quick_assistant.click_tray_to_show': false,
'feature.quick_assistant.enabled': false,
@ -648,7 +646,7 @@ export const DefaultPreferences: PreferencesType = {
'code_tools',
'notes'
],
'ui.theme_mode': ThemeMode.system,
'ui.theme_mode': PreferenceTypes.ThemeMode.system,
'ui.theme_user.color_primary': '#00b96b',
'ui.window_style': 'opaque'
}
@ -658,8 +656,8 @@ export const DefaultPreferences: PreferencesType = {
/**
* :
* - 总配置项: 184
* - 总配置项: 189
* - electronStore项: 1
* - redux项: 183
* - redux项: 188
* - localStorage项: 0
*/

View File

@ -7,9 +7,17 @@ import type {
} from '@shared/data/preferenceTypes'
const logger = loggerService.withContext('PreferenceService')
/**
* Renderer-side PreferenceService providing cached access to preferences
* with real-time synchronization across windows using useSyncExternalStore
* Renderer-side PreferenceService providing cached access to preferences with real-time synchronization
*
* Features:
* - Caching system for fast access to frequently used preferences
* - Optimistic and pessimistic update strategies
* - Real-time synchronization across windows via IPC
* - Race condition handling for concurrent updates
* - Batch operations for multiple preferences
* - Integration with React's useSyncExternalStore
*/
export class PreferenceService {
private static instance: PreferenceService
@ -53,6 +61,7 @@ export class PreferenceService {
/**
* Get the singleton instance of PreferenceService
* @returns The singleton PreferenceService instance
*/
public static getInstance(): PreferenceService {
if (!PreferenceService.instance) {
@ -63,6 +72,7 @@ export class PreferenceService {
/**
* Setup IPC change listener for preference updates from main process
* Establishes communication channel for real-time preference synchronization
*/
private setupChangeListeners() {
if (!window.api?.preference?.onChanged) {
@ -83,6 +93,7 @@ export class PreferenceService {
/**
* Notify all relevant listeners about preference changes
* @param key The preference key that changed
*/
private notifyChangeListeners(key: string) {
// Notify global listeners
@ -96,7 +107,9 @@ export class PreferenceService {
}
/**
* Get a single preference value with caching
* Get a single preference value with caching and auto-subscription
* @param key The preference key to retrieve
* @returns Promise resolving to the preference value with defaults applied
*/
public async get<K extends PreferenceKeyType>(key: K): Promise<PreferenceDefaultScopeType[K]> {
// Check cache first
@ -127,6 +140,10 @@ export class PreferenceService {
/**
* Set a single preference value with configurable update strategy
* @param key The preference key to update
* @param value The new value to set
* @param options Update strategy options (optimistic by default)
* @returns Promise that resolves when update completes
*/
public async set<K extends PreferenceKeyType>(
key: K,
@ -142,6 +159,10 @@ export class PreferenceService {
/**
* Optimistic update: Queue request to prevent race conditions
* Updates UI immediately, then syncs to database with rollback on failure
* @param key The preference key to update
* @param value The new value to set
* @returns Promise that resolves when update completes
*/
private async setOptimistic<K extends PreferenceKeyType>(
key: K,
@ -153,6 +174,10 @@ export class PreferenceService {
/**
* Execute optimistic update with proper race condition handling
* @param key The preference key to update
* @param value The new value to set
* @param requestId Unique identifier for this update request
* @returns Promise that resolves when update completes
*/
private async executeOptimisticUpdate(key: PreferenceKeyType, value: any, requestId: string): Promise<void> {
const existingState = this.optimisticValues.get(key)
@ -190,6 +215,10 @@ export class PreferenceService {
/**
* Pessimistic update: Wait for database confirmation before updating UI
* Updates database first, then UI on success
* @param key The preference key to update
* @param value The new value to set
* @returns Promise that resolves when update completes
*/
private async setPessimistic<K extends PreferenceKeyType>(
key: K,
@ -210,7 +239,9 @@ export class PreferenceService {
}
/**
* Get multiple preferences at once, return is Partial<PreferenceDefaultScopeType>
* Get multiple preferences at once with caching and auto-subscription
* @param keys Array of preference keys to retrieve
* @returns Promise resolving to partial object with preference values
*/
public async getMultipleRaw(keys: PreferenceKeyType[]): Promise<Partial<PreferenceDefaultScopeType>> {
// Check which keys are already cached
@ -261,6 +292,8 @@ export class PreferenceService {
/**
* Get multiple preferences at once and return them as a record of key-value pairs
* @param keys Object mapping local names to preference keys
* @returns Promise resolving to object with mapped preference values
*/
public async getMultiple<T extends Record<string, PreferenceKeyType>>(
keys: T
@ -277,6 +310,9 @@ export class PreferenceService {
/**
* Set multiple preferences at once with configurable update strategy
* @param updates Object containing preference key-value pairs to update
* @param options Update strategy options (optimistic by default)
* @returns Promise that resolves when all updates complete
*/
public async setMultiple(
updates: Partial<PreferenceDefaultScopeType>,
@ -291,6 +327,8 @@ export class PreferenceService {
/**
* Optimistic batch update: Update UI immediately, then sync to database
* @param updates Object containing preference key-value pairs to update
* @returns Promise that resolves when batch update completes
*/
private async setMultipleOptimistic(updates: Partial<PreferenceDefaultScopeType>): Promise<void> {
const batchRequestId = this.generateRequestId()
@ -346,6 +384,8 @@ export class PreferenceService {
/**
* Pessimistic batch update: Wait for database confirmation before updating UI
* @param updates Object containing preference key-value pairs to update
* @returns Promise that resolves when batch update completes
*/
private async setMultiplePessimistic(updates: Partial<PreferenceDefaultScopeType>): Promise<void> {
try {
@ -365,7 +405,9 @@ export class PreferenceService {
}
/**
* Subscribe to a specific key for change notifications
* Subscribe to specific keys for change notifications from main process
* @param keys Array of preference keys to subscribe to
* @returns Promise that resolves when subscription is established
*/
private async subscribeToKeyInternal(keys: PreferenceKeyType[]): Promise<void> {
const keysToSubscribe = keys.filter((key) => !this.subscribedKeys.has(key))
@ -382,6 +424,8 @@ export class PreferenceService {
/**
* Subscribe to global preference changes (for useSyncExternalStore)
* @param callback Function to call when any preference changes
* @returns Unsubscribe function
*/
public subscribeAllChanges = (callback: () => void): (() => void) => {
this.allChangesListeners.add(callback)
@ -392,6 +436,8 @@ export class PreferenceService {
/**
* Subscribe to specific key changes (for useSyncExternalStore)
* @param key The preference key to watch for changes
* @returns Function that takes a callback and returns an unsubscribe function
*/
public subscribeChange =
(key: PreferenceKeyType) =>
@ -416,6 +462,8 @@ export class PreferenceService {
/**
* Get cached value without async fetch
* @param key The preference key to retrieve from cache
* @returns The cached value or undefined if not cached
*/
public getCachedValue<K extends PreferenceKeyType>(key: K): PreferenceDefaultScopeType[K] | undefined {
return this.cache[key]
@ -423,14 +471,16 @@ export class PreferenceService {
/**
* Check if a preference is cached
* @param key The preference key to check
* @returns True if the key is cached, false otherwise
*/
public isCached(key: PreferenceKeyType): boolean {
return key in this.cache && this.cache[key] !== undefined
}
/**
* Load all preferences from main process at once
* Provides optimal performance by loading complete preference set into memory
* Load all preferences from main process at once for optimal performance
* @returns Promise resolving to all preference values
*/
public async preloadAll(): Promise<PreferenceDefaultScopeType> {
try {
@ -458,6 +508,7 @@ export class PreferenceService {
/**
* Check if all preferences are loaded in cache
* @returns True if full cache has been loaded, false otherwise
*/
public isFullyCached(): boolean {
return this.fullCacheLoaded
@ -465,6 +516,8 @@ export class PreferenceService {
/**
* Preload specific preferences into cache
* @param keys Array of preference keys to preload
* @returns Promise that resolves when preloading completes
*/
public async preload(keys: PreferenceKeyType[]): Promise<void> {
const uncachedKeys = keys.filter((key) => !this.isCached(key))
@ -480,7 +533,9 @@ export class PreferenceService {
}
/**
* Confirm an optimistic update (called when main process confirms the update)
* Confirm an optimistic update when main process confirms the update
* @param key The preference key that was updated
* @param requestId The unique identifier for the update request
*/
private confirmOptimistic(key: PreferenceKeyType, requestId: string): void {
const optimisticState = this.optimisticValues.get(key)
@ -498,7 +553,9 @@ export class PreferenceService {
}
/**
* Rollback an optimistic update (called on failure)
* Rollback an optimistic update when main process update fails
* @param key The preference key to rollback
* @param requestId The unique identifier for the failed update request
*/
private rollbackOptimistic(key: PreferenceKeyType, requestId: string): void {
const optimisticState = this.optimisticValues.get(key)
@ -523,7 +580,8 @@ export class PreferenceService {
}
/**
* Get all pending optimistic updates (for debugging)
* Get all pending optimistic updates for debugging purposes
* @returns Array of pending optimistic update information
*/
public getPendingOptimisticUpdates(): Array<{
key: string
@ -545,13 +603,18 @@ export class PreferenceService {
/**
* Generate unique request ID for tracking concurrent requests
* @returns Unique request identifier string
*/
private generateRequestId(): string {
return `req_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`
}
/**
* Add request to queue for a specific key
* Add request to queue for a specific key to prevent race conditions
* @param key The preference key to update
* @param requestId Unique identifier for this request
* @param value The value to set
* @returns Promise that resolves when the request is processed
*/
private enqueueRequest(key: PreferenceKeyType, requestId: string, value: any): Promise<void> {
return new Promise<void>((resolve, reject) => {
@ -571,6 +634,8 @@ export class PreferenceService {
/**
* Process the next queued request for a key
* @param key The preference key to process requests for
* @returns Promise that resolves when processing completes
*/
private async processNextQueuedRequest(key: PreferenceKeyType): Promise<void> {
const queue = this.requestQueues.get(key)
@ -589,6 +654,7 @@ export class PreferenceService {
/**
* Complete current request and process next in queue
* @param key The preference key to complete processing for
*/
private completeQueuedRequest(key: PreferenceKeyType): void {
const queue = this.requestQueues.get(key)
@ -606,7 +672,7 @@ export class PreferenceService {
}
/**
* Clear all cached preferences (for testing/debugging)
* Clear all cached preferences for testing/debugging
*/
public clearCache(): void {
this.cache = {}
@ -615,7 +681,7 @@ export class PreferenceService {
}
/**
* Cleanup service (call when shutting down)
* Cleanup service resources - call when shutting down
*/
public cleanup(): void {
if (this.changeListenerCleanup) {

View File

@ -17,9 +17,9 @@ const logger = loggerService.withContext('usePreference')
*
* @param key - The preference key to manage (must be a valid PreferenceKeyType)
* @param options - Optional configuration for update behavior:
* - strategy: 'optimistic' (default) for immediate UI updates, 'pessimistic' for database-first updates
* - optimistic: true (default) for immediate UI updates, false for database-first updates
* @returns A tuple [value, setValue] where:
* - value: Current preference value or undefined if not loaded/cached
* - value: Current preference value with defaults applied (never undefined)
* - setValue: Async function to update the preference value
*
* @example
@ -28,19 +28,14 @@ const logger = loggerService.withContext('usePreference')
* const [theme, setTheme] = usePreference('app.theme.mode')
*
* // Pessimistic updates for critical settings
* const [apiKey, setApiKey] = usePreference('api.key', { strategy: 'pessimistic' })
* const [apiKey, setApiKey] = usePreference('api.key', { optimistic: false })
*
* // Simple optimistic updates
* const [fontSize, setFontSize] = usePreference('chat.message.font_size', {
* strategy: 'optimistic'
* optimistic: true
* })
*
* // Conditional rendering based on preference value
* if (theme === undefined) {
* return <LoadingSpinner />
* }
*
* // Updating preference value
* // Value is never undefined - defaults are applied automatically
* const handleThemeChange = async (newTheme: string) => {
* try {
* await setTheme(newTheme) // UI updates immediately with optimistic strategy
@ -62,7 +57,7 @@ const logger = loggerService.withContext('usePreference')
* ```typescript
* // Advanced usage with form handling for message font size
* const [fontSize, setFontSize] = usePreference('chat.message.font_size', {
* strategy: 'optimistic' // Immediate feedback for UI preferences
* optimistic: true // Immediate feedback for UI preferences
* })
*
* const handleFontSizeChange = useCallback(async (size: number) => {
@ -75,7 +70,7 @@ const logger = loggerService.withContext('usePreference')
* return (
* <input
* type="number"
* value={fontSize ?? 14}
* value={fontSize}
* onChange={(e) => handleFontSizeChange(Number(e.target.value))}
* min={8}
* max={72}
@ -131,9 +126,9 @@ export function usePreference<K extends PreferenceKeyType>(
* @param keys - Object mapping local names to preference keys. Keys are your custom names,
* values must be valid PreferenceKeyType identifiers
* @param options - Optional configuration for update behavior:
* - strategy: 'optimistic' (default) for immediate UI updates, 'pessimistic' for database-first updates
* - optimistic: true (default) for immediate UI updates, false for database-first updates
* @returns A tuple [values, updateValues] where:
* - values: Object with your local keys mapped to current preference values (undefined if not loaded)
* - values: Object with your local keys mapped to current preference values with defaults applied
* - updateValues: Async function to batch update multiple preferences at once
*
* @example
@ -149,12 +144,12 @@ export function usePreference<K extends PreferenceKeyType>(
* const [apiSettings, setApiSettings] = useMultiplePreferences({
* apiKey: 'api.key',
* endpoint: 'api.endpoint'
* }, { strategy: 'pessimistic' })
* }, { optimistic: false })
*
* // Accessing individual values with type safety
* const currentTheme = uiSettings.theme // string | undefined
* const currentFontSize = uiSettings.fontSize // number | undefined
* const showLines = uiSettings.showLineNumbers // boolean | undefined
* // Accessing individual values with type safety (defaults applied automatically)
* const currentTheme = uiSettings.theme // string (never undefined)
* const currentFontSize = uiSettings.fontSize // number (never undefined)
* const showLines = uiSettings.showLineNumbers // boolean (never undefined)
*
* // Batch updating multiple preferences
* const resetToDefaults = async () => {
@ -198,11 +193,7 @@ export function usePreference<K extends PreferenceKeyType>(
* }
* }
*
* // Conditional rendering based on loading state
* if (Object.values(settings).every(val => val === undefined)) {
* return <SettingsSkeletonLoader />
* }
*
* // No need to check for undefined - defaults are applied automatically
* return (
* <form onSubmit={(e) => {
* e.preventDefault()
@ -214,13 +205,13 @@ export function usePreference<K extends PreferenceKeyType>(
* <input
* name="maxBackups"
* type="number"
* defaultValue={settings.maxBackups ?? 10}
* defaultValue={settings.maxBackups}
* min="0"
* />
* <input
* name="syncInterval"
* type="number"
* defaultValue={settings.syncInterval ?? 3600}
* defaultValue={settings.syncInterval}
* min="60"
* />
* <button type="submit">Save Backup Settings</button>
@ -241,12 +232,13 @@ export function usePreference<K extends PreferenceKeyType>(
*
* // Single subscription handles all code preferences
* // More efficient than 5 separate usePreference calls
* // No need for null checks - defaults are already applied
* const codeConfig = useMemo(() => ({
* showLineNumbers: codePrefs.showLineNumbers ?? false,
* wrappable: codePrefs.wrappable ?? false,
* collapsible: codePrefs.collapsible ?? false,
* autocompletion: codePrefs.autocompletion ?? true,
* foldGutter: codePrefs.foldGutter ?? false
* showLineNumbers: codePrefs.showLineNumbers,
* wrappable: codePrefs.wrappable,
* collapsible: codePrefs.collapsible,
* autocompletion: codePrefs.autocompletion,
* foldGutter: codePrefs.foldGutter
* }), [codePrefs])
*
* return <CodeBlock config={codeConfig} />