Skip to content

Commit df21c61

Browse files
committed
add default alignment preference
1 parent 901310b commit df21c61

8 files changed

Lines changed: 177 additions & 66 deletions

File tree

packages/models/src/Domain/Syncable/UserPrefs/PrefDefaults.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const PrefDefaults = {
4343
[PrefKey.SuperNoteExportEmbedBehavior]: 'reference',
4444
[PrefKey.SuperNoteExportUseMDFrontmatter]: true,
4545
[PrefKey.SuperNoteExportPDFPageSize]: 'A4',
46+
[PrefKey.SuperNoteImageAlignment]: 'left',
4647
[PrefKey.SystemViewPreferences]: {},
4748
[PrefKey.AuthenticatorNames]: '',
4849
[PrefKey.ComponentPreferences]: {},

packages/models/src/Domain/Syncable/UserPrefs/PrefKey.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export enum PrefKey {
3636
SuperNoteExportEmbedBehavior = 'superNoteExportEmbedBehavior',
3737
SuperNoteExportUseMDFrontmatter = 'superNoteExportUseMDFrontmatter',
3838
SuperNoteExportPDFPageSize = 'superNoteExportPDFPageSize',
39+
SuperNoteImageAlignment = 'superNoteImageAlignment',
3940
AuthenticatorNames = 'authenticatorNames',
4041
PaneGesturesEnabled = 'paneGesturesEnabled',
4142
ComponentPreferences = 'componentPreferences',
@@ -101,4 +102,5 @@ export type PrefValue = {
101102
[PrefKey.AddImportsToTag]: boolean
102103
[PrefKey.AlwaysCreateNewTagForImports]: boolean
103104
[PrefKey.ExistingTagForImports]: string | undefined
105+
[PrefKey.SuperNoteImageAlignment]: 'left' | 'center' | 'right'
104106
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { classNames } from '@standardnotes/snjs'
2+
import IconButton from '@/Components/Button/IconButton'
3+
import StyledTooltip from '@/Components/StyledTooltip/StyledTooltip'
4+
import { ElementFormatType } from 'lexical'
5+
6+
export function getCSSValueFromAlignment(format: ElementFormatType) {
7+
switch (format) {
8+
case 'start':
9+
case 'left':
10+
return 'start'
11+
case 'right':
12+
case 'end':
13+
return 'end'
14+
default:
15+
return 'center'
16+
}
17+
}
18+
19+
export function ImageAlignmentOptions({
20+
alignment,
21+
changeAlignment,
22+
}: {
23+
alignment: ElementFormatType
24+
changeAlignment: (format: ElementFormatType) => void
25+
}) {
26+
return (
27+
<>
28+
<StyledTooltip label="Left align">
29+
<IconButton
30+
className={classNames(alignment === 'left' && '!bg-info', 'rounded p-1 hover:bg-contrast')}
31+
icon="format-align-left"
32+
title="Left align"
33+
focusable={true}
34+
onClick={() => changeAlignment('left')}
35+
/>
36+
</StyledTooltip>
37+
<StyledTooltip label="Center align">
38+
<IconButton
39+
className={classNames(alignment === 'center' && '!bg-info', 'rounded p-1 hover:bg-contrast')}
40+
icon="format-align-center"
41+
title="Center align"
42+
focusable={true}
43+
onClick={() => changeAlignment('center')}
44+
/>
45+
</StyledTooltip>
46+
<StyledTooltip label="Right align">
47+
<IconButton
48+
className={classNames(alignment === 'right' && '!bg-info', 'rounded p-1 hover:bg-contrast')}
49+
icon="format-align-right"
50+
title="Right align"
51+
focusable={true}
52+
onClick={() => changeAlignment('right')}
53+
/>
54+
</StyledTooltip>
55+
</>
56+
)
57+
}

packages/web/src/javascripts/Components/FilePreview/ImagePreview.tsx

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { classNames, IconType } from '@standardnotes/snjs'
1+
import { IconType, PrefKey } from '@standardnotes/snjs'
22
import { FunctionComponent, useCallback, useEffect, useState } from 'react'
3-
import IconButton from '../Button/IconButton'
3+
import IconButton from '@/Components/Button/IconButton'
44
import { OptionalSuperEmbeddedImageProps } from './OptionalSuperEmbeddedImageProps'
5-
import StyledTooltip from '../StyledTooltip/StyledTooltip'
5+
import usePreference from '@/Hooks/usePreference'
6+
import { getCSSValueFromAlignment, ImageAlignmentOptions } from './ImageAlignmentOptions'
67

78
type Props = {
89
objectUrl: string
@@ -114,21 +115,9 @@ const ImagePreview: FunctionComponent<Props> = ({
114115
</>
115116
)
116117

117-
let justifyContent: 'start' | 'center' | 'end' = 'center'
118-
if (alignment) {
119-
switch (alignment) {
120-
case 'start':
121-
case 'left':
122-
justifyContent = 'start'
123-
break
124-
case 'right':
125-
case 'end':
126-
justifyContent = 'end'
127-
break
128-
default:
129-
break
130-
}
131-
}
118+
const defaultSuperImageAlignment = usePreference(PrefKey.SuperNoteImageAlignment)
119+
const finalAlignment = alignment || defaultSuperImageAlignment
120+
const justifyContent = isEmbeddedInSuper ? getCSSValueFromAlignment(finalAlignment) : 'center'
132121

133122
return (
134123
<div className="group relative flex h-full min-h-0 w-full items-center" style={{ justifyContent }}>
@@ -170,33 +159,7 @@ const ImagePreview: FunctionComponent<Props> = ({
170159
<div className="flex divide-x divide-border rounded border border-border bg-default">
171160
{changeAlignment && (
172161
<div className="flex items-center gap-1 px-1 py-0.5">
173-
<StyledTooltip label="Left align">
174-
<IconButton
175-
className={classNames(justifyContent === 'start' && '!bg-info', 'rounded p-1 hover:bg-contrast')}
176-
icon="format-align-left"
177-
title="Left align"
178-
focusable={true}
179-
onClick={() => changeAlignment('left')}
180-
/>
181-
</StyledTooltip>
182-
<StyledTooltip label="Center align">
183-
<IconButton
184-
className={classNames(justifyContent === 'center' && '!bg-info', 'rounded p-1 hover:bg-contrast')}
185-
icon="format-align-center"
186-
title="Center align"
187-
focusable={true}
188-
onClick={() => changeAlignment('center')}
189-
/>
190-
</StyledTooltip>
191-
<StyledTooltip label="Right align">
192-
<IconButton
193-
className={classNames(justifyContent === 'end' && '!bg-info', 'rounded p-1 hover:bg-contrast')}
194-
icon="format-align-right"
195-
title="Right align"
196-
focusable={true}
197-
onClick={() => changeAlignment('right')}
198-
/>
199-
</StyledTooltip>
162+
<ImageAlignmentOptions alignment={finalAlignment} changeAlignment={changeAlignment} />
200163
</div>
201164
)}
202165
<div className="flex items-center px-2 py-0.5 text-sm">{imageResizer}</div>

packages/web/src/javascripts/Components/Preferences/Panes/General/Defaults.tsx

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { PrefKey, Platform } from '@standardnotes/snjs'
1+
import { PrefKey, Platform, PrefValue } from '@standardnotes/snjs'
22
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
33
import { WebApplication } from '@/Application/WebApplication'
4-
import { FunctionComponent, useState } from 'react'
4+
import { FunctionComponent, useMemo, useState } from 'react'
55
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
66
import Switch from '@/Components/Switch/Switch'
77
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
88
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
99
import usePreference from '@/Hooks/usePreference'
10+
import Dropdown from '@/Components/Dropdown/Dropdown'
11+
import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
1012
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
1113

1214
type Props = {
@@ -27,6 +29,27 @@ const Defaults: FunctionComponent<Props> = ({ application }) => {
2729
const addNoteToParentFolders = usePreference(PrefKey.NoteAddToParentFolders)
2830

2931
const alwaysShowSuperToolbar = usePreference(PrefKey.AlwaysShowSuperToolbar)
32+
const defaultSuperImageAlignment = usePreference(PrefKey.SuperNoteImageAlignment)
33+
const imageAlignmentOptions = useMemo(
34+
(): DropdownItem[] => [
35+
{
36+
icon: 'format-align-left',
37+
label: 'Left align',
38+
value: 'left',
39+
},
40+
{
41+
icon: 'format-align-center',
42+
label: 'Center align',
43+
value: 'center',
44+
},
45+
{
46+
icon: 'format-align-right',
47+
label: 'Right align',
48+
value: 'right',
49+
},
50+
],
51+
[],
52+
)
3053

3154
const toggleSpellcheck = () => {
3255
application.toggleGlobalSpellcheck().catch(console.error)
@@ -79,23 +102,46 @@ const Defaults: FunctionComponent<Props> = ({ application }) => {
79102
</div>
80103
<HorizontalSeparator classes="my-4" />
81104
{!isMobile && (
82-
<div className="flex justify-between gap-2 md:items-center">
83-
<div className="flex flex-col">
84-
<Subtitle>Use always-visible toolbar in Super notes</Subtitle>
85-
<Text>
86-
When enabled, the Super toolbar will always be shown at the top of the note. It can be temporarily
87-
toggled using Cmd/Ctrl+Shift+K. When disabled, the Super toolbar will only be shown as a floating
88-
toolbar when text is selected.
89-
</Text>
105+
<>
106+
<div className="flex justify-between gap-2 md:items-center">
107+
<div className="flex flex-col">
108+
<Subtitle>Use always-visible toolbar in Super notes</Subtitle>
109+
<Text>
110+
When enabled, the Super toolbar will always be shown at the top of the note. It can be temporarily
111+
toggled using Cmd/Ctrl+Shift+K. When disabled, the Super toolbar will only be shown as a floating
112+
toolbar when text is selected.
113+
</Text>
114+
</div>
115+
<Switch
116+
onChange={() => {
117+
application
118+
.setPreference(PrefKey.AlwaysShowSuperToolbar, !alwaysShowSuperToolbar)
119+
.catch(console.error)
120+
}}
121+
checked={alwaysShowSuperToolbar}
122+
/>
90123
</div>
91-
<Switch
92-
onChange={() => {
93-
application.setPreference(PrefKey.AlwaysShowSuperToolbar, !alwaysShowSuperToolbar).catch(console.error)
124+
<HorizontalSeparator classes="my-4" />
125+
</>
126+
)}
127+
<div>
128+
<Subtitle>Default image alignment in Super notes</Subtitle>
129+
<div className="mt-2">
130+
<Dropdown
131+
label="Default image alignment in super notes"
132+
items={imageAlignmentOptions}
133+
value={defaultSuperImageAlignment}
134+
onChange={(alignment) => {
135+
application
136+
.setPreference(
137+
PrefKey.SuperNoteImageAlignment,
138+
alignment as PrefValue[PrefKey.SuperNoteImageAlignment],
139+
)
140+
.catch(console.error)
94141
}}
95-
checked={alwaysShowSuperToolbar}
96142
/>
97143
</div>
98-
)}
144+
</div>
99145
</PreferencesSegment>
100146
</PreferencesGroup>
101147
)

packages/web/src/javascripts/Components/SuperEditor/Plugins/InlineFilePlugin/InlineFileComponent.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BlockWithAlignableContents } from '@lexical/react/LexicalBlockWithAlignableContents'
2-
import { Platform, classNames } from '@standardnotes/snjs'
2+
import { Platform, PrefKey, classNames } from '@standardnotes/snjs'
33
import { ElementFormatType, NodeKey } from 'lexical'
44
import { InlineFileNode } from './InlineFileNode'
55
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
@@ -9,6 +9,8 @@ import { $createFileNode } from '../EncryptedFilePlugin/Nodes/FileUtils'
99
import { isIOS } from '@standardnotes/ui-services'
1010
import Icon from '@/Components/Icon/Icon'
1111
import Spinner from '@/Components/Spinner/Spinner'
12+
import usePreference from '@/Hooks/usePreference'
13+
import { getCSSValueFromAlignment, ImageAlignmentOptions } from '@/Components/FilePreview/ImageAlignmentOptions'
1214

1315
type Props = {
1416
fileName: string | undefined
@@ -19,11 +21,12 @@ type Props = {
1921
focus: string
2022
}>
2123
format: ElementFormatType | null
24+
setFormat: (format: ElementFormatType) => void
2225
node: InlineFileNode
2326
nodeKey: NodeKey
2427
}
2528

26-
const InlineFileComponent = ({ className, src, mimeType, fileName, format, node, nodeKey }: Props) => {
29+
const InlineFileComponent = ({ className, src, mimeType, fileName, format, setFormat, node, nodeKey }: Props) => {
2730
const application = useApplication()
2831
const [editor] = useLexicalComposerContext()
2932

@@ -57,11 +60,28 @@ const InlineFileComponent = ({ className, src, mimeType, fileName, format, node,
5760

5861
const isPDF = mimeType === 'application/pdf'
5962

63+
const defaultSuperImageAlignment = usePreference(PrefKey.SuperNoteImageAlignment)
64+
const finalAlignment = format || defaultSuperImageAlignment
65+
const alignItems: 'start' | 'center' | 'end' = getCSSValueFromAlignment(finalAlignment)
66+
const changeAlignment = useCallback(
67+
(format: ElementFormatType) => {
68+
editor.update(() => {
69+
setFormat(format)
70+
})
71+
},
72+
[editor, setFormat],
73+
)
74+
6075
return (
6176
<BlockWithAlignableContents className={className} format={format} nodeKey={nodeKey}>
6277
{mimeType.startsWith('image') ? (
63-
<div className="relative flex min-h-[2rem] flex-col items-center gap-2.5">
78+
<div className="group relative flex min-h-[2rem] flex-col gap-2.5" style={{ alignItems }}>
6479
<img alt={fileName} src={src} />
80+
<div className="invisible absolute bottom-full left-1/2 z-10 -translate-x-1/2 px-1 pb-1 focus-within:visible group-hover:visible [.embedBlockFocused_&]:visible">
81+
<div className="flex gap-1 rounded border border-border bg-default px-1 py-0.5">
82+
<ImageAlignmentOptions alignment={finalAlignment} changeAlignment={changeAlignment} />
83+
</div>
84+
</div>
6585
</div>
6686
) : mimeType.startsWith('video') ? (
6787
<video className="h-full w-full" controls autoPlay>

packages/web/src/javascripts/Components/SuperEditor/Plugins/RemoteImagePlugin/RemoteImageComponent.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import Spinner from '@/Components/Spinner/Spinner'
44
import { isDesktopApplication } from '@/Utils'
55
import { BlockWithAlignableContents } from '@lexical/react/LexicalBlockWithAlignableContents'
66
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
7-
import { classNames, Platform } from '@standardnotes/snjs'
7+
import { classNames, Platform, PrefKey } from '@standardnotes/snjs'
88
import { $getNodeByKey, CLICK_COMMAND, COMMAND_PRIORITY_LOW, ElementFormatType, NodeKey } from 'lexical'
99
import { useCallback, useEffect, useRef, useState } from 'react'
1010
import { $createFileNode } from '../EncryptedFilePlugin/Nodes/FileUtils'
1111
import { RemoteImageNode } from './RemoteImageNode'
1212
import { isIOS } from '@standardnotes/ui-services'
1313
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection'
14+
import usePreference from '@/Hooks/usePreference'
15+
import { getCSSValueFromAlignment, ImageAlignmentOptions } from '@/Components/FilePreview/ImageAlignmentOptions'
1416

1517
type Props = {
1618
src: string
@@ -21,10 +23,11 @@ type Props = {
2123
focus: string
2224
}>
2325
format: ElementFormatType | null
26+
setFormat: (format: ElementFormatType) => void
2427
nodeKey: NodeKey
2528
}
2629

27-
const RemoteImageComponent = ({ className, src, alt, node, format, nodeKey }: Props) => {
30+
const RemoteImageComponent = ({ className, src, alt, node, format, nodeKey, setFormat }: Props) => {
2831
const application = useApplication()
2932
const [editor] = useLexicalComposerContext()
3033

@@ -91,16 +94,34 @@ const RemoteImageComponent = ({ className, src, alt, node, format, nodeKey }: Pr
9194
)
9295
}, [editor, isSelected, nodeKey, setSelected])
9396

97+
const changeAlignment = useCallback(
98+
(format: ElementFormatType) => {
99+
editor.update(() => {
100+
setFormat(format)
101+
})
102+
},
103+
[editor, setFormat],
104+
)
105+
106+
const defaultSuperImageAlignment = usePreference(PrefKey.SuperNoteImageAlignment)
107+
const finalAlignment = format || defaultSuperImageAlignment
108+
const alignItems: 'start' | 'center' | 'end' = getCSSValueFromAlignment(finalAlignment)
109+
94110
return (
95111
<BlockWithAlignableContents className={className} format={format} nodeKey={nodeKey}>
96-
<div ref={ref} className="relative flex min-h-[2rem] flex-col items-center gap-2.5">
112+
<div ref={ref} className="group relative flex min-h-[2rem] flex-col gap-2.5" style={{ alignItems }}>
97113
<img
98114
alt={alt}
99115
src={src}
100116
onLoad={() => {
101117
setDidImageLoad(true)
102118
}}
103119
/>
120+
<div className="invisible absolute bottom-full left-1/2 z-10 -translate-x-1/2 px-1 pb-1 focus-within:visible group-hover:visible [.embedBlockFocused_&]:visible">
121+
<div className="flex gap-1 rounded border border-border bg-default px-1 py-0.5">
122+
<ImageAlignmentOptions alignment={finalAlignment} changeAlignment={changeAlignment} />
123+
</div>
124+
</div>
104125
{didImageLoad && canShowSaveButton && (
105126
<button
106127
className={classNames(

packages/web/src/javascripts/Components/SuperEditor/Plugins/RemoteImagePlugin/RemoteImageNode.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export class RemoteImageNode extends DecoratorBlockNode {
9494
<RemoteImageComponent
9595
className={className}
9696
format={this.__format}
97+
setFormat={this.setFormat.bind(this)}
9798
nodeKey={this.getKey()}
9899
node={this}
99100
src={this.__src}

0 commit comments

Comments
 (0)