diff --git a/packages/models/src/Domain/Syncable/Note/Note.ts b/packages/models/src/Domain/Syncable/Note/Note.ts index a1e0c8c62..122aefdf9 100644 --- a/packages/models/src/Domain/Syncable/Note/Note.ts +++ b/packages/models/src/Domain/Syncable/Note/Note.ts @@ -5,6 +5,7 @@ import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface' import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload' import { NoteContent, NoteContentSpecialized } from './NoteContent' +import { EditorLineWidth } from '../UserPrefs' export const isNote = (x: ItemInterface): x is SNNote => x.content_type === ContentType.Note @@ -15,6 +16,7 @@ export class SNNote extends DecryptedItem implements NoteContentSpe public readonly preview_plain: string public readonly preview_html: string public readonly spellcheck?: boolean + public readonly editorWidth?: EditorLineWidth public readonly noteType?: NoteType public readonly authorizedForListed: boolean @@ -30,6 +32,7 @@ export class SNNote extends DecryptedItem implements NoteContentSpe this.preview_plain = String(this.payload.content.preview_plain || '') this.preview_html = String(this.payload.content.preview_html || '') this.spellcheck = this.payload.content.spellcheck + this.editorWidth = this.payload.content.editorWidth this.noteType = this.payload.content.noteType this.editorIdentifier = this.payload.content.editorIdentifier this.authorizedForListed = this.payload.content.authorizedForListed || false diff --git a/packages/models/src/Domain/Syncable/Note/NoteContent.ts b/packages/models/src/Domain/Syncable/Note/NoteContent.ts index 26a957697..21abafc1b 100644 --- a/packages/models/src/Domain/Syncable/Note/NoteContent.ts +++ b/packages/models/src/Domain/Syncable/Note/NoteContent.ts @@ -1,5 +1,6 @@ import { FeatureIdentifier, NoteType } from '@standardnotes/features' import { ItemContent } from '../../Abstract/Content/ItemContent' +import { EditorLineWidth } from '../UserPrefs' export interface NoteContentSpecialized { title: string @@ -8,6 +9,7 @@ export interface NoteContentSpecialized { preview_plain?: string preview_html?: string spellcheck?: boolean + editorWidth?: EditorLineWidth noteType?: NoteType editorIdentifier?: FeatureIdentifier | string authorizedForListed?: boolean diff --git a/packages/models/src/Domain/Syncable/Note/NoteMutator.ts b/packages/models/src/Domain/Syncable/Note/NoteMutator.ts index 9e4d7bd0b..453d848aa 100644 --- a/packages/models/src/Domain/Syncable/Note/NoteMutator.ts +++ b/packages/models/src/Domain/Syncable/Note/NoteMutator.ts @@ -5,6 +5,7 @@ import { NoteToNoteReference } from '../../Abstract/Reference/NoteToNoteReferenc import { ContentType } from '@standardnotes/common' import { ContentReferenceType } from '../../Abstract/Item' import { FeatureIdentifier, NoteType } from '@standardnotes/features' +import { EditorLineWidth } from '../UserPrefs' export class NoteMutator extends DecryptedItemMutator { set title(title: string) { @@ -31,6 +32,10 @@ export class NoteMutator extends DecryptedItemMutator { this.mutableContent.spellcheck = spellcheck } + set editorWidth(editorWidth: EditorLineWidth) { + this.mutableContent.editorWidth = editorWidth + } + set noteType(noteType: NoteType) { this.mutableContent.noteType = noteType } diff --git a/packages/models/src/Domain/Syncable/UserPrefs/PrefKey.ts b/packages/models/src/Domain/Syncable/UserPrefs/PrefKey.ts index 257286d66..af603e011 100644 --- a/packages/models/src/Domain/Syncable/UserPrefs/PrefKey.ts +++ b/packages/models/src/Domain/Syncable/UserPrefs/PrefKey.ts @@ -12,6 +12,7 @@ export enum PrefKey { EditorSpellcheck = 'spellcheck', EditorResizersEnabled = 'marginResizersEnabled', EditorLineHeight = 'editorLineHeight', + EditorLineWidth = 'editorLineWidth', EditorFontSize = 'editorFontSize', SortNotesBy = 'sortBy', SortNotesReverse = 'sortReverse', @@ -65,6 +66,13 @@ export enum EditorLineHeight { Loose = 'Loose', } +export enum EditorLineWidth { + Narrow = 'Narrow', + Wide = 'Wide', + Dynamic = 'Dynamic', + FullWidth = 'FullWidth', +} + export enum EditorFontSize { ExtraSmall = 'ExtraSmall', Small = 'Small', @@ -107,6 +115,7 @@ export type PrefValue = { [PrefKey.NewNoteTitleFormat]: NewNoteTitleFormat [PrefKey.CustomNoteTitleFormat]: string [PrefKey.EditorLineHeight]: EditorLineHeight + [PrefKey.EditorLineWidth]: EditorLineWidth [PrefKey.EditorFontSize]: EditorFontSize [PrefKey.UpdateSavingStatusIndicator]: boolean [PrefKey.DarkMode]: boolean diff --git a/packages/ui-services/src/Keyboard/KeyboardCommands.ts b/packages/ui-services/src/Keyboard/KeyboardCommands.ts index c416e9307..5369e22b0 100644 --- a/packages/ui-services/src/Keyboard/KeyboardCommands.ts +++ b/packages/ui-services/src/Keyboard/KeyboardCommands.ts @@ -37,3 +37,5 @@ export const SUPER_EXPORT_JSON = createKeyboardCommand('SUPER_EXPORT_JSON') export const SUPER_EXPORT_MARKDOWN = createKeyboardCommand('SUPER_EXPORT_MARKDOWN') export const SUPER_EXPORT_HTML = createKeyboardCommand('SUPER_EXPORT_HTML') export const OPEN_PREFERENCES_COMMAND = createKeyboardCommand('OPEN_PREFERENCES_COMMAND') + +export const CHANGE_EDITOR_WIDTH_COMMAND = createKeyboardCommand('CHANGE_EDITOR_WIDTH_COMMAND') diff --git a/packages/ui-services/src/Keyboard/getKeyboardShortcuts.ts b/packages/ui-services/src/Keyboard/getKeyboardShortcuts.ts index bb79f0bc4..684694d16 100644 --- a/packages/ui-services/src/Keyboard/getKeyboardShortcuts.ts +++ b/packages/ui-services/src/Keyboard/getKeyboardShortcuts.ts @@ -29,6 +29,7 @@ import { SUPER_SEARCH_NEXT_RESULT, SUPER_SEARCH_PREVIOUS_RESULT, SUPER_SEARCH_TOGGLE_REPLACE_MODE, + CHANGE_EDITOR_WIDTH_COMMAND, } from './KeyboardCommands' import { KeyboardKey } from './KeyboardKey' import { KeyboardModifier } from './KeyboardModifier' @@ -182,5 +183,11 @@ export function getKeyboardShortcuts(platform: Platform, _environment: Environme modifiers: [primaryModifier], preventDefault: true, }, + { + command: CHANGE_EDITOR_WIDTH_COMMAND, + key: 'j', + modifiers: [primaryModifier, KeyboardModifier.Shift], + preventDefault: true, + }, ] } diff --git a/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx b/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx index 8a515556c..637febc7d 100644 --- a/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx +++ b/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx @@ -30,6 +30,7 @@ import DotOrgNotice from './DotOrgNotice' import LinkingControllerProvider from '@/Controllers/LinkingControllerProvider' import ImportModal from '../ImportModal/ImportModal' import IosKeyboardClose from '../IosKeyboardClose/IosKeyboardClose' +import EditorWidthSelectionModalWrapper from '../EditorWidthSelectionModal/EditorWidthSelectionModal' type Props = { application: WebApplication @@ -268,6 +269,7 @@ const ApplicationView: FunctionComponent = ({ application, mainApplicatio + {currentItem?.label} - + { + return ( +
+ ) +} + +const EditorWidthSelectionModal = ({ + initialValue, + handleChange, + close, + note, +}: { + initialValue: EditorLineWidth + handleChange: (value: EditorLineWidth, setGlobally: boolean) => void + close: () => void + note: SNNote | undefined +}) => { + const application = useApplication() + const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm) + + const [value, setValue] = useState(() => initialValue) + const [setGlobally, setSetGlobally] = useState(false) + + const options = useMemo( + () => [ + { + label: 'Narrow', + value: EditorLineWidth.Narrow, + }, + { + label: 'Wide', + value: EditorLineWidth.Wide, + }, + { + label: 'Dynamic', + value: EditorLineWidth.Dynamic, + }, + { + label: 'Full width', + value: EditorLineWidth.FullWidth, + }, + ], + [], + ) + + const accept = useCallback(() => { + handleChange(value, setGlobally) + close() + }, [close, handleChange, setGlobally, value]) + + const actions = useMemo( + (): ModalAction[] => [ + { + label: 'Cancel', + type: 'cancel', + onClick: close, + mobileSlot: 'left', + }, + { + label: 'Done', + type: 'primary', + onClick: accept, + mobileSlot: 'right', + }, + ], + [accept, close], + ) + + useEffect(() => { + return application.keyboardService.addCommandHandler({ + command: ESCAPE_COMMAND, + onKeyDown() { + close() + return + }, + }) + }, [application.keyboardService, close]) + + const DynamicMargin = ( +
+
+
{EditorMargins[value]}
+ +
+
+ ) + + return ( + } + customFooter={<>} + disableCustomHeader={isMobileScreen} + actions={actions} + className={{ + content: 'select-none md:min-w-[40vw]', + description: 'flex min-h-[50vh] flex-col', + }} + > +
+
+ {DynamicMargin} +
+
+ {value === EditorLineWidth.Narrow || value === EditorLineWidth.Wide + ? `Max. ${EditorMaxWidths[value]}` + : EditorMaxWidths[value]} +
+ +
+
+ {DynamicMargin} +
+
+ {!!note && ( +
+ +
+ )} + + setValue(value as EditorLineWidth)} /> +
+ + +
+
+ + ) +} + +const EditorWidthSelectionModalWrapper = () => { + const application = useApplication() + const { notesController } = application.getViewControllerManager() + + const [isOpen, setIsOpen] = useState(false) + const [isGlobal, setIsGlobal] = useState(false) + + const note = notesController.selectedNotesCount === 1 && !isGlobal ? notesController.selectedNotes[0] : undefined + + const lineWidth = note + ? notesController.getEditorWidthForNote(note) + : application.getPreference(PrefKey.EditorLineWidth, PrefDefaults[PrefKey.EditorLineWidth]) + + const setLineWidth = useCallback( + (lineWidth: EditorLineWidth, setGlobally: boolean) => { + if (note && !setGlobally) { + notesController.setNoteEditorWidth(note, lineWidth).catch(console.error) + } else { + application.setPreference(PrefKey.EditorLineWidth, lineWidth).catch(console.error) + } + }, + [application, note, notesController], + ) + + const toggle = useCallback(() => { + setIsOpen((open) => !open) + }, []) + + useEffect(() => { + return application.keyboardService.addCommandHandler({ + command: CHANGE_EDITOR_WIDTH_COMMAND, + onKeyDown: (_, data) => { + if (typeof data === 'boolean' && data) { + setIsGlobal(data) + } else { + setIsGlobal(false) + } + toggle() + }, + }) + }, [application, toggle]) + + return ( + + + + ) +} + +export default observer(EditorWidthSelectionModalWrapper) diff --git a/packages/web/src/javascripts/Components/EditorWidthSelectionModal/EditorWidths.ts b/packages/web/src/javascripts/Components/EditorWidthSelectionModal/EditorWidths.ts new file mode 100644 index 000000000..3d515a34f --- /dev/null +++ b/packages/web/src/javascripts/Components/EditorWidthSelectionModal/EditorWidths.ts @@ -0,0 +1,15 @@ +import { EditorLineWidth } from '@standardnotes/snjs' + +export const EditorMaxWidths: { [k in EditorLineWidth]: string } = { + [EditorLineWidth.Narrow]: '512px', + [EditorLineWidth.Wide]: '720px', + [EditorLineWidth.Dynamic]: '80%', + [EditorLineWidth.FullWidth]: '100%', +} + +export const EditorMargins: { [k in EditorLineWidth]: string } = { + [EditorLineWidth.Narrow]: 'auto', + [EditorLineWidth.Wide]: 'auto', + [EditorLineWidth.Dynamic]: '10%', + [EditorLineWidth.FullWidth]: '0', +} diff --git a/packages/web/src/javascripts/Components/Icon/IconNameToSvgMapping.tsx b/packages/web/src/javascripts/Components/Icon/IconNameToSvgMapping.tsx index 3395fb95d..5c376a5a2 100644 --- a/packages/web/src/javascripts/Components/Icon/IconNameToSvgMapping.tsx +++ b/packages/web/src/javascripts/Components/Icon/IconNameToSvgMapping.tsx @@ -42,6 +42,7 @@ export const IconNameToSvgMapping = { 'hashtag-off': icons.HashtagOffIcon, 'keyboard-close': icons.KeyboardCloseIcon, 'link-off': icons.LinkOffIcon, + 'line-width': icons.LineWidthIcon, 'list-bulleted': icons.ListBulleted, 'list-numbered': icons.ListNumbered, 'lock-filled': icons.LockFilledIcon, diff --git a/packages/web/src/javascripts/Components/NoteView/NoteView.test.ts b/packages/web/src/javascripts/Components/NoteView/NoteView.test.ts index 1c3aa3eb9..bdefe078d 100644 --- a/packages/web/src/javascripts/Components/NoteView/NoteView.test.ts +++ b/packages/web/src/javascripts/Components/NoteView/NoteView.test.ts @@ -35,6 +35,7 @@ describe('NoteView', () => { notesController = {} as jest.Mocked notesController.setShowProtectedWarning = jest.fn() notesController.getSpellcheckStateForNote = jest.fn() + notesController.getEditorWidthForNote = jest.fn() viewControllerManager = { notesController: notesController, diff --git a/packages/web/src/javascripts/Components/NoteView/NoteView.tsx b/packages/web/src/javascripts/Components/NoteView/NoteView.tsx index 5aea61fb0..ccfaa2959 100644 --- a/packages/web/src/javascripts/Components/NoteView/NoteView.tsx +++ b/packages/web/src/javascripts/Components/NoteView/NoteView.tsx @@ -2,20 +2,20 @@ import { AbstractComponent } from '@/Components/Abstract/PureComponent' import ChangeEditorButton from '@/Components/ChangeEditor/ChangeEditorButton' import ComponentView from '@/Components/ComponentView/ComponentView' import NotesOptionsPanel from '@/Components/NotesOptions/NotesOptionsPanel' -import PanelResizer, { PanelResizeType, PanelSide } from '@/Components/PanelResizer/PanelResizer' import PinNoteButton from '@/Components/PinNoteButton/PinNoteButton' import ProtectedItemOverlay from '@/Components/ProtectedItemOverlay/ProtectedItemOverlay' import { ElementIds } from '@/Constants/ElementIDs' import { PrefDefaults } from '@/Constants/PrefDefaults' import { StringDeleteNote, STRING_DELETE_LOCKED_ATTEMPT, STRING_DELETE_PLACEHOLDER_ATTEMPT } from '@/Constants/Strings' import { log, LoggingDomain } from '@/Logging' -import { debounce, isDesktopApplication, isMobileScreen, isTabletOrMobileScreen } from '@/Utils' +import { debounce, isDesktopApplication, isMobileScreen } from '@/Utils' import { classNames } from '@standardnotes/utils' import { ApplicationEvent, ComponentArea, ComponentViewerInterface, ContentType, + EditorLineWidth, isPayloadSourceInternalChange, isPayloadSourceRetrieved, NoteType, @@ -26,7 +26,7 @@ import { SNNote, } from '@standardnotes/snjs' import { confirmDialog, DELETE_NOTE_KEYBOARD_COMMAND, KeyboardKey } from '@standardnotes/ui-services' -import { ChangeEventHandler, createRef, KeyboardEventHandler, RefObject } from 'react' +import { ChangeEventHandler, createRef, CSSProperties, KeyboardEventHandler, RefObject } from 'react' import { SuperEditor } from '../SuperEditor/SuperEditor' import IndicatorCircle from '../IndicatorCircle/IndicatorCircle' import LinkedItemBubblesContainer from '../LinkedItems/LinkedItemBubblesContainer' @@ -44,6 +44,7 @@ import { import { SuperEditorContentId } from '../SuperEditor/Constants' import { NoteViewController } from './Controller/NoteViewController' import { PlainEditor, PlainEditorInterface } from './PlainEditor/PlainEditor' +import { EditorMargins, EditorMaxWidths } from '../EditorWidthSelectionModal/EditorWidths' const MinimumStatusDuration = 400 @@ -58,7 +59,7 @@ type State = { editorStateDidLoad: boolean editorTitle: string isDesktop?: boolean - marginResizersEnabled?: boolean + editorLineWidth: EditorLineWidth noteLocked: boolean noteStatus?: NoteStatus saveError?: boolean @@ -68,10 +69,6 @@ type State = { syncTakingTooLong: boolean monospaceFont?: boolean plainEditorFocused?: boolean - leftResizerWidth: number - leftResizerOffset: number - rightResizerWidth: number - rightResizerOffset: number updateSavingIndicator?: boolean editorFeatureIdentifier?: string @@ -112,6 +109,7 @@ class NoteView extends AbstractComponent { availableStackComponents: [], editorStateDidLoad: false, editorTitle: '', + editorLineWidth: PrefDefaults[PrefKey.EditorLineWidth], isDesktop: isDesktopApplication(), noteStatus: undefined, noteLocked: this.controller.item.locked, @@ -119,10 +117,6 @@ class NoteView extends AbstractComponent { spellcheck: true, stackComponentViewers: [], syncTakingTooLong: false, - leftResizerWidth: 0, - leftResizerOffset: 0, - rightResizerWidth: 0, - rightResizerOffset: 0, editorFeatureIdentifier: this.controller.item.editorIdentifier, noteType: this.controller.item.noteType, } @@ -280,6 +274,8 @@ class NoteView extends AbstractComponent { this.reloadSpellcheck().catch(console.error) + this.reloadLineWidth() + const isTemplateNoteInsertedToBeInteractableWithEditor = source === PayloadEmitSource.LocalInserted && note.dirty if (isTemplateNoteInsertedToBeInteractableWithEditor) { return @@ -660,6 +656,14 @@ class NoteView extends AbstractComponent { } } + reloadLineWidth() { + const editorLineWidth = this.viewControllerManager.notesController.getEditorWidthForNote(this.note) + + this.setState({ + editorLineWidth, + }) + } + async reloadPreferences() { log(LoggingDomain.NoteView, 'Reload preferences') const monospaceFont = this.application.getPreference( @@ -667,10 +671,6 @@ class NoteView extends AbstractComponent { PrefDefaults[PrefKey.EditorMonospaceEnabled], ) - const marginResizersEnabled = - !isTabletOrMobileScreen() && - this.application.getPreference(PrefKey.EditorResizersEnabled, PrefDefaults[PrefKey.EditorResizersEnabled]) - const updateSavingIndicator = this.application.getPreference( PrefKey.UpdateSavingStatusIndicator, PrefDefaults[PrefKey.UpdateSavingStatusIndicator], @@ -678,30 +678,14 @@ class NoteView extends AbstractComponent { await this.reloadSpellcheck() + this.reloadLineWidth() + this.setState({ monospaceFont, - marginResizersEnabled, updateSavingIndicator, }) reloadFont(monospaceFont) - - if (marginResizersEnabled) { - const width = this.application.getPreference(PrefKey.EditorWidth, PrefDefaults[PrefKey.EditorWidth]) - if (width != null) { - this.setState({ - leftResizerWidth: width, - rightResizerWidth: width, - }) - } - const left = this.application.getPreference(PrefKey.EditorLeft, PrefDefaults[PrefKey.EditorLeft]) - if (left != null) { - this.setState({ - leftResizerOffset: left, - rightResizerOffset: left, - }) - } - } } async reloadStackComponents() { @@ -896,24 +880,18 @@ class NoteView extends AbstractComponent {
*]:mx-[var(--editor-margin)] [&>*]:max-w-[var(--editor-max-width)]', + )} + style={ + { + '--editor-margin': EditorMargins[this.state.editorLineWidth], + '--editor-max-width': EditorMaxWidths[this.state.editorLineWidth], + } as CSSProperties + } ref={this.editorContentRef} > - {this.state.marginResizersEnabled && this.editorContentRef.current ? ( - - ) : null} - {editorMode === 'component' && this.state.editorComponentViewer && (
{ />
)} - - {this.state.marginResizersEnabled && this.editorContentRef.current ? ( - - ) : null}
diff --git a/packages/web/src/javascripts/Components/NotesOptions/NotesOptions.tsx b/packages/web/src/javascripts/Components/NotesOptions/NotesOptions.tsx index 85ca9118f..607f62aa6 100644 --- a/packages/web/src/javascripts/Components/NotesOptions/NotesOptions.tsx +++ b/packages/web/src/javascripts/Components/NotesOptions/NotesOptions.tsx @@ -3,6 +3,7 @@ import { observer } from 'mobx-react-lite' import { useState, useEffect, useMemo, useCallback } from 'react' import { NoteType, Platform, SNNote } from '@standardnotes/snjs' import { + CHANGE_EDITOR_WIDTH_COMMAND, OPEN_NOTE_HISTORY_COMMAND, PIN_NOTE_COMMAND, SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND, @@ -165,6 +166,14 @@ const NotesOptions = ({ commandService.triggerCommand(SUPER_SHOW_MARKDOWN_PREVIEW) }, [commandService]) + const toggleLineWidthModal = useCallback(() => { + application.keyboardService.triggerCommand(CHANGE_EDITOR_WIDTH_COMMAND) + }, [application.keyboardService]) + const editorWidthShortcut = useMemo( + () => application.keyboardService.keyboardShortcutForCommand(CHANGE_EDITOR_WIDTH_COMMAND), + [application], + ) + const unauthorized = notes.some((note) => !application.isAuthorizedToRenderItem(note)) if (unauthorized) { return @@ -186,6 +195,11 @@ const NotesOptions = ({ {historyShortcut && } + + + Editor width + {editorWidthShortcut && } + )} { [], ) - const [marginResizers, setMarginResizers] = useState(() => - application.getPreference(PrefKey.EditorResizersEnabled, PrefDefaults[PrefKey.EditorResizersEnabled]), + const [editorWidth, setEditorWidth] = useState(() => + application.getPreference(PrefKey.EditorLineWidth, PrefDefaults[PrefKey.EditorLineWidth]), ) - const toggleMarginResizers = () => { - setMarginResizers(!marginResizers) - application.setPreference(PrefKey.EditorResizersEnabled, !marginResizers).catch(console.error) - } + const toggleEditorWidthModal = useCallback(() => { + application.keyboardService.triggerCommand(CHANGE_EDITOR_WIDTH_COMMAND, true) + }, [application.keyboardService]) + + useEffect(() => { + return application.addSingleEventObserver(ApplicationEvent.PreferencesChanged, async () => { + setEditorWidth(application.getPreference(PrefKey.EditorLineWidth, PrefDefaults[PrefKey.EditorLineWidth])) + }) + }, [application]) return ( Editor appearance
-
-
- Margin Resizers - Allows left and right editor margins to be resized. -
- -
-
Monospace Font @@ -114,6 +113,20 @@ const EditorDefaults = ({ application }: Props) => { />
+ +
+ Editor width + Sets the max editor width for all notes +
+ +
+
diff --git a/packages/web/src/javascripts/Components/RadioButtonGroup/RadioButtonGroup.tsx b/packages/web/src/javascripts/Components/RadioButtonGroup/RadioButtonGroup.tsx new file mode 100644 index 000000000..cac192103 --- /dev/null +++ b/packages/web/src/javascripts/Components/RadioButtonGroup/RadioButtonGroup.tsx @@ -0,0 +1,41 @@ +import { VisuallyHidden, Radio, RadioGroup, useRadioStore } from '@ariakit/react' +import { classNames } from '@standardnotes/utils' + +type Props = { + items: { label: string; value: string }[] + value: string + onChange: (value: string) => void +} + +const RadioButtonGroup = ({ value, items, onChange }: Props) => { + const radio = useRadioStore({ + value, + orientation: 'horizontal', + setValue(value) { + onChange(value as string) + }, + }) + + return ( + + {items.map(({ label, value: itemValue }) => ( + + ))} + + ) +} + +export default RadioButtonGroup diff --git a/packages/web/src/javascripts/Constants/PrefDefaults.ts b/packages/web/src/javascripts/Constants/PrefDefaults.ts index 054efedc3..9524c6f74 100644 --- a/packages/web/src/javascripts/Constants/PrefDefaults.ts +++ b/packages/web/src/javascripts/Constants/PrefDefaults.ts @@ -1,4 +1,11 @@ -import { PrefKey, CollectionSort, NewNoteTitleFormat, EditorLineHeight, EditorFontSize } from '@standardnotes/models' +import { + PrefKey, + CollectionSort, + NewNoteTitleFormat, + EditorLineHeight, + EditorFontSize, + EditorLineWidth, +} from '@standardnotes/models' import { FeatureIdentifier } from '@standardnotes/snjs' export const PrefDefaults = { @@ -10,6 +17,7 @@ export const PrefDefaults = { [PrefKey.EditorSpellcheck]: true, [PrefKey.EditorResizersEnabled]: false, [PrefKey.EditorLineHeight]: EditorLineHeight.Normal, + [PrefKey.EditorLineWidth]: EditorLineWidth.FullWidth, [PrefKey.EditorFontSize]: EditorFontSize.Normal, [PrefKey.SortNotesBy]: CollectionSort.CreatedAt, [PrefKey.SortNotesReverse]: false, diff --git a/packages/web/src/javascripts/Controllers/NotesController/NotesController.ts b/packages/web/src/javascripts/Controllers/NotesController/NotesController.ts index de1c0fea2..8d4a03f3d 100644 --- a/packages/web/src/javascripts/Controllers/NotesController/NotesController.ts +++ b/packages/web/src/javascripts/Controllers/NotesController/NotesController.ts @@ -10,6 +10,7 @@ import { InternalEventBus, PrefKey, ApplicationEvent, + EditorLineWidth, } from '@standardnotes/snjs' import { makeObservable, observable, action, computed, runInAction } from 'mobx' import { WebApplication } from '../../Application/WebApplication' @@ -358,6 +359,23 @@ export class NotesController extends AbstractViewController implements NotesCont this.application.sync.sync().catch(console.error) } + getEditorWidthForNote(note: SNNote) { + return ( + note.editorWidth ?? this.application.getPreference(PrefKey.EditorLineWidth, PrefDefaults[PrefKey.EditorLineWidth]) + ) + } + + async setNoteEditorWidth(note: SNNote, editorWidth: EditorLineWidth) { + await this.application.mutator.changeItem( + note, + (mutator) => { + mutator.editorWidth = editorWidth + }, + false, + ) + this.application.sync.sync().catch(console.error) + } + async addTagToSelectedNotes(tag: SNTag): Promise { const selectedNotes = this.getSelectedNotesList() await Promise.all(