feat(settings): add editor settings for indentation

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2023-10-13 19:30:53 +02:00
parent 3bc9708871
commit 395305dcb7
14 changed files with 852 additions and 15 deletions

View file

@ -609,9 +609,19 @@
"label": "Line Wrapping", "label": "Line Wrapping",
"help": "Breaks long lines so they're visible without scrolling." "help": "Breaks long lines so they're visible without scrolling."
}, },
"spellCheck":{ "spellCheck": {
"label": "Spell checking", "label": "Spell checking",
"help": "Enables browser spell checking." "help": "Enables browser spell checking."
},
"indentWithTabs": {
"label": "Indentation character",
"help": "Sets which characters should be used for indentation",
"tabs": "Tabs",
"spaces": "Spaces"
},
"indentSpaces": {
"label": "Indentation size",
"help": "Sets the amount of spaces for indentation."
} }
}, },
"global": { "global": {

View file

@ -45,6 +45,7 @@ import ReactCodeMirror from '@uiw/react-codemirror'
import React, { useCallback, useEffect, useMemo } from 'react' import React, { useCallback, useEffect, useMemo } from 'react'
import { useUiNotifications } from '../../notifications/ui-notification-boundary' import { useUiNotifications } from '../../notifications/ui-notification-boundary'
import { Lock as IconLock } from 'react-bootstrap-icons' import { Lock as IconLock } from 'react-bootstrap-icons'
import { useCodeMirrorIndentationExtension } from './hooks/codemirror-extensions/use-code-mirror-indentation-extension'
export type EditorPaneProps = ScrollProps export type EditorPaneProps = ScrollProps
@ -71,6 +72,7 @@ export const EditorPane: React.FC<EditorPaneProps> = ({ scrollState, onScroll, o
const fileInsertExtension = useCodeMirrorFileInsertExtension() const fileInsertExtension = useCodeMirrorFileInsertExtension()
const spellCheckExtension = useCodeMirrorSpellCheckExtension() const spellCheckExtension = useCodeMirrorSpellCheckExtension()
const lineWrappingExtension = useCodeMirrorLineWrappingExtension() const lineWrappingExtension = useCodeMirrorLineWrappingExtension()
const indentationExtension = useCodeMirrorIndentationExtension()
const cursorActivityExtension = useCursorActivityCallback() const cursorActivityExtension = useCursorActivityCallback()
const autoCompletionExtension = useCodeMirrorAutocompletionsExtension() const autoCompletionExtension = useCodeMirrorAutocompletionsExtension()
@ -107,7 +109,8 @@ export const EditorPane: React.FC<EditorPaneProps> = ({ scrollState, onScroll, o
cursorActivityExtension, cursorActivityExtension,
updateViewContextExtension, updateViewContextExtension,
yjsExtension, yjsExtension,
spellCheckExtension spellCheckExtension,
indentationExtension
], ],
[ [
linterExtension, linterExtension,
@ -120,7 +123,8 @@ export const EditorPane: React.FC<EditorPaneProps> = ({ scrollState, onScroll, o
updateViewContextExtension, updateViewContextExtension,
yjsExtension, yjsExtension,
spellCheckExtension, spellCheckExtension,
lineWrappingExtension lineWrappingExtension,
indentationExtension
] ]
) )

View file

@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
import type { Extension } from '@codemirror/state'
import { useMemo } from 'react'
import { indentUnit } from '@codemirror/language'
/**
* Creates a {@link Extension codemirror extension} that manages the indentation config.
*/
export const useCodeMirrorIndentationExtension = (): Extension => {
const indentWithTabs = useApplicationState((state) => state.editorConfig.indentWithTabs)
const indentSpaces = useApplicationState((state) => state.editorConfig.indentSpaces)
return useMemo(() => indentUnit.of(indentWithTabs ? '\t' : ' '.repeat(indentSpaces)), [indentWithTabs, indentSpaces])
}

View file

@ -0,0 +1,587 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EditorSettingsTabContent hides space settings indentWithTabs is true 1`] = `
<div>
<div
class="list-group"
>
<div
class="list-group-item"
>
<div
class="row"
>
<div
class="col-md-3"
>
settings.editor.ligatures.label
</div>
<div
class="col-md-4"
>
<div
class="btn-group"
role="group"
>
<button
class="btn btn-outline-secondary"
data-testid="onOffButtonGroupOn"
name="settings-ligatures"
title="common.on"
type="radio"
>
common.on
</button>
<button
class="btn btn-secondary"
data-testid="onOffButtonGroupOff"
name="settings-ligatures"
title="common.off"
type="radio"
>
common.off
</button>
</div>
</div>
<div
class="col-md-5"
>
settings.editor.ligatures.help
</div>
</div>
</div>
<div
class="list-group-item"
>
<div
class="row"
>
<div
class="col-md-3"
>
settings.editor.smartPaste.label
</div>
<div
class="col-md-4"
>
<div
class="btn-group"
role="group"
>
<button
class="btn btn-outline-secondary"
data-testid="onOffButtonGroupOn"
name="settings-smart-paste"
title="common.on"
type="radio"
>
common.on
</button>
<button
class="btn btn-secondary"
data-testid="onOffButtonGroupOff"
name="settings-smart-paste"
title="common.off"
type="radio"
>
common.off
</button>
</div>
</div>
<div
class="col-md-5"
>
settings.editor.smartPaste.help
</div>
</div>
</div>
<div
class="list-group-item"
>
<div
class="row"
>
<div
class="col-md-3"
>
settings.editor.syncScroll.label
</div>
<div
class="col-md-4"
>
<div
class="btn-group"
role="group"
>
<button
class="btn btn-outline-secondary"
data-testid="onOffButtonGroupOn"
name="settings-sync-scroll"
title="common.on"
type="radio"
>
common.on
</button>
<button
class="btn btn-secondary"
data-testid="onOffButtonGroupOff"
name="settings-sync-scroll"
title="common.off"
type="radio"
>
common.off
</button>
</div>
</div>
<div
class="col-md-5"
>
settings.editor.syncScroll.help
</div>
</div>
</div>
<div
class="list-group-item"
>
<div
class="row"
>
<div
class="col-md-3"
>
settings.editor.lineWrapping.label
</div>
<div
class="col-md-4"
>
<div
class="btn-group"
role="group"
>
<button
class="btn btn-outline-secondary"
data-testid="onOffButtonGroupOn"
name="settings-line-wrapping"
title="common.on"
type="radio"
>
common.on
</button>
<button
class="btn btn-secondary"
data-testid="onOffButtonGroupOff"
name="settings-line-wrapping"
title="common.off"
type="radio"
>
common.off
</button>
</div>
</div>
<div
class="col-md-5"
>
settings.editor.lineWrapping.help
</div>
</div>
</div>
<div
class="list-group-item"
>
<div
class="row"
>
<div
class="col-md-3"
>
settings.editor.spellCheck.label
</div>
<div
class="col-md-4"
>
<div
class="btn-group"
role="group"
>
<button
class="btn btn-outline-secondary"
data-testid="onOffButtonGroupOn"
name="settings-spell-check"
title="common.on"
type="radio"
>
common.on
</button>
<button
class="btn btn-secondary"
data-testid="onOffButtonGroupOff"
name="settings-spell-check"
title="common.off"
type="radio"
>
common.off
</button>
</div>
</div>
<div
class="col-md-5"
>
settings.editor.spellCheck.help
</div>
</div>
</div>
<div
class="list-group-item"
>
<div
class="row"
>
<div
class="col-md-3"
>
settings.editor.indentWithTabs.label
</div>
<div
class="col-md-4"
>
<div
class="btn-group"
role="group"
>
<button
class="btn btn-secondary"
data-testid="onOffButtonGroupOn"
name="settings-indent-with-tabs"
title="settings.editor.indentWithTabs.tabs"
type="radio"
>
settings.editor.indentWithTabs.tabs
</button>
<button
class="btn btn-outline-secondary"
data-testid="onOffButtonGroupOff"
name="settings-indent-with-tabs"
title="settings.editor.indentWithTabs.spaces"
type="radio"
>
settings.editor.indentWithTabs.spaces
</button>
</div>
</div>
<div
class="col-md-5"
>
settings.editor.indentWithTabs.help
</div>
</div>
</div>
</div>
</div>
`;
exports[`EditorSettingsTabContent renders space settings when indentWithTabs is false 1`] = `
<div>
<div
class="list-group"
>
<div
class="list-group-item"
>
<div
class="row"
>
<div
class="col-md-3"
>
settings.editor.ligatures.label
</div>
<div
class="col-md-4"
>
<div
class="btn-group"
role="group"
>
<button
class="btn btn-outline-secondary"
data-testid="onOffButtonGroupOn"
name="settings-ligatures"
title="common.on"
type="radio"
>
common.on
</button>
<button
class="btn btn-secondary"
data-testid="onOffButtonGroupOff"
name="settings-ligatures"
title="common.off"
type="radio"
>
common.off
</button>
</div>
</div>
<div
class="col-md-5"
>
settings.editor.ligatures.help
</div>
</div>
</div>
<div
class="list-group-item"
>
<div
class="row"
>
<div
class="col-md-3"
>
settings.editor.smartPaste.label
</div>
<div
class="col-md-4"
>
<div
class="btn-group"
role="group"
>
<button
class="btn btn-outline-secondary"
data-testid="onOffButtonGroupOn"
name="settings-smart-paste"
title="common.on"
type="radio"
>
common.on
</button>
<button
class="btn btn-secondary"
data-testid="onOffButtonGroupOff"
name="settings-smart-paste"
title="common.off"
type="radio"
>
common.off
</button>
</div>
</div>
<div
class="col-md-5"
>
settings.editor.smartPaste.help
</div>
</div>
</div>
<div
class="list-group-item"
>
<div
class="row"
>
<div
class="col-md-3"
>
settings.editor.syncScroll.label
</div>
<div
class="col-md-4"
>
<div
class="btn-group"
role="group"
>
<button
class="btn btn-outline-secondary"
data-testid="onOffButtonGroupOn"
name="settings-sync-scroll"
title="common.on"
type="radio"
>
common.on
</button>
<button
class="btn btn-secondary"
data-testid="onOffButtonGroupOff"
name="settings-sync-scroll"
title="common.off"
type="radio"
>
common.off
</button>
</div>
</div>
<div
class="col-md-5"
>
settings.editor.syncScroll.help
</div>
</div>
</div>
<div
class="list-group-item"
>
<div
class="row"
>
<div
class="col-md-3"
>
settings.editor.lineWrapping.label
</div>
<div
class="col-md-4"
>
<div
class="btn-group"
role="group"
>
<button
class="btn btn-outline-secondary"
data-testid="onOffButtonGroupOn"
name="settings-line-wrapping"
title="common.on"
type="radio"
>
common.on
</button>
<button
class="btn btn-secondary"
data-testid="onOffButtonGroupOff"
name="settings-line-wrapping"
title="common.off"
type="radio"
>
common.off
</button>
</div>
</div>
<div
class="col-md-5"
>
settings.editor.lineWrapping.help
</div>
</div>
</div>
<div
class="list-group-item"
>
<div
class="row"
>
<div
class="col-md-3"
>
settings.editor.spellCheck.label
</div>
<div
class="col-md-4"
>
<div
class="btn-group"
role="group"
>
<button
class="btn btn-outline-secondary"
data-testid="onOffButtonGroupOn"
name="settings-spell-check"
title="common.on"
type="radio"
>
common.on
</button>
<button
class="btn btn-secondary"
data-testid="onOffButtonGroupOff"
name="settings-spell-check"
title="common.off"
type="radio"
>
common.off
</button>
</div>
</div>
<div
class="col-md-5"
>
settings.editor.spellCheck.help
</div>
</div>
</div>
<div
class="list-group-item"
>
<div
class="row"
>
<div
class="col-md-3"
>
settings.editor.indentWithTabs.label
</div>
<div
class="col-md-4"
>
<div
class="btn-group"
role="group"
>
<button
class="btn btn-outline-secondary"
data-testid="onOffButtonGroupOn"
name="settings-indent-with-tabs"
title="settings.editor.indentWithTabs.tabs"
type="radio"
>
settings.editor.indentWithTabs.tabs
</button>
<button
class="btn btn-secondary"
data-testid="onOffButtonGroupOff"
name="settings-indent-with-tabs"
title="settings.editor.indentWithTabs.spaces"
type="radio"
>
settings.editor.indentWithTabs.spaces
</button>
</div>
</div>
<div
class="col-md-5"
>
settings.editor.indentWithTabs.help
</div>
</div>
</div>
<div
class="list-group-item"
>
<div
class="row"
>
<div
class="col-md-3"
>
settings.editor.indentSpaces.label
</div>
<div
class="col-md-4"
>
<input
class="w-auto form-control"
min="1"
type="number"
value="7"
/>
</div>
<div
class="col-md-5"
>
settings.editor.indentSpaces.help
</div>
</div>
</div>
</div>
</div>
`;

View file

@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { mockAppState } from '../../../../test-utils/mock-app-state'
import { render } from '@testing-library/react'
import { EditorSettingsTabContent } from './editor-settings-tab-content'
import { mockI18n } from '../../../../test-utils/mock-i18n'
jest.mock('../../../../hooks/common/use-application-state')
describe('EditorSettingsTabContent', () => {
beforeEach(async () => {
await mockI18n()
})
afterEach(() => {
jest.resetAllMocks()
jest.resetModules()
})
it('renders space settings when indentWithTabs is false', () => {
mockAppState({
editorConfig: {
syncScroll: false,
spellCheck: false,
smartPaste: false,
lineWrapping: false,
ligatures: false,
indentWithTabs: false,
indentSpaces: 7
}
})
const view = render(<EditorSettingsTabContent />)
expect(view.container).toMatchSnapshot()
})
it('hides space settings indentWithTabs is true', () => {
mockAppState({
editorConfig: {
syncScroll: false,
spellCheck: false,
smartPaste: false,
lineWrapping: false,
ligatures: false,
indentWithTabs: true,
indentSpaces: 7
}
})
const view = render(<EditorSettingsTabContent />)
expect(view.container).toMatchSnapshot()
})
})

View file

@ -12,12 +12,16 @@ import React from 'react'
import { ListGroup } from 'react-bootstrap' import { ListGroup } from 'react-bootstrap'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { SpellcheckSettingButtonGroup } from './spellcheck-setting-button-group' import { SpellcheckSettingButtonGroup } from './spellcheck-setting-button-group'
import { IndentWithTabsSettingButtonGroup } from './indent-with-tabs-setting-button-group'
import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { IndentSpacesSettingInput } from './indent-spaces-setting-input'
/** /**
* Shows the editor specific settings. * Shows the editor specific settings.
*/ */
export const EditorSettingsTabContent: React.FC = () => { export const EditorSettingsTabContent: React.FC = () => {
useTranslation() useTranslation()
const useTabs = useApplicationState((state) => state.editorConfig.indentWithTabs)
return ( return (
<ListGroup> <ListGroup>
@ -36,6 +40,14 @@ export const EditorSettingsTabContent: React.FC = () => {
<SettingLine i18nKey={'editor.spellCheck'}> <SettingLine i18nKey={'editor.spellCheck'}>
<SpellcheckSettingButtonGroup /> <SpellcheckSettingButtonGroup />
</SettingLine> </SettingLine>
<SettingLine i18nKey={'editor.indentWithTabs'}>
<IndentWithTabsSettingButtonGroup />
</SettingLine>
{!useTabs && (
<SettingLine i18nKey={'editor.indentSpaces'}>
<IndentSpacesSettingInput />
</SettingLine>
)}
</ListGroup> </ListGroup>
) )
} }

View file

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React from 'react'
import { Form } from 'react-bootstrap'
import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { setEditorIndentSpaces } from '../../../../redux/editor/methods'
import { useCallback } from 'react'
/**
* Input to change the number of spaces that are used for indentation in the editor.
*/
export const IndentSpacesSettingInput: React.FC = () => {
const spaces = useApplicationState((state) => state.editorConfig.indentSpaces)
const onChangeHandler = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const value = parseInt(event.target.value)
if (value > 0) {
setEditorIndentSpaces(value)
}
}, [])
return <Form.Control className={'w-auto'} type={'number'} min={1} value={spaces} onChange={onChangeHandler} />
}

View file

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { setEditorIndentWithTabs } from '../../../../redux/editor/methods'
import { OnOffButtonGroup } from '../utils/on-off-button-group'
import React from 'react'
/**
* Allows to change whether spellchecking is enabled or not in the editor.
*/
export const IndentWithTabsSettingButtonGroup: React.FC = () => {
const enabled = useApplicationState((state) => state.editorConfig.indentWithTabs)
return (
<OnOffButtonGroup
value={enabled}
onSelect={setEditorIndentWithTabs}
overrideButtonOnI18nKey={'settings.editor.indentWithTabs.tabs'}
overrideButtonOffI18nKey={'settings.editor.indentWithTabs.spaces'}
/>
)
}

View file

@ -1,5 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Settings On-Off Button Group accepts custom labels 1`] = `
<div>
<div
class="btn-group"
role="group"
>
<button
class="btn btn-secondary"
data-testid="onOffButtonGroupOn"
name="dark-mode"
title="test.custom-on"
type="radio"
>
test.custom-on
</button>
<button
class="btn btn-outline-secondary"
data-testid="onOffButtonGroupOff"
name="dark-mode"
title="test.custom-off"
type="radio"
>
test.custom-off
</button>
</div>
</div>
`;
exports[`Settings On-Off Button Group can switch value 1`] = ` exports[`Settings On-Off Button Group can switch value 1`] = `
<div> <div>
<div <div

View file

@ -30,4 +30,16 @@ describe('Settings On-Off Button Group', () => {
}) })
expect(value).toBeFalsy() expect(value).toBeFalsy()
}) })
it('accepts custom labels', () => {
const view = render(
<OnOffButtonGroup
value={true}
onSelect={() => {}}
overrideButtonOnI18nKey={'test.custom-on'}
overrideButtonOffI18nKey={'test.custom-off'}
/>
)
expect(view.container).toMatchSnapshot()
})
}) })

View file

@ -16,6 +16,8 @@ enum OnOffState {
export interface OnOffButtonGroupProps { export interface OnOffButtonGroupProps {
value: boolean value: boolean
onSelect: (value: boolean) => void onSelect: (value: boolean) => void
overrideButtonOnI18nKey?: string
overrideButtonOffI18nKey?: string
} }
/** /**
@ -23,8 +25,15 @@ export interface OnOffButtonGroupProps {
* *
* @param onSelect callback that is executed if the on/off value has changed * @param onSelect callback that is executed if the on/off value has changed
* @param value the current on/off value that should be visible * @param value the current on/off value that should be visible
* @param overrideButtonOnI18nKey Set to override the i18n key for the on-button
* @param overrideButtonOffI18nKey Set to override the i18n key for the off-button
*/ */
export const OnOffButtonGroup: React.FC<OnOffButtonGroupProps> = ({ onSelect, value }) => { export const OnOffButtonGroup: React.FC<OnOffButtonGroupProps> = ({
onSelect,
value,
overrideButtonOffI18nKey,
overrideButtonOnI18nKey
}) => {
const buttonGroupValue = useMemo(() => (value ? OnOffState.ON : OnOffState.OFF), [value]) const buttonGroupValue = useMemo(() => (value ? OnOffState.ON : OnOffState.OFF), [value])
const onButtonSelect = useCallback( const onButtonSelect = useCallback(
(value: OnOffState) => { (value: OnOffState) => {
@ -39,16 +48,16 @@ export const OnOffButtonGroup: React.FC<OnOffButtonGroupProps> = ({ onSelect, va
onSelect={onButtonSelect} onSelect={onButtonSelect}
selected={buttonGroupValue === OnOffState.ON} selected={buttonGroupValue === OnOffState.ON}
value={OnOffState.ON} value={OnOffState.ON}
i18nKeyTooltip={'common.on'} i18nKeyTooltip={overrideButtonOnI18nKey ?? 'common.on'}
i18nKeyLabel={'common.on'} i18nKeyLabel={overrideButtonOnI18nKey ?? 'common.on'}
{...testId('onOffButtonGroupOn')} {...testId('onOffButtonGroupOn')}
/> />
<SettingsToggleButton <SettingsToggleButton
onSelect={onButtonSelect} onSelect={onButtonSelect}
selected={buttonGroupValue === OnOffState.OFF} selected={buttonGroupValue === OnOffState.OFF}
value={OnOffState.OFF} value={OnOffState.OFF}
i18nKeyTooltip={'common.off'} i18nKeyTooltip={overrideButtonOffI18nKey ?? 'common.off'}
i18nKeyLabel={'common.off'} i18nKeyLabel={overrideButtonOffI18nKey ?? 'common.off'}
{...testId('onOffButtonGroupOff')} {...testId('onOffButtonGroupOff')}
/> />
</ToggleButtonGroup> </ToggleButtonGroup>

View file

@ -10,7 +10,9 @@ import type {
SetEditorLineWrappingAction, SetEditorLineWrappingAction,
SetEditorSmartPasteAction, SetEditorSmartPasteAction,
SetEditorSyncScrollAction, SetEditorSyncScrollAction,
SetEditorSpellCheckAction SetEditorSpellCheckAction,
SetEditorIndentWithTabsAction,
SetEditorIndentSpacesAction
} from './types' } from './types'
import { EditorConfigActionType } from './types' import { EditorConfigActionType } from './types'
@ -54,6 +56,22 @@ export const setEditorSpellCheck = (spellCheck: boolean): void => {
store.dispatch(action) store.dispatch(action)
} }
export const setEditorIndentWithTabs = (indentWithTabs: boolean): void => {
const action: SetEditorIndentWithTabsAction = {
type: EditorConfigActionType.SET_INDENT_WITH_TABS,
indentWithTabs
}
store.dispatch(action)
}
export const setEditorIndentSpaces = (indentSpaces: number): void => {
const action: SetEditorIndentSpacesAction = {
type: EditorConfigActionType.SET_INDENT_SPACES,
indentSpaces
}
store.dispatch(action)
}
export const loadFromLocalStorage = (): void => { export const loadFromLocalStorage = (): void => {
const action: LoadFromLocalStorageAction = { const action: LoadFromLocalStorageAction = {
type: EditorConfigActionType.LOAD_FROM_LOCAL_STORAGE type: EditorConfigActionType.LOAD_FROM_LOCAL_STORAGE

View file

@ -15,7 +15,9 @@ export const initialState: EditorConfig = {
syncScroll: true, syncScroll: true,
smartPaste: true, smartPaste: true,
spellCheck: true, spellCheck: true,
lineWrapping: true lineWrapping: true,
indentWithTabs: false,
indentSpaces: 2
} }
export const EditorConfigReducer: Reducer<EditorConfig, EditorConfigActions> = ( export const EditorConfigReducer: Reducer<EditorConfig, EditorConfigActions> = (
@ -61,6 +63,20 @@ export const EditorConfigReducer: Reducer<EditorConfig, EditorConfigActions> = (
} }
saveToLocalStorage(newState) saveToLocalStorage(newState)
return newState return newState
case EditorConfigActionType.SET_INDENT_WITH_TABS:
newState = {
...state,
indentWithTabs: action.indentWithTabs
}
saveToLocalStorage(newState)
return newState
case EditorConfigActionType.SET_INDENT_SPACES:
newState = {
...state,
indentSpaces: action.indentSpaces
}
saveToLocalStorage(newState)
return newState
default: default:
return state return state
} }
@ -72,13 +88,15 @@ export const loadFromLocalStorage = (): EditorConfig | undefined => {
if (!stored) { if (!stored) {
return undefined return undefined
} }
const storedConfiguration = JSON.parse(stored) as Record<string, boolean> const storedConfiguration = JSON.parse(stored) as Partial<EditorConfig>
return { return {
ligatures: storedConfiguration?.ligatures === true ?? true, ligatures: storedConfiguration?.ligatures === true ?? true,
syncScroll: storedConfiguration?.syncScroll === true ?? true, syncScroll: storedConfiguration?.syncScroll === true ?? true,
smartPaste: storedConfiguration?.smartPaste === true ?? true, smartPaste: storedConfiguration?.smartPaste === true ?? true,
spellCheck: storedConfiguration?.spellCheck === true ?? false, spellCheck: storedConfiguration?.spellCheck === true ?? true,
lineWrapping: storedConfiguration?.lineWrapping === true ?? true lineWrapping: storedConfiguration?.lineWrapping === true ?? true,
indentWithTabs: storedConfiguration?.indentWithTabs === true ?? false,
indentSpaces: storedConfiguration?.indentSpaces ?? 2
} }
} catch (_) { } catch (_) {
return undefined return undefined

View file

@ -6,13 +6,14 @@
import type { Action } from 'redux' import type { Action } from 'redux'
export enum EditorConfigActionType { export enum EditorConfigActionType {
SET_EDITOR_VIEW_MODE = 'editor/view-mode/set',
SET_SYNC_SCROLL = 'editor/syncScroll/set', SET_SYNC_SCROLL = 'editor/syncScroll/set',
LOAD_FROM_LOCAL_STORAGE = 'editor/preferences/load', LOAD_FROM_LOCAL_STORAGE = 'editor/preferences/load',
SET_LIGATURES = 'editor/preferences/setLigatures', SET_LIGATURES = 'editor/preferences/setLigatures',
SET_LINE_WRAPPING = 'editor/preferences/setLineWrapping', SET_LINE_WRAPPING = 'editor/preferences/setLineWrapping',
SET_SMART_PASTE = 'editor/preferences/setSmartPaste', SET_SMART_PASTE = 'editor/preferences/setSmartPaste',
SET_SPELL_CHECK = 'editor/preferences/setSpellCheck' SET_SPELL_CHECK = 'editor/preferences/setSpellCheck',
SET_INDENT_WITH_TABS = 'editor/preferences/setIndentWithTabs',
SET_INDENT_SPACES = 'editor/preferences/setIndentSpaces'
} }
export interface EditorConfig { export interface EditorConfig {
@ -21,6 +22,8 @@ export interface EditorConfig {
smartPaste: boolean smartPaste: boolean
spellCheck: boolean spellCheck: boolean
lineWrapping: boolean lineWrapping: boolean
indentWithTabs: boolean
indentSpaces: number
} }
export type EditorConfigActions = export type EditorConfigActions =
@ -29,6 +32,8 @@ export type EditorConfigActions =
| SetEditorSmartPasteAction | SetEditorSmartPasteAction
| SetEditorLineWrappingAction | SetEditorLineWrappingAction
| SetEditorSpellCheckAction | SetEditorSpellCheckAction
| SetEditorIndentWithTabsAction
| SetEditorIndentSpacesAction
| LoadFromLocalStorageAction | LoadFromLocalStorageAction
export interface LoadFromLocalStorageAction extends Action<EditorConfigActionType> { export interface LoadFromLocalStorageAction extends Action<EditorConfigActionType> {
@ -59,3 +64,13 @@ export interface SetEditorSpellCheckAction extends Action<EditorConfigActionType
type: EditorConfigActionType.SET_SPELL_CHECK type: EditorConfigActionType.SET_SPELL_CHECK
spellCheck: boolean spellCheck: boolean
} }
export interface SetEditorIndentWithTabsAction extends Action<EditorConfigActionType> {
type: EditorConfigActionType.SET_INDENT_WITH_TABS
indentWithTabs: boolean
}
export interface SetEditorIndentSpacesAction extends Action<EditorConfigActionType> {
type: EditorConfigActionType.SET_INDENT_SPACES
indentSpaces: number
}