feat: Replaced margin resizers with "Editor width" options. You can set it globally from Preferences > Appearance or per-note from the note options menu (#2324)
This commit is contained in:
@@ -5,6 +5,7 @@ import { DecryptedItem } from '../../Abstract/Item/Implementations/DecryptedItem
|
|||||||
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
|
import { ItemInterface } from '../../Abstract/Item/Interfaces/ItemInterface'
|
||||||
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
|
import { DecryptedPayloadInterface } from '../../Abstract/Payload/Interfaces/DecryptedPayload'
|
||||||
import { NoteContent, NoteContentSpecialized } from './NoteContent'
|
import { NoteContent, NoteContentSpecialized } from './NoteContent'
|
||||||
|
import { EditorLineWidth } from '../UserPrefs'
|
||||||
|
|
||||||
export const isNote = (x: ItemInterface): x is SNNote => x.content_type === ContentType.Note
|
export const isNote = (x: ItemInterface): x is SNNote => x.content_type === ContentType.Note
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ export class SNNote extends DecryptedItem<NoteContent> implements NoteContentSpe
|
|||||||
public readonly preview_plain: string
|
public readonly preview_plain: string
|
||||||
public readonly preview_html: string
|
public readonly preview_html: string
|
||||||
public readonly spellcheck?: boolean
|
public readonly spellcheck?: boolean
|
||||||
|
public readonly editorWidth?: EditorLineWidth
|
||||||
public readonly noteType?: NoteType
|
public readonly noteType?: NoteType
|
||||||
public readonly authorizedForListed: boolean
|
public readonly authorizedForListed: boolean
|
||||||
|
|
||||||
@@ -30,6 +32,7 @@ export class SNNote extends DecryptedItem<NoteContent> implements NoteContentSpe
|
|||||||
this.preview_plain = String(this.payload.content.preview_plain || '')
|
this.preview_plain = String(this.payload.content.preview_plain || '')
|
||||||
this.preview_html = String(this.payload.content.preview_html || '')
|
this.preview_html = String(this.payload.content.preview_html || '')
|
||||||
this.spellcheck = this.payload.content.spellcheck
|
this.spellcheck = this.payload.content.spellcheck
|
||||||
|
this.editorWidth = this.payload.content.editorWidth
|
||||||
this.noteType = this.payload.content.noteType
|
this.noteType = this.payload.content.noteType
|
||||||
this.editorIdentifier = this.payload.content.editorIdentifier
|
this.editorIdentifier = this.payload.content.editorIdentifier
|
||||||
this.authorizedForListed = this.payload.content.authorizedForListed || false
|
this.authorizedForListed = this.payload.content.authorizedForListed || false
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||||
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
import { ItemContent } from '../../Abstract/Content/ItemContent'
|
||||||
|
import { EditorLineWidth } from '../UserPrefs'
|
||||||
|
|
||||||
export interface NoteContentSpecialized {
|
export interface NoteContentSpecialized {
|
||||||
title: string
|
title: string
|
||||||
@@ -8,6 +9,7 @@ export interface NoteContentSpecialized {
|
|||||||
preview_plain?: string
|
preview_plain?: string
|
||||||
preview_html?: string
|
preview_html?: string
|
||||||
spellcheck?: boolean
|
spellcheck?: boolean
|
||||||
|
editorWidth?: EditorLineWidth
|
||||||
noteType?: NoteType
|
noteType?: NoteType
|
||||||
editorIdentifier?: FeatureIdentifier | string
|
editorIdentifier?: FeatureIdentifier | string
|
||||||
authorizedForListed?: boolean
|
authorizedForListed?: boolean
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { NoteToNoteReference } from '../../Abstract/Reference/NoteToNoteReferenc
|
|||||||
import { ContentType } from '@standardnotes/common'
|
import { ContentType } from '@standardnotes/common'
|
||||||
import { ContentReferenceType } from '../../Abstract/Item'
|
import { ContentReferenceType } from '../../Abstract/Item'
|
||||||
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
import { FeatureIdentifier, NoteType } from '@standardnotes/features'
|
||||||
|
import { EditorLineWidth } from '../UserPrefs'
|
||||||
|
|
||||||
export class NoteMutator extends DecryptedItemMutator<NoteContent> {
|
export class NoteMutator extends DecryptedItemMutator<NoteContent> {
|
||||||
set title(title: string) {
|
set title(title: string) {
|
||||||
@@ -31,6 +32,10 @@ export class NoteMutator extends DecryptedItemMutator<NoteContent> {
|
|||||||
this.mutableContent.spellcheck = spellcheck
|
this.mutableContent.spellcheck = spellcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set editorWidth(editorWidth: EditorLineWidth) {
|
||||||
|
this.mutableContent.editorWidth = editorWidth
|
||||||
|
}
|
||||||
|
|
||||||
set noteType(noteType: NoteType) {
|
set noteType(noteType: NoteType) {
|
||||||
this.mutableContent.noteType = noteType
|
this.mutableContent.noteType = noteType
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export enum PrefKey {
|
|||||||
EditorSpellcheck = 'spellcheck',
|
EditorSpellcheck = 'spellcheck',
|
||||||
EditorResizersEnabled = 'marginResizersEnabled',
|
EditorResizersEnabled = 'marginResizersEnabled',
|
||||||
EditorLineHeight = 'editorLineHeight',
|
EditorLineHeight = 'editorLineHeight',
|
||||||
|
EditorLineWidth = 'editorLineWidth',
|
||||||
EditorFontSize = 'editorFontSize',
|
EditorFontSize = 'editorFontSize',
|
||||||
SortNotesBy = 'sortBy',
|
SortNotesBy = 'sortBy',
|
||||||
SortNotesReverse = 'sortReverse',
|
SortNotesReverse = 'sortReverse',
|
||||||
@@ -65,6 +66,13 @@ export enum EditorLineHeight {
|
|||||||
Loose = 'Loose',
|
Loose = 'Loose',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum EditorLineWidth {
|
||||||
|
Narrow = 'Narrow',
|
||||||
|
Wide = 'Wide',
|
||||||
|
Dynamic = 'Dynamic',
|
||||||
|
FullWidth = 'FullWidth',
|
||||||
|
}
|
||||||
|
|
||||||
export enum EditorFontSize {
|
export enum EditorFontSize {
|
||||||
ExtraSmall = 'ExtraSmall',
|
ExtraSmall = 'ExtraSmall',
|
||||||
Small = 'Small',
|
Small = 'Small',
|
||||||
@@ -107,6 +115,7 @@ export type PrefValue = {
|
|||||||
[PrefKey.NewNoteTitleFormat]: NewNoteTitleFormat
|
[PrefKey.NewNoteTitleFormat]: NewNoteTitleFormat
|
||||||
[PrefKey.CustomNoteTitleFormat]: string
|
[PrefKey.CustomNoteTitleFormat]: string
|
||||||
[PrefKey.EditorLineHeight]: EditorLineHeight
|
[PrefKey.EditorLineHeight]: EditorLineHeight
|
||||||
|
[PrefKey.EditorLineWidth]: EditorLineWidth
|
||||||
[PrefKey.EditorFontSize]: EditorFontSize
|
[PrefKey.EditorFontSize]: EditorFontSize
|
||||||
[PrefKey.UpdateSavingStatusIndicator]: boolean
|
[PrefKey.UpdateSavingStatusIndicator]: boolean
|
||||||
[PrefKey.DarkMode]: boolean
|
[PrefKey.DarkMode]: boolean
|
||||||
|
|||||||
@@ -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_MARKDOWN = createKeyboardCommand('SUPER_EXPORT_MARKDOWN')
|
||||||
export const SUPER_EXPORT_HTML = createKeyboardCommand('SUPER_EXPORT_HTML')
|
export const SUPER_EXPORT_HTML = createKeyboardCommand('SUPER_EXPORT_HTML')
|
||||||
export const OPEN_PREFERENCES_COMMAND = createKeyboardCommand('OPEN_PREFERENCES_COMMAND')
|
export const OPEN_PREFERENCES_COMMAND = createKeyboardCommand('OPEN_PREFERENCES_COMMAND')
|
||||||
|
|
||||||
|
export const CHANGE_EDITOR_WIDTH_COMMAND = createKeyboardCommand('CHANGE_EDITOR_WIDTH_COMMAND')
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
SUPER_SEARCH_NEXT_RESULT,
|
SUPER_SEARCH_NEXT_RESULT,
|
||||||
SUPER_SEARCH_PREVIOUS_RESULT,
|
SUPER_SEARCH_PREVIOUS_RESULT,
|
||||||
SUPER_SEARCH_TOGGLE_REPLACE_MODE,
|
SUPER_SEARCH_TOGGLE_REPLACE_MODE,
|
||||||
|
CHANGE_EDITOR_WIDTH_COMMAND,
|
||||||
} from './KeyboardCommands'
|
} from './KeyboardCommands'
|
||||||
import { KeyboardKey } from './KeyboardKey'
|
import { KeyboardKey } from './KeyboardKey'
|
||||||
import { KeyboardModifier } from './KeyboardModifier'
|
import { KeyboardModifier } from './KeyboardModifier'
|
||||||
@@ -182,5 +183,11 @@ export function getKeyboardShortcuts(platform: Platform, _environment: Environme
|
|||||||
modifiers: [primaryModifier],
|
modifiers: [primaryModifier],
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
command: CHANGE_EDITOR_WIDTH_COMMAND,
|
||||||
|
key: 'j',
|
||||||
|
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import DotOrgNotice from './DotOrgNotice'
|
|||||||
import LinkingControllerProvider from '@/Controllers/LinkingControllerProvider'
|
import LinkingControllerProvider from '@/Controllers/LinkingControllerProvider'
|
||||||
import ImportModal from '../ImportModal/ImportModal'
|
import ImportModal from '../ImportModal/ImportModal'
|
||||||
import IosKeyboardClose from '../IosKeyboardClose/IosKeyboardClose'
|
import IosKeyboardClose from '../IosKeyboardClose/IosKeyboardClose'
|
||||||
|
import EditorWidthSelectionModalWrapper from '../EditorWidthSelectionModal/EditorWidthSelectionModal'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -268,6 +269,7 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
|||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
<FilePreviewModalWrapper application={application} viewControllerManager={viewControllerManager} />
|
<FilePreviewModalWrapper application={application} viewControllerManager={viewControllerManager} />
|
||||||
<PermissionsModalWrapper application={application} />
|
<PermissionsModalWrapper application={application} />
|
||||||
|
<EditorWidthSelectionModalWrapper />
|
||||||
<ConfirmDeleteAccountContainer
|
<ConfirmDeleteAccountContainer
|
||||||
application={application}
|
application={application}
|
||||||
viewControllerManager={viewControllerManager}
|
viewControllerManager={viewControllerManager}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { DropdownItem } from './DropdownItem'
|
|||||||
import { classNames } from '@standardnotes/snjs'
|
import { classNames } from '@standardnotes/snjs'
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectArrow,
|
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectLabel,
|
SelectLabel,
|
||||||
SelectPopover,
|
SelectPopover,
|
||||||
@@ -102,7 +101,7 @@ const Dropdown = ({
|
|||||||
) : null}
|
) : null}
|
||||||
<div className="text-base lg:text-sm">{currentItem?.label}</div>
|
<div className="text-base lg:text-sm">{currentItem?.label}</div>
|
||||||
</div>
|
</div>
|
||||||
<SelectArrow className={classNames('text-passive-1', isExpanded && 'rotate-180')} />
|
<Icon type="chevron-down" size="normal" className={isExpanded ? 'rotate-180' : ''} />
|
||||||
</Select>
|
</Select>
|
||||||
<SelectPopover
|
<SelectPopover
|
||||||
store={select}
|
store={select}
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||||
|
import { classNames, EditorLineWidth, PrefKey, SNNote } from '@standardnotes/snjs'
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import Button from '../Button/Button'
|
||||||
|
import Modal, { ModalAction } from '../Modal/Modal'
|
||||||
|
import ModalDialogButtons from '../Modal/ModalDialogButtons'
|
||||||
|
import RadioButtonGroup from '../RadioButtonGroup/RadioButtonGroup'
|
||||||
|
import { EditorMargins, EditorMaxWidths } from './EditorWidths'
|
||||||
|
import { useApplication } from '../ApplicationProvider'
|
||||||
|
import ModalOverlay from '../Modal/ModalOverlay'
|
||||||
|
import { CHANGE_EDITOR_WIDTH_COMMAND, ESCAPE_COMMAND } from '@standardnotes/ui-services'
|
||||||
|
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
||||||
|
import { observer } from 'mobx-react-lite'
|
||||||
|
import Switch from '../Switch/Switch'
|
||||||
|
|
||||||
|
const DoubleSidedArrow = ({ className }: { className?: string }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'relative h-[2px] w-full bg-current',
|
||||||
|
'before:absolute before:-left-px before:top-1/2 before:h-0 before:w-0 before:-translate-y-1/2 before:border-r-[6px] before:border-t-[6px] before:border-b-[6px] before:border-current before:border-b-transparent before:border-t-transparent',
|
||||||
|
'after:absolute after:-right-px after:top-1/2 after:h-0 after:w-0 after:-translate-y-1/2 after:border-l-[6px] after:border-t-[6px] after:border-b-[6px] after:border-current after:border-b-transparent after:border-t-transparent',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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<EditorLineWidth>(() => 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 = (
|
||||||
|
<div className="text-center text-sm text-passive-2">
|
||||||
|
<div className={value !== EditorLineWidth.Dynamic ? 'hidden' : ''}>
|
||||||
|
<div className="mb-2">{EditorMargins[value]}</div>
|
||||||
|
<DoubleSidedArrow />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="Set editor width"
|
||||||
|
close={close}
|
||||||
|
customHeader={<></>}
|
||||||
|
customFooter={<></>}
|
||||||
|
disableCustomHeader={isMobileScreen}
|
||||||
|
actions={actions}
|
||||||
|
className={{
|
||||||
|
content: 'select-none md:min-w-[40vw]',
|
||||||
|
description: 'flex min-h-[50vh] flex-col',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex min-h-0 flex-grow flex-col overflow-hidden rounded bg-passive-5 p-4 pb-0">
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'grid flex-grow grid-cols-[0fr_1fr_0fr] gap-3 rounded rounded-b-none bg-default px-2 pt-4 shadow-[0_1px_4px_rgba(0,0,0,0.12),0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-200 md:px-4',
|
||||||
|
value === EditorLineWidth.Narrow && 'md:grid-cols-[1fr_60%_1fr]',
|
||||||
|
value === EditorLineWidth.Wide && 'md:grid-cols-[1fr_70%_1fr]',
|
||||||
|
value === EditorLineWidth.Dynamic && 'md:grid-cols-[1fr_80%_1fr]',
|
||||||
|
value === EditorLineWidth.FullWidth && 'md:grid-cols-[1fr_95%_1fr]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{DynamicMargin}
|
||||||
|
<div className="flex flex-col text-info">
|
||||||
|
<div className="mb-2 text-center text-sm">
|
||||||
|
{value === EditorLineWidth.Narrow || value === EditorLineWidth.Wide
|
||||||
|
? `Max. ${EditorMaxWidths[value]}`
|
||||||
|
: EditorMaxWidths[value]}
|
||||||
|
</div>
|
||||||
|
<DoubleSidedArrow />
|
||||||
|
<div className="w-full flex-grow bg-[linear-gradient(transparent_50%,var(--sn-stylekit-info-color)_50%)] bg-[length:100%_2.5rem] bg-repeat-y opacity-10" />
|
||||||
|
</div>
|
||||||
|
{DynamicMargin}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!!note && (
|
||||||
|
<div className="border-t border-border bg-default px-4 py-2">
|
||||||
|
<label className="flex items-center gap-2">
|
||||||
|
<Switch checked={setGlobally} onChange={setSetGlobally} />
|
||||||
|
Set globally {note.editorWidth != undefined && '(will not apply to current note)'}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<ModalDialogButtons className="justify-center md:justify-between">
|
||||||
|
<RadioButtonGroup items={options} value={value} onChange={(value) => setValue(value as EditorLineWidth)} />
|
||||||
|
<div className="hidden items-center gap-2 md:flex">
|
||||||
|
<Button onClick={close}>Cancel</Button>
|
||||||
|
<Button onClick={accept} primary>
|
||||||
|
Apply
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</ModalDialogButtons>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<ModalOverlay isOpen={isOpen}>
|
||||||
|
<EditorWidthSelectionModal initialValue={lineWidth} handleChange={setLineWidth} close={toggle} note={note} />
|
||||||
|
</ModalOverlay>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(EditorWidthSelectionModalWrapper)
|
||||||
@@ -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',
|
||||||
|
}
|
||||||
@@ -42,6 +42,7 @@ export const IconNameToSvgMapping = {
|
|||||||
'hashtag-off': icons.HashtagOffIcon,
|
'hashtag-off': icons.HashtagOffIcon,
|
||||||
'keyboard-close': icons.KeyboardCloseIcon,
|
'keyboard-close': icons.KeyboardCloseIcon,
|
||||||
'link-off': icons.LinkOffIcon,
|
'link-off': icons.LinkOffIcon,
|
||||||
|
'line-width': icons.LineWidthIcon,
|
||||||
'list-bulleted': icons.ListBulleted,
|
'list-bulleted': icons.ListBulleted,
|
||||||
'list-numbered': icons.ListNumbered,
|
'list-numbered': icons.ListNumbered,
|
||||||
'lock-filled': icons.LockFilledIcon,
|
'lock-filled': icons.LockFilledIcon,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ describe('NoteView', () => {
|
|||||||
notesController = {} as jest.Mocked<NotesController>
|
notesController = {} as jest.Mocked<NotesController>
|
||||||
notesController.setShowProtectedWarning = jest.fn()
|
notesController.setShowProtectedWarning = jest.fn()
|
||||||
notesController.getSpellcheckStateForNote = jest.fn()
|
notesController.getSpellcheckStateForNote = jest.fn()
|
||||||
|
notesController.getEditorWidthForNote = jest.fn()
|
||||||
|
|
||||||
viewControllerManager = {
|
viewControllerManager = {
|
||||||
notesController: notesController,
|
notesController: notesController,
|
||||||
|
|||||||
@@ -2,20 +2,20 @@ import { AbstractComponent } from '@/Components/Abstract/PureComponent'
|
|||||||
import ChangeEditorButton from '@/Components/ChangeEditor/ChangeEditorButton'
|
import ChangeEditorButton from '@/Components/ChangeEditor/ChangeEditorButton'
|
||||||
import ComponentView from '@/Components/ComponentView/ComponentView'
|
import ComponentView from '@/Components/ComponentView/ComponentView'
|
||||||
import NotesOptionsPanel from '@/Components/NotesOptions/NotesOptionsPanel'
|
import NotesOptionsPanel from '@/Components/NotesOptions/NotesOptionsPanel'
|
||||||
import PanelResizer, { PanelResizeType, PanelSide } from '@/Components/PanelResizer/PanelResizer'
|
|
||||||
import PinNoteButton from '@/Components/PinNoteButton/PinNoteButton'
|
import PinNoteButton from '@/Components/PinNoteButton/PinNoteButton'
|
||||||
import ProtectedItemOverlay from '@/Components/ProtectedItemOverlay/ProtectedItemOverlay'
|
import ProtectedItemOverlay from '@/Components/ProtectedItemOverlay/ProtectedItemOverlay'
|
||||||
import { ElementIds } from '@/Constants/ElementIDs'
|
import { ElementIds } from '@/Constants/ElementIDs'
|
||||||
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
||||||
import { StringDeleteNote, STRING_DELETE_LOCKED_ATTEMPT, STRING_DELETE_PLACEHOLDER_ATTEMPT } from '@/Constants/Strings'
|
import { StringDeleteNote, STRING_DELETE_LOCKED_ATTEMPT, STRING_DELETE_PLACEHOLDER_ATTEMPT } from '@/Constants/Strings'
|
||||||
import { log, LoggingDomain } from '@/Logging'
|
import { log, LoggingDomain } from '@/Logging'
|
||||||
import { debounce, isDesktopApplication, isMobileScreen, isTabletOrMobileScreen } from '@/Utils'
|
import { debounce, isDesktopApplication, isMobileScreen } from '@/Utils'
|
||||||
import { classNames } from '@standardnotes/utils'
|
import { classNames } from '@standardnotes/utils'
|
||||||
import {
|
import {
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
ComponentArea,
|
ComponentArea,
|
||||||
ComponentViewerInterface,
|
ComponentViewerInterface,
|
||||||
ContentType,
|
ContentType,
|
||||||
|
EditorLineWidth,
|
||||||
isPayloadSourceInternalChange,
|
isPayloadSourceInternalChange,
|
||||||
isPayloadSourceRetrieved,
|
isPayloadSourceRetrieved,
|
||||||
NoteType,
|
NoteType,
|
||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
SNNote,
|
SNNote,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { confirmDialog, DELETE_NOTE_KEYBOARD_COMMAND, KeyboardKey } from '@standardnotes/ui-services'
|
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 { SuperEditor } from '../SuperEditor/SuperEditor'
|
||||||
import IndicatorCircle from '../IndicatorCircle/IndicatorCircle'
|
import IndicatorCircle from '../IndicatorCircle/IndicatorCircle'
|
||||||
import LinkedItemBubblesContainer from '../LinkedItems/LinkedItemBubblesContainer'
|
import LinkedItemBubblesContainer from '../LinkedItems/LinkedItemBubblesContainer'
|
||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
import { SuperEditorContentId } from '../SuperEditor/Constants'
|
import { SuperEditorContentId } from '../SuperEditor/Constants'
|
||||||
import { NoteViewController } from './Controller/NoteViewController'
|
import { NoteViewController } from './Controller/NoteViewController'
|
||||||
import { PlainEditor, PlainEditorInterface } from './PlainEditor/PlainEditor'
|
import { PlainEditor, PlainEditorInterface } from './PlainEditor/PlainEditor'
|
||||||
|
import { EditorMargins, EditorMaxWidths } from '../EditorWidthSelectionModal/EditorWidths'
|
||||||
|
|
||||||
const MinimumStatusDuration = 400
|
const MinimumStatusDuration = 400
|
||||||
|
|
||||||
@@ -58,7 +59,7 @@ type State = {
|
|||||||
editorStateDidLoad: boolean
|
editorStateDidLoad: boolean
|
||||||
editorTitle: string
|
editorTitle: string
|
||||||
isDesktop?: boolean
|
isDesktop?: boolean
|
||||||
marginResizersEnabled?: boolean
|
editorLineWidth: EditorLineWidth
|
||||||
noteLocked: boolean
|
noteLocked: boolean
|
||||||
noteStatus?: NoteStatus
|
noteStatus?: NoteStatus
|
||||||
saveError?: boolean
|
saveError?: boolean
|
||||||
@@ -68,10 +69,6 @@ type State = {
|
|||||||
syncTakingTooLong: boolean
|
syncTakingTooLong: boolean
|
||||||
monospaceFont?: boolean
|
monospaceFont?: boolean
|
||||||
plainEditorFocused?: boolean
|
plainEditorFocused?: boolean
|
||||||
leftResizerWidth: number
|
|
||||||
leftResizerOffset: number
|
|
||||||
rightResizerWidth: number
|
|
||||||
rightResizerOffset: number
|
|
||||||
|
|
||||||
updateSavingIndicator?: boolean
|
updateSavingIndicator?: boolean
|
||||||
editorFeatureIdentifier?: string
|
editorFeatureIdentifier?: string
|
||||||
@@ -112,6 +109,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
availableStackComponents: [],
|
availableStackComponents: [],
|
||||||
editorStateDidLoad: false,
|
editorStateDidLoad: false,
|
||||||
editorTitle: '',
|
editorTitle: '',
|
||||||
|
editorLineWidth: PrefDefaults[PrefKey.EditorLineWidth],
|
||||||
isDesktop: isDesktopApplication(),
|
isDesktop: isDesktopApplication(),
|
||||||
noteStatus: undefined,
|
noteStatus: undefined,
|
||||||
noteLocked: this.controller.item.locked,
|
noteLocked: this.controller.item.locked,
|
||||||
@@ -119,10 +117,6 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
spellcheck: true,
|
spellcheck: true,
|
||||||
stackComponentViewers: [],
|
stackComponentViewers: [],
|
||||||
syncTakingTooLong: false,
|
syncTakingTooLong: false,
|
||||||
leftResizerWidth: 0,
|
|
||||||
leftResizerOffset: 0,
|
|
||||||
rightResizerWidth: 0,
|
|
||||||
rightResizerOffset: 0,
|
|
||||||
editorFeatureIdentifier: this.controller.item.editorIdentifier,
|
editorFeatureIdentifier: this.controller.item.editorIdentifier,
|
||||||
noteType: this.controller.item.noteType,
|
noteType: this.controller.item.noteType,
|
||||||
}
|
}
|
||||||
@@ -280,6 +274,8 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
|
|
||||||
this.reloadSpellcheck().catch(console.error)
|
this.reloadSpellcheck().catch(console.error)
|
||||||
|
|
||||||
|
this.reloadLineWidth()
|
||||||
|
|
||||||
const isTemplateNoteInsertedToBeInteractableWithEditor = source === PayloadEmitSource.LocalInserted && note.dirty
|
const isTemplateNoteInsertedToBeInteractableWithEditor = source === PayloadEmitSource.LocalInserted && note.dirty
|
||||||
if (isTemplateNoteInsertedToBeInteractableWithEditor) {
|
if (isTemplateNoteInsertedToBeInteractableWithEditor) {
|
||||||
return
|
return
|
||||||
@@ -660,6 +656,14 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reloadLineWidth() {
|
||||||
|
const editorLineWidth = this.viewControllerManager.notesController.getEditorWidthForNote(this.note)
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
editorLineWidth,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async reloadPreferences() {
|
async reloadPreferences() {
|
||||||
log(LoggingDomain.NoteView, 'Reload preferences')
|
log(LoggingDomain.NoteView, 'Reload preferences')
|
||||||
const monospaceFont = this.application.getPreference(
|
const monospaceFont = this.application.getPreference(
|
||||||
@@ -667,10 +671,6 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
PrefDefaults[PrefKey.EditorMonospaceEnabled],
|
PrefDefaults[PrefKey.EditorMonospaceEnabled],
|
||||||
)
|
)
|
||||||
|
|
||||||
const marginResizersEnabled =
|
|
||||||
!isTabletOrMobileScreen() &&
|
|
||||||
this.application.getPreference(PrefKey.EditorResizersEnabled, PrefDefaults[PrefKey.EditorResizersEnabled])
|
|
||||||
|
|
||||||
const updateSavingIndicator = this.application.getPreference(
|
const updateSavingIndicator = this.application.getPreference(
|
||||||
PrefKey.UpdateSavingStatusIndicator,
|
PrefKey.UpdateSavingStatusIndicator,
|
||||||
PrefDefaults[PrefKey.UpdateSavingStatusIndicator],
|
PrefDefaults[PrefKey.UpdateSavingStatusIndicator],
|
||||||
@@ -678,30 +678,14 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
|
|
||||||
await this.reloadSpellcheck()
|
await this.reloadSpellcheck()
|
||||||
|
|
||||||
|
this.reloadLineWidth()
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
monospaceFont,
|
monospaceFont,
|
||||||
marginResizersEnabled,
|
|
||||||
updateSavingIndicator,
|
updateSavingIndicator,
|
||||||
})
|
})
|
||||||
|
|
||||||
reloadFont(monospaceFont)
|
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() {
|
async reloadStackComponents() {
|
||||||
@@ -896,24 +880,18 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
id={ElementIds.EditorContent}
|
id={ElementIds.EditorContent}
|
||||||
className={`${ElementIds.EditorContent} z-editor-content overflow-auto`}
|
className={classNames(
|
||||||
|
ElementIds.EditorContent,
|
||||||
|
'z-editor-content overflow-auto [&>*]: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}
|
ref={this.editorContentRef}
|
||||||
>
|
>
|
||||||
{this.state.marginResizersEnabled && this.editorContentRef.current ? (
|
|
||||||
<PanelResizer
|
|
||||||
minWidth={300}
|
|
||||||
hoverable={true}
|
|
||||||
collapsable={false}
|
|
||||||
panel={this.editorContentRef.current}
|
|
||||||
side={PanelSide.Left}
|
|
||||||
type={PanelResizeType.OffsetAndWidth}
|
|
||||||
left={this.state.leftResizerOffset}
|
|
||||||
width={this.state.leftResizerWidth}
|
|
||||||
resizeFinishCallback={this.onPanelResizeFinish}
|
|
||||||
modifyElementWidth={true}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{editorMode === 'component' && this.state.editorComponentViewer && (
|
{editorMode === 'component' && this.state.editorComponentViewer && (
|
||||||
<div className="component-view flex-grow">
|
<div className="component-view flex-grow">
|
||||||
<ComponentView
|
<ComponentView
|
||||||
@@ -950,21 +928,6 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.state.marginResizersEnabled && this.editorContentRef.current ? (
|
|
||||||
<PanelResizer
|
|
||||||
minWidth={300}
|
|
||||||
hoverable={true}
|
|
||||||
collapsable={false}
|
|
||||||
panel={this.editorContentRef.current}
|
|
||||||
side={PanelSide.Right}
|
|
||||||
type={PanelResizeType.OffsetAndWidth}
|
|
||||||
left={this.state.rightResizerOffset}
|
|
||||||
width={this.state.rightResizerWidth}
|
|
||||||
resizeFinishCallback={this.onPanelResizeFinish}
|
|
||||||
modifyElementWidth={true}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="editor-pane-component-stack">
|
<div id="editor-pane-component-stack">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { observer } from 'mobx-react-lite'
|
|||||||
import { useState, useEffect, useMemo, useCallback } from 'react'
|
import { useState, useEffect, useMemo, useCallback } from 'react'
|
||||||
import { NoteType, Platform, SNNote } from '@standardnotes/snjs'
|
import { NoteType, Platform, SNNote } from '@standardnotes/snjs'
|
||||||
import {
|
import {
|
||||||
|
CHANGE_EDITOR_WIDTH_COMMAND,
|
||||||
OPEN_NOTE_HISTORY_COMMAND,
|
OPEN_NOTE_HISTORY_COMMAND,
|
||||||
PIN_NOTE_COMMAND,
|
PIN_NOTE_COMMAND,
|
||||||
SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND,
|
SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND,
|
||||||
@@ -165,6 +166,14 @@ const NotesOptions = ({
|
|||||||
commandService.triggerCommand(SUPER_SHOW_MARKDOWN_PREVIEW)
|
commandService.triggerCommand(SUPER_SHOW_MARKDOWN_PREVIEW)
|
||||||
}, [commandService])
|
}, [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))
|
const unauthorized = notes.some((note) => !application.isAuthorizedToRenderItem(note))
|
||||||
if (unauthorized) {
|
if (unauthorized) {
|
||||||
return <ProtectedUnauthorizedLabel />
|
return <ProtectedUnauthorizedLabel />
|
||||||
@@ -186,6 +195,11 @@ const NotesOptions = ({
|
|||||||
{historyShortcut && <KeyboardShortcutIndicator className="ml-auto" shortcut={historyShortcut} />}
|
{historyShortcut && <KeyboardShortcutIndicator className="ml-auto" shortcut={historyShortcut} />}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<HorizontalSeparator classes="my-2" />
|
<HorizontalSeparator classes="my-2" />
|
||||||
|
<MenuItem onClick={toggleLineWidthModal}>
|
||||||
|
<Icon type="line-width" className={iconClass} />
|
||||||
|
Editor width
|
||||||
|
{editorWidthShortcut && <KeyboardShortcutIndicator className="ml-auto" shortcut={editorWidthShortcut} />}
|
||||||
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<MenuSwitchButtonItem
|
<MenuSwitchButtonItem
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { WebApplication } from '@/Application/WebApplication'
|
import { WebApplication } from '@/Application/WebApplication'
|
||||||
import Dropdown from '@/Components/Dropdown/Dropdown'
|
import Dropdown from '@/Components/Dropdown/Dropdown'
|
||||||
|
import Icon from '@/Components/Icon/Icon'
|
||||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||||
import Switch from '@/Components/Switch/Switch'
|
import Switch from '@/Components/Switch/Switch'
|
||||||
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
||||||
import { EditorFontSize, EditorLineHeight, PrefKey } from '@standardnotes/snjs'
|
import { ApplicationEvent, EditorFontSize, EditorLineHeight, EditorLineWidth, PrefKey } from '@standardnotes/snjs'
|
||||||
import { useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { Subtitle, Title, Text } from '../../PreferencesComponents/Content'
|
import { Subtitle, Title, Text } from '../../PreferencesComponents/Content'
|
||||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||||
|
import { CHANGE_EDITOR_WIDTH_COMMAND } from '@standardnotes/ui-services'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -59,28 +61,25 @@ const EditorDefaults = ({ application }: Props) => {
|
|||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
|
||||||
const [marginResizers, setMarginResizers] = useState(() =>
|
const [editorWidth, setEditorWidth] = useState(() =>
|
||||||
application.getPreference(PrefKey.EditorResizersEnabled, PrefDefaults[PrefKey.EditorResizersEnabled]),
|
application.getPreference(PrefKey.EditorLineWidth, PrefDefaults[PrefKey.EditorLineWidth]),
|
||||||
)
|
)
|
||||||
|
|
||||||
const toggleMarginResizers = () => {
|
const toggleEditorWidthModal = useCallback(() => {
|
||||||
setMarginResizers(!marginResizers)
|
application.keyboardService.triggerCommand(CHANGE_EDITOR_WIDTH_COMMAND, true)
|
||||||
application.setPreference(PrefKey.EditorResizersEnabled, !marginResizers).catch(console.error)
|
}, [application.keyboardService])
|
||||||
}
|
|
||||||
|
useEffect(() => {
|
||||||
|
return application.addSingleEventObserver(ApplicationEvent.PreferencesChanged, async () => {
|
||||||
|
setEditorWidth(application.getPreference(PrefKey.EditorLineWidth, PrefDefaults[PrefKey.EditorLineWidth]))
|
||||||
|
})
|
||||||
|
}, [application])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PreferencesGroup>
|
<PreferencesGroup>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Title>Editor appearance</Title>
|
<Title>Editor appearance</Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<Subtitle>Margin Resizers</Subtitle>
|
|
||||||
<Text>Allows left and right editor margins to be resized.</Text>
|
|
||||||
</div>
|
|
||||||
<Switch onChange={toggleMarginResizers} checked={marginResizers} />
|
|
||||||
</div>
|
|
||||||
<HorizontalSeparator classes="my-4" />
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Subtitle>Monospace Font</Subtitle>
|
<Subtitle>Monospace Font</Subtitle>
|
||||||
@@ -114,6 +113,20 @@ const EditorDefaults = ({ application }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<HorizontalSeparator classes="my-4" />
|
||||||
|
<div>
|
||||||
|
<Subtitle>Editor width</Subtitle>
|
||||||
|
<Text>Sets the max editor width for all notes</Text>
|
||||||
|
<div className="mt-2">
|
||||||
|
<button
|
||||||
|
className="flex w-full min-w-55 items-center justify-between rounded border border-border bg-default py-1.5 px-3.5 text-left text-base text-foreground md:w-fit lg:text-sm"
|
||||||
|
onClick={toggleEditorWidthModal}
|
||||||
|
>
|
||||||
|
{editorWidth === EditorLineWidth.FullWidth ? 'Full width' : editorWidth}
|
||||||
|
<Icon type="chevron-down" size="normal" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
</PreferencesGroup>
|
</PreferencesGroup>
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<RadioGroup store={radio} className="flex divide-x divide-border rounded border border-border">
|
||||||
|
{items.map(({ label, value: itemValue }) => (
|
||||||
|
<label
|
||||||
|
className={classNames(
|
||||||
|
'flex-grow select-none py-1.5 px-3.5 text-center',
|
||||||
|
'first:rounded-tl first:rounded-bl last:rounded-tr last:rounded-br',
|
||||||
|
itemValue === value &&
|
||||||
|
'bg-info-backdrop font-medium text-info ring-1 ring-inset ring-info focus-within:ring-2',
|
||||||
|
)}
|
||||||
|
key={itemValue}
|
||||||
|
>
|
||||||
|
<VisuallyHidden>
|
||||||
|
<Radio value={itemValue} />
|
||||||
|
</VisuallyHidden>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RadioButtonGroup
|
||||||
@@ -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'
|
import { FeatureIdentifier } from '@standardnotes/snjs'
|
||||||
|
|
||||||
export const PrefDefaults = {
|
export const PrefDefaults = {
|
||||||
@@ -10,6 +17,7 @@ export const PrefDefaults = {
|
|||||||
[PrefKey.EditorSpellcheck]: true,
|
[PrefKey.EditorSpellcheck]: true,
|
||||||
[PrefKey.EditorResizersEnabled]: false,
|
[PrefKey.EditorResizersEnabled]: false,
|
||||||
[PrefKey.EditorLineHeight]: EditorLineHeight.Normal,
|
[PrefKey.EditorLineHeight]: EditorLineHeight.Normal,
|
||||||
|
[PrefKey.EditorLineWidth]: EditorLineWidth.FullWidth,
|
||||||
[PrefKey.EditorFontSize]: EditorFontSize.Normal,
|
[PrefKey.EditorFontSize]: EditorFontSize.Normal,
|
||||||
[PrefKey.SortNotesBy]: CollectionSort.CreatedAt,
|
[PrefKey.SortNotesBy]: CollectionSort.CreatedAt,
|
||||||
[PrefKey.SortNotesReverse]: false,
|
[PrefKey.SortNotesReverse]: false,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
InternalEventBus,
|
InternalEventBus,
|
||||||
PrefKey,
|
PrefKey,
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
|
EditorLineWidth,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { makeObservable, observable, action, computed, runInAction } from 'mobx'
|
import { makeObservable, observable, action, computed, runInAction } from 'mobx'
|
||||||
import { WebApplication } from '../../Application/WebApplication'
|
import { WebApplication } from '../../Application/WebApplication'
|
||||||
@@ -358,6 +359,23 @@ export class NotesController extends AbstractViewController implements NotesCont
|
|||||||
this.application.sync.sync().catch(console.error)
|
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<NoteMutator>(
|
||||||
|
note,
|
||||||
|
(mutator) => {
|
||||||
|
mutator.editorWidth = editorWidth
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
this.application.sync.sync().catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
async addTagToSelectedNotes(tag: SNTag): Promise<void> {
|
async addTagToSelectedNotes(tag: SNTag): Promise<void> {
|
||||||
const selectedNotes = this.getSelectedNotesList()
|
const selectedNotes = this.getSelectedNotesList()
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
|||||||
Reference in New Issue
Block a user