chore: prettier files

This commit is contained in:
Mo
2022-05-03 10:51:40 -05:00
parent f5a90060ea
commit 43d94fbcbf
127 changed files with 1365 additions and 2428 deletions

View File

@@ -8,10 +8,7 @@ import { findDOMNode, unmountComponentAtNode } from 'preact/compat'
export type PureComponentState = Partial<Record<string, any>>
export type PureComponentProps = Partial<Record<string, any>>
export abstract class PureComponent<
P = PureComponentProps,
S = PureComponentState,
> extends Component<P, S> {
export abstract class PureComponent<P = PureComponentProps, S = PureComponentState> extends Component<P, S> {
private unsubApp!: () => void
private unsubState!: () => void
private reactionDisposers: IReactionDisposer[] = []

View File

@@ -107,10 +107,8 @@ export const ConfirmPassword: FunctionComponent<Props> = observer(
</div>
<div className="px-3 mb-3 text-sm">
Because your notes are encrypted using your password,{' '}
<span className="color-dark-red">
Standard Notes does not have a password reset option
</span>
. If you forget your password, you will permanently lose access to your data.
<span className="color-dark-red">Standard Notes does not have a password reset option</span>. If you forget
your password, you will permanently lose access to your data.
</div>
<form onSubmit={handleConfirmFormSubmit} className="px-3 mb-1">
<DecoratedPasswordInput

View File

@@ -111,19 +111,10 @@ export const CreateAccount: FunctionComponent<Props> = observer(
ref={passwordInputRef}
value={password}
/>
<Button
className="btn-w-full mt-1"
label="Next"
variant="primary"
onClick={handleRegisterFormSubmit}
/>
<Button className="btn-w-full mt-1" label="Next" variant="primary" onClick={handleRegisterFormSubmit} />
</form>
<div className="h-1px my-2 bg-border"></div>
<AdvancedOptions
application={application}
appState={appState}
onVaultChange={onVaultChange}
/>
<AdvancedOptions application={application} appState={appState} onVaultChange={onVaultChange} />
</>
)
},

View File

@@ -26,9 +26,7 @@ const iconClassName = 'color-neutral mr-2'
export const GeneralAccountMenu: FunctionComponent<Props> = observer(
({ application, appState, setMenuPane, closeMenu, mainApplicationGroup }) => {
const [isSyncingInProgress, setIsSyncingInProgress] = useState(false)
const [lastSyncDate, setLastSyncDate] = useState(
formatLastSyncDate(application.sync.getLastSyncDate() as Date),
)
const [lastSyncDate, setLastSyncDate] = useState(formatLastSyncDate(application.sync.getLastSyncDate() as Date))
const doSynchronization = async () => {
setIsSyncingInProgress(true)
@@ -97,8 +95,8 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
<>
<div className="px-3 mb-1">
<div className="mb-3 color-foreground">
Youre offline. Sign in to sync your notes and preferences across all your devices
and enable end-to-end encryption.
Youre offline. Sign in to sync your notes and preferences across all your devices and enable end-to-end
encryption.
</div>
<div className="flex items-center color-grey-1">
<Icon type="cloud-off" className="mr-2" />
@@ -114,10 +112,7 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
initialFocus={!application.hasAccount() ? CREATE_ACCOUNT_INDEX : SWITCHER_INDEX}
>
<MenuItemSeparator />
<WorkspaceSwitcherOption
mainApplicationGroup={mainApplicationGroup}
appState={appState}
/>
<WorkspaceSwitcherOption mainApplicationGroup={mainApplicationGroup} appState={appState} />
<MenuItemSeparator />
{user ? (
<MenuItem

View File

@@ -19,188 +19,186 @@ type Props = {
setMenuPane: (pane: AccountMenuPane) => void
}
export const SignInPane: FunctionComponent<Props> = observer(
({ application, appState, setMenuPane }) => {
const { notesAndTagsCount } = appState.accountMenu
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [isEphemeral, setIsEphemeral] = useState(false)
export const SignInPane: FunctionComponent<Props> = observer(({ application, appState, setMenuPane }) => {
const { notesAndTagsCount } = appState.accountMenu
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [isEphemeral, setIsEphemeral] = useState(false)
const [isStrictSignin, setIsStrictSignin] = useState(false)
const [isSigningIn, setIsSigningIn] = useState(false)
const [shouldMergeLocal, setShouldMergeLocal] = useState(true)
const [isVault, setIsVault] = useState(false)
const [isStrictSignin, setIsStrictSignin] = useState(false)
const [isSigningIn, setIsSigningIn] = useState(false)
const [shouldMergeLocal, setShouldMergeLocal] = useState(true)
const [isVault, setIsVault] = useState(false)
const emailInputRef = useRef<HTMLInputElement>(null)
const passwordInputRef = useRef<HTMLInputElement>(null)
const emailInputRef = useRef<HTMLInputElement>(null)
const passwordInputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
if (emailInputRef?.current) {
emailInputRef.current?.focus()
}
if (isDev && window.devAccountEmail) {
setEmail(window.devAccountEmail)
setPassword(window.devAccountPassword as string)
}
}, [])
const resetInvalid = () => {
if (error.length) {
setError('')
}
useEffect(() => {
if (emailInputRef?.current) {
emailInputRef.current?.focus()
}
const handleEmailChange = (text: string) => {
setEmail(text)
if (isDev && window.devAccountEmail) {
setEmail(window.devAccountEmail)
setPassword(window.devAccountPassword as string)
}
}, [])
const handlePasswordChange = (text: string) => {
if (error.length) {
setError('')
}
setPassword(text)
const resetInvalid = () => {
if (error.length) {
setError('')
}
}
const handleEphemeralChange = () => {
setIsEphemeral(!isEphemeral)
const handleEmailChange = (text: string) => {
setEmail(text)
}
const handlePasswordChange = (text: string) => {
if (error.length) {
setError('')
}
setPassword(text)
}
const handleStrictSigninChange = () => {
setIsStrictSignin(!isStrictSignin)
}
const handleEphemeralChange = () => {
setIsEphemeral(!isEphemeral)
}
const handleShouldMergeChange = () => {
setShouldMergeLocal(!shouldMergeLocal)
}
const handleStrictSigninChange = () => {
setIsStrictSignin(!isStrictSignin)
}
const signIn = () => {
setIsSigningIn(true)
emailInputRef?.current?.blur()
passwordInputRef?.current?.blur()
const handleShouldMergeChange = () => {
setShouldMergeLocal(!shouldMergeLocal)
}
application
.signIn(email, password, isStrictSignin, isEphemeral, shouldMergeLocal)
.then((res) => {
if (res.error) {
throw new Error(res.error.message)
}
appState.accountMenu.closeAccountMenu()
})
.catch((err) => {
console.error(err)
setError(err.message ?? err.toString())
setPassword('')
passwordInputRef?.current?.blur()
})
.finally(() => {
setIsSigningIn(false)
})
}
const signIn = () => {
setIsSigningIn(true)
emailInputRef?.current?.blur()
passwordInputRef?.current?.blur()
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
handleSignInFormSubmit(e)
}
}
const onVaultChange = useCallback(
(newIsVault: boolean, vaultedEmail?: string) => {
setIsVault(newIsVault)
if (newIsVault && vaultedEmail) {
setEmail(vaultedEmail)
application
.signIn(email, password, isStrictSignin, isEphemeral, shouldMergeLocal)
.then((res) => {
if (res.error) {
throw new Error(res.error.message)
}
},
[setEmail],
)
appState.accountMenu.closeAccountMenu()
})
.catch((err) => {
console.error(err)
setError(err.message ?? err.toString())
setPassword('')
passwordInputRef?.current?.blur()
})
.finally(() => {
setIsSigningIn(false)
})
}
const handleSignInFormSubmit = (e: Event) => {
e.preventDefault()
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
handleSignInFormSubmit(e)
}
}
if (!email || email.length === 0) {
emailInputRef?.current?.focus()
return
const onVaultChange = useCallback(
(newIsVault: boolean, vaultedEmail?: string) => {
setIsVault(newIsVault)
if (newIsVault && vaultedEmail) {
setEmail(vaultedEmail)
}
},
[setEmail],
)
if (!password || password.length === 0) {
passwordInputRef?.current?.focus()
return
}
const handleSignInFormSubmit = (e: Event) => {
e.preventDefault()
signIn()
if (!email || email.length === 0) {
emailInputRef?.current?.focus()
return
}
return (
<>
<div className="flex items-center px-3 mt-1 mb-3">
<IconButton
icon="arrow-left"
title="Go back"
className="flex mr-2 color-neutral p-0"
onClick={() => setMenuPane(AccountMenuPane.GeneralMenu)}
focusable={true}
disabled={isSigningIn}
/>
<div className="sn-account-menu-headline">Sign in</div>
</div>
<div className="px-3 mb-1">
<DecoratedInput
className={`mb-2 ${error ? 'border-dark-red' : null}`}
left={[<Icon type="email" className="color-neutral" />]}
type="email"
placeholder="Email"
value={email}
onChange={handleEmailChange}
onFocus={resetInvalid}
onKeyDown={handleKeyDown}
disabled={isSigningIn || isVault}
ref={emailInputRef}
/>
<DecoratedPasswordInput
className={`mb-2 ${error ? 'border-dark-red' : null}`}
disabled={isSigningIn}
left={[<Icon type="password" className="color-neutral" />]}
onChange={handlePasswordChange}
onFocus={resetInvalid}
onKeyDown={handleKeyDown}
placeholder="Password"
ref={passwordInputRef}
value={password}
/>
{error ? <div className="color-dark-red my-2">{error}</div> : null}
<Button
className="btn-w-full mt-1 mb-3"
label={isSigningIn ? 'Signing in...' : 'Sign in'}
variant="primary"
onClick={handleSignInFormSubmit}
disabled={isSigningIn}
/>
<Checkbox
name="is-ephemeral"
label="Stay signed in"
checked={!isEphemeral}
disabled={isSigningIn}
onChange={handleEphemeralChange}
/>
{notesAndTagsCount > 0 ? (
<Checkbox
name="should-merge-local"
label={`Merge local data (${notesAndTagsCount} notes and tags)`}
checked={shouldMergeLocal}
disabled={isSigningIn}
onChange={handleShouldMergeChange}
/>
) : null}
</div>
<div className="h-1px my-2 bg-border"></div>
<AdvancedOptions
appState={appState}
application={application}
if (!password || password.length === 0) {
passwordInputRef?.current?.focus()
return
}
signIn()
}
return (
<>
<div className="flex items-center px-3 mt-1 mb-3">
<IconButton
icon="arrow-left"
title="Go back"
className="flex mr-2 color-neutral p-0"
onClick={() => setMenuPane(AccountMenuPane.GeneralMenu)}
focusable={true}
disabled={isSigningIn}
onVaultChange={onVaultChange}
onStrictSignInChange={handleStrictSigninChange}
/>
</>
)
},
)
<div className="sn-account-menu-headline">Sign in</div>
</div>
<div className="px-3 mb-1">
<DecoratedInput
className={`mb-2 ${error ? 'border-dark-red' : null}`}
left={[<Icon type="email" className="color-neutral" />]}
type="email"
placeholder="Email"
value={email}
onChange={handleEmailChange}
onFocus={resetInvalid}
onKeyDown={handleKeyDown}
disabled={isSigningIn || isVault}
ref={emailInputRef}
/>
<DecoratedPasswordInput
className={`mb-2 ${error ? 'border-dark-red' : null}`}
disabled={isSigningIn}
left={[<Icon type="password" className="color-neutral" />]}
onChange={handlePasswordChange}
onFocus={resetInvalid}
onKeyDown={handleKeyDown}
placeholder="Password"
ref={passwordInputRef}
value={password}
/>
{error ? <div className="color-dark-red my-2">{error}</div> : null}
<Button
className="btn-w-full mt-1 mb-3"
label={isSigningIn ? 'Signing in...' : 'Sign in'}
variant="primary"
onClick={handleSignInFormSubmit}
disabled={isSigningIn}
/>
<Checkbox
name="is-ephemeral"
label="Stay signed in"
checked={!isEphemeral}
disabled={isSigningIn}
onChange={handleEphemeralChange}
/>
{notesAndTagsCount > 0 ? (
<Checkbox
name="should-merge-local"
label={`Merge local data (${notesAndTagsCount} notes and tags)`}
checked={shouldMergeLocal}
disabled={isSigningIn}
onChange={handleShouldMergeChange}
/>
) : null}
</div>
<div className="h-1px my-2 bg-border"></div>
<AdvancedOptions
appState={appState}
application={application}
disabled={isSigningIn}
onVaultChange={onVaultChange}
onStrictSignInChange={handleStrictSigninChange}
/>
</>
)
})

View File

@@ -13,65 +13,55 @@ type Props = {
appState: AppState
}
export const WorkspaceSwitcherOption: FunctionComponent<Props> = observer(
({ mainApplicationGroup, appState }) => {
const buttonRef = useRef<HTMLButtonElement>(null)
const menuRef = useRef<HTMLDivElement>(null)
const [isOpen, setIsOpen] = useState(false)
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>()
export const WorkspaceSwitcherOption: FunctionComponent<Props> = observer(({ mainApplicationGroup, appState }) => {
const buttonRef = useRef<HTMLButtonElement>(null)
const menuRef = useRef<HTMLDivElement>(null)
const [isOpen, setIsOpen] = useState(false)
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>()
const toggleMenu = () => {
if (!isOpen) {
const menuPosition = calculateSubmenuStyle(buttonRef.current)
if (menuPosition) {
setMenuStyle(menuPosition)
}
const toggleMenu = () => {
if (!isOpen) {
const menuPosition = calculateSubmenuStyle(buttonRef.current)
if (menuPosition) {
setMenuStyle(menuPosition)
}
setIsOpen(!isOpen)
}
useEffect(() => {
if (isOpen) {
setTimeout(() => {
const newMenuPosition = calculateSubmenuStyle(buttonRef.current, menuRef.current)
setIsOpen(!isOpen)
}
if (newMenuPosition) {
setMenuStyle(newMenuPosition)
}
})
}
}, [isOpen])
useEffect(() => {
if (isOpen) {
setTimeout(() => {
const newMenuPosition = calculateSubmenuStyle(buttonRef.current, menuRef.current)
return (
<>
<button
ref={buttonRef}
className="sn-dropdown-item justify-between focus:bg-info-backdrop focus:shadow-none"
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
role="menuitem"
onClick={toggleMenu}
>
<div className="flex items-center">
<Icon type="user-switch" className="color-neutral mr-2" />
Switch workspace
</div>
<Icon type="chevron-right" className="color-neutral" />
</button>
{isOpen && (
<div
ref={menuRef}
className="sn-dropdown max-h-120 min-w-68 py-2 fixed overflow-y-auto"
style={menuStyle}
>
<WorkspaceSwitcherMenu
mainApplicationGroup={mainApplicationGroup}
appState={appState}
isOpen={isOpen}
/>
</div>
)}
</>
)
},
)
if (newMenuPosition) {
setMenuStyle(newMenuPosition)
}
})
}
}, [isOpen])
return (
<>
<button
ref={buttonRef}
className="sn-dropdown-item justify-between focus:bg-info-backdrop focus:shadow-none"
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
role="menuitem"
onClick={toggleMenu}
>
<div className="flex items-center">
<Icon type="user-switch" className="color-neutral mr-2" />
Switch workspace
</div>
<Icon type="chevron-right" className="color-neutral" />
</button>
{isOpen && (
<div ref={menuRef} className="sn-dropdown max-h-120 min-w-68 py-2 fixed overflow-y-auto" style={menuStyle}>
<WorkspaceSwitcherMenu mainApplicationGroup={mainApplicationGroup} appState={appState} isOpen={isOpen} />
</div>
)}
</>
)
})

View File

@@ -51,9 +51,7 @@ const MenuPaneSelector: FunctionComponent<PaneSelectorProps> = observer(
/>
)
case AccountMenuPane.SignIn:
return (
<SignInPane appState={appState} application={application} setMenuPane={setMenuPane} />
)
return <SignInPane appState={appState} application={application} setMenuPane={setMenuPane} />
case AccountMenuPane.Register:
return (
<CreateAccount
@@ -82,8 +80,7 @@ const MenuPaneSelector: FunctionComponent<PaneSelectorProps> = observer(
export const AccountMenu: FunctionComponent<Props> = observer(
({ application, appState, onClickOutside, mainApplicationGroup }) => {
const { currentPane, setCurrentPane, shouldAnimateCloseMenu, closeAccountMenu } =
appState.accountMenu
const { currentPane, setCurrentPane, shouldAnimateCloseMenu, closeAccountMenu } = appState.accountMenu
const ref = useRef<HTMLDivElement>(null)
useCloseOnClickOutside(ref, () => {

View File

@@ -21,12 +21,7 @@ interface Props {
* IconButton component with an icon
* preventDefault is already handled within the component
*/
export const RoundIconButton: FunctionComponent<Props> = ({
onClick,
type,
className,
icon: iconType,
}) => {
export const RoundIconButton: FunctionComponent<Props> = ({ onClick, type, className, icon: iconType }) => {
const click = (e: MouseEvent) => {
e.preventDefault()
onClick()

View File

@@ -34,10 +34,7 @@ type Props = {
onDismiss: (challenge: Challenge) => Promise<void>
}
const validateValues = (
values: ChallengeModalValues,
prompts: ChallengePrompt[],
): ChallengeModalValues | undefined => {
const validateValues = (values: ChallengeModalValues, prompts: ChallengePrompt[]): ChallengeModalValues | undefined => {
let hasInvalidValues = false
const validatedValues = { ...values }
for (const prompt of prompts) {
@@ -75,10 +72,9 @@ export const ChallengeModal: FunctionComponent<Props> = ({
const [isProcessing, setIsProcessing] = useState(false)
const [, setProcessingPrompts] = useState<ChallengePrompt[]>([])
const [bypassModalFocusLock, setBypassModalFocusLock] = useState(false)
const shouldShowForgotPasscode = [
ChallengeReason.ApplicationUnlock,
ChallengeReason.Migration,
].includes(challenge.reason)
const shouldShowForgotPasscode = [ChallengeReason.ApplicationUnlock, ChallengeReason.Migration].includes(
challenge.reason,
)
const shouldShowWorkspaceSwitcher = challenge.reason === ChallengeReason.ApplicationUnlock
const submit = async () => {
@@ -268,10 +264,7 @@ export const ChallengeModal: FunctionComponent<Props> = ({
</Button>
)}
{shouldShowWorkspaceSwitcher && (
<LockscreenWorkspaceSwitcher
mainApplicationGroup={mainApplicationGroup}
appState={appState}
/>
<LockscreenWorkspaceSwitcher mainApplicationGroup={mainApplicationGroup} appState={appState} />
)}
</DialogContent>
</DialogOverlay>

View File

@@ -1,8 +1,4 @@
import {
ChallengePrompt,
ChallengeValidation,
ProtectionSessionDurations,
} from '@standardnotes/snjs'
import { ChallengePrompt, ChallengeValidation, ProtectionSessionDurations } from '@standardnotes/snjs'
import { FunctionComponent } from 'preact'
import { useEffect, useRef } from 'preact/hooks'
import { DecoratedInput } from '@/Components/Input/DecoratedInput'
@@ -17,13 +13,7 @@ type Props = {
isInvalid: boolean
}
export const ChallengeModalPrompt: FunctionComponent<Props> = ({
prompt,
values,
index,
onValueChange,
isInvalid,
}) => {
export const ChallengeModalPrompt: FunctionComponent<Props> = ({ prompt, values, index, onValueChange, isInvalid }) => {
const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
@@ -49,9 +39,7 @@ export const ChallengeModalPrompt: FunctionComponent<Props> = ({
return (
<label
className={`cursor-pointer px-2 py-1.5 rounded ${
selected
? 'bg-default color-foreground font-semibold'
: 'color-grey-0 hover:bg-grey-3'
selected ? 'bg-default color-foreground font-semibold' : 'color-grey-0 hover:bg-grey-3'
}`}
>
<input
@@ -88,9 +76,7 @@ export const ChallengeModalPrompt: FunctionComponent<Props> = ({
onChange={(value) => onValueChange(value, prompt)}
/>
)}
{isInvalid && (
<div className="text-sm color-danger mt-2">Invalid authentication, please try again.</div>
)}
{isInvalid && <div className="text-sm color-danger mt-2">Invalid authentication, please try again.</div>}
</div>
)
}

View File

@@ -13,10 +13,7 @@ type Props = {
appState: AppState
}
export const LockscreenWorkspaceSwitcher: FunctionComponent<Props> = ({
mainApplicationGroup,
appState,
}) => {
export const LockscreenWorkspaceSwitcher: FunctionComponent<Props> = ({ mainApplicationGroup, appState }) => {
const buttonRef = useRef<HTMLButtonElement>(null)
const menuRef = useRef<HTMLDivElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
@@ -51,20 +48,12 @@ export const LockscreenWorkspaceSwitcher: FunctionComponent<Props> = ({
return (
<div ref={containerRef}>
<Button
ref={buttonRef}
onClick={toggleMenu}
className="flex items-center justify-center min-w-76 mt-2"
>
<Button ref={buttonRef} onClick={toggleMenu} className="flex items-center justify-center min-w-76 mt-2">
<Icon type="user-switch" className="color-neutral mr-2" />
Switch workspace
</Button>
{isOpen && (
<div
ref={menuRef}
className="sn-dropdown max-h-120 min-w-68 py-2 fixed overflow-y-auto"
style={menuStyle}
>
<div ref={menuRef} className="sn-dropdown max-h-120 min-w-68 py-2 fixed overflow-y-auto" style={menuStyle}>
<WorkspaceSwitcherMenu
mainApplicationGroup={mainApplicationGroup}
appState={appState}

View File

@@ -8,13 +8,7 @@ type CheckboxProps = {
label: string
}
export const Checkbox: FunctionComponent<CheckboxProps> = ({
name,
checked,
onChange,
disabled,
label,
}) => {
export const Checkbox: FunctionComponent<CheckboxProps> = ({ name, checked, onChange, disabled, label }) => {
return (
<label htmlFor={name} className="flex items-center fit-content mb-2">
<input

View File

@@ -5,18 +5,13 @@ interface IProps {
dismissDeprecationMessage: () => void
}
export const IsDeprecated: FunctionalComponent<IProps> = ({
deprecationMessage,
dismissDeprecationMessage,
}) => {
export const IsDeprecated: FunctionalComponent<IProps> = ({ deprecationMessage, dismissDeprecationMessage }) => {
return (
<div className={'sn-component'}>
<div className={'sk-app-bar no-edges no-top-edge dynamic-height'}>
<div className={'left'}>
<div className={'sk-app-bar-item'}>
<div className={'sk-label warning'}>
{deprecationMessage || 'This extension is deprecated.'}
</div>
<div className={'sk-label warning'}>{deprecationMessage || 'This extension is deprecated.'}</div>
</div>
</div>
<div className={'right'}>

View File

@@ -7,21 +7,17 @@ export const OfflineRestricted: FunctionalComponent = () => {
<div className={'sk-panel-content'}>
<div className={'sk-panel-section stretch'}>
<div className={'sk-panel-column'} />
<div className={'sk-h1 sk-bold'}>
You have restricted this component to not use a hosted version.
</div>
<div className={'sk-subtitle'}>
Locally-installed components are not available in the web application.
</div>
<div className={'sk-h1 sk-bold'}>You have restricted this component to not use a hosted version.</div>
<div className={'sk-subtitle'}>Locally-installed components are not available in the web application.</div>
<div className={'sk-panel-row'} />
<div className={'sk-panel-row'}>
<div className={'sk-panel-column'}>
<div className={'sk-p'}>To continue, choose from the following options:</div>
<ul>
<li className={'sk-p'}>
Enable the Hosted option for this component by opening the Preferences {'>'}{' '}
General {'>'} Advanced Settings menu and toggling 'Use hosted when local is
unavailable' under this component's options. Then press Reload.
Enable the Hosted option for this component by opening the Preferences {'>'} General {'>'} Advanced
Settings menu and toggling 'Use hosted when local is unavailable' under this component's options.
Then press Reload.
</li>
<li className={'sk-p'}>Use the desktop application.</li>
</ul>

View File

@@ -10,13 +10,8 @@ export const UrlMissing: FunctionalComponent<IProps> = ({ componentName }) => {
<div className={'sk-panel static'}>
<div className={'sk-panel-content'}>
<div className={'sk-panel-section stretch'}>
<div className={'sk-panel-section-title'}>
This extension is missing its URL property.
</div>
<p>
In order to access your note immediately, please switch from {componentName} to the
Plain Editor.
</p>
<div className={'sk-panel-section-title'}>This extension is missing its URL property.</div>
<p>In order to access your note immediately, please switch from {componentName} to the Plain Editor.</p>
<br />
<p>Please contact help@standardnotes.com to remedy this issue.</p>
</div>

View File

@@ -43,9 +43,7 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
const [hasIssueLoading, setHasIssueLoading] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const [featureStatus, setFeatureStatus] = useState<FeatureStatus>(
componentViewer.getFeatureStatus(),
)
const [featureStatus, setFeatureStatus] = useState<FeatureStatus>(componentViewer.getFeatureStatus())
const [isComponentValid, setIsComponentValid] = useState(true)
const [error, setError] = useState<ComponentViewerError | undefined>(undefined)
const [deprecationMessage, setDeprecationMessage] = useState<string | undefined>(undefined)
@@ -199,10 +197,7 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
/>
)}
{deprecationMessage && !isDeprecationMessageDismissed && (
<IsDeprecated
deprecationMessage={deprecationMessage}
dismissDeprecationMessage={dismissDeprecationMessage}
/>
<IsDeprecated deprecationMessage={deprecationMessage} dismissDeprecationMessage={dismissDeprecationMessage} />
)}
{error === ComponentViewerError.OfflineRestricted && <OfflineRestricted />}
{error === ComponentViewerError.MissingUrl && <UrlMissing componentName={component.name} />}

View File

@@ -1,11 +1,4 @@
import {
ListboxArrow,
ListboxButton,
ListboxInput,
ListboxList,
ListboxOption,
ListboxPopover,
} from '@reach/listbox'
import { ListboxArrow, ListboxButton, ListboxInput, ListboxList, ListboxOption, ListboxPopover } from '@reach/listbox'
import VisuallyHidden from '@reach/visually-hidden'
import { FunctionComponent } from 'preact'
import { Icon } from '@/Components/Icon'
@@ -53,14 +46,7 @@ const CustomDropdownButton: FunctionComponent<ListboxButtonProps> = ({
</>
)
export const Dropdown: FunctionComponent<DropdownProps> = ({
id,
label,
items,
value,
onChange,
disabled,
}) => {
export const Dropdown: FunctionComponent<DropdownProps> = ({ id, label, items, value, onChange, disabled }) => {
const labelId = `${id}-label`
const handleChange = (value: string) => {
@@ -72,12 +58,7 @@ export const Dropdown: FunctionComponent<DropdownProps> = ({
return (
<>
<VisuallyHidden id={labelId}>{label}</VisuallyHidden>
<ListboxInput
value={value}
onChange={handleChange}
aria-labelledby={labelId}
disabled={disabled}
>
<ListboxInput value={value} onChange={handleChange} aria-labelledby={labelId} disabled={disabled}>
<ListboxButton
className="sn-dropdown-button"
children={({ value, label, isExpanded }) => {
@@ -105,10 +86,7 @@ export const Dropdown: FunctionComponent<DropdownProps> = ({
>
{item.icon ? (
<div className="flex mr-3">
<Icon
type={item.icon}
className={`sn-icon--small ${item.iconClassName ?? ''}`}
/>
<Icon type={item.icon} className={`sn-icon--small ${item.iconClassName ?? ''}`} />
</div>
) : null}
<div className="text-input">{item.label}</div>

View File

@@ -2,12 +2,7 @@ import { WebAppEvent, WebApplication } from '@/UIModels/Application'
import { ApplicationGroup } from '@/UIModels/ApplicationGroup'
import { PureComponent } from '@/Components/Abstract/PureComponent'
import { preventRefreshing } from '@/Utils'
import {
ApplicationEvent,
ContentType,
CollectionSort,
ApplicationDescriptor,
} from '@standardnotes/snjs'
import { ApplicationEvent, ContentType, CollectionSort, ApplicationDescriptor } from '@standardnotes/snjs'
import {
STRING_NEW_UPDATE_READY,
STRING_CONFIRM_APP_QUIT_DURING_UPGRADE,
@@ -241,9 +236,7 @@ export class Footer extends PureComponent<Props, State> {
style: 'percent',
})
statusManager.setMessage(
`Syncing ${stats.uploadTotalCount} items (${stringPercentage} complete)`,
)
statusManager.setMessage(`Syncing ${stats.uploadTotalCount} items (${stringPercentage} complete)`)
} else {
statusManager.setMessage('')
}
@@ -330,9 +323,7 @@ export class Footer extends PureComponent<Props, State> {
betaMessageClickHandler = () => {
alertDialog({
title: 'You are using a beta version of the app',
text:
'If you wish to go back to a stable version, make sure to sign out ' +
'of this beta app first.',
text: 'If you wish to go back to a stable version, make sure to sign out ' + 'of this beta app first.',
}).catch(console.error)
}
@@ -357,11 +348,7 @@ export class Footer extends PureComponent<Props, State> {
' w-8 h-full flex items-center justify-center cursor-pointer rounded-full'
}
>
<div
className={
this.state.hasError ? 'danger' : (this.user ? 'info' : 'neutral') + ' w-5 h-5'
}
>
<div className={this.state.hasError ? 'danger' : (this.user ? 'info' : 'neutral') + ' w-5 h-5'}>
<Icon type="account-circle" className="hover:color-info w-5 h-5 max-h-5" />
</div>
</div>
@@ -382,10 +369,7 @@ export class Footer extends PureComponent<Props, State> {
<div className="h-5">
<Icon
type="tune"
className={
(this.state.showQuickSettingsMenu ? 'color-info' : '') +
' rounded hover:color-info'
}
className={(this.state.showQuickSettingsMenu ? 'color-info' : '') + ' rounded hover:color-info'}
/>
</div>
</div>
@@ -401,10 +385,7 @@ export class Footer extends PureComponent<Props, State> {
<Fragment>
<div className="sk-app-bar-item border" />
<div className="sk-app-bar-item">
<a
onClick={this.betaMessageClickHandler}
className="no-decoration sk-label title"
>
<a onClick={this.betaMessageClickHandler} className="no-decoration sk-label title">
You are using a beta version of the app
</a>
</div>
@@ -439,10 +420,7 @@ export class Footer extends PureComponent<Props, State> {
</div>
)}
{this.state.showSyncResolution && (
<SyncResolutionMenu
close={this.syncResolutionClickHandler}
application={this.application}
/>
<SyncResolutionMenu close={this.syncResolutionClickHandler} application={this.application} />
)}
</div>
)}

View File

@@ -39,9 +39,7 @@ export const DecoratedInput: FunctionalComponent<DecoratedInputProps> = forwardR
const classNames = getClassNames(hasLeftDecorations, hasRightDecorations)
return (
<div
className={`${classNames.container} ${disabled ? classNames.disabled : ''} ${className}`}
>
<div className={`${classNames.container} ${disabled ? classNames.disabled : ''} ${className}`}>
{left && (
<div className="flex items-center px-2 py-1.5">
{left.map((leftChild) => (

View File

@@ -22,8 +22,8 @@ const Toggle: FunctionComponent<{
/**
* Password input that has a toggle to show/hide password and can be decorated on the left and right side
*/
export const DecoratedPasswordInput: FunctionComponent<Omit<DecoratedInputProps, 'type'>> =
forwardRef((props, ref: Ref<HTMLInputElement>) => {
export const DecoratedPasswordInput: FunctionComponent<Omit<DecoratedInputProps, 'type'>> = forwardRef(
(props, ref: Ref<HTMLInputElement>) => {
const [isToggled, setIsToggled] = useState(false)
const rightSideDecorations = props.right ? [...props.right] : []
@@ -33,10 +33,8 @@ export const DecoratedPasswordInput: FunctionComponent<Omit<DecoratedInputProps,
{...props}
ref={ref}
type={isToggled ? 'text' : 'password'}
right={[
...rightSideDecorations,
<Toggle isToggled={isToggled} setIsToggled={setIsToggled} />,
]}
right={[...rightSideDecorations, <Toggle isToggled={isToggled} setIsToggled={setIsToggled} />]}
/>
)
})
},
)

View File

@@ -1,12 +1,4 @@
import {
JSX,
FunctionComponent,
ComponentChildren,
VNode,
RefCallback,
ComponentChild,
toChildArray,
} from 'preact'
import { JSX, FunctionComponent, ComponentChildren, VNode, RefCallback, ComponentChild, toChildArray } from 'preact'
import { useEffect, useRef } from 'preact/hooks'
import { JSXInternal } from 'preact/src/jsx'
import { MenuItem, MenuItemListElement } from './MenuItem'
@@ -70,11 +62,7 @@ export const Menu: FunctionComponent<MenuProps> = ({
}
}
const mapMenuItems = (
child: ComponentChild,
index: number,
array: ComponentChild[],
): ComponentChild => {
const mapMenuItems = (child: ComponentChild, index: number, array: ComponentChild[]): ComponentChild => {
if (!child || (Array.isArray(child) && child.length < 1)) {
return
}
@@ -84,8 +72,7 @@ export const Menu: FunctionComponent<MenuProps> = ({
}
const _child = child as VNode<unknown>
const isFirstMenuItem =
index === array.findIndex((child) => (child as VNode<unknown>).type === MenuItem)
const isFirstMenuItem = index === array.findIndex((child) => (child as VNode<unknown>).type === MenuItem)
const hasMultipleItems = Array.isArray(_child.props.children)
? Array.from(_child.props.children as ComponentChild[]).some(

View File

@@ -65,15 +65,9 @@ export const MenuItem: FunctionComponent<MenuItemProps> = forwardRef(
onBlur={onBlur}
{...(type === MenuItemType.RadioButton ? { 'aria-checked': checked } : {})}
>
{type === MenuItemType.IconButton && icon ? (
<Icon type={icon} className={iconClassName} />
) : null}
{type === MenuItemType.IconButton && icon ? <Icon type={icon} className={iconClassName} /> : null}
{type === MenuItemType.RadioButton && typeof checked === 'boolean' ? (
<div
className={`pseudo-radio-btn ${
checked ? 'pseudo-radio-btn--checked' : ''
} mr-2 flex-shrink-0`}
></div>
<div className={`pseudo-radio-btn ${checked ? 'pseudo-radio-btn--checked' : ''} mr-2 flex-shrink-0`}></div>
) : null}
{children}
</button>
@@ -81,9 +75,7 @@ export const MenuItem: FunctionComponent<MenuItemProps> = forwardRef(
},
)
export const MenuItemSeparator: FunctionComponent = () => (
<div role="separator" className="h-1px my-2 bg-border"></div>
)
export const MenuItemSeparator: FunctionComponent = () => <div role="separator" className="h-1px my-2 bg-border"></div>
type ListElementProps = {
isFirstMenuItem: boolean

View File

@@ -27,9 +27,7 @@ export const MultipleSelectedNotes = observer(({ application, appState }: Props)
<div className="flex-grow flex flex-col justify-center items-center w-full max-w-md">
<IlNotesIcon className="block" />
<h2 className="text-lg m-0 text-center mt-4">{count} selected notes</h2>
<p className="text-sm mt-2 text-center max-w-60">
Actions will be performed on all selected notes.
</p>
<p className="text-sm mt-2 text-center max-w-60">Actions will be performed on all selected notes.</p>
</div>
</div>
)

View File

@@ -6,12 +6,7 @@ import { ApplicationEvent, PrefKey } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
import {
PanelSide,
ResizeFinishCallback,
PanelResizer,
PanelResizeType,
} from '@/Components/PanelResizer'
import { PanelSide, ResizeFinishCallback, PanelResizer, PanelResizeType } from '@/Components/PanelResizer'
type Props = {
application: WebApplication

View File

@@ -74,9 +74,7 @@ describe('editor-view', () => {
it("should hide the note if at the time of the session expiration the note wasn't edited for longer than the allowed idle time", async () => {
jest
.spyOn(ctrl, 'getSecondsElapsedSinceLastEdit')
.mockImplementation(
() => ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction + 5,
)
.mockImplementation(() => ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction + 5)
await ctrl.onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
@@ -84,8 +82,7 @@ describe('editor-view', () => {
})
it('should postpone the note hiding by correct time if the time passed after its last modification is less than the allowed idle time', async () => {
const secondsElapsedSinceLastEdit =
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - 3
const secondsElapsedSinceLastEdit = ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - 3
Object.defineProperty(ctrl.note, 'userModifiedDate', {
value: new Date(Date.now() - secondsElapsedSinceLastEdit * 1000),
@@ -95,8 +92,7 @@ describe('editor-view', () => {
await ctrl.onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
const secondsAfterWhichTheNoteShouldHide =
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction -
secondsElapsedSinceLastEdit
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - secondsElapsedSinceLastEdit
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
expect(setShowProtectedWarningSpy).not.toHaveBeenCalled()
@@ -114,8 +110,7 @@ describe('editor-view', () => {
await ctrl.onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
let secondsAfterWhichTheNoteShouldHide =
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction -
secondsElapsedSinceLastModification
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - secondsElapsedSinceLastModification
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
// A new modification has just happened
@@ -124,8 +119,7 @@ describe('editor-view', () => {
configurable: true,
})
secondsAfterWhichTheNoteShouldHide =
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction
secondsAfterWhichTheNoteShouldHide = ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
expect(setShowProtectedWarningSpy).not.toHaveBeenCalled()
@@ -152,9 +146,7 @@ describe('editor-view', () => {
describe('dismissProtectedWarning', () => {
describe('the note has protection sources', () => {
it('should reveal note contents if the authorization has been passed', async () => {
jest
.spyOn(ctrl['application'], 'authorizeNoteAccess')
.mockImplementation(async () => Promise.resolve(true))
jest.spyOn(ctrl['application'], 'authorizeNoteAccess').mockImplementation(async () => Promise.resolve(true))
await ctrl.dismissProtectedWarning()
@@ -162,9 +154,7 @@ describe('editor-view', () => {
})
it('should not reveal note contents if the authorization has not been passed', async () => {
jest
.spyOn(ctrl['application'], 'authorizeNoteAccess')
.mockImplementation(async () => Promise.resolve(false))
jest.spyOn(ctrl['application'], 'authorizeNoteAccess').mockImplementation(async () => Promise.resolve(false))
await ctrl.dismissProtectedWarning()

View File

@@ -20,11 +20,7 @@ import {
import { debounce, isDesktopApplication } from '@/Utils'
import { KeyboardModifier, KeyboardKey } from '@/Services/IOService'
import { EventSource } from '@/UIModels/AppState'
import {
STRING_DELETE_PLACEHOLDER_ATTEMPT,
STRING_DELETE_LOCKED_ATTEMPT,
StringDeleteNote,
} from '@/Strings'
import { STRING_DELETE_PLACEHOLDER_ATTEMPT, STRING_DELETE_LOCKED_ATTEMPT, StringDeleteNote } from '@/Strings'
import { confirmDialog } from '@/Services/AlertService'
import { PureComponent } from '@/Components/Abstract/PureComponent'
import { ProtectedNoteOverlay } from '@/Components/ProtectedNoteOverlay'
@@ -50,10 +46,7 @@ function sortAlphabetically(array: SNComponent[]): SNComponent[] {
return array.sort((a, b) => (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1))
}
export const transactionForAssociateComponentWithCurrentNote = (
component: SNComponent,
note: SNNote,
) => {
export const transactionForAssociateComponentWithCurrentNote = (component: SNComponent, note: SNNote) => {
const transaction: TransactionalMutation = {
itemUuid: component.uuid,
mutate: (m: ItemMutator) => {
@@ -65,10 +58,7 @@ export const transactionForAssociateComponentWithCurrentNote = (
return transaction
}
export const transactionForDisassociateComponentWithCurrentNote = (
component: SNComponent,
note: SNNote,
) => {
export const transactionForDisassociateComponentWithCurrentNote = (component: SNComponent, note: SNNote) => {
const transaction: TransactionalMutation = {
itemUuid: component.uuid,
mutate: (m: ItemMutator) => {
@@ -209,11 +199,9 @@ export class NoteView extends PureComponent<Props, State> {
this.registerKeyboardShortcuts()
this.removeInnerNoteObserver = this.controller.addNoteInnerValueChangeObserver(
(note, source) => {
this.onNoteInnerChange(note, source)
},
)
this.removeInnerNoteObserver = this.controller.addNoteInnerValueChangeObserver((note, source) => {
this.onNoteInnerChange(note, source)
})
this.autorun(() => {
this.setState({
@@ -281,18 +269,15 @@ export class NoteView extends PureComponent<Props, State> {
this.reloadSpellcheck().catch(console.error)
const isTemplateNoteInsertedToBeInteractableWithEditor =
source === PayloadEmitSource.LocalInserted && note.dirty
const isTemplateNoteInsertedToBeInteractableWithEditor = source === PayloadEmitSource.LocalInserted && note.dirty
if (isTemplateNoteInsertedToBeInteractableWithEditor) {
return
}
if (note.lastSyncBegan || note.dirty) {
if (note.lastSyncEnd) {
const shouldShowSavingStatus =
note.lastSyncBegan && note.lastSyncBegan.getTime() > note.lastSyncEnd.getTime()
const shouldShowSavedStatus =
note.lastSyncBegan && note.lastSyncEnd.getTime() > note.lastSyncBegan.getTime()
const shouldShowSavingStatus = note.lastSyncBegan && note.lastSyncBegan.getTime() > note.lastSyncEnd.getTime()
const shouldShowSavedStatus = note.lastSyncBegan && note.lastSyncEnd.getTime() > note.lastSyncBegan.getTime()
if (note.dirty || shouldShowSavingStatus) {
this.showSavingStatus()
} else if (this.state.noteStatus && shouldShowSavedStatus) {
@@ -367,15 +352,11 @@ export class NoteView extends PureComponent<Props, State> {
hideProtectedNoteIfInactive(): void {
const secondsElapsedSinceLastEdit = this.getSecondsElapsedSinceLastEdit()
if (
secondsElapsedSinceLastEdit >=
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction
) {
if (secondsElapsedSinceLastEdit >= ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction) {
this.setShowProtectedOverlay(true)
} else {
const secondsUntilTheNextCheck =
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction -
secondsElapsedSinceLastEdit
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - secondsElapsedSinceLastEdit
this.startNoteProtectionInactivityTimer(secondsUntilTheNextCheck)
}
}
@@ -406,36 +387,24 @@ export class NoteView extends PureComponent<Props, State> {
}
streamItems() {
this.removeComponentStreamObserver = this.application.streamItems(
ContentType.Component,
async ({ source }) => {
if (
isPayloadSourceInternalChange(source) ||
source === PayloadEmitSource.InitialObserverRegistrationPush
) {
return
}
if (!this.note) {
return
}
await this.reloadStackComponents()
this.debounceReloadEditorComponent()
},
)
this.removeComponentStreamObserver = this.application.streamItems(ContentType.Component, async ({ source }) => {
if (isPayloadSourceInternalChange(source) || source === PayloadEmitSource.InitialObserverRegistrationPush) {
return
}
if (!this.note) {
return
}
await this.reloadStackComponents()
this.debounceReloadEditorComponent()
})
}
private createComponentViewer(component: SNComponent) {
const viewer = this.application.componentManager.createComponentViewer(
component,
this.note.uuid,
)
const viewer = this.application.componentManager.createComponentViewer(component, this.note.uuid)
return viewer
}
public editorComponentViewerRequestsReload = async (
viewer: ComponentViewer,
force?: boolean,
): Promise<void> => {
public editorComponentViewerRequestsReload = async (viewer: ComponentViewer, force?: boolean): Promise<void> => {
if (this.state.editorComponentViewerDidAlreadyReload && !force) {
return
}
@@ -698,10 +667,7 @@ export class NoteView extends PureComponent<Props, State> {
async reloadPreferences() {
const monospaceFont = this.application.getPreference(PrefKey.EditorMonospaceEnabled, true)
const marginResizersEnabled = this.application.getPreference(
PrefKey.EditorResizersEnabled,
true,
)
const marginResizersEnabled = this.application.getPreference(PrefKey.EditorResizersEnabled, true)
await this.reloadSpellcheck()
@@ -758,9 +724,7 @@ export class NoteView extends PureComponent<Props, State> {
const newViewers: ComponentViewer[] = []
for (const component of needsNewViewer) {
newViewers.push(
this.application.componentManager.createComponentViewer(component, this.note.uuid),
)
newViewers.push(this.application.componentManager.createComponentViewer(component, this.note.uuid))
}
for (const viewer of needsDestroyViewer) {
@@ -773,9 +737,7 @@ export class NoteView extends PureComponent<Props, State> {
}
stackComponentExpanded = (component: SNComponent): boolean => {
return !!this.state.stackComponentViewers.find(
(viewer) => viewer.componentUuid === component.uuid,
)
return !!this.state.stackComponentViewers.find((viewer) => viewer.componentUuid === component.uuid)
}
toggleStackComponent = async (component: SNComponent) => {
@@ -942,9 +904,7 @@ export class NoteView extends PureComponent<Props, State> {
className="sk-app-bar-item"
>
<div className="sk-label warning flex items-center">
{this.state.showLockedIcon && (
<Icon type="pencil-off" className="flex fill-current mr-2" />
)}
{this.state.showLockedIcon && <Icon type="pencil-off" className="flex fill-current mr-2" />}
{this.state.lockText}
</div>
</div>
@@ -984,9 +944,7 @@ export class NoteView extends PureComponent<Props, State> {
>
{this.state.noteStatus?.message}
</div>
{this.state.noteStatus?.desc && (
<div className="desc">{this.state.noteStatus.desc}</div>
)}
{this.state.noteStatus?.desc && <div className="desc">{this.state.noteStatus.desc}</div>}
</div>
</div>
{this.appState.features.isFilesEnabled && (
@@ -1022,11 +980,7 @@ export class NoteView extends PureComponent<Props, State> {
</div>
)}
<div
id={ElementIds.EditorContent}
className={ElementIds.EditorContent}
ref={this.editorContentRef}
>
<div id={ElementIds.EditorContent} className={ElementIds.EditorContent} ref={this.editorContentRef}>
{this.state.marginResizersEnabled && this.editorContentRef.current ? (
<PanelResizer
minWidth={300}
@@ -1053,22 +1007,20 @@ export class NoteView extends PureComponent<Props, State> {
</div>
)}
{this.state.editorStateDidLoad &&
!this.state.editorComponentViewer &&
!this.state.textareaUnloading && (
<textarea
autocomplete="off"
className="editable font-editor"
dir="auto"
id={ElementIds.NoteTextEditor}
onChange={this.onTextAreaChange}
value={this.state.editorText}
readonly={this.state.noteLocked}
onFocus={this.onContentFocus}
spellcheck={this.state.spellcheck}
ref={(ref) => this.onSystemEditorLoad(ref)}
></textarea>
)}
{this.state.editorStateDidLoad && !this.state.editorComponentViewer && !this.state.textareaUnloading && (
<textarea
autocomplete="off"
className="editable font-editor"
dir="auto"
id={ElementIds.NoteTextEditor}
onChange={this.onTextAreaChange}
value={this.state.editorText}
readonly={this.state.noteLocked}
onFocus={this.onContentFocus}
spellcheck={this.state.spellcheck}
ref={(ref) => this.onSystemEditorLoad(ref)}
></textarea>
)}
{this.state.marginResizersEnabled && this.editorContentRef.current ? (
<PanelResizer
@@ -1101,9 +1053,7 @@ export class NoteView extends PureComponent<Props, State> {
<div className="sk-app-bar-item-column">
<div
className={
(this.stackComponentExpanded(component) && component.active
? 'info '
: '') +
(this.stackComponentExpanded(component) && component.active ? 'info ' : '') +
(!this.stackComponentExpanded(component) ? 'neutral ' : '') +
' sk-circle small'
}

View File

@@ -15,9 +15,7 @@ export const NotesContextMenu = observer(({ application, appState }: Props) => {
const { contextMenuOpen, contextMenuPosition, contextMenuMaxHeight } = appState.notes
const contextMenuRef = useRef<HTMLDivElement>(null)
const [closeOnBlur] = useCloseOnBlur(contextMenuRef, (open: boolean) =>
appState.notes.setContextMenuOpen(open),
)
const [closeOnBlur] = useCloseOnBlur(contextMenuRef, (open: boolean) => appState.notes.setContextMenuOpen(open))
useCloseOnClickOutside(contextMenuRef, () => appState.notes.setContextMenuOpen(false))

View File

@@ -16,30 +16,14 @@ type Props = {
export const NotesListOptionsMenu: FunctionComponent<Props> = observer(
({ closeDisplayOptionsMenu, closeOnBlur, application, isOpen }) => {
const [sortBy, setSortBy] = useState(() =>
application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt),
)
const [sortReverse, setSortReverse] = useState(() =>
application.getPreference(PrefKey.SortNotesReverse, false),
)
const [hidePreview, setHidePreview] = useState(() =>
application.getPreference(PrefKey.NotesHideNotePreview, false),
)
const [hideDate, setHideDate] = useState(() =>
application.getPreference(PrefKey.NotesHideDate, false),
)
const [hideTags, setHideTags] = useState(() =>
application.getPreference(PrefKey.NotesHideTags, true),
)
const [hidePinned, setHidePinned] = useState(() =>
application.getPreference(PrefKey.NotesHidePinned, false),
)
const [showArchived, setShowArchived] = useState(() =>
application.getPreference(PrefKey.NotesShowArchived, false),
)
const [showTrashed, setShowTrashed] = useState(() =>
application.getPreference(PrefKey.NotesShowTrashed, false),
)
const [sortBy, setSortBy] = useState(() => application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt))
const [sortReverse, setSortReverse] = useState(() => application.getPreference(PrefKey.SortNotesReverse, false))
const [hidePreview, setHidePreview] = useState(() => application.getPreference(PrefKey.NotesHideNotePreview, false))
const [hideDate, setHideDate] = useState(() => application.getPreference(PrefKey.NotesHideDate, false))
const [hideTags, setHideTags] = useState(() => application.getPreference(PrefKey.NotesHideTags, true))
const [hidePinned, setHidePinned] = useState(() => application.getPreference(PrefKey.NotesHidePinned, false))
const [showArchived, setShowArchived] = useState(() => application.getPreference(PrefKey.NotesShowArchived, false))
const [showTrashed, setShowTrashed] = useState(() => application.getPreference(PrefKey.NotesShowTrashed, false))
const [hideProtected, setHideProtected] = useState(() =>
application.getPreference(PrefKey.NotesHideProtected, false),
)

View File

@@ -76,11 +76,7 @@ type ListedActionsMenuProps = {
recalculateMenuStyle: () => void
}
const ListedActionsMenu: FunctionComponent<ListedActionsMenuProps> = ({
application,
note,
recalculateMenuStyle,
}) => {
const ListedActionsMenu: FunctionComponent<ListedActionsMenuProps> = ({ application, note, recalculateMenuStyle }) => {
const [menuGroups, setMenuGroups] = useState<ListedMenuGroup[]>([])
const [isFetchingAccounts, setIsFetchingAccounts] = useState(true)
@@ -251,11 +247,7 @@ export const ListedActionsOption: FunctionComponent<Props> = ({ application, not
return (
<div ref={menuContainerRef}>
<Disclosure open={isMenuOpen} onChange={toggleListedMenu}>
<DisclosureButton
ref={menuButtonRef}
onBlur={closeOnBlur}
className="sn-dropdown-item justify-between"
>
<DisclosureButton ref={menuButtonRef} onBlur={closeOnBlur} className="sn-dropdown-item justify-between">
<div className="flex items-center">
<Icon type="listed" className="color-neutral mr-2" />
Listed actions
@@ -271,11 +263,7 @@ export const ListedActionsOption: FunctionComponent<Props> = ({ application, not
className="sn-dropdown flex flex-col max-h-120 min-w-68 pb-1 fixed overflow-y-auto"
>
{isMenuOpen && (
<ListedActionsMenu
application={application}
note={note}
recalculateMenuStyle={recalculateMenuStyle}
/>
<ListedActionsMenu application={application} note={note} recalculateMenuStyle={recalculateMenuStyle} />
)}
</DisclosurePanel>
</Disclosure>

View File

@@ -15,76 +15,72 @@ type Props = {
onClickPreprocessing?: () => Promise<void>
}
export const NotesOptionsPanel = observer(
({ application, appState, onClickPreprocessing }: Props) => {
const [open, setOpen] = useState(false)
const [position, setPosition] = useState({
top: 0,
right: 0,
})
const [maxHeight, setMaxHeight] = useState<number | 'auto'>('auto')
const buttonRef = useRef<HTMLButtonElement>(null)
const panelRef = useRef<HTMLDivElement>(null)
const [closeOnBlur] = useCloseOnBlur(panelRef, setOpen)
export const NotesOptionsPanel = observer(({ application, appState, onClickPreprocessing }: Props) => {
const [open, setOpen] = useState(false)
const [position, setPosition] = useState({
top: 0,
right: 0,
})
const [maxHeight, setMaxHeight] = useState<number | 'auto'>('auto')
const buttonRef = useRef<HTMLButtonElement>(null)
const panelRef = useRef<HTMLDivElement>(null)
const [closeOnBlur] = useCloseOnBlur(panelRef, setOpen)
return (
<Disclosure
open={open}
onChange={async () => {
const rect = buttonRef.current?.getBoundingClientRect()
if (rect) {
const { clientHeight } = document.documentElement
const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect()
const footerHeightInPx = footerElementRect?.height
if (footerHeightInPx) {
setMaxHeight(clientHeight - rect.bottom - footerHeightInPx - 2)
}
setPosition({
top: rect.bottom,
right: document.body.clientWidth - rect.right,
})
const newOpenState = !open
if (newOpenState && onClickPreprocessing) {
await onClickPreprocessing()
}
setOpen(newOpenState)
return (
<Disclosure
open={open}
onChange={async () => {
const rect = buttonRef.current?.getBoundingClientRect()
if (rect) {
const { clientHeight } = document.documentElement
const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect()
const footerHeightInPx = footerElementRect?.height
if (footerHeightInPx) {
setMaxHeight(clientHeight - rect.bottom - footerHeightInPx - 2)
}
setPosition({
top: rect.bottom,
right: document.body.clientWidth - rect.right,
})
const newOpenState = !open
if (newOpenState && onClickPreprocessing) {
await onClickPreprocessing()
}
setOpen(newOpenState)
}
}}
>
<DisclosureButton
onKeyDown={(event) => {
if (event.key === 'Escape') {
setOpen(false)
}
}}
onBlur={closeOnBlur}
ref={buttonRef}
className="sn-icon-button border-contrast"
>
<DisclosureButton
onKeyDown={(event) => {
if (event.key === 'Escape') {
setOpen(false)
}
}}
onBlur={closeOnBlur}
ref={buttonRef}
className="sn-icon-button border-contrast"
>
<VisuallyHidden>Actions</VisuallyHidden>
<Icon type="more" className="block" />
</DisclosureButton>
<DisclosurePanel
onKeyDown={(event) => {
if (event.key === 'Escape') {
setOpen(false)
buttonRef.current?.focus()
}
}}
ref={panelRef}
style={{
...position,
maxHeight,
}}
className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col pt-2 overflow-y-auto fixed"
onBlur={closeOnBlur}
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
>
{open && (
<NotesOptions application={application} appState={appState} closeOnBlur={closeOnBlur} />
)}
</DisclosurePanel>
</Disclosure>
)
},
)
<VisuallyHidden>Actions</VisuallyHidden>
<Icon type="more" className="block" />
</DisclosureButton>
<DisclosurePanel
onKeyDown={(event) => {
if (event.key === 'Escape') {
setOpen(false)
buttonRef.current?.focus()
}
}}
ref={panelRef}
style={{
...position,
maxHeight,
}}
className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col pt-2 overflow-y-auto fixed"
onBlur={closeOnBlur}
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
>
{open && <NotesOptions application={application} appState={appState} closeOnBlur={closeOnBlur} />}
</DisclosurePanel>
</Disclosure>
)
})

View File

@@ -10,12 +10,7 @@ import { NoAccountWarning } from '@/Components/NoAccountWarning'
import { NotesList } from '@/Components/NotesList'
import { NotesListOptionsMenu } from '@/Components/NotesList/NotesListOptionsMenu'
import { SearchOptions } from '@/Components/SearchOptions'
import {
PanelSide,
ResizeFinishCallback,
PanelResizer,
PanelResizeType,
} from '@/Components/PanelResizer'
import { PanelSide, ResizeFinishCallback, PanelResizer, PanelResizeType } from '@/Components/PanelResizer'
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
@@ -51,10 +46,7 @@ export const NotesView: FunctionComponent<Props> = observer(({ application, appS
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false)
const [focusedSearch, setFocusedSearch] = useState(false)
const [closeDisplayOptMenuOnBlur] = useCloseOnBlur(
displayOptionsMenuRef,
setShowDisplayOptionsMenu,
)
const [closeDisplayOptMenuOnBlur] = useCloseOnBlur(displayOptionsMenuRef, setShowDisplayOptionsMenu)
useEffect(() => {
handleFilterTextChanged()
@@ -125,12 +117,7 @@ export const NotesView: FunctionComponent<Props> = observer(({ application, appS
}
}
const panelResizeFinishCallback: ResizeFinishCallback = (
width,
_lastLeft,
_isMaxWidth,
isCollapsed,
) => {
const panelResizeFinishCallback: ResizeFinishCallback = (width, _lastLeft, _isMaxWidth, isCollapsed) => {
application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error)
appState.noteTags.reloadTagsContainerMaxWidth()
appState.panelDidResize(PANEL_NAME_NOTES, isCollapsed)
@@ -229,9 +216,7 @@ export const NotesView: FunctionComponent<Props> = observer(({ application, appS
</div>
</div>
</div>
{completedFullSync && !renderedNotes.length ? (
<p className="empty-notes-list faded">No notes.</p>
) : null}
{completedFullSync && !renderedNotes.length ? <p className="empty-notes-list faded">No notes.</p> : null}
{!completedFullSync && !renderedNotes.length ? (
<p className="empty-notes-list faded">Loading notes...</p>
) : null}

View File

@@ -34,9 +34,9 @@ const ConfirmOtherSessionsSignOut = observer(({ application, appState }: Props)
</AlertDialogLabel>
<AlertDialogDescription className="sk-panel-row">
<p className="color-foreground">
This action will sign out all other devices signed into your account, and remove
your data from those devices when they next regain connection to the internet.
You may sign back in on those devices at any time.
This action will sign out all other devices signed into your account, and remove your data from
those devices when they next regain connection to the internet. You may sign back in on those
devices at any time.
</p>
</AlertDialogDescription>
<div className="flex my-1 mt-4">
@@ -49,11 +49,7 @@ const ConfirmOtherSessionsSignOut = observer(({ application, appState }: Props)
application.revokeAllOtherSessions().catch(console.error)
closeDialog()
application.alertService
.alert(
'You have successfully revoked your sessions from other devices.',
undefined,
'Finish',
)
.alert('You have successfully revoked your sessions from other devices.', undefined, 'Finish')
.catch(console.error)
}}
>

View File

@@ -107,9 +107,7 @@ export class PanelResizer extends Component<Props, State> {
isAtMaxWidth = () => {
const marginOfError = 5
const difference = Math.abs(
Math.round(this.lastWidth + this.lastLeft) - Math.round(this.getParentRect().width),
)
const difference = Math.abs(Math.round(this.lastWidth + this.lastLeft) - Math.round(this.getParentRect().width))
return difference < marginOfError
}
@@ -159,12 +157,7 @@ export class PanelResizer extends Component<Props, State> {
if (finish) {
this.finishSettingWidth()
if (this.props.resizeFinishCallback) {
this.props.resizeFinishCallback(
this.lastWidth,
this.lastLeft,
this.isAtMaxWidth(),
this.isCollapsed(),
)
this.props.resizeFinishCallback(this.lastWidth, this.lastLeft, this.isAtMaxWidth(), this.isCollapsed())
}
}
}
@@ -184,12 +177,7 @@ export class PanelResizer extends Component<Props, State> {
}
this.finishSettingWidth()
this.props.resizeFinishCallback?.(
this.lastWidth,
this.lastLeft,
this.isAtMaxWidth(),
this.isCollapsed(),
)
this.props.resizeFinishCallback?.(this.lastWidth, this.lastLeft, this.isAtMaxWidth(), this.isCollapsed())
}
handleWidthEvent(event?: MouseEvent) {

View File

@@ -109,9 +109,7 @@ export class PasswordWizard extends PureComponent<Props, State> {
const currentPassword = this.state.formData.currentPassword
const newPass = this.state.formData.newPassword
if (!currentPassword || currentPassword.length === 0) {
this.application.alertService
.alert('Please enter your current password.')
.catch(console.error)
this.application.alertService.alert('Please enter your current password.').catch(console.error)
return false
}
@@ -120,9 +118,7 @@ export class PasswordWizard extends PureComponent<Props, State> {
return false
}
if (newPass !== this.state.formData.newPasswordConfirmation) {
this.application.alertService
.alert('Your new password does not match its confirmation.')
.catch(console.error)
this.application.alertService.alert('Your new password does not match its confirmation.').catch(console.error)
this.setFormDataState({
status: undefined,
}).catch(console.error)
@@ -131,9 +127,7 @@ export class PasswordWizard extends PureComponent<Props, State> {
if (!this.application.getUser()?.email) {
this.application.alertService
.alert(
"We don't have your email stored. Please sign out then log back in to fix this issue.",
)
.alert("We don't have your email stored. Please sign out then log back in to fix this issue.")
.catch(console.error)
this.setFormDataState({
status: undefined,
@@ -142,9 +136,7 @@ export class PasswordWizard extends PureComponent<Props, State> {
}
/** Validate current password */
const success = await this.application.validateAccountPassword(
this.state.formData.currentPassword as string,
)
const success = await this.application.validateAccountPassword(this.state.formData.currentPassword as string)
if (!success) {
this.application.alertService
.alert('The current password you entered is not correct. Please try again.')
@@ -194,9 +186,7 @@ export class PasswordWizard extends PureComponent<Props, State> {
dismiss = () => {
if (this.state.lockContinue) {
this.application.alertService
.alert('Cannot close window until pending tasks are complete.')
.catch(console.error)
this.application.alertService.alert('Cannot close window until pending tasks are complete.').catch(console.error)
} else {
this.dismissModal()
}
@@ -211,25 +201,19 @@ export class PasswordWizard extends PureComponent<Props, State> {
})
}
handleCurrentPasswordInputChange = ({
currentTarget,
}: JSX.TargetedEvent<HTMLInputElement, Event>) => {
handleCurrentPasswordInputChange = ({ currentTarget }: JSX.TargetedEvent<HTMLInputElement, Event>) => {
this.setFormDataState({
currentPassword: currentTarget.value,
}).catch(console.error)
}
handleNewPasswordInputChange = ({
currentTarget,
}: JSX.TargetedEvent<HTMLInputElement, Event>) => {
handleNewPasswordInputChange = ({ currentTarget }: JSX.TargetedEvent<HTMLInputElement, Event>) => {
this.setFormDataState({
newPassword: currentTarget.value,
}).catch(console.error)
}
handleNewPasswordConfirmationInputChange = ({
currentTarget,
}: JSX.TargetedEvent<HTMLInputElement, Event>) => {
handleNewPasswordConfirmationInputChange = ({ currentTarget }: JSX.TargetedEvent<HTMLInputElement, Event>) => {
this.setFormDataState({
newPasswordConfirmation: currentTarget.value,
}).catch(console.error)
@@ -283,10 +267,7 @@ export class PasswordWizard extends PureComponent<Props, State> {
/>
<div className="sk-panel-row" />
<label
htmlFor="password-wiz-confirm-new-password"
className="block mb-1"
>
<label htmlFor="password-wiz-confirm-new-password" className="block mb-1">
Confirm New Password
</label>
@@ -304,12 +285,10 @@ export class PasswordWizard extends PureComponent<Props, State> {
)}
{this.state.step === Steps.FinishStep && (
<div className="sk-panel-section">
<div className="sk-label sk-bold info">
Your password has been successfully changed.
</div>
<div className="sk-label sk-bold info">Your password has been successfully changed.</div>
<p className="sk-p">
Please ensure you are running the latest version of Standard Notes on all
platforms to ensure maximum compatibility.
Please ensure you are running the latest version of Standard Notes on all platforms to ensure
maximum compatibility.
</p>
</div>
)}

View File

@@ -77,10 +77,7 @@ export class PermissionsModal extends Component<Props> {
</div>
</div>
<div className="sk-panel-footer">
<button
onClick={this.accept}
className="sn-button info block w-full text-base py-3"
>
<button onClick={this.accept} className="sn-button info block w-full text-base py-3">
Continue
</button>
</div>

View File

@@ -1,8 +1,5 @@
import { FunctionalComponent } from 'preact'
import {
PreferencesGroup,
PreferencesSegment,
} from '@/Components/Preferences/PreferencesComponents'
import { PreferencesGroup, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
import { OfflineSubscription } from '@/Components/Preferences/Panes/Account/OfflineSubscription'
import { WebApplication } from '@/UIModels/Application'
import { observer } from 'mobx-react-lite'
@@ -17,25 +14,23 @@ interface IProps {
extensionsLatestVersions: ExtensionsLatestVersions
}
export const Advanced: FunctionalComponent<IProps> = observer(
({ application, appState, extensionsLatestVersions }) => {
return (
<PreferencesGroup>
<PreferencesSegment>
<AccordionItem title={'Advanced Settings'}>
<div className="flex flex-row items-center">
<div className="flex-grow flex flex-col">
<OfflineSubscription application={application} appState={appState} />
<Extensions
className={'mt-3'}
application={application}
extensionsLatestVersions={extensionsLatestVersions}
/>
</div>
export const Advanced: FunctionalComponent<IProps> = observer(({ application, appState, extensionsLatestVersions }) => {
return (
<PreferencesGroup>
<PreferencesSegment>
<AccordionItem title={'Advanced Settings'}>
<div className="flex flex-row items-center">
<div className="flex-grow flex flex-col">
<OfflineSubscription application={application} appState={appState} />
<Extensions
className={'mt-3'}
application={application}
extensionsLatestVersions={extensionsLatestVersions}
/>
</div>
</AccordionItem>
</PreferencesSegment>
</PreferencesGroup>
)
},
)
</div>
</AccordionItem>
</PreferencesSegment>
</PreferencesGroup>
)
})

View File

@@ -1,11 +1,6 @@
import { AccountMenuPane } from '@/Components/AccountMenu'
import { Button } from '@/Components/Button/Button'
import {
PreferencesGroup,
PreferencesSegment,
Text,
Title,
} from '@/Components/Preferences/PreferencesComponents'
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
import { WebApplication } from '@/UIModels/Application'
import { AppState } from '@/UIModels/AppState'
import { observer } from 'mobx-react-lite'
@@ -35,21 +30,12 @@ export const Authentication: FunctionComponent<{
<AccountIllustration className="mb-3" />
<Title>You're not signed in</Title>
<Text className="text-center mb-3">
Sign in to sync your notes and preferences across all your devices and enable end-to-end
encryption.
Sign in to sync your notes and preferences across all your devices and enable end-to-end encryption.
</Text>
<Button
variant="primary"
label="Create free account"
onClick={clickRegister}
className="mb-3"
/>
<Button variant="primary" label="Create free account" onClick={clickRegister} className="mb-3" />
<div className="text-input">
Already have an account?{' '}
<button
className="border-0 p-0 bg-default color-info underline cursor-pointer"
onClick={clickSignIn}
>
<button className="border-0 p-0 bg-default color-info underline cursor-pointer" onClick={clickSignIn}>
Sign in
</button>
</div>

View File

@@ -10,10 +10,7 @@ const labelClassName = 'block mb-1'
const inputClassName = 'sk-input contrast'
export const ChangeEmailForm: FunctionalComponent<Props> = ({
setNewEmail,
setCurrentPassword,
}) => {
export const ChangeEmailForm: FunctionalComponent<Props> = ({ setNewEmail, setCurrentPassword }) => {
return (
<div className="w-full flex flex-col">
<div className="mt-2 mb-3">

View File

@@ -5,8 +5,8 @@ export const ChangeEmailSuccess: FunctionalComponent = () => {
<div>
<div className={'sk-label sk-bold info mt-2'}>Your email has been successfully changed.</div>
<p className={'sk-p'}>
Please ensure you are running the latest version of Standard Notes on all platforms to
ensure maximum compatibility.
Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum
compatibility.
</p>
</div>
)

View File

@@ -63,9 +63,7 @@ export const ChangeEmail: FunctionalComponent<Props> = ({ onCloseDialog, applica
const validateNewEmail = async () => {
if (!isEmailValid(newEmail)) {
applicationAlertService
.alert(
'The email you entered has an invalid format. Please review your input and try again.',
)
.alert('The email you entered has an invalid format. Please review your input and try again.')
.catch(console.error)
return false
@@ -95,9 +93,7 @@ export const ChangeEmail: FunctionalComponent<Props> = ({ onCloseDialog, applica
const dismiss = () => {
if (lockContinue) {
applicationAlertService
.alert('Cannot close window until pending tasks are complete.')
.catch(console.error)
applicationAlertService.alert('Cannot close window until pending tasks are complete.').catch(console.error)
} else {
onCloseDialog()
}
@@ -139,9 +135,7 @@ export const ChangeEmail: FunctionalComponent<Props> = ({ onCloseDialog, applica
const handleDialogClose = () => {
if (lockContinue) {
applicationAlertService
.alert('Cannot close window until pending tasks are complete.')
.catch(console.error)
applicationAlertService.alert('Cannot close window until pending tasks are complete.').catch(console.error)
} else {
onCloseDialog()
}
@@ -158,12 +152,7 @@ export const ChangeEmail: FunctionalComponent<Props> = ({ onCloseDialog, applica
{currentStep === Steps.FinishStep && <ChangeEmailSuccess />}
</ModalDialogDescription>
<ModalDialogButtons className="px-4.5">
<Button
className="min-w-20"
variant="primary"
label={submitButtonTitle}
onClick={handleSubmit}
/>
<Button className="min-w-20" variant="primary" label={submitButtonTitle} onClick={handleSubmit} />
</ModalDialogButtons>
</ModalDialog>
</div>

View File

@@ -30,10 +30,7 @@ export const Credentials: FunctionComponent<Props> = observer(({ application }:
const passwordCreatedOn = dateToLocalizedString(passwordCreatedAtTimestamp)
const presentPasswordWizard = useCallback(() => {
render(
<PasswordWizard application={application} />,
document.body.appendChild(document.createElement('div')),
)
render(<PasswordWizard application={application} />, document.body.appendChild(document.createElement('div')))
}, [application])
return (
@@ -57,17 +54,9 @@ export const Credentials: FunctionComponent<Props> = observer(({ application }:
<Text>
Current password was set on <span className="font-bold">{passwordCreatedOn}</span>
</Text>
<Button
className="min-w-20 mt-3"
variant="normal"
label="Change password"
onClick={presentPasswordWizard}
/>
<Button className="min-w-20 mt-3" variant="normal" label="Change password" onClick={presentPasswordWizard} />
{isChangeEmailDialogOpen && (
<ChangeEmail
onCloseDialog={() => setIsChangeEmailDialogOpen(false)}
application={application}
/>
<ChangeEmail onCloseDialog={() => setIsChangeEmailDialogOpen(false)} application={application} />
)}
</PreferencesSegment>
</PreferencesGroup>

View File

@@ -28,9 +28,7 @@ export const OfflineSubscription: FunctionalComponent<IProps> = observer(({ appl
}, [application])
const shouldShowOfflineSubscription = () => {
return (
!application.hasAccount() || application.isThirdPartyHostUsed() || hasUserPreviouslyStoredCode
)
return !application.hasAccount() || application.isThirdPartyHostUsed() || hasUserPreviouslyStoredCode
}
const handleSubscriptionCodeSubmit = async (event: Event) => {
@@ -98,8 +96,8 @@ export const OfflineSubscription: FunctionalComponent<IProps> = observer(({ appl
</div>
{(isSuccessfullyActivated || isSuccessfullyRemoved) && (
<div className={'mt-3 mb-3 info'}>
Your offline subscription code has been successfully{' '}
{isSuccessfullyActivated ? 'activated' : 'removed'}.
Your offline subscription code has been successfully {isSuccessfullyActivated ? 'activated' : 'removed'}
.
</div>
)}
{hasUserPreviouslyStoredCode && (

View File

@@ -33,11 +33,7 @@ const SignOutView: FunctionComponent<{
appState.accountMenu.setOtherSessionsSignOut(true)
}}
/>
<Button
variant="normal"
label="Manage sessions"
onClick={() => appState.openSessionsModal()}
/>
<Button variant="normal" label="Manage sessions" onClick={() => appState.openSessionsModal()} />
</div>
</PreferencesSegment>
<PreferencesSegment>

View File

@@ -12,8 +12,7 @@ export const NoSubscription: FunctionalComponent<{
const [purchaseFlowError, setPurchaseFlowError] = useState<string | undefined>(undefined)
const onPurchaseClick = async () => {
const errorMessage =
'There was an error when attempting to redirect you to the subscription page.'
const errorMessage = 'There was an error when attempting to redirect you to the subscription page.'
setIsLoadingPurchaseFlow(true)
try {
if (!(await loadPurchaseFlowUrl(application))) {
@@ -32,18 +31,9 @@ export const NoSubscription: FunctionalComponent<{
{isLoadingPurchaseFlow && <Text>Redirecting you to the subscription page...</Text>}
{purchaseFlowError && <Text className="color-danger">{purchaseFlowError}</Text>}
<div className="flex">
<LinkButton
className="min-w-20 mt-3 mr-3"
label="Learn More"
link={window.plansUrl as string}
/>
<LinkButton className="min-w-20 mt-3 mr-3" label="Learn More" link={window.plansUrl as string} />
{application.hasAccount() && (
<Button
className="min-w-20 mt-3"
variant="primary"
label="Subscribe"
onClick={onPurchaseClick}
/>
<Button className="min-w-20 mt-3" variant="primary" label="Subscribe" onClick={onPurchaseClick} />
)}
</div>
</>

View File

@@ -1,8 +1,4 @@
import {
PreferencesGroup,
PreferencesSegment,
Title,
} from '@/Components/Preferences/PreferencesComponents'
import { PreferencesGroup, PreferencesSegment, Title } from '@/Components/Preferences/PreferencesComponents'
import { WebApplication } from '@/UIModels/Application'
import { SubscriptionInformation } from './SubscriptionInformation'
import { NoSubscription } from './NoSubscription'
@@ -15,31 +11,26 @@ type Props = {
appState: AppState
}
export const Subscription: FunctionComponent<Props> = observer(
({ application, appState }: Props) => {
const subscriptionState = appState.subscription
const { userSubscription } = subscriptionState
export const Subscription: FunctionComponent<Props> = observer(({ application, appState }: Props) => {
const subscriptionState = appState.subscription
const { userSubscription } = subscriptionState
const now = new Date().getTime()
const now = new Date().getTime()
return (
<PreferencesGroup>
<PreferencesSegment>
<div className="flex flex-row items-center">
<div className="flex-grow flex flex-col">
<Title>Subscription</Title>
{userSubscription && userSubscription.endsAt > now ? (
<SubscriptionInformation
subscriptionState={subscriptionState}
application={application}
/>
) : (
<NoSubscription application={application} />
)}
</div>
return (
<PreferencesGroup>
<PreferencesSegment>
<div className="flex flex-row items-center">
<div className="flex-grow flex flex-col">
<Title>Subscription</Title>
{userSubscription && userSubscription.endsAt > now ? (
<SubscriptionInformation subscriptionState={subscriptionState} application={application} />
) : (
<NoSubscription application={application} />
)}
</div>
</PreferencesSegment>
</PreferencesGroup>
)
},
)
</div>
</PreferencesSegment>
</PreferencesGroup>
)
})

View File

@@ -10,49 +10,16 @@ type Props = {
application: WebApplication
}
const StatusText = observer(
({ subscriptionState }: { subscriptionState: Props['subscriptionState'] }) => {
const {
userSubscriptionName,
userSubscriptionExpirationDate,
isUserSubscriptionExpired,
isUserSubscriptionCanceled,
} = subscriptionState
const expirationDateString = userSubscriptionExpirationDate?.toLocaleString()
if (isUserSubscriptionCanceled) {
return (
<Text className="mt-1">
Your{' '}
<span className="font-bold">
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription has been canceled{' '}
{isUserSubscriptionExpired ? (
<span className="font-bold">and expired on {expirationDateString}</span>
) : (
<span className="font-bold">but will remain valid until {expirationDateString}</span>
)}
. You may resubscribe below if you wish.
</Text>
)
}
if (isUserSubscriptionExpired) {
return (
<Text className="mt-1">
Your{' '}
<span className="font-bold">
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription <span className="font-bold">expired on {expirationDateString}</span>. You may
resubscribe below if you wish.
</Text>
)
}
const StatusText = observer(({ subscriptionState }: { subscriptionState: Props['subscriptionState'] }) => {
const {
userSubscriptionName,
userSubscriptionExpirationDate,
isUserSubscriptionExpired,
isUserSubscriptionCanceled,
} = subscriptionState
const expirationDateString = userSubscriptionExpirationDate?.toLocaleString()
if (isUserSubscriptionCanceled) {
return (
<Text className="mt-1">
Your{' '}
@@ -60,11 +27,42 @@ const StatusText = observer(
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription will be <span className="font-bold">renewed on {expirationDateString}</span>.
subscription has been canceled{' '}
{isUserSubscriptionExpired ? (
<span className="font-bold">and expired on {expirationDateString}</span>
) : (
<span className="font-bold">but will remain valid until {expirationDateString}</span>
)}
. You may resubscribe below if you wish.
</Text>
)
},
)
}
if (isUserSubscriptionExpired) {
return (
<Text className="mt-1">
Your{' '}
<span className="font-bold">
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription <span className="font-bold">expired on {expirationDateString}</span>. You may resubscribe below if
you wish.
</Text>
)
}
return (
<Text className="mt-1">
Your{' '}
<span className="font-bold">
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription will be <span className="font-bold">renewed on {expirationDateString}</span>.
</Text>
)
})
export const SubscriptionInformation = observer(({ subscriptionState, application }: Props) => {
const manageSubscription = async () => {

View File

@@ -1,9 +1,4 @@
import {
PreferencesGroup,
PreferencesSegment,
Text,
Title,
} from '@/Components/Preferences/PreferencesComponents'
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
import { Button } from '@/Components/Button/Button'
import { SyncQueueStrategy, dateToLocalizedString } from '@standardnotes/snjs'
import { STRING_GENERIC_SYNC_ERROR } from '@/Strings'
@@ -22,9 +17,7 @@ export const formatLastSyncDate = (lastUpdatedDate: Date) => {
export const Sync: FunctionComponent<Props> = observer(({ application }: Props) => {
const [isSyncingInProgress, setIsSyncingInProgress] = useState(false)
const [lastSyncDate, setLastSyncDate] = useState(
formatLastSyncDate(application.sync.getLastSyncDate() as Date),
)
const [lastSyncDate, setLastSyncDate] = useState(formatLastSyncDate(application.sync.getLastSyncDate() as Date))
const doSynchronization = async () => {
setIsSyncingInProgress(true)

View File

@@ -3,14 +3,7 @@ import { usePremiumModal } from '@/Hooks/usePremiumModal'
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
import { Switch } from '@/Components/Switch'
import { WebApplication } from '@/UIModels/Application'
import {
ContentType,
FeatureIdentifier,
FeatureStatus,
PrefKey,
GetFeatures,
SNTheme,
} from '@standardnotes/snjs'
import { ContentType, FeatureIdentifier, FeatureStatus, PrefKey, GetFeatures, SNTheme } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { useEffect, useState } from 'preact/hooks'
@@ -31,8 +24,7 @@ type Props = {
export const Appearance: FunctionComponent<Props> = observer(({ application }) => {
const premiumModal = usePremiumModal()
const isEntitledToMidnightTheme =
application.features.getFeatureStatus(FeatureIdentifier.MidnightTheme) ===
FeatureStatus.Entitled
application.features.getFeatureStatus(FeatureIdentifier.MidnightTheme) === FeatureStatus.Entitled
const [themeItems, setThemeItems] = useState<DropdownItem[]>([])
const [autoLightTheme, setAutoLightTheme] = useState<string>(
@@ -100,9 +92,7 @@ export const Appearance: FunctionComponent<Props> = observer(({ application }) =
if (item.icon === 'premium-feature') {
premiumModal.activate(`${item.label} theme`)
} else {
application
.setPreference(PrefKey.AutoLightThemeIdentifier, value as FeatureIdentifier)
.catch(console.error)
application.setPreference(PrefKey.AutoLightThemeIdentifier, value as FeatureIdentifier).catch(console.error)
setAutoLightTheme(value)
}
}
@@ -111,9 +101,7 @@ export const Appearance: FunctionComponent<Props> = observer(({ application }) =
if (item.icon === 'premium-feature') {
premiumModal.activate(`${item.label} theme`)
} else {
application
.setPreference(PrefKey.AutoDarkThemeIdentifier, value as FeatureIdentifier)
.catch(console.error)
application.setPreference(PrefKey.AutoDarkThemeIdentifier, value as FeatureIdentifier).catch(console.error)
setAutoDarkTheme(value)
}
}

View File

@@ -66,18 +66,13 @@ export const CloudBackupProvider: FunctionComponent<Props> = ({
const performBackupNow = async () => {
// A backup is performed anytime the setting is updated with the integration token, so just update it here
try {
await application.settings.updateSetting(
backupFrequencySettingName,
backupFrequency as string,
)
await application.settings.updateSetting(backupFrequencySettingName, backupFrequency as string)
void application.alertService.alert(
'A backup has been triggered for this provider. Please allow a couple minutes for your backup to be processed.',
)
} catch (err) {
application.alertService
.alert(
'There was an error while trying to trigger a backup for this provider. Please try again.',
)
.alert('There was an error while trying to trigger a backup for this provider. Please try again.')
.catch(console.error)
}
}
@@ -184,8 +179,8 @@ export const CloudBackupProvider: FunctionComponent<Props> = ({
{authBegan && (
<div>
<p className="sk-panel-row">
Complete authentication from the newly opened window. Upon completion, a confirmation
code will be displayed. Enter this code below:
Complete authentication from the newly opened window. Upon completion, a confirmation code will be
displayed. Enter this code below:
</p>
<div className={'mt-1'}>
<input

View File

@@ -22,11 +22,7 @@ import { Switch } from '@/Components/Switch'
import { convertStringifiedBooleanToBoolean } from '@/Utils'
import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
const providerData = [
{ name: CloudProvider.Dropbox },
{ name: CloudProvider.Google },
{ name: CloudProvider.OneDrive },
]
const providerData = [{ name: CloudProvider.Dropbox }, { name: CloudProvider.Google }, { name: CloudProvider.OneDrive }]
type Props = {
application: WebApplication
@@ -62,20 +58,12 @@ export const CloudLink: FunctionComponent<Props> = ({ application }) => {
}, [application])
useEffect(() => {
const dailyDropboxBackupStatus = application.features.getFeatureStatus(
FeatureIdentifier.DailyDropboxBackup,
const dailyDropboxBackupStatus = application.features.getFeatureStatus(FeatureIdentifier.DailyDropboxBackup)
const dailyGdriveBackupStatus = application.features.getFeatureStatus(FeatureIdentifier.DailyGDriveBackup)
const dailyOneDriveBackupStatus = application.features.getFeatureStatus(FeatureIdentifier.DailyOneDriveBackup)
const isCloudBackupsAllowed = [dailyDropboxBackupStatus, dailyGdriveBackupStatus, dailyOneDriveBackupStatus].every(
(status) => status === FeatureStatus.Entitled,
)
const dailyGdriveBackupStatus = application.features.getFeatureStatus(
FeatureIdentifier.DailyGDriveBackup,
)
const dailyOneDriveBackupStatus = application.features.getFeatureStatus(
FeatureIdentifier.DailyOneDriveBackup,
)
const isCloudBackupsAllowed = [
dailyDropboxBackupStatus,
dailyGdriveBackupStatus,
dailyOneDriveBackupStatus,
].every((status) => status === FeatureStatus.Entitled)
setIsEntitledToCloudBackups(isCloudBackupsAllowed)
loadIsFailedCloudBackupEmailMutedSetting().catch(console.error)
@@ -114,9 +102,8 @@ export const CloudLink: FunctionComponent<Props> = ({ application }) => {
{!isEntitledToCloudBackups && (
<>
<Text>
A <span className={'font-bold'}>Plus</span> or{' '}
<span className={'font-bold'}>Pro</span> subscription plan is required to enable Cloud
Backups.{' '}
A <span className={'font-bold'}>Plus</span> or <span className={'font-bold'}>Pro</span> subscription plan
is required to enable Cloud Backups.{' '}
<a target="_blank" href="https://standardnotes.com/features">
Learn more
</a>
@@ -127,8 +114,8 @@ export const CloudLink: FunctionComponent<Props> = ({ application }) => {
)}
<div>
<Text className={additionalClass}>
Configure the integrations below to enable automatic daily backups of your encrypted
data set to your third-party cloud provider.
Configure the integrations below to enable automatic daily backups of your encrypted data set to your
third-party cloud provider.
</Text>
<div>
<HorizontalSeparator classes={`mt-3 mb-3 ${additionalClass}`} />

View File

@@ -160,8 +160,7 @@ export const DataBackups = observer(({ application, appState }: Props) => {
{!isDesktopApplication() && (
<Text className="mb-3">
Backups are automatically created on desktop and can be managed via the "Backups"
top-level menu.
Backups are automatically created on desktop and can be managed via the "Backups" top-level menu.
</Text>
)}
@@ -171,43 +170,25 @@ export const DataBackups = observer(({ application, appState }: Props) => {
<form className="sk-panel-form sk-panel-row">
<div className="sk-input-group">
<label className="sk-horizontal-group tight">
<input
type="radio"
onChange={() => setIsBackupEncrypted(true)}
checked={isBackupEncrypted}
/>
<input type="radio" onChange={() => setIsBackupEncrypted(true)} checked={isBackupEncrypted} />
<Subtitle>Encrypted</Subtitle>
</label>
<label className="sk-horizontal-group tight">
<input
type="radio"
onChange={() => setIsBackupEncrypted(false)}
checked={!isBackupEncrypted}
/>
<input type="radio" onChange={() => setIsBackupEncrypted(false)} checked={!isBackupEncrypted} />
<Subtitle>Decrypted</Subtitle>
</label>
</div>
</form>
)}
<Button
variant="normal"
onClick={downloadDataArchive}
label="Download backup"
className="mt-2"
/>
<Button variant="normal" onClick={downloadDataArchive} label="Download backup" className="mt-2" />
</PreferencesSegment>
<PreferencesSegment>
<Subtitle>Import a previously saved backup file</Subtitle>
<div class="flex flex-row items-center mt-3">
<Button variant="normal" label="Import backup" onClick={handleImportFile} />
<input
type="file"
ref={fileInputRef}
onChange={importFileSelected}
className="hidden"
/>
<input type="file" ref={fileInputRef} onChange={importFileSelected} className="hidden" />
{isImportDataLoading && <div className="sk-spinner normal info ml-4" />}
</div>
</PreferencesSegment>

View File

@@ -27,9 +27,7 @@ type Props = {
export const EmailBackups = observer(({ application }: Props) => {
const [isLoading, setIsLoading] = useState(false)
const [emailFrequency, setEmailFrequency] = useState<EmailBackupFrequency>(
EmailBackupFrequency.Disabled,
)
const [emailFrequency, setEmailFrequency] = useState<EmailBackupFrequency>(EmailBackupFrequency.Disabled)
const [emailFrequencyOptions, setEmailFrequencyOptions] = useState<DropdownItem[]>([])
const [isFailedBackupEmailMuted, setIsFailedBackupEmailMuted] = useState(true)
const [isEntitledToEmailBackups, setIsEntitledToEmailBackups] = useState(false)
@@ -64,9 +62,7 @@ export const EmailBackups = observer(({ application }: Props) => {
}, [application])
useEffect(() => {
const emailBackupsFeatureStatus = application.features.getFeatureStatus(
FeatureIdentifier.DailyEmailBackup,
)
const emailBackupsFeatureStatus = application.features.getFeatureStatus(FeatureIdentifier.DailyEmailBackup)
setIsEntitledToEmailBackups(emailBackupsFeatureStatus === FeatureStatus.Entitled)
const frequencyOptions = []
@@ -109,10 +105,7 @@ export const EmailBackups = observer(({ application }: Props) => {
const previousValue = isFailedBackupEmailMuted
setIsFailedBackupEmailMuted(!isFailedBackupEmailMuted)
const updateResult = await updateSetting(
SettingName.MuteFailedBackupsEmails,
`${!isFailedBackupEmailMuted}`,
)
const updateResult = await updateSetting(SettingName.MuteFailedBackupsEmails, `${!isFailedBackupEmailMuted}`)
if (!updateResult) {
setIsFailedBackupEmailMuted(previousValue)
}
@@ -132,9 +125,8 @@ export const EmailBackups = observer(({ application }: Props) => {
{!isEntitledToEmailBackups && (
<>
<Text>
A <span className={'font-bold'}>Plus</span> or{' '}
<span className={'font-bold'}>Pro</span> subscription plan is required to enable Email
Backups.{' '}
A <span className={'font-bold'}>Plus</span> or <span className={'font-bold'}>Pro</span> subscription plan
is required to enable Email Backups.{' '}
<a target="_blank" href="https://standardnotes.com/features">
Learn more
</a>
@@ -146,8 +138,7 @@ export const EmailBackups = observer(({ application }: Props) => {
<div className={isEntitledToEmailBackups ? '' : 'faded cursor-default pointer-events-none'}>
{!isDesktopApplication() && (
<Text className="mb-3">
Daily encrypted email backups of your entire data set delivered directly to your
inbox.
Daily encrypted email backups of your entire data set delivered directly to your inbox.
</Text>
)}
<Subtitle>Email frequency</Subtitle>

View File

@@ -17,10 +17,10 @@ export const CloudLink: FunctionComponent = () => (
<div className="h-2 w-full" />
<Subtitle>Who can read my private notes?</Subtitle>
<Text>
Quite simply: no one but you. Not us, not your ISP, not a hacker, and not a government
agency. As long as you keep your password safe, and your password is reasonably strong,
then you are the only person in the world with the ability to decrypt your notes. For more
on how we handle your privacy and security, check out our easy to read{' '}
Quite simply: no one but you. Not us, not your ISP, not a hacker, and not a government agency. As long as you
keep your password safe, and your password is reasonably strong, then you are the only person in the world
with the ability to decrypt your notes. For more on how we handle your privacy and security, check out our
easy to read{' '}
<a target="_blank" href="https://standardnotes.com/privacy">
Privacy Manifesto.
</a>
@@ -29,21 +29,17 @@ export const CloudLink: FunctionComponent = () => (
<PreferencesSegment>
<Subtitle>Can I collaborate with others on a note?</Subtitle>
<Text>
Because of our encrypted architecture, Standard Notes does not currently provide a
real-time collaboration solution. Multiple users can share the same account however, but
editing at the same time may result in sync conflicts, which may result in the duplication
of notes.
Because of our encrypted architecture, Standard Notes does not currently provide a real-time collaboration
solution. Multiple users can share the same account however, but editing at the same time may result in sync
conflicts, which may result in the duplication of notes.
</Text>
</PreferencesSegment>
<PreferencesSegment>
<Subtitle>Can I use Standard Notes totally offline?</Subtitle>
<Text>
Standard Notes can be used totally offline without an account, and without an internet
connection. You can find{' '}
<a
target="_blank"
href="https://standardnotes.com/help/59/can-i-use-standard-notes-totally-offline"
>
Standard Notes can be used totally offline without an account, and without an internet connection. You can
find{' '}
<a target="_blank" href="https://standardnotes.com/help/59/can-i-use-standard-notes-totally-offline">
more details here.
</a>
</Text>
@@ -57,38 +53,25 @@ export const CloudLink: FunctionComponent = () => (
<PreferencesSegment>
<Title>Community forum</Title>
<Text>
If you have an issue, found a bug or want to suggest a feature, you can browse or post to
the forum. Its recommended for non-account related issues. Please read our{' '}
If you have an issue, found a bug or want to suggest a feature, you can browse or post to the forum. Its
recommended for non-account related issues. Please read our{' '}
<a target="_blank" href="https://standardnotes.com/longevity/">
Longevity statement
</a>{' '}
before advocating for a feature request.
</Text>
<LinkButton
className="mt-3"
label="Go to the forum"
link="https://forum.standardnotes.org/"
/>
<LinkButton className="mt-3" label="Go to the forum" link="https://forum.standardnotes.org/" />
</PreferencesSegment>
</PreferencesGroup>
<PreferencesGroup>
<PreferencesSegment>
<Title>Community groups</Title>
<Text>
Want to meet other passionate note-takers and privacy enthusiasts? Want to share your
feedback with us? Join the Standard Notes community groups for discussions on security,
themes, editors and more.
Want to meet other passionate note-takers and privacy enthusiasts? Want to share your feedback with us? Join
the Standard Notes community groups for discussions on security, themes, editors and more.
</Text>
<LinkButton
className="mt-3"
link="https://standardnotes.com/slack"
label="Join our Slack"
/>
<LinkButton
className="mt-3"
link="https://standardnotes.com/discord"
label="Join our Discord"
/>
<LinkButton className="mt-3" link="https://standardnotes.com/slack" label="Join our Slack" />
<LinkButton className="mt-3" link="https://standardnotes.com/discord" label="Join our Discord" />
</PreferencesSegment>
</PreferencesGroup>
<PreferencesGroup>

View File

@@ -1,12 +1,7 @@
import { DisplayStringForContentType, SNComponent } from '@standardnotes/snjs'
import { Button } from '@/Components/Button/Button'
import { FunctionComponent } from 'preact'
import {
Title,
Text,
Subtitle,
PreferencesSegment,
} from '@/Components/Preferences/PreferencesComponents'
import { Title, Text, Subtitle, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
export const ConfirmCustomExtension: FunctionComponent<{
component: SNComponent
@@ -59,21 +54,11 @@ export const ConfirmCustomExtension: FunctionComponent<{
<div className="min-h-3" />
<div className="flex flex-row">
<Button
className="min-w-20"
variant="normal"
label="Cancel"
onClick={() => callback(false)}
/>
<Button className="min-w-20" variant="normal" label="Cancel" onClick={() => callback(false)} />
<div className="min-w-3" />
<Button
className="min-w-20"
variant="normal"
label="Install"
onClick={() => callback(true)}
/>
<Button className="min-w-20" variant="normal" label="Install" onClick={() => callback(true)} />
</div>
</PreferencesSegment>
)

View File

@@ -1,10 +1,6 @@
import { FunctionComponent } from 'preact'
import { SNComponent } from '@standardnotes/snjs'
import {
PreferencesSegment,
SubtitleLight,
Title,
} from '@/Components/Preferences/PreferencesComponents'
import { PreferencesSegment, SubtitleLight, Title } from '@/Components/Preferences/PreferencesComponents'
import { Switch } from '@/Components/Switch'
import { WebApplication } from '@/UIModels/Application'
import { useState } from 'preact/hooks'
@@ -30,12 +26,7 @@ export interface ExtensionItemProps {
toggleActivate?: (extension: SNComponent) => void
}
export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({
application,
extension,
first,
uninstall,
}) => {
export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ application, extension, first, uninstall }) => {
const [offlineOnly, setOfflineOnly] = useState(extension.offlineOnly ?? false)
const [extensionName, setExtensionName] = useState(extension.name)
@@ -95,12 +86,7 @@ export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({
<>
<div className="min-h-2" />
<div className="flex flex-row">
<Button
className="min-w-20"
variant="normal"
label="Uninstall"
onClick={() => uninstall(extension)}
/>
<Button className="min-w-20" variant="normal" label="Uninstall" onClick={() => uninstall(extension)} />
</div>
</>
</PreferencesSegment>

View File

@@ -29,10 +29,7 @@ export class ExtensionsLatestVersions {
}
}
function collectFeatures(
features: FeatureDescription[] | undefined,
versionMap: Map<string, string>,
) {
function collectFeatures(features: FeatureDescription[] | undefined, versionMap: Map<string, string>) {
if (features == undefined) {
return
}

View File

@@ -11,11 +11,7 @@ import { ExtensionItem } from './ExtensionItem'
import { ConfirmCustomExtension } from './ConfirmCustomExtension'
const loadExtensions = (application: WebApplication) =>
application.items.getItems([
ContentType.ActionsExtension,
ContentType.Component,
ContentType.Theme,
]) as SNComponent[]
application.items.getItems([ContentType.ActionsExtension, ContentType.Component, ContentType.Theme]) as SNComponent[]
export const Extensions: FunctionComponent<{
application: WebApplication
@@ -23,9 +19,7 @@ export const Extensions: FunctionComponent<{
className?: string
}> = observer(({ application, extensionsLatestVersions, className = '' }) => {
const [customUrl, setCustomUrl] = useState('')
const [confirmableExtension, setConfirmableExtension] = useState<SNComponent | undefined>(
undefined,
)
const [confirmableExtension, setConfirmableExtension] = useState<SNComponent | undefined>(undefined)
const [extensions, setExtensions] = useState(loadExtensions(application))
const confirmableEnd = useRef<HTMLDivElement>(null)
@@ -122,10 +116,7 @@ export const Extensions: FunctionComponent<{
)}
{confirmableExtension && (
<PreferencesSegment>
<ConfirmCustomExtension
component={confirmableExtension}
callback={handleConfirmExtensionSubmit}
/>
<ConfirmCustomExtension component={confirmableExtension} callback={handleConfirmExtensionSubmit} />
<div ref={confirmableEnd} />
</PreferencesSegment>
)}

View File

@@ -29,19 +29,16 @@ export const LabsPane: FunctionComponent<Props> = ({ application }) => {
const [experimentalFeatures, setExperimentalFeatures] = useState<ExperimentalFeatureItem[]>([])
const reloadExperimentalFeatures = useCallback(() => {
const experimentalFeatures = application.features
.getExperimentalFeatures()
.map((featureIdentifier) => {
const feature = FindNativeFeature(featureIdentifier)
return {
identifier: featureIdentifier,
name: feature?.name ?? featureIdentifier,
description: feature?.description ?? '',
isEnabled: application.features.isExperimentalFeatureEnabled(featureIdentifier),
isEntitled:
application.features.getFeatureStatus(featureIdentifier) === FeatureStatus.Entitled,
}
})
const experimentalFeatures = application.features.getExperimentalFeatures().map((featureIdentifier) => {
const feature = FindNativeFeature(featureIdentifier)
return {
identifier: featureIdentifier,
name: feature?.name ?? featureIdentifier,
description: feature?.description ?? '',
isEnabled: application.features.isExperimentalFeatureEnabled(featureIdentifier),
isEntitled: application.features.getFeatureStatus(featureIdentifier) === FeatureStatus.Entitled,
}
})
setExperimentalFeatures(experimentalFeatures)
}, [application.features])
@@ -56,35 +53,32 @@ export const LabsPane: FunctionComponent<Props> = ({ application }) => {
<PreferencesSegment>
<Title>Labs</Title>
<div>
{experimentalFeatures.map(
({ identifier, name, description, isEnabled, isEntitled }, index: number) => {
const toggleFeature = () => {
if (!isEntitled) {
premiumModal.activate(name)
return
}
application.features.toggleExperimentalFeature(identifier)
reloadExperimentalFeatures()
{experimentalFeatures.map(({ identifier, name, description, isEnabled, isEntitled }, index: number) => {
const toggleFeature = () => {
if (!isEntitled) {
premiumModal.activate(name)
return
}
const showHorizontalSeparator =
experimentalFeatures.length > 1 && index !== experimentalFeatures.length - 1
application.features.toggleExperimentalFeature(identifier)
reloadExperimentalFeatures()
}
return (
<>
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>{name}</Subtitle>
<Text>{description}</Text>
</div>
<Switch onChange={toggleFeature} checked={isEnabled} />
const showHorizontalSeparator = experimentalFeatures.length > 1 && index !== experimentalFeatures.length - 1
return (
<>
<div className="flex items-center justify-between">
<div className="flex flex-col">
<Subtitle>{name}</Subtitle>
<Text>{description}</Text>
</div>
{showHorizontalSeparator && <HorizontalSeparator classes="mt-5 mb-3" />}
</>
)
},
)}
<Switch onChange={toggleFeature} checked={isEnabled} />
</div>
{showHorizontalSeparator && <HorizontalSeparator classes="mt-5 mb-3" />}
</>
)
})}
{experimentalFeatures.length === 0 && (
<div className="flex items-center justify-between">
<div className="flex flex-col">

View File

@@ -21,11 +21,7 @@ export const General: FunctionComponent<GeneralProps> = observer(
<Tools application={application} />
<Defaults application={application} />
<LabsPane application={application} />
<Advanced
application={application}
appState={appState}
extensionsLatestVersions={extensionsLatestVersions}
/>
<Advanced application={application} appState={appState} extensionsLatestVersions={extensionsLatestVersions} />
</PreferencesPane>
),
)

View File

@@ -17,10 +17,10 @@ export const HelpAndFeedback: FunctionComponent = () => (
<div className="h-2 w-full" />
<Subtitle>Who can read my private notes?</Subtitle>
<Text>
Quite simply: no one but you. Not us, not your ISP, not a hacker, and not a government
agency. As long as you keep your password safe, and your password is reasonably strong,
then you are the only person in the world with the ability to decrypt your notes. For more
on how we handle your privacy and security, check out our easy to read{' '}
Quite simply: no one but you. Not us, not your ISP, not a hacker, and not a government agency. As long as you
keep your password safe, and your password is reasonably strong, then you are the only person in the world
with the ability to decrypt your notes. For more on how we handle your privacy and security, check out our
easy to read{' '}
<a target="_blank" href="https://standardnotes.com/privacy">
Privacy Manifesto.
</a>
@@ -29,21 +29,17 @@ export const HelpAndFeedback: FunctionComponent = () => (
<PreferencesSegment>
<Subtitle>Can I collaborate with others on a note?</Subtitle>
<Text>
Because of our encrypted architecture, Standard Notes does not currently provide a
real-time collaboration solution. Multiple users can share the same account however, but
editing at the same time may result in sync conflicts, which may result in the duplication
of notes.
Because of our encrypted architecture, Standard Notes does not currently provide a real-time collaboration
solution. Multiple users can share the same account however, but editing at the same time may result in sync
conflicts, which may result in the duplication of notes.
</Text>
</PreferencesSegment>
<PreferencesSegment>
<Subtitle>Can I use Standard Notes totally offline?</Subtitle>
<Text>
Standard Notes can be used totally offline without an account, and without an internet
connection. You can find{' '}
<a
target="_blank"
href="https://standardnotes.com/help/59/can-i-use-standard-notes-totally-offline"
>
Standard Notes can be used totally offline without an account, and without an internet connection. You can
find{' '}
<a target="_blank" href="https://standardnotes.com/help/59/can-i-use-standard-notes-totally-offline">
more details here.
</a>
</Text>
@@ -57,38 +53,25 @@ export const HelpAndFeedback: FunctionComponent = () => (
<PreferencesSegment>
<Title>Community forum</Title>
<Text>
If you have an issue, found a bug or want to suggest a feature, you can browse or post to
the forum. Its recommended for non-account related issues. Please read our{' '}
If you have an issue, found a bug or want to suggest a feature, you can browse or post to the forum. Its
recommended for non-account related issues. Please read our{' '}
<a target="_blank" href="https://standardnotes.com/longevity/">
Longevity statement
</a>{' '}
before advocating for a feature request.
</Text>
<LinkButton
className="mt-3"
label="Go to the forum"
link="https://forum.standardnotes.org/"
/>
<LinkButton className="mt-3" label="Go to the forum" link="https://forum.standardnotes.org/" />
</PreferencesSegment>
</PreferencesGroup>
<PreferencesGroup>
<PreferencesSegment>
<Title>Community groups</Title>
<Text>
Want to meet other passionate note-takers and privacy enthusiasts? Want to share your
feedback with us? Join the Standard Notes community groups for discussions on security,
themes, editors and more.
Want to meet other passionate note-takers and privacy enthusiasts? Want to share your feedback with us? Join
the Standard Notes community groups for discussions on security, themes, editors and more.
</Text>
<LinkButton
className="mt-3"
link="https://standardnotes.com/slack"
label="Join our Slack"
/>
<LinkButton
className="mt-3"
link="https://standardnotes.com/discord"
label="Join our Discord"
/>
<LinkButton className="mt-3" link="https://standardnotes.com/slack" label="Join our Slack" />
<LinkButton className="mt-3" link="https://standardnotes.com/discord" label="Join our Discord" />
</PreferencesSegment>
</PreferencesGroup>
<PreferencesGroup>

View File

@@ -11,11 +11,7 @@ type Props = {
application: WebApplication
}
export const ListedAccountItem: FunctionalComponent<Props> = ({
account,
showSeparator,
application,
}) => {
export const ListedAccountItem: FunctionalComponent<Props> = ({ account, showSeparator, application }) => {
const [isLoading, setIsLoading] = useState(false)
const [accountInfo, setAccountInfo] = useState<ListedAccountInfo>()

View File

@@ -84,8 +84,8 @@ export const Listed = observer(({ application }: Props) => {
<div className="h-2 w-full" />
<Subtitle>What is Listed?</Subtitle>
<Text>
Listed is a free blogging platform that allows you to create a public journal published
directly from your notes.{' '}
Listed is a free blogging platform that allows you to create a public journal published directly from your
notes.{' '}
<a target="_blank" href="https://listed.to" rel="noreferrer noopener">
Learn more
</a>

View File

@@ -3,12 +3,7 @@ import { STRING_E2E_ENABLED, STRING_ENC_NOT_ENABLED, STRING_LOCAL_ENC_ENABLED }
import { AppState } from '@/UIModels/AppState'
import { observer } from 'mobx-react-lite'
import { ComponentChild, FunctionComponent } from 'preact'
import {
PreferencesGroup,
PreferencesSegment,
Text,
Title,
} from '@/Components/Preferences/PreferencesComponents'
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
const formatCount = (count: number, itemType: string) => `${count} / ${count} ${itemType}`

View File

@@ -18,12 +18,7 @@ import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
import { ApplicationEvent } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { AppState } from '@/UIModels/AppState'
import {
PreferencesSegment,
Title,
Text,
PreferencesGroup,
} from '@/Components/Preferences/PreferencesComponents'
import { PreferencesSegment, Title, Text, PreferencesGroup } from '@/Components/Preferences/PreferencesComponents'
import { Button } from '@/Components/Button/Button'
type Props = {
@@ -35,8 +30,7 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
const keyStorageInfo = StringUtils.keyStorageInfo(application)
const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions()
const { setIsEncryptionEnabled, setIsBackupEncrypted, setEncryptionStatusString } =
appState.accountMenu
const { setIsEncryptionEnabled, setIsBackupEncrypted, setEncryptionStatusString } = appState.accountMenu
const passcodeInputRef = useRef<HTMLInputElement>(null)
@@ -109,9 +103,7 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
setPasscodeConfirmation(value)
}
const submitPasscodeForm = async (
event: TargetedEvent<HTMLFormElement> | TargetedMouseEvent<HTMLButtonElement>,
) => {
const submitPasscodeForm = async (event: TargetedEvent<HTMLFormElement> | TargetedMouseEvent<HTMLButtonElement>) => {
event.preventDefault()
if (!passcode || passcode.length === 0) {
@@ -183,22 +175,18 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
{!hasPasscode && canAddPasscode && (
<>
<Text className="mb-3">
Add a passcode to lock the application and encrypt on-device key storage.
</Text>
<Text className="mb-3">Add a passcode to lock the application and encrypt on-device key storage.</Text>
{keyStorageInfo && <Text className="mb-3">{keyStorageInfo}</Text>}
{!showPasscodeForm && (
<Button label="Add passcode" onClick={handleAddPassCode} variant="primary" />
)}
{!showPasscodeForm && <Button label="Add passcode" onClick={handleAddPassCode} variant="primary" />}
</>
)}
{!hasPasscode && !canAddPasscode && (
<Text>
Adding a passcode is not supported in temporary sessions. Please sign out, then sign
back in with the "Stay signed in" option checked.
Adding a passcode is not supported in temporary sessions. Please sign out, then sign back in with the
"Stay signed in" option checked.
</Text>
)}
@@ -221,12 +209,7 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
placeholder="Confirm Passcode"
/>
<div className="min-h-2" />
<Button
variant="primary"
onClick={submitPasscodeForm}
label="Set Passcode"
className="mr-3"
/>
<Button variant="primary" onClick={submitPasscodeForm} label="Set Passcode" className="mr-3" />
<Button variant="normal" onClick={() => setShowPasscodeForm(false)} label="Cancel" />
</form>
)}
@@ -235,17 +218,8 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
<>
<Text>Passcode lock is enabled.</Text>
<div className="flex flex-row mt-3">
<Button
variant="normal"
label="Change Passcode"
onClick={changePasscodePressed}
className="mr-3"
/>
<Button
dangerStyle={true}
label="Remove Passcode"
onClick={removePasscodePressed}
/>
<Button variant="normal" label="Change Passcode" onClick={changePasscodePressed} className="mr-3" />
<Button dangerStyle={true} label="Remove Passcode" onClick={removePasscodePressed} />
</div>
</>
)}
@@ -258,16 +232,12 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
<PreferencesGroup>
<PreferencesSegment>
<Title>Autolock</Title>
<Text className="mb-3">
The autolock timer begins when the window or tab loses focus.
</Text>
<Text className="mb-3">The autolock timer begins when the window or tab loses focus.</Text>
<div className="flex flex-row items-center">
{passcodeAutoLockOptions.map((option) => {
return (
<a
className={`sk-a info mr-3 ${
option.value === selectedAutoLockInterval ? 'boxed' : ''
}`}
className={`sk-a info mr-3 ${option.value === selectedAutoLockInterval ? 'boxed' : ''}`}
onClick={() => selectAutoLockInterval(option.value)}
>
{option.label}

View File

@@ -71,9 +71,7 @@ export const Privacy: FunctionalComponent<Props> = observer(({ application }: Pr
const toggleMuteSignInEmails = async () => {
const previousValue = signInEmailsMutedValue
const newValue =
previousValue === MuteSignInEmailsOption.Muted
? MuteSignInEmailsOption.NotMuted
: MuteSignInEmailsOption.Muted
previousValue === MuteSignInEmailsOption.Muted ? MuteSignInEmailsOption.NotMuted : MuteSignInEmailsOption.Muted
setSignInEmailsMutedValue(newValue)
const updateResult = await updateSetting(SettingName.MuteSignInEmails, newValue)
@@ -107,8 +105,8 @@ export const Privacy: FunctionalComponent<Props> = observer(({ application }: Pr
<div className="flex flex-col">
<Subtitle>Disable sign-in notification emails</Subtitle>
<Text>
Disables email notifications when a new sign-in occurs on your account. (Email
notifications are available to paid subscribers).
Disables email notifications when a new sign-in occurs on your account. (Email notifications are
available to paid subscribers).
</Text>
</div>
{isLoading ? (
@@ -125,9 +123,9 @@ export const Privacy: FunctionalComponent<Props> = observer(({ application }: Pr
<div className="flex flex-col">
<Subtitle>Session user agent logging</Subtitle>
<Text>
User agent logging allows you to identify the devices or browsers signed into your
account. For increased privacy, you can disable this feature, which will remove all
saved user agent values from our server, and disable future logging of this value.
User agent logging allows you to identify the devices or browsers signed into your account. For
increased privacy, you can disable this feature, which will remove all saved user agent values from our
server, and disable future logging of this value.
</Text>
</div>
{isLoading ? (

View File

@@ -3,12 +3,7 @@ import { FunctionalComponent } from 'preact'
import { useCallback, useState, useEffect } from 'preact/hooks'
import { ApplicationEvent } from '@standardnotes/snjs'
import { isSameDay } from '@/Utils'
import {
PreferencesGroup,
PreferencesSegment,
Title,
Text,
} from '@/Components/Preferences/PreferencesComponents'
import { PreferencesGroup, PreferencesSegment, Title, Text } from '@/Components/Preferences/PreferencesComponents'
import { Button } from '@/Components/Button/Button'
type Props = {
@@ -47,9 +42,7 @@ export const Protections: FunctionalComponent<Props> = ({ application }) => {
return null
}, [application])
const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(
getProtectionsDisabledUntil(),
)
const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(getProtectionsDisabledUntil())
useEffect(() => {
const removeUnprotectedSessionBeginObserver = application.addEventObserver(async () => {
@@ -85,17 +78,11 @@ export const Protections: FunctionalComponent<Props> = ({ application }) => {
<Text className="info">Protections are enabled.</Text>
)}
<Text className="mt-2">
Actions like viewing or searching protected notes, exporting decrypted backups, or
revoking an active session require additional authentication such as entering your account
password or application passcode.
Actions like viewing or searching protected notes, exporting decrypted backups, or revoking an active session
require additional authentication such as entering your account password or application passcode.
</Text>
{protectionsDisabledUntil && (
<Button
className="mt-3"
variant="primary"
label="End Unprotected Access"
onClick={enableProtections}
/>
<Button className="mt-3" variant="primary" label="End Unprotected Access" onClick={enableProtections} />
)}
</PreferencesSegment>
</PreferencesGroup>

View File

@@ -14,9 +14,7 @@ const DisclosureIconButton: FunctionComponent<{
<DisclosureButton
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={`no-border cursor-pointer bg-transparent hover:brightness-130 p-0 ${
className ?? ''
}`}
className={`no-border cursor-pointer bg-transparent hover:brightness-130 p-0 ${className ?? ''}`}
>
<Icon type={icon} />
</DisclosureButton>
@@ -56,8 +54,8 @@ export const AuthAppInfoTooltip: FunctionComponent = () => {
className={`bg-inverted-default color-inverted-default text-center rounded shadow-overlay
py-1.5 px-2 absolute w-103 -top-10 -left-51`}
>
Some apps, like Google Authenticator, do not back up and restore your secret keys if you
lose your device or get a new one.
Some apps, like Google Authenticator, do not back up and restore your secret keys if you lose your device or
get a new one.
</div>
</DisclosurePanel>
</div>

View File

@@ -76,18 +76,8 @@ export const SaveSecretKey: FunctionComponent<{
</div>
</ModalDialogDescription>
<ModalDialogButtons>
<Button
className="min-w-20"
variant="normal"
label="Back"
onClick={() => act.openScanQRCode()}
/>
<Button
className="min-w-20"
variant="primary"
label="Next"
onClick={() => act.openVerification()}
/>
<Button className="min-w-20" variant="normal" label="Back" onClick={() => act.openScanQRCode()} />
<Button className="min-w-20" variant="primary" label="Next" onClick={() => act.openVerification()} />
</ModalDialogButtons>
</ModalDialog>
)

View File

@@ -19,16 +19,10 @@ export const ScanQRCode: FunctionComponent<{
}> = observer(({ activation: act }) => {
return (
<ModalDialog>
<ModalDialogLabel closeDialog={act.cancelActivation}>
Step 1 of 3 - Scan QR code
</ModalDialogLabel>
<ModalDialogLabel closeDialog={act.cancelActivation}>Step 1 of 3 - Scan QR code</ModalDialogLabel>
<ModalDialogDescription className="h-33">
<div className="w-25 h-25 flex items-center justify-center bg-info">
<QRCode
className="border-neutral-contrast-bg border-solid border-2"
value={act.qrCode}
size={100}
/>
<QRCode className="border-neutral-contrast-bg border-solid border-2" value={act.qrCode} size={100} />
</div>
<div className="min-w-5" />
<div className="flex-grow flex flex-col">
@@ -59,18 +53,8 @@ export const ScanQRCode: FunctionComponent<{
</div>
</ModalDialogDescription>
<ModalDialogButtons>
<Button
className="min-w-20"
variant="normal"
label="Cancel"
onClick={() => act.cancelActivation()}
/>
<Button
className="min-w-20"
variant="primary"
label="Next"
onClick={() => act.openSaveSecretKey()}
/>
<Button className="min-w-20" variant="normal" label="Cancel" onClick={() => act.cancelActivation()} />
<Button className="min-w-20" variant="primary" label="Next" onClick={() => act.openSaveSecretKey()} />
</ModalDialogButtons>
</ModalDialog>
)

View File

@@ -25,12 +25,7 @@ export class TwoFactorActivation {
makeAutoObservable<
TwoFactorActivation,
| '_secretKey'
| '_authCode'
| '_step'
| '_enable2FAVerification'
| 'inputOtpToken'
| 'inputSecretKey'
'_secretKey' | '_authCode' | '_step' | '_enable2FAVerification' | 'inputOtpToken' | 'inputSecretKey'
>(
this,
{

View File

@@ -10,23 +10,16 @@ export const is2FADisabled = (status: TwoFactorStatus): status is 'two-factor-di
export const is2FAActivation = (status: TwoFactorStatus): status is TwoFactorActivation =>
(status as TwoFactorActivation)?.type === 'two-factor-activation'
export const is2FAEnabled = (status: TwoFactorStatus): status is 'two-factor-enabled' =>
status === 'two-factor-enabled'
export const is2FAEnabled = (status: TwoFactorStatus): status is 'two-factor-enabled' => status === 'two-factor-enabled'
export class TwoFactorAuth {
private _status: TwoFactorStatus | 'fetching' = 'fetching'
private _errorMessage: string | null
constructor(
private readonly mfaProvider: MfaProvider,
private readonly userProvider: UserProvider,
) {
constructor(private readonly mfaProvider: MfaProvider, private readonly userProvider: UserProvider) {
this._errorMessage = null
makeAutoObservable<
TwoFactorAuth,
'_status' | '_errorMessage' | 'deactivateMfa' | 'startActivation'
>(
makeAutoObservable<TwoFactorAuth, '_status' | '_errorMessage' | 'deactivateMfa' | 'startActivation'>(
this,
{
_status: observable,

View File

@@ -1,10 +1,5 @@
import { FunctionComponent } from 'preact'
import {
Title,
Text,
PreferencesGroup,
PreferencesSegment,
} from '@/Components/Preferences/PreferencesComponents'
import { Title, Text, PreferencesGroup, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
import { Switch } from '@/Components/Switch'
import { observer } from 'mobx-react-lite'
import { is2FAActivation, is2FADisabled, TwoFactorAuth } from './TwoFactorAuth'

View File

@@ -16,18 +16,11 @@ export const TwoFactorSuccess: FunctionComponent<{
<ModalDialogLabel closeDialog={act.finishActivation}>Successfully Enabled</ModalDialogLabel>
<ModalDialogDescription>
<div className="flex flex-row items-center justify-center pt-2">
<Subtitle>
Two-factor authentication has been successfully enabled for your account.
</Subtitle>
<Subtitle>Two-factor authentication has been successfully enabled for your account.</Subtitle>
</div>
</ModalDialogDescription>
<ModalDialogButtons>
<Button
className="min-w-20"
variant="primary"
label="Finish"
onClick={act.finishActivation}
/>
<Button className="min-w-20" variant="primary" label="Finish" onClick={act.finishActivation} />
</ModalDialogButtons>
</ModalDialog>
))

View File

@@ -18,9 +18,7 @@ export const Verification: FunctionComponent<{
const authTokenClass = act.verificationStatus === 'invalid-auth-code' ? 'border-danger' : ''
return (
<ModalDialog>
<ModalDialogLabel closeDialog={act.cancelActivation}>
Step 3 of 3 - Verification
</ModalDialogLabel>
<ModalDialogLabel closeDialog={act.cancelActivation}>Step 3 of 3 - Verification</ModalDialogLabel>
<ModalDialogDescription className="h-33">
<div className="flex-grow flex flex-col">
<div className="flex flex-row items-center mb-4">
@@ -45,21 +43,12 @@ export const Verification: FunctionComponent<{
</ModalDialogDescription>
<ModalDialogButtons>
{act.verificationStatus === 'invalid-auth-code' && (
<div className="text-sm color-danger flex-grow">
Incorrect authentication code, please try again.
</div>
<div className="text-sm color-danger flex-grow">Incorrect authentication code, please try again.</div>
)}
{act.verificationStatus === 'invalid-secret' && (
<div className="text-sm color-danger flex-grow">
Incorrect secret key, please try again.
</div>
<div className="text-sm color-danger flex-grow">Incorrect secret key, please try again.</div>
)}
<Button
className="min-w-20"
variant="normal"
label="Back"
onClick={act.openSaveSecretKey}
/>
<Button className="min-w-20" variant="normal" label="Back" onClick={act.openSaveSecretKey} />
<Button className="min-w-20" variant="primary" label="Next" onClick={act.enable2FA} />
</ModalDialogButtons>
</ModalDialog>

View File

@@ -7,15 +7,13 @@ export const Title: FunctionComponent = ({ children }) => (
</>
)
export const Subtitle: FunctionComponent<{ className?: string }> = ({
children,
className = '',
}) => <h4 className={`font-medium text-sm m-0 mb-1 ${className}`}>{children}</h4>
export const Subtitle: FunctionComponent<{ className?: string }> = ({ children, className = '' }) => (
<h4 className={`font-medium text-sm m-0 mb-1 ${className}`}>{children}</h4>
)
export const SubtitleLight: FunctionComponent<{ className?: string }> = ({
children,
className = '',
}) => <h4 className={`font-normal text-sm m-0 mb-1 ${className}`}>{children}</h4>
export const SubtitleLight: FunctionComponent<{ className?: string }> = ({ children, className = '' }) => (
<h4 className={`font-normal text-sm m-0 mb-1 ${className}`}>{children}</h4>
)
export const Text: FunctionComponent<{ className?: string }> = ({ children, className = '' }) => (
<p className={`${className} text-xs`}>{children}</p>

View File

@@ -10,13 +10,7 @@ interface Props {
onClick: () => void
}
export const MenuItem: FunctionComponent<Props> = ({
iconType,
label,
selected,
onClick,
hasBubble,
}) => (
export const MenuItem: FunctionComponent<Props> = ({ iconType, label, selected, onClick, hasBubble }) => (
<div
className={`preferences-menu-item select-none ${selected ? 'selected' : ''}`}
onClick={(e) => {

View File

@@ -4,9 +4,7 @@ export const PreferencesPane: FunctionComponent = ({ children }) => (
<div className="color-foreground flex-grow flex flex-row overflow-y-auto min-h-0">
<div className="flex-grow flex flex-col py-6 items-center">
<div className="w-125 max-w-125 flex flex-col">
{children != undefined && Array.isArray(children)
? children.filter((child) => child != undefined)
: children}
{children != undefined && Array.isArray(children) ? children.filter((child) => child != undefined) : children}
</div>
</div>
<div className="flex-basis-55 flex-shrink" />

View File

@@ -59,27 +59,16 @@ const READY_PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [
export class PreferencesMenu {
private _selectedPane: PreferenceId = 'account'
private _menu: PreferencesMenuItem[]
private _extensionLatestVersions: ExtensionsLatestVersions = new ExtensionsLatestVersions(
new Map(),
)
private _extensionLatestVersions: ExtensionsLatestVersions = new ExtensionsLatestVersions(new Map())
constructor(
private application: WebApplication,
private readonly _enableUnfinishedFeatures: boolean,
) {
this._menu = this._enableUnfinishedFeatures
? PREFERENCES_MENU_ITEMS
: READY_PREFERENCES_MENU_ITEMS
constructor(private application: WebApplication, private readonly _enableUnfinishedFeatures: boolean) {
this._menu = this._enableUnfinishedFeatures ? PREFERENCES_MENU_ITEMS : READY_PREFERENCES_MENU_ITEMS
this.loadLatestVersions()
makeAutoObservable<
PreferencesMenu,
| '_selectedPane'
| '_twoFactorAuth'
| '_extensionPanes'
| '_extensionLatestVersions'
| 'loadLatestVersions'
'_selectedPane' | '_twoFactorAuth' | '_extensionPanes' | '_extensionLatestVersions' | 'loadLatestVersions'
>(this, {
_twoFactorAuth: observable,
_selectedPane: observable,

View File

@@ -67,14 +67,12 @@ const PaneSelector: FunctionComponent<PreferencesProps & { menu: PreferencesMenu
},
)
const PreferencesCanvas: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = observer(
(props) => (
<div className="flex flex-row flex-grow min-h-0 justify-between">
<PreferencesMenuView menu={props.menu} />
<PaneSelector {...props} />
</div>
),
)
const PreferencesCanvas: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = observer((props) => (
<div className="flex flex-row flex-grow min-h-0 justify-between">
<PreferencesMenuView menu={props.menu} />
<PaneSelector {...props} />
</div>
))
export const PreferencesView: FunctionComponent<PreferencesProps> = observer((props) => {
const menu = useMemo(

View File

@@ -51,8 +51,8 @@ export const PremiumFeaturesModal: FunctionalComponent<Props> = ({
<div className="text-lg text-center font-bold mb-1">Enable Premium Features</div>
</AlertDialogLabel>
<AlertDialogDescription className="text-sm text-center color-grey-1 px-4.5 mb-2">
In order to use <span className="font-semibold">{featureName}</span> and other premium
features, please purchase a subscription or upgrade your current plan.
In order to use <span className="font-semibold">{featureName}</span> and other premium features, please
purchase a subscription or upgrade your current plan.
</AlertDialogDescription>
<div className="p-4">
<button

View File

@@ -8,12 +8,7 @@ import { useEffect, useRef, useState } from 'preact/hooks'
import { FloatingLabelInput } from '@/Components/Input/FloatingLabelInput'
import { isEmailValid } from '@/Utils'
import { loadPurchaseFlowUrl } from '@/Components/PurchaseFlow/PurchaseFlowWrapper'
import {
BlueDotIcon,
CircleIcon,
DiamondIcon,
CreateAccountIllustration,
} from '@standardnotes/stylekit'
import { BlueDotIcon, CircleIcon, DiamondIcon, CreateAccountIllustration } from '@standardnotes/stylekit'
type Props = {
appState: AppState
@@ -145,9 +140,7 @@ export const CreateAccount: FunctionComponent<Props> = observer(({ appState, app
disabled={isCreatingAccount}
isInvalid={isEmailInvalid}
/>
{isEmailInvalid ? (
<div className="color-dark-red mb-4">Please provide a valid email.</div>
) : null}
{isEmailInvalid ? <div className="color-dark-red mb-4">Please provide a valid email.</div> : null}
<FloatingLabelInput
className="min-w-90 xs:min-w-auto mb-4"
id="purchase-create-account-password"

View File

@@ -114,9 +114,7 @@ export const SignIn: FunctionComponent<Props> = observer(({ appState, applicatio
<form onSubmit={handleSignIn}>
<div className="flex flex-col">
<FloatingLabelInput
className={`min-w-90 xs:min-w-auto ${
isEmailInvalid && !otherErrorMessage ? 'mb-2' : 'mb-4'
}`}
className={`min-w-90 xs:min-w-auto ${isEmailInvalid && !otherErrorMessage ? 'mb-2' : 'mb-4'}`}
id="purchase-sign-in-email"
type="email"
label="Email"
@@ -140,9 +138,7 @@ export const SignIn: FunctionComponent<Props> = observer(({ appState, applicatio
disabled={isSigningIn}
isInvalid={isPasswordInvalid}
/>
{otherErrorMessage ? (
<div className="color-dark-red mb-4">{otherErrorMessage}</div>
) : null}
{otherErrorMessage ? <div className="color-dark-red mb-4">{otherErrorMessage}</div> : null}
</div>
<Button
className={`${isSigningIn ? 'min-w-30' : 'min-w-24'} py-2.5 mb-5`}

View File

@@ -16,11 +16,7 @@ type PurchaseFlowViewProps = {
application: WebApplication
}
const PurchaseFlowPaneSelector: FunctionComponent<PaneSelectorProps> = ({
currentPane,
appState,
application,
}) => {
const PurchaseFlowPaneSelector: FunctionComponent<PaneSelectorProps> = ({ currentPane, appState, application }) => {
switch (currentPane) {
case PurchaseFlowPane.CreateAccount:
return <CreateAccount appState={appState} application={application} />
@@ -29,41 +25,35 @@ const PurchaseFlowPaneSelector: FunctionComponent<PaneSelectorProps> = ({
}
}
export const PurchaseFlowView: FunctionComponent<PurchaseFlowViewProps> = observer(
({ appState, application }) => {
const { currentPane } = appState.purchaseFlow
export const PurchaseFlowView: FunctionComponent<PurchaseFlowViewProps> = observer(({ appState, application }) => {
const { currentPane } = appState.purchaseFlow
return (
<div className="flex items-center justify-center overflow-hidden h-full w-full absolute top-left-0 z-index-purchase-flow bg-grey-super-light">
<div className="relative fit-content">
<div className="relative p-12 xs:px-8 mb-4 bg-default border-1 border-solid border-main rounded xs:rounded-0">
<SNLogoFull className="mb-5" />
<PurchaseFlowPaneSelector
currentPane={currentPane}
appState={appState}
application={application}
/>
</div>
<div className="flex justify-end xs:px-4">
<a
className="mr-3 font-medium color-grey-1"
href="https://standardnotes.com/privacy"
target="_blank"
rel="noopener noreferrer"
>
Privacy
</a>
<a
className="font-medium color-grey-1"
href="https://standardnotes.com/help"
target="_blank"
rel="noopener noreferrer"
>
Help
</a>
</div>
return (
<div className="flex items-center justify-center overflow-hidden h-full w-full absolute top-left-0 z-index-purchase-flow bg-grey-super-light">
<div className="relative fit-content">
<div className="relative p-12 xs:px-8 mb-4 bg-default border-1 border-solid border-main rounded xs:rounded-0">
<SNLogoFull className="mb-5" />
<PurchaseFlowPaneSelector currentPane={currentPane} appState={appState} application={application} />
</div>
<div className="flex justify-end xs:px-4">
<a
className="mr-3 font-medium color-grey-1"
href="https://standardnotes.com/privacy"
target="_blank"
rel="noopener noreferrer"
>
Privacy
</a>
<a
className="font-medium color-grey-1"
href="https://standardnotes.com/help"
target="_blank"
rel="noopener noreferrer"
>
Help
</a>
</div>
</div>
)
},
)
</div>
)
})

View File

@@ -10,9 +10,7 @@ export type PurchaseFlowWrapperProps = {
application: WebApplication
}
export const getPurchaseFlowUrl = async (
application: WebApplication,
): Promise<string | undefined> => {
export const getPurchaseFlowUrl = async (application: WebApplication): Promise<string | undefined> => {
const currentUrl = window.location.origin
const successUrl = isDesktopApplication() ? 'standardnotes://' : currentUrl
if (application.noAccount()) {

View File

@@ -9,8 +9,7 @@ export const quickSettingsKeyDownHandler = (
themesMenuOpen: boolean,
) => {
if (quickSettingsMenuRef?.current) {
const items: NodeListOf<HTMLButtonElement> =
quickSettingsMenuRef.current.querySelectorAll(':scope > button')
const items: NodeListOf<HTMLButtonElement> = quickSettingsMenuRef.current.querySelectorAll(':scope > button')
const currentFocusedIndex = Array.from(items).findIndex((btn) => btn === document.activeElement)
if (!themesMenuOpen) {
@@ -45,9 +44,7 @@ export const themesMenuKeyDownHandler = (
) => {
if (themesMenuRef?.current) {
const themes = themesMenuRef.current.querySelectorAll('button')
const currentFocusedIndex = Array.from(themes).findIndex(
(themeBtn) => themeBtn === document.activeElement,
)
const currentFocusedIndex = Array.from(themes).findIndex((themeBtn) => themeBtn === document.activeElement)
switch (event.key) {
case 'Escape':

View File

@@ -14,15 +14,9 @@ type Props = {
isEnabled: boolean
}
export const FocusModeSwitch: FunctionComponent<Props> = ({
application,
onToggle,
onClose,
isEnabled,
}) => {
export const FocusModeSwitch: FunctionComponent<Props> = ({ application, onToggle, onClose, isEnabled }) => {
const premiumModal = usePremiumModal()
const isEntitled =
application.features.getFeatureStatus(FeatureIdentifier.FocusMode) === FeatureStatus.Entitled
const isEntitled = application.features.getFeatureStatus(FeatureIdentifier.FocusMode) === FeatureStatus.Entitled
const toggle = useCallback(
(e: JSXInternal.TargetedMouseEvent<HTMLButtonElement>) => {
@@ -40,10 +34,7 @@ export const FocusModeSwitch: FunctionComponent<Props> = ({
return (
<>
<button
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none justify-between"
onClick={toggle}
>
<button className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none justify-between" onClick={toggle}>
<div className="flex items-center">
<Icon type="menu-close" className="color-neutral mr-2" />
Focused Writing

View File

@@ -25,10 +25,7 @@ export const ThemesMenuButton: FunctionComponent<Props> = ({ application, item,
() => application.features.getFeatureStatus(item.identifier) === FeatureStatus.Entitled,
[application, item.identifier],
)
const canActivateTheme = useMemo(
() => isEntitledToTheme || isThirdPartyTheme,
[isEntitledToTheme, isThirdPartyTheme],
)
const canActivateTheme = useMemo(() => isEntitledToTheme || isThirdPartyTheme, [isEntitledToTheme, isThirdPartyTheme])
const toggleTheme: JSXInternal.MouseEventHandler<HTMLButtonElement> = (e) => {
e.preventDefault()
@@ -61,14 +58,8 @@ export const ThemesMenuButton: FunctionComponent<Props> = ({ application, item,
) : (
<>
<div className="flex items-center">
<div
className={`pseudo-radio-btn ${
item.component?.active ? 'pseudo-radio-btn--checked' : ''
} mr-2`}
></div>
<span className={item.component?.active ? 'font-semibold' : undefined}>
{item.name}
</span>
<div className={`pseudo-radio-btn ${item.component?.active ? 'pseudo-radio-btn--checked' : ''} mr-2`}></div>
<span className={item.component?.active ? 'font-semibold' : undefined}>{item.name}</span>
</div>
{item.component && canActivateTheme ? (
<div

View File

@@ -1,14 +1,7 @@
import { WebApplication } from '@/UIModels/Application'
import { AppState } from '@/UIModels/AppState'
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
import {
ComponentArea,
ContentType,
FeatureIdentifier,
GetFeatures,
SNComponent,
SNTheme,
} from '@standardnotes/snjs'
import { ComponentArea, ContentType, FeatureIdentifier, GetFeatures, SNComponent, SNTheme } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
@@ -25,8 +18,7 @@ import { sortThemes } from '@/Utils/SortThemes'
const focusModeAnimationDuration = 1255
const MENU_CLASSNAME =
'sn-menu-border sn-dropdown min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto'
const MENU_CLASSNAME = 'sn-menu-border sn-dropdown min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto'
type MenuProps = {
appState: AppState
@@ -48,266 +40,241 @@ const toggleFocusMode = (enabled: boolean) => {
}
}
export const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(
({ application, appState, onClickOutside }) => {
const {
closeQuickSettingsMenu,
shouldAnimateCloseMenu,
focusModeEnabled,
setFocusModeEnabled,
} = appState.quickSettingsMenu
const [themes, setThemes] = useState<ThemeItem[]>([])
const [toggleableComponents, setToggleableComponents] = useState<SNComponent[]>([])
const [themesMenuOpen, setThemesMenuOpen] = useState(false)
const [themesMenuPosition, setThemesMenuPosition] = useState({})
const [defaultThemeOn, setDefaultThemeOn] = useState(false)
export const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(({ application, appState, onClickOutside }) => {
const { closeQuickSettingsMenu, shouldAnimateCloseMenu, focusModeEnabled, setFocusModeEnabled } =
appState.quickSettingsMenu
const [themes, setThemes] = useState<ThemeItem[]>([])
const [toggleableComponents, setToggleableComponents] = useState<SNComponent[]>([])
const [themesMenuOpen, setThemesMenuOpen] = useState(false)
const [themesMenuPosition, setThemesMenuPosition] = useState({})
const [defaultThemeOn, setDefaultThemeOn] = useState(false)
const themesMenuRef = useRef<HTMLDivElement>(null)
const themesButtonRef = useRef<HTMLButtonElement>(null)
const prefsButtonRef = useRef<HTMLButtonElement>(null)
const quickSettingsMenuRef = useRef<HTMLDivElement>(null)
const defaultThemeButtonRef = useRef<HTMLButtonElement>(null)
const themesMenuRef = useRef<HTMLDivElement>(null)
const themesButtonRef = useRef<HTMLButtonElement>(null)
const prefsButtonRef = useRef<HTMLButtonElement>(null)
const quickSettingsMenuRef = useRef<HTMLDivElement>(null)
const defaultThemeButtonRef = useRef<HTMLButtonElement>(null)
const mainRef = useRef<HTMLDivElement>(null)
useCloseOnClickOutside(mainRef, () => {
onClickOutside()
const mainRef = useRef<HTMLDivElement>(null)
useCloseOnClickOutside(mainRef, () => {
onClickOutside()
})
useEffect(() => {
toggleFocusMode(focusModeEnabled)
}, [focusModeEnabled])
const reloadThemes = useCallback(() => {
const themes = application.items.getDisplayableItems<SNTheme>(ContentType.Theme).map((item) => {
return {
name: item.name,
identifier: item.identifier,
component: item,
}
}) as ThemeItem[]
GetFeatures()
.filter((feature) => feature.content_type === ContentType.Theme && !feature.layerable)
.forEach((theme) => {
if (themes.findIndex((item) => item.identifier === theme.identifier) === -1) {
themes.push({
name: theme.name as string,
identifier: theme.identifier,
})
}
})
setThemes(themes.sort(sortThemes))
setDefaultThemeOn(!themes.map((item) => item?.component).find((theme) => theme?.active && !theme.isLayerable()))
}, [application])
const reloadToggleableComponents = useCallback(() => {
const toggleableComponents = application.items
.getDisplayableItems<SNComponent>(ContentType.Component)
.filter(
(component) =>
[ComponentArea.EditorStack].includes(component.area) &&
component.identifier !== FeatureIdentifier.DeprecatedFoldersComponent,
)
setToggleableComponents(toggleableComponents)
}, [application])
useEffect(() => {
if (!themes.length) {
reloadThemes()
}
}, [reloadThemes, themes.length])
useEffect(() => {
const cleanupItemStream = application.streamItems(ContentType.Theme, () => {
reloadThemes()
})
useEffect(() => {
toggleFocusMode(focusModeEnabled)
}, [focusModeEnabled])
return () => {
cleanupItemStream()
}
}, [application, reloadThemes])
const reloadThemes = useCallback(() => {
const themes = application.items
.getDisplayableItems<SNTheme>(ContentType.Theme)
.map((item) => {
return {
name: item.name,
identifier: item.identifier,
component: item,
}
}) as ThemeItem[]
useEffect(() => {
const cleanupItemStream = application.streamItems(ContentType.Component, () => {
reloadToggleableComponents()
})
GetFeatures()
.filter((feature) => feature.content_type === ContentType.Theme && !feature.layerable)
.forEach((theme) => {
if (themes.findIndex((item) => item.identifier === theme.identifier) === -1) {
themes.push({
name: theme.name as string,
identifier: theme.identifier,
})
}
})
return () => {
cleanupItemStream()
}
}, [application, reloadToggleableComponents])
setThemes(themes.sort(sortThemes))
useEffect(() => {
if (themesMenuOpen) {
defaultThemeButtonRef.current?.focus()
}
}, [themesMenuOpen])
setDefaultThemeOn(
!themes
.map((item) => item?.component)
.find((theme) => theme?.active && !theme.isLayerable()),
)
}, [application])
useEffect(() => {
prefsButtonRef.current?.focus()
}, [])
const reloadToggleableComponents = useCallback(() => {
const toggleableComponents = application.items
.getDisplayableItems<SNComponent>(ContentType.Component)
.filter(
(component) =>
[ComponentArea.EditorStack].includes(component.area) &&
component.identifier !== FeatureIdentifier.DeprecatedFoldersComponent,
)
const [closeOnBlur] = useCloseOnBlur(themesMenuRef, setThemesMenuOpen)
setToggleableComponents(toggleableComponents)
}, [application])
useEffect(() => {
if (!themes.length) {
reloadThemes()
}
}, [reloadThemes, themes.length])
useEffect(() => {
const cleanupItemStream = application.streamItems(ContentType.Theme, () => {
reloadThemes()
const toggleThemesMenu = () => {
if (!themesMenuOpen && themesButtonRef.current) {
const themesButtonRect = themesButtonRef.current.getBoundingClientRect()
setThemesMenuPosition({
left: themesButtonRect.right,
bottom: document.documentElement.clientHeight - themesButtonRect.bottom,
})
setThemesMenuOpen(true)
} else {
setThemesMenuOpen(false)
}
}
return () => {
cleanupItemStream()
}
}, [application, reloadThemes])
const openPreferences = () => {
closeQuickSettingsMenu()
appState.preferences.openPreferences()
}
useEffect(() => {
const cleanupItemStream = application.streamItems(ContentType.Component, () => {
reloadToggleableComponents()
})
const toggleComponent = (component: SNComponent) => {
if (component.isTheme()) {
application.mutator.toggleTheme(component).catch(console.error)
} else {
application.mutator.toggleComponent(component).catch(console.error)
}
}
return () => {
cleanupItemStream()
}
}, [application, reloadToggleableComponents])
useEffect(() => {
if (themesMenuOpen) {
defaultThemeButtonRef.current?.focus()
}
}, [themesMenuOpen])
useEffect(() => {
prefsButtonRef.current?.focus()
}, [])
const [closeOnBlur] = useCloseOnBlur(themesMenuRef, setThemesMenuOpen)
const toggleThemesMenu = () => {
if (!themesMenuOpen && themesButtonRef.current) {
const themesButtonRect = themesButtonRef.current.getBoundingClientRect()
setThemesMenuPosition({
left: themesButtonRect.right,
bottom: document.documentElement.clientHeight - themesButtonRect.bottom,
})
setThemesMenuOpen(true)
} else {
const handleBtnKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (event) => {
switch (event.key) {
case 'Escape':
setThemesMenuOpen(false)
}
themesButtonRef.current?.focus()
break
case 'ArrowRight':
if (!themesMenuOpen) {
toggleThemesMenu()
}
}
}
const openPreferences = () => {
closeQuickSettingsMenu()
appState.preferences.openPreferences()
const handleQuickSettingsKeyDown: JSXInternal.KeyboardEventHandler<HTMLDivElement> = (event) => {
quickSettingsKeyDownHandler(closeQuickSettingsMenu, event, quickSettingsMenuRef, themesMenuOpen)
}
const handlePanelKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {
themesMenuKeyDownHandler(event, themesMenuRef, setThemesMenuOpen, themesButtonRef)
}
const toggleDefaultTheme = () => {
const activeTheme = themes.map((item) => item.component).find((theme) => theme?.active && !theme.isLayerable())
if (activeTheme) {
application.mutator.toggleTheme(activeTheme).catch(console.error)
}
}
const toggleComponent = (component: SNComponent) => {
if (component.isTheme()) {
application.mutator.toggleTheme(component).catch(console.error)
} else {
application.mutator.toggleComponent(component).catch(console.error)
}
}
const handleBtnKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (event) => {
switch (event.key) {
case 'Escape':
setThemesMenuOpen(false)
themesButtonRef.current?.focus()
break
case 'ArrowRight':
if (!themesMenuOpen) {
toggleThemesMenu()
}
}
}
const handleQuickSettingsKeyDown: JSXInternal.KeyboardEventHandler<HTMLDivElement> = (
event,
) => {
quickSettingsKeyDownHandler(
closeQuickSettingsMenu,
event,
quickSettingsMenuRef,
themesMenuOpen,
)
}
const handlePanelKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {
themesMenuKeyDownHandler(event, themesMenuRef, setThemesMenuOpen, themesButtonRef)
}
const toggleDefaultTheme = () => {
const activeTheme = themes
.map((item) => item.component)
.find((theme) => theme?.active && !theme.isLayerable())
if (activeTheme) {
application.mutator.toggleTheme(activeTheme).catch(console.error)
}
}
return (
<div ref={mainRef} className="sn-component">
<div
className={`sn-quick-settings-menu absolute ${MENU_CLASSNAME} ${
shouldAnimateCloseMenu ? 'slide-up-animation' : 'sn-dropdown--animated'
}`}
ref={quickSettingsMenuRef}
onKeyDown={handleQuickSettingsKeyDown}
>
<div className="px-3 mt-1 mb-2 font-semibold color-text uppercase">Quick Settings</div>
<Disclosure open={themesMenuOpen} onChange={toggleThemesMenu}>
<DisclosureButton
onKeyDown={handleBtnKeyDown}
onBlur={closeOnBlur}
ref={themesButtonRef}
className="sn-dropdown-item justify-between focus:bg-info-backdrop focus:shadow-none"
>
<div className="flex items-center">
<Icon type="themes" className="color-neutral mr-2" />
Themes
</div>
<Icon type="chevron-right" className="color-neutral" />
</DisclosureButton>
<DisclosurePanel
onBlur={closeOnBlur}
ref={themesMenuRef}
onKeyDown={handlePanelKeyDown}
style={{
...themesMenuPosition,
}}
className={`${MENU_CLASSNAME} fixed sn-dropdown--animated`}
>
<div className="px-3 my-1 font-semibold color-text uppercase">Themes</div>
<button
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none"
onClick={toggleDefaultTheme}
onBlur={closeOnBlur}
ref={defaultThemeButtonRef}
>
<div
className={`pseudo-radio-btn ${
defaultThemeOn ? 'pseudo-radio-btn--checked' : ''
} mr-2`}
></div>
Default
</button>
{themes.map((theme) => (
<ThemesMenuButton
item={theme}
application={application}
key={theme.component?.uuid ?? theme.identifier}
onBlur={closeOnBlur}
/>
))}
</DisclosurePanel>
</Disclosure>
{toggleableComponents.map((component) => (
<button
className="sn-dropdown-item justify-between focus:bg-info-backdrop focus:shadow-none"
onClick={() => {
toggleComponent(component)
}}
>
<div className="flex items-center">
<Icon type="window" className="color-neutral mr-2" />
{component.name}
</div>
<Switch checked={component.active} className="px-0" />
</button>
))}
<FocusModeSwitch
application={application}
onToggle={setFocusModeEnabled}
onClose={closeQuickSettingsMenu}
isEnabled={focusModeEnabled}
/>
<div className="h-1px my-2 bg-border"></div>
<button
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none"
onClick={openPreferences}
ref={prefsButtonRef}
return (
<div ref={mainRef} className="sn-component">
<div
className={`sn-quick-settings-menu absolute ${MENU_CLASSNAME} ${
shouldAnimateCloseMenu ? 'slide-up-animation' : 'sn-dropdown--animated'
}`}
ref={quickSettingsMenuRef}
onKeyDown={handleQuickSettingsKeyDown}
>
<div className="px-3 mt-1 mb-2 font-semibold color-text uppercase">Quick Settings</div>
<Disclosure open={themesMenuOpen} onChange={toggleThemesMenu}>
<DisclosureButton
onKeyDown={handleBtnKeyDown}
onBlur={closeOnBlur}
ref={themesButtonRef}
className="sn-dropdown-item justify-between focus:bg-info-backdrop focus:shadow-none"
>
<Icon type="more" className="color-neutral mr-2" />
Open Preferences
<div className="flex items-center">
<Icon type="themes" className="color-neutral mr-2" />
Themes
</div>
<Icon type="chevron-right" className="color-neutral" />
</DisclosureButton>
<DisclosurePanel
onBlur={closeOnBlur}
ref={themesMenuRef}
onKeyDown={handlePanelKeyDown}
style={{
...themesMenuPosition,
}}
className={`${MENU_CLASSNAME} fixed sn-dropdown--animated`}
>
<div className="px-3 my-1 font-semibold color-text uppercase">Themes</div>
<button
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none"
onClick={toggleDefaultTheme}
onBlur={closeOnBlur}
ref={defaultThemeButtonRef}
>
<div className={`pseudo-radio-btn ${defaultThemeOn ? 'pseudo-radio-btn--checked' : ''} mr-2`}></div>
Default
</button>
{themes.map((theme) => (
<ThemesMenuButton
item={theme}
application={application}
key={theme.component?.uuid ?? theme.identifier}
onBlur={closeOnBlur}
/>
))}
</DisclosurePanel>
</Disclosure>
{toggleableComponents.map((component) => (
<button
className="sn-dropdown-item justify-between focus:bg-info-backdrop focus:shadow-none"
onClick={() => {
toggleComponent(component)
}}
>
<div className="flex items-center">
<Icon type="window" className="color-neutral mr-2" />
{component.name}
</div>
<Switch checked={component.active} className="px-0" />
</button>
</div>
))}
<FocusModeSwitch
application={application}
onToggle={setFocusModeEnabled}
onClose={closeQuickSettingsMenu}
isEnabled={focusModeEnabled}
/>
<div className="h-1px my-2 bg-border"></div>
<button
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none"
onClick={openPreferences}
ref={prefsButtonRef}
>
<Icon type="more" className="color-neutral mr-2" />
Open Preferences
</button>
</div>
)
},
)
</div>
)
})

View File

@@ -1,12 +1,5 @@
import { WebApplication } from '@/UIModels/Application'
import {
Action,
ActionVerb,
HistoryEntry,
NoteHistoryEntry,
RevisionListEntry,
SNNote,
} from '@standardnotes/snjs'
import { Action, ActionVerb, HistoryEntry, NoteHistoryEntry, RevisionListEntry, SNNote } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { StateUpdater, useCallback, useState } from 'preact/hooks'
@@ -55,26 +48,19 @@ export const HistoryListContainer: FunctionComponent<Props> = observer(
const fetchLegacyHistory = async () => {
const actionExtensions = application.actionsManager.getExtensions()
actionExtensions.forEach(async (ext) => {
const actionExtension = await application.actionsManager.loadExtensionInContextOfItem(
ext,
note,
)
const actionExtension = await application.actionsManager.loadExtensionInContextOfItem(ext, note)
if (!actionExtension) {
return
}
const isLegacyNoteHistoryExt = actionExtension?.actions.some(
(action) => action.verb === ActionVerb.Nested,
)
const isLegacyNoteHistoryExt = actionExtension?.actions.some((action) => action.verb === ActionVerb.Nested)
if (!isLegacyNoteHistoryExt) {
return
}
const legacyHistoryEntries = actionExtension.actions.filter(
(action) => action.subactions?.[0],
)
const legacyHistoryEntries = actionExtension.actions.filter((action) => action.subactions?.[0])
setLegacyHistory(legacyHistoryEntries)
})
@@ -114,10 +100,7 @@ export const HistoryListContainer: FunctionComponent<Props> = observer(
throw new Error('Could not find revision action url')
}
const response = await application.actionsManager.runAction(
revisionListEntry.subactions[0],
note,
)
const response = await application.actionsManager.runAction(revisionListEntry.subactions[0], note)
if (!response) {
throw new Error('Could not fetch revision')
@@ -131,13 +114,7 @@ export const HistoryListContainer: FunctionComponent<Props> = observer(
setIsFetchingSelectedRevision(false)
}
},
[
application.actionsManager,
note,
setIsFetchingSelectedRevision,
setSelectedRemoteEntry,
setSelectedRevision,
],
[application.actionsManager, note, setIsFetchingSelectedRevision, setSelectedRemoteEntry, setSelectedRevision],
)
const fetchAndSetRemoteRevision = useCallback(
@@ -150,10 +127,7 @@ export const HistoryListContainer: FunctionComponent<Props> = observer(
setSelectedRemoteEntry(undefined)
try {
const remoteRevision = await application.historyManager.fetchRemoteRevision(
note,
revisionListEntry,
)
const remoteRevision = await application.historyManager.fetchRemoteRevision(note, revisionListEntry)
setSelectedRevision(remoteRevision)
setSelectedRemoteEntry(revisionListEntry)
} catch (err) {
@@ -177,17 +151,11 @@ export const HistoryListContainer: FunctionComponent<Props> = observer(
)
return (
<div
className={
'flex flex-col min-w-60 border-0 border-r-1px border-solid border-main overflow-auto h-full'
}
>
<div className={'flex flex-col min-w-60 border-0 border-r-1px border-solid border-main overflow-auto h-full'}>
<div className="flex border-0 border-b-1 border-solid border-main">
<TabButton type={RevisionListTabType.Remote} />
<TabButton type={RevisionListTabType.Session} />
{legacyHistory && legacyHistory.length > 0 && (
<TabButton type={RevisionListTabType.Legacy} />
)}
{legacyHistory && legacyHistory.length > 0 && <TabButton type={RevisionListTabType.Legacy} />}
</div>
<div className={'min-h-0 overflow-auto py-1.5 h-full'}>
{selectedTab === RevisionListTabType.Session && (

View File

@@ -6,23 +6,15 @@ type HistoryListItemProps = {
onClick: () => void
}
export const HistoryListItem: FunctionComponent<HistoryListItemProps> = ({
children,
isSelected,
onClick,
}) => {
export const HistoryListItem: FunctionComponent<HistoryListItemProps> = ({ children, isSelected, onClick }) => {
return (
<button
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
className={`sn-dropdown-item py-2.5 focus:bg-contrast focus:shadow-none ${
isSelected ? 'bg-info-backdrop' : ''
}`}
className={`sn-dropdown-item py-2.5 focus:bg-contrast focus:shadow-none ${isSelected ? 'bg-info-backdrop' : ''}`}
onClick={onClick}
data-selected={isSelected}
>
<div
className={`pseudo-radio-btn ${isSelected ? 'pseudo-radio-btn--checked' : ''} mr-2`}
></div>
<div className={`pseudo-radio-btn ${isSelected ? 'pseudo-radio-btn--checked' : ''} mr-2`}></div>
{children}
</button>
)

View File

@@ -68,9 +68,7 @@ export const LegacyHistoryList: FunctionComponent<Props> = ({
</HistoryListItem>
)
})}
{!legacyHistory?.length && (
<div className="color-grey-0 select-none">No legacy history found</div>
)}
{!legacyHistory?.length && <div className="color-grey-0 select-none">No legacy history found</div>}
</div>
)
}

View File

@@ -43,13 +43,7 @@ export const RemoteHistoryList: FunctionComponent<RemoteHistoryListProps> = obse
if (firstEntry && !selectedEntryUuid.length) {
selectFirstEntry()
}
}, [
fetchAndSetRemoteRevision,
firstEntry,
remoteHistory,
selectFirstEntry,
selectedEntryUuid.length,
])
}, [fetchAndSetRemoteRevision, firstEntry, remoteHistory, selectFirstEntry, selectedEntryUuid.length])
return (
<div
@@ -76,9 +70,7 @@ export const RemoteHistoryList: FunctionComponent<RemoteHistoryListProps> = obse
>
<div className="flex flex-grow items-center justify-between">
<div>{previewHistoryEntryTitle(entry)}</div>
{!application.features.hasMinimumRole(entry.required_role) && (
<Icon type="premium-feature" />
)}
{!application.features.hasMinimumRole(entry.required_role) && <Icon type="premium-feature" />}
</div>
</HistoryListItem>
))}

View File

@@ -22,8 +22,7 @@ const getPremiumContentCopy = (planName: string | undefined) => {
export const RevisionContentLocked: FunctionComponent<{
appState: AppState
}> = observer(({ appState }) => {
const { userSubscriptionName, isUserSubscriptionExpired, isUserSubscriptionCanceled } =
appState.subscription
const { userSubscriptionName, isUserSubscriptionExpired, isUserSubscriptionCanceled } = appState.subscription
return (
<div className="flex w-full h-full items-center justify-center">

View File

@@ -46,13 +46,9 @@ const RevisionContentPlaceholder: FunctionComponent<RevisionContentPlaceholderPr
: '-z-index-1'
}`}
>
{isFetchingSelectedRevision && (
<div className={`sk-spinner w-5 h-5 spinner-info ${ABSOLUTE_CENTER_CLASSNAME}`} />
)}
{isFetchingSelectedRevision && <div className={`sk-spinner w-5 h-5 spinner-info ${ABSOLUTE_CENTER_CLASSNAME}`} />}
{!isFetchingSelectedRevision && !selectedRevision ? (
<div className={`color-grey-0 select-none ${ABSOLUTE_CENTER_CLASSNAME}`}>
No revision selected
</div>
<div className={`color-grey-0 select-none ${ABSOLUTE_CENTER_CLASSNAME}`}>No revision selected</div>
) : null}
</div>
)
@@ -87,8 +83,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
try {
const initialRemoteHistory = await application.historyManager.remoteHistoryForItem(note)
const remoteHistoryAsGroups =
sortRevisionListIntoGroups<RevisionListEntry>(initialRemoteHistory)
const remoteHistoryAsGroups = sortRevisionListIntoGroups<RevisionListEntry>(initialRemoteHistory)
setRemoteHistory(remoteHistoryAsGroups)
} catch (err) {
@@ -240,9 +235,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
isFetchingSelectedRevision={isFetchingSelectedRevision}
showContentLockedScreen={showContentLockedScreen}
/>
{showContentLockedScreen && !selectedRevision && (
<RevisionContentLocked appState={appState} />
)}
{showContentLockedScreen && !selectedRevision && <RevisionContentLocked appState={appState} />}
{selectedRevision && templateNoteForRevision && (
<SelectedRevisionContent
application={application}
@@ -267,11 +260,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
{selectedRevision && (
<div class="flex items-center">
{selectedRemoteEntry && (
<Button
className="py-1.35 mr-2.5"
onClick={deleteSelectedRevision}
variant="normal"
>
<Button className="py-1.35 mr-2.5" onClick={deleteSelectedRevision} variant="normal">
{isDeletingRevision ? (
<div className="sk-spinner my-1 w-3 h-3 spinner-info" />
) : (
@@ -285,12 +274,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
onClick={restoreAsCopy}
variant="normal"
/>
<Button
className="py-1.35"
label="Restore version"
onClick={restore}
variant="primary"
/>
<Button className="py-1.35" label="Restore version" onClick={restore} variant="primary" />
</div>
)}
</div>

View File

@@ -24,8 +24,7 @@ export const SelectedRevisionContent: FunctionComponent<SelectedRevisionContentP
return undefined
}
const componentViewer =
application.componentManager.createComponentViewer(editorForCurrentNote)
const componentViewer = application.componentManager.createComponentViewer(editorForCurrentNote)
componentViewer.setReadonly(true)
componentViewer.lockReadonly = true
componentViewer.overrideContextItem = templateNoteForRevision

View File

@@ -75,9 +75,7 @@ export const SessionHistoryList: FunctionComponent<Props> = ({
</Fragment>
) : null),
)}
{!sessionHistoryLength && (
<div className="color-grey-0 select-none">No session history found</div>
)}
{!sessionHistoryLength && <div className="color-grey-0 select-none">No session history found</div>}
</div>
)
}

View File

@@ -25,9 +25,7 @@ export const formatDateAsMonthYearString = (date: Date) =>
export const getGroupIndexForEntry = (entry: RevisionEntry, groups: ListGroup<RevisionEntry>[]) => {
const todayAsDate = new Date()
const entryDate = new Date(
(entry as RevisionListEntry).created_at ?? (entry as NoteHistoryEntry).payload.created_at,
)
const entryDate = new Date((entry as RevisionListEntry).created_at ?? (entry as NoteHistoryEntry).payload.created_at)
const differenceBetweenDatesInDays = calculateDifferenceBetweenDatesInDays(todayAsDate, entryDate)
@@ -52,9 +50,7 @@ const GROUP_TITLE_TODAY = 'Today'
const GROUP_TITLE_WEEK = 'This Week'
const GROUP_TITLE_YEAR = 'More Than A Year Ago'
export const sortRevisionListIntoGroups = <EntryType extends RevisionEntry>(
revisionList: EntryType[] | undefined,
) => {
export const sortRevisionListIntoGroups = <EntryType extends RevisionEntry>(revisionList: EntryType[] | undefined) => {
const sortedGroups: ListGroup<EntryType>[] = [
{
title: GROUP_TITLE_TODAY,
@@ -82,10 +78,7 @@ export const sortRevisionListIntoGroups = <EntryType extends RevisionEntry>(
} else {
addBeforeLastGroup({
title: formatDateAsMonthYearString(
new Date(
(entry as RevisionListEntry).created_at ??
(entry as NoteHistoryEntry).payload.created_at,
),
new Date((entry as RevisionListEntry).created_at ?? (entry as NoteHistoryEntry).payload.created_at),
),
entries: [entry],
})

View File

@@ -18,28 +18,16 @@ export const SearchOptions = observer(({ appState }: Props) => {
}
return (
<div
role="tablist"
className="search-options justify-center"
onMouseDown={(e) => e.preventDefault()}
>
<div role="tablist" className="search-options justify-center" onMouseDown={(e) => e.preventDefault()}>
<Bubble
label="Protected Contents"
selected={includeProtectedContents}
onSelect={toggleIncludeProtectedContents}
/>
<Bubble
label="Archived"
selected={includeArchived}
onSelect={searchOptions.toggleIncludeArchived}
/>
<Bubble label="Archived" selected={includeArchived} onSelect={searchOptions.toggleIncludeArchived} />
<Bubble
label="Trashed"
selected={includeTrashed}
onSelect={searchOptions.toggleIncludeTrashed}
/>
<Bubble label="Trashed" selected={includeTrashed} onSelect={searchOptions.toggleIncludeTrashed} />
</div>
)
})

View File

@@ -1,11 +1,5 @@
import { AppState } from '@/UIModels/AppState'
import {
SNApplication,
SessionStrings,
UuidString,
isNullOrUndefined,
RemoteSession,
} from '@standardnotes/snjs'
import { SNApplication, SessionStrings, UuidString, isNullOrUndefined, RemoteSession } from '@standardnotes/snjs'
import { FunctionComponent } from 'preact'
import { useState, useEffect, useRef, useMemo } from 'preact/hooks'
import { Dialog } from '@reach/dialog'

View File

@@ -28,11 +28,7 @@ export const AccordionItem: FunctionalComponent<Props> = ({ title, className = '
data-is-expanded={isExpanded}
/>
</div>
<div
className={'accordion-contents-container cursor-auto'}
data-is-expanded={isExpanded}
ref={elementRef}
>
<div className={'accordion-contents-container cursor-auto'} data-is-expanded={isExpanded} ref={elementRef}>
{children}
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More