chore: prettier files
This commit is contained in:
@@ -8,10 +8,7 @@ import { findDOMNode, unmountComponentAtNode } from 'preact/compat'
|
|||||||
export type PureComponentState = Partial<Record<string, any>>
|
export type PureComponentState = Partial<Record<string, any>>
|
||||||
export type PureComponentProps = Partial<Record<string, any>>
|
export type PureComponentProps = Partial<Record<string, any>>
|
||||||
|
|
||||||
export abstract class PureComponent<
|
export abstract class PureComponent<P = PureComponentProps, S = PureComponentState> extends Component<P, S> {
|
||||||
P = PureComponentProps,
|
|
||||||
S = PureComponentState,
|
|
||||||
> extends Component<P, S> {
|
|
||||||
private unsubApp!: () => void
|
private unsubApp!: () => void
|
||||||
private unsubState!: () => void
|
private unsubState!: () => void
|
||||||
private reactionDisposers: IReactionDisposer[] = []
|
private reactionDisposers: IReactionDisposer[] = []
|
||||||
|
|||||||
@@ -107,10 +107,8 @@ export const ConfirmPassword: FunctionComponent<Props> = observer(
|
|||||||
</div>
|
</div>
|
||||||
<div className="px-3 mb-3 text-sm">
|
<div className="px-3 mb-3 text-sm">
|
||||||
Because your notes are encrypted using your password,{' '}
|
Because your notes are encrypted using your password,{' '}
|
||||||
<span className="color-dark-red">
|
<span className="color-dark-red">Standard Notes does not have a password reset option</span>. If you forget
|
||||||
Standard Notes does not have a password reset option
|
your password, you will permanently lose access to your data.
|
||||||
</span>
|
|
||||||
. If you forget your password, you will permanently lose access to your data.
|
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleConfirmFormSubmit} className="px-3 mb-1">
|
<form onSubmit={handleConfirmFormSubmit} className="px-3 mb-1">
|
||||||
<DecoratedPasswordInput
|
<DecoratedPasswordInput
|
||||||
|
|||||||
@@ -111,19 +111,10 @@ export const CreateAccount: FunctionComponent<Props> = observer(
|
|||||||
ref={passwordInputRef}
|
ref={passwordInputRef}
|
||||||
value={password}
|
value={password}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button className="btn-w-full mt-1" label="Next" variant="primary" onClick={handleRegisterFormSubmit} />
|
||||||
className="btn-w-full mt-1"
|
|
||||||
label="Next"
|
|
||||||
variant="primary"
|
|
||||||
onClick={handleRegisterFormSubmit}
|
|
||||||
/>
|
|
||||||
</form>
|
</form>
|
||||||
<div className="h-1px my-2 bg-border"></div>
|
<div className="h-1px my-2 bg-border"></div>
|
||||||
<AdvancedOptions
|
<AdvancedOptions application={application} appState={appState} onVaultChange={onVaultChange} />
|
||||||
application={application}
|
|
||||||
appState={appState}
|
|
||||||
onVaultChange={onVaultChange}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ const iconClassName = 'color-neutral mr-2'
|
|||||||
export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
||||||
({ application, appState, setMenuPane, closeMenu, mainApplicationGroup }) => {
|
({ application, appState, setMenuPane, closeMenu, mainApplicationGroup }) => {
|
||||||
const [isSyncingInProgress, setIsSyncingInProgress] = useState(false)
|
const [isSyncingInProgress, setIsSyncingInProgress] = useState(false)
|
||||||
const [lastSyncDate, setLastSyncDate] = useState(
|
const [lastSyncDate, setLastSyncDate] = useState(formatLastSyncDate(application.sync.getLastSyncDate() as Date))
|
||||||
formatLastSyncDate(application.sync.getLastSyncDate() as Date),
|
|
||||||
)
|
|
||||||
|
|
||||||
const doSynchronization = async () => {
|
const doSynchronization = async () => {
|
||||||
setIsSyncingInProgress(true)
|
setIsSyncingInProgress(true)
|
||||||
@@ -97,8 +95,8 @@ export const GeneralAccountMenu: FunctionComponent<Props> = observer(
|
|||||||
<>
|
<>
|
||||||
<div className="px-3 mb-1">
|
<div className="px-3 mb-1">
|
||||||
<div className="mb-3 color-foreground">
|
<div className="mb-3 color-foreground">
|
||||||
You’re offline. Sign in to sync your notes and preferences across all your devices
|
You’re offline. Sign in to sync your notes and preferences across all your devices and enable end-to-end
|
||||||
and enable end-to-end encryption.
|
encryption.
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center color-grey-1">
|
<div className="flex items-center color-grey-1">
|
||||||
<Icon type="cloud-off" className="mr-2" />
|
<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}
|
initialFocus={!application.hasAccount() ? CREATE_ACCOUNT_INDEX : SWITCHER_INDEX}
|
||||||
>
|
>
|
||||||
<MenuItemSeparator />
|
<MenuItemSeparator />
|
||||||
<WorkspaceSwitcherOption
|
<WorkspaceSwitcherOption mainApplicationGroup={mainApplicationGroup} appState={appState} />
|
||||||
mainApplicationGroup={mainApplicationGroup}
|
|
||||||
appState={appState}
|
|
||||||
/>
|
|
||||||
<MenuItemSeparator />
|
<MenuItemSeparator />
|
||||||
{user ? (
|
{user ? (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|||||||
@@ -19,188 +19,186 @@ type Props = {
|
|||||||
setMenuPane: (pane: AccountMenuPane) => void
|
setMenuPane: (pane: AccountMenuPane) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SignInPane: FunctionComponent<Props> = observer(
|
export const SignInPane: FunctionComponent<Props> = observer(({ application, appState, setMenuPane }) => {
|
||||||
({ application, appState, setMenuPane }) => {
|
const { notesAndTagsCount } = appState.accountMenu
|
||||||
const { notesAndTagsCount } = appState.accountMenu
|
const [email, setEmail] = useState('')
|
||||||
const [email, setEmail] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
const [error, setError] = useState('')
|
||||||
const [error, setError] = useState('')
|
const [isEphemeral, setIsEphemeral] = useState(false)
|
||||||
const [isEphemeral, setIsEphemeral] = useState(false)
|
|
||||||
|
|
||||||
const [isStrictSignin, setIsStrictSignin] = useState(false)
|
const [isStrictSignin, setIsStrictSignin] = useState(false)
|
||||||
const [isSigningIn, setIsSigningIn] = useState(false)
|
const [isSigningIn, setIsSigningIn] = useState(false)
|
||||||
const [shouldMergeLocal, setShouldMergeLocal] = useState(true)
|
const [shouldMergeLocal, setShouldMergeLocal] = useState(true)
|
||||||
const [isVault, setIsVault] = useState(false)
|
const [isVault, setIsVault] = useState(false)
|
||||||
|
|
||||||
const emailInputRef = useRef<HTMLInputElement>(null)
|
const emailInputRef = useRef<HTMLInputElement>(null)
|
||||||
const passwordInputRef = useRef<HTMLInputElement>(null)
|
const passwordInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (emailInputRef?.current) {
|
if (emailInputRef?.current) {
|
||||||
emailInputRef.current?.focus()
|
emailInputRef.current?.focus()
|
||||||
}
|
|
||||||
if (isDev && window.devAccountEmail) {
|
|
||||||
setEmail(window.devAccountEmail)
|
|
||||||
setPassword(window.devAccountPassword as string)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const resetInvalid = () => {
|
|
||||||
if (error.length) {
|
|
||||||
setError('')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (isDev && window.devAccountEmail) {
|
||||||
const handleEmailChange = (text: string) => {
|
setEmail(window.devAccountEmail)
|
||||||
setEmail(text)
|
setPassword(window.devAccountPassword as string)
|
||||||
}
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handlePasswordChange = (text: string) => {
|
const resetInvalid = () => {
|
||||||
if (error.length) {
|
if (error.length) {
|
||||||
setError('')
|
setError('')
|
||||||
}
|
|
||||||
setPassword(text)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleEphemeralChange = () => {
|
const handleEmailChange = (text: string) => {
|
||||||
setIsEphemeral(!isEphemeral)
|
setEmail(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePasswordChange = (text: string) => {
|
||||||
|
if (error.length) {
|
||||||
|
setError('')
|
||||||
}
|
}
|
||||||
|
setPassword(text)
|
||||||
|
}
|
||||||
|
|
||||||
const handleStrictSigninChange = () => {
|
const handleEphemeralChange = () => {
|
||||||
setIsStrictSignin(!isStrictSignin)
|
setIsEphemeral(!isEphemeral)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleShouldMergeChange = () => {
|
const handleStrictSigninChange = () => {
|
||||||
setShouldMergeLocal(!shouldMergeLocal)
|
setIsStrictSignin(!isStrictSignin)
|
||||||
}
|
}
|
||||||
|
|
||||||
const signIn = () => {
|
const handleShouldMergeChange = () => {
|
||||||
setIsSigningIn(true)
|
setShouldMergeLocal(!shouldMergeLocal)
|
||||||
emailInputRef?.current?.blur()
|
}
|
||||||
passwordInputRef?.current?.blur()
|
|
||||||
|
|
||||||
application
|
const signIn = () => {
|
||||||
.signIn(email, password, isStrictSignin, isEphemeral, shouldMergeLocal)
|
setIsSigningIn(true)
|
||||||
.then((res) => {
|
emailInputRef?.current?.blur()
|
||||||
if (res.error) {
|
passwordInputRef?.current?.blur()
|
||||||
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 handleKeyDown = (e: KeyboardEvent) => {
|
application
|
||||||
if (e.key === 'Enter') {
|
.signIn(email, password, isStrictSignin, isEphemeral, shouldMergeLocal)
|
||||||
handleSignInFormSubmit(e)
|
.then((res) => {
|
||||||
}
|
if (res.error) {
|
||||||
}
|
throw new Error(res.error.message)
|
||||||
|
|
||||||
const onVaultChange = useCallback(
|
|
||||||
(newIsVault: boolean, vaultedEmail?: string) => {
|
|
||||||
setIsVault(newIsVault)
|
|
||||||
if (newIsVault && vaultedEmail) {
|
|
||||||
setEmail(vaultedEmail)
|
|
||||||
}
|
}
|
||||||
},
|
appState.accountMenu.closeAccountMenu()
|
||||||
[setEmail],
|
})
|
||||||
)
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
setError(err.message ?? err.toString())
|
||||||
|
setPassword('')
|
||||||
|
passwordInputRef?.current?.blur()
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsSigningIn(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handleSignInFormSubmit = (e: Event) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
e.preventDefault()
|
if (e.key === 'Enter') {
|
||||||
|
handleSignInFormSubmit(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!email || email.length === 0) {
|
const onVaultChange = useCallback(
|
||||||
emailInputRef?.current?.focus()
|
(newIsVault: boolean, vaultedEmail?: string) => {
|
||||||
return
|
setIsVault(newIsVault)
|
||||||
|
if (newIsVault && vaultedEmail) {
|
||||||
|
setEmail(vaultedEmail)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[setEmail],
|
||||||
|
)
|
||||||
|
|
||||||
if (!password || password.length === 0) {
|
const handleSignInFormSubmit = (e: Event) => {
|
||||||
passwordInputRef?.current?.focus()
|
e.preventDefault()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
signIn()
|
if (!email || email.length === 0) {
|
||||||
|
emailInputRef?.current?.focus()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (!password || password.length === 0) {
|
||||||
<>
|
passwordInputRef?.current?.focus()
|
||||||
<div className="flex items-center px-3 mt-1 mb-3">
|
return
|
||||||
<IconButton
|
}
|
||||||
icon="arrow-left"
|
|
||||||
title="Go back"
|
signIn()
|
||||||
className="flex mr-2 color-neutral p-0"
|
}
|
||||||
onClick={() => setMenuPane(AccountMenuPane.GeneralMenu)}
|
|
||||||
focusable={true}
|
return (
|
||||||
disabled={isSigningIn}
|
<>
|
||||||
/>
|
<div className="flex items-center px-3 mt-1 mb-3">
|
||||||
<div className="sn-account-menu-headline">Sign in</div>
|
<IconButton
|
||||||
</div>
|
icon="arrow-left"
|
||||||
<div className="px-3 mb-1">
|
title="Go back"
|
||||||
<DecoratedInput
|
className="flex mr-2 color-neutral p-0"
|
||||||
className={`mb-2 ${error ? 'border-dark-red' : null}`}
|
onClick={() => setMenuPane(AccountMenuPane.GeneralMenu)}
|
||||||
left={[<Icon type="email" className="color-neutral" />]}
|
focusable={true}
|
||||||
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}
|
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}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|||||||
@@ -13,65 +13,55 @@ type Props = {
|
|||||||
appState: AppState
|
appState: AppState
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WorkspaceSwitcherOption: FunctionComponent<Props> = observer(
|
export const WorkspaceSwitcherOption: FunctionComponent<Props> = observer(({ mainApplicationGroup, appState }) => {
|
||||||
({ mainApplicationGroup, appState }) => {
|
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
const menuRef = useRef<HTMLDivElement>(null)
|
||||||
const menuRef = useRef<HTMLDivElement>(null)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>()
|
||||||
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>()
|
|
||||||
|
|
||||||
const toggleMenu = () => {
|
const toggleMenu = () => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
const menuPosition = calculateSubmenuStyle(buttonRef.current)
|
const menuPosition = calculateSubmenuStyle(buttonRef.current)
|
||||||
if (menuPosition) {
|
if (menuPosition) {
|
||||||
setMenuStyle(menuPosition)
|
setMenuStyle(menuPosition)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsOpen(!isOpen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
setIsOpen(!isOpen)
|
||||||
if (isOpen) {
|
}
|
||||||
setTimeout(() => {
|
|
||||||
const newMenuPosition = calculateSubmenuStyle(buttonRef.current, menuRef.current)
|
|
||||||
|
|
||||||
if (newMenuPosition) {
|
useEffect(() => {
|
||||||
setMenuStyle(newMenuPosition)
|
if (isOpen) {
|
||||||
}
|
setTimeout(() => {
|
||||||
})
|
const newMenuPosition = calculateSubmenuStyle(buttonRef.current, menuRef.current)
|
||||||
}
|
|
||||||
}, [isOpen])
|
|
||||||
|
|
||||||
return (
|
if (newMenuPosition) {
|
||||||
<>
|
setMenuStyle(newMenuPosition)
|
||||||
<button
|
}
|
||||||
ref={buttonRef}
|
})
|
||||||
className="sn-dropdown-item justify-between focus:bg-info-backdrop focus:shadow-none"
|
}
|
||||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
}, [isOpen])
|
||||||
role="menuitem"
|
|
||||||
onClick={toggleMenu}
|
return (
|
||||||
>
|
<>
|
||||||
<div className="flex items-center">
|
<button
|
||||||
<Icon type="user-switch" className="color-neutral mr-2" />
|
ref={buttonRef}
|
||||||
Switch workspace
|
className="sn-dropdown-item justify-between focus:bg-info-backdrop focus:shadow-none"
|
||||||
</div>
|
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||||
<Icon type="chevron-right" className="color-neutral" />
|
role="menuitem"
|
||||||
</button>
|
onClick={toggleMenu}
|
||||||
{isOpen && (
|
>
|
||||||
<div
|
<div className="flex items-center">
|
||||||
ref={menuRef}
|
<Icon type="user-switch" className="color-neutral mr-2" />
|
||||||
className="sn-dropdown max-h-120 min-w-68 py-2 fixed overflow-y-auto"
|
Switch workspace
|
||||||
style={menuStyle}
|
</div>
|
||||||
>
|
<Icon type="chevron-right" className="color-neutral" />
|
||||||
<WorkspaceSwitcherMenu
|
</button>
|
||||||
mainApplicationGroup={mainApplicationGroup}
|
{isOpen && (
|
||||||
appState={appState}
|
<div ref={menuRef} className="sn-dropdown max-h-120 min-w-68 py-2 fixed overflow-y-auto" style={menuStyle}>
|
||||||
isOpen={isOpen}
|
<WorkspaceSwitcherMenu mainApplicationGroup={mainApplicationGroup} appState={appState} isOpen={isOpen} />
|
||||||
/>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</>
|
||||||
</>
|
)
|
||||||
)
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -51,9 +51,7 @@ const MenuPaneSelector: FunctionComponent<PaneSelectorProps> = observer(
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case AccountMenuPane.SignIn:
|
case AccountMenuPane.SignIn:
|
||||||
return (
|
return <SignInPane appState={appState} application={application} setMenuPane={setMenuPane} />
|
||||||
<SignInPane appState={appState} application={application} setMenuPane={setMenuPane} />
|
|
||||||
)
|
|
||||||
case AccountMenuPane.Register:
|
case AccountMenuPane.Register:
|
||||||
return (
|
return (
|
||||||
<CreateAccount
|
<CreateAccount
|
||||||
@@ -82,8 +80,7 @@ const MenuPaneSelector: FunctionComponent<PaneSelectorProps> = observer(
|
|||||||
|
|
||||||
export const AccountMenu: FunctionComponent<Props> = observer(
|
export const AccountMenu: FunctionComponent<Props> = observer(
|
||||||
({ application, appState, onClickOutside, mainApplicationGroup }) => {
|
({ application, appState, onClickOutside, mainApplicationGroup }) => {
|
||||||
const { currentPane, setCurrentPane, shouldAnimateCloseMenu, closeAccountMenu } =
|
const { currentPane, setCurrentPane, shouldAnimateCloseMenu, closeAccountMenu } = appState.accountMenu
|
||||||
appState.accountMenu
|
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
useCloseOnClickOutside(ref, () => {
|
useCloseOnClickOutside(ref, () => {
|
||||||
|
|||||||
@@ -21,12 +21,7 @@ interface Props {
|
|||||||
* IconButton component with an icon
|
* IconButton component with an icon
|
||||||
* preventDefault is already handled within the component
|
* preventDefault is already handled within the component
|
||||||
*/
|
*/
|
||||||
export const RoundIconButton: FunctionComponent<Props> = ({
|
export const RoundIconButton: FunctionComponent<Props> = ({ onClick, type, className, icon: iconType }) => {
|
||||||
onClick,
|
|
||||||
type,
|
|
||||||
className,
|
|
||||||
icon: iconType,
|
|
||||||
}) => {
|
|
||||||
const click = (e: MouseEvent) => {
|
const click = (e: MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
onClick()
|
onClick()
|
||||||
|
|||||||
@@ -34,10 +34,7 @@ type Props = {
|
|||||||
onDismiss: (challenge: Challenge) => Promise<void>
|
onDismiss: (challenge: Challenge) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateValues = (
|
const validateValues = (values: ChallengeModalValues, prompts: ChallengePrompt[]): ChallengeModalValues | undefined => {
|
||||||
values: ChallengeModalValues,
|
|
||||||
prompts: ChallengePrompt[],
|
|
||||||
): ChallengeModalValues | undefined => {
|
|
||||||
let hasInvalidValues = false
|
let hasInvalidValues = false
|
||||||
const validatedValues = { ...values }
|
const validatedValues = { ...values }
|
||||||
for (const prompt of prompts) {
|
for (const prompt of prompts) {
|
||||||
@@ -75,10 +72,9 @@ export const ChallengeModal: FunctionComponent<Props> = ({
|
|||||||
const [isProcessing, setIsProcessing] = useState(false)
|
const [isProcessing, setIsProcessing] = useState(false)
|
||||||
const [, setProcessingPrompts] = useState<ChallengePrompt[]>([])
|
const [, setProcessingPrompts] = useState<ChallengePrompt[]>([])
|
||||||
const [bypassModalFocusLock, setBypassModalFocusLock] = useState(false)
|
const [bypassModalFocusLock, setBypassModalFocusLock] = useState(false)
|
||||||
const shouldShowForgotPasscode = [
|
const shouldShowForgotPasscode = [ChallengeReason.ApplicationUnlock, ChallengeReason.Migration].includes(
|
||||||
ChallengeReason.ApplicationUnlock,
|
challenge.reason,
|
||||||
ChallengeReason.Migration,
|
)
|
||||||
].includes(challenge.reason)
|
|
||||||
const shouldShowWorkspaceSwitcher = challenge.reason === ChallengeReason.ApplicationUnlock
|
const shouldShowWorkspaceSwitcher = challenge.reason === ChallengeReason.ApplicationUnlock
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
@@ -268,10 +264,7 @@ export const ChallengeModal: FunctionComponent<Props> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{shouldShowWorkspaceSwitcher && (
|
{shouldShowWorkspaceSwitcher && (
|
||||||
<LockscreenWorkspaceSwitcher
|
<LockscreenWorkspaceSwitcher mainApplicationGroup={mainApplicationGroup} appState={appState} />
|
||||||
mainApplicationGroup={mainApplicationGroup}
|
|
||||||
appState={appState}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</DialogOverlay>
|
</DialogOverlay>
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import { ChallengePrompt, ChallengeValidation, ProtectionSessionDurations } from '@standardnotes/snjs'
|
||||||
ChallengePrompt,
|
|
||||||
ChallengeValidation,
|
|
||||||
ProtectionSessionDurations,
|
|
||||||
} from '@standardnotes/snjs'
|
|
||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { useEffect, useRef } from 'preact/hooks'
|
import { useEffect, useRef } from 'preact/hooks'
|
||||||
import { DecoratedInput } from '@/Components/Input/DecoratedInput'
|
import { DecoratedInput } from '@/Components/Input/DecoratedInput'
|
||||||
@@ -17,13 +13,7 @@ type Props = {
|
|||||||
isInvalid: boolean
|
isInvalid: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChallengeModalPrompt: FunctionComponent<Props> = ({
|
export const ChallengeModalPrompt: FunctionComponent<Props> = ({ prompt, values, index, onValueChange, isInvalid }) => {
|
||||||
prompt,
|
|
||||||
values,
|
|
||||||
index,
|
|
||||||
onValueChange,
|
|
||||||
isInvalid,
|
|
||||||
}) => {
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -49,9 +39,7 @@ export const ChallengeModalPrompt: FunctionComponent<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
className={`cursor-pointer px-2 py-1.5 rounded ${
|
className={`cursor-pointer px-2 py-1.5 rounded ${
|
||||||
selected
|
selected ? 'bg-default color-foreground font-semibold' : 'color-grey-0 hover:bg-grey-3'
|
||||||
? 'bg-default color-foreground font-semibold'
|
|
||||||
: 'color-grey-0 hover:bg-grey-3'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -88,9 +76,7 @@ export const ChallengeModalPrompt: FunctionComponent<Props> = ({
|
|||||||
onChange={(value) => onValueChange(value, prompt)}
|
onChange={(value) => onValueChange(value, prompt)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isInvalid && (
|
{isInvalid && <div className="text-sm color-danger mt-2">Invalid authentication, please try again.</div>}
|
||||||
<div className="text-sm color-danger mt-2">Invalid authentication, please try again.</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ type Props = {
|
|||||||
appState: AppState
|
appState: AppState
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LockscreenWorkspaceSwitcher: FunctionComponent<Props> = ({
|
export const LockscreenWorkspaceSwitcher: FunctionComponent<Props> = ({ mainApplicationGroup, appState }) => {
|
||||||
mainApplicationGroup,
|
|
||||||
appState,
|
|
||||||
}) => {
|
|
||||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||||
const menuRef = useRef<HTMLDivElement>(null)
|
const menuRef = useRef<HTMLDivElement>(null)
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
@@ -51,20 +48,12 @@ export const LockscreenWorkspaceSwitcher: FunctionComponent<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
<Button
|
<Button ref={buttonRef} onClick={toggleMenu} className="flex items-center justify-center min-w-76 mt-2">
|
||||||
ref={buttonRef}
|
|
||||||
onClick={toggleMenu}
|
|
||||||
className="flex items-center justify-center min-w-76 mt-2"
|
|
||||||
>
|
|
||||||
<Icon type="user-switch" className="color-neutral mr-2" />
|
<Icon type="user-switch" className="color-neutral mr-2" />
|
||||||
Switch workspace
|
Switch workspace
|
||||||
</Button>
|
</Button>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div
|
<div ref={menuRef} className="sn-dropdown max-h-120 min-w-68 py-2 fixed overflow-y-auto" style={menuStyle}>
|
||||||
ref={menuRef}
|
|
||||||
className="sn-dropdown max-h-120 min-w-68 py-2 fixed overflow-y-auto"
|
|
||||||
style={menuStyle}
|
|
||||||
>
|
|
||||||
<WorkspaceSwitcherMenu
|
<WorkspaceSwitcherMenu
|
||||||
mainApplicationGroup={mainApplicationGroup}
|
mainApplicationGroup={mainApplicationGroup}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
|
|||||||
@@ -8,13 +8,7 @@ type CheckboxProps = {
|
|||||||
label: string
|
label: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Checkbox: FunctionComponent<CheckboxProps> = ({
|
export const Checkbox: FunctionComponent<CheckboxProps> = ({ name, checked, onChange, disabled, label }) => {
|
||||||
name,
|
|
||||||
checked,
|
|
||||||
onChange,
|
|
||||||
disabled,
|
|
||||||
label,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<label htmlFor={name} className="flex items-center fit-content mb-2">
|
<label htmlFor={name} className="flex items-center fit-content mb-2">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -5,18 +5,13 @@ interface IProps {
|
|||||||
dismissDeprecationMessage: () => void
|
dismissDeprecationMessage: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IsDeprecated: FunctionalComponent<IProps> = ({
|
export const IsDeprecated: FunctionalComponent<IProps> = ({ deprecationMessage, dismissDeprecationMessage }) => {
|
||||||
deprecationMessage,
|
|
||||||
dismissDeprecationMessage,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<div className={'sn-component'}>
|
<div className={'sn-component'}>
|
||||||
<div className={'sk-app-bar no-edges no-top-edge dynamic-height'}>
|
<div className={'sk-app-bar no-edges no-top-edge dynamic-height'}>
|
||||||
<div className={'left'}>
|
<div className={'left'}>
|
||||||
<div className={'sk-app-bar-item'}>
|
<div className={'sk-app-bar-item'}>
|
||||||
<div className={'sk-label warning'}>
|
<div className={'sk-label warning'}>{deprecationMessage || 'This extension is deprecated.'}</div>
|
||||||
{deprecationMessage || 'This extension is deprecated.'}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={'right'}>
|
<div className={'right'}>
|
||||||
|
|||||||
@@ -7,21 +7,17 @@ export const OfflineRestricted: FunctionalComponent = () => {
|
|||||||
<div className={'sk-panel-content'}>
|
<div className={'sk-panel-content'}>
|
||||||
<div className={'sk-panel-section stretch'}>
|
<div className={'sk-panel-section stretch'}>
|
||||||
<div className={'sk-panel-column'} />
|
<div className={'sk-panel-column'} />
|
||||||
<div className={'sk-h1 sk-bold'}>
|
<div className={'sk-h1 sk-bold'}>You have restricted this component to not use a hosted version.</div>
|
||||||
You have restricted this component to not use a hosted version.
|
<div className={'sk-subtitle'}>Locally-installed components are not available in the web application.</div>
|
||||||
</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-row'}>
|
<div className={'sk-panel-row'}>
|
||||||
<div className={'sk-panel-column'}>
|
<div className={'sk-panel-column'}>
|
||||||
<div className={'sk-p'}>To continue, choose from the following options:</div>
|
<div className={'sk-p'}>To continue, choose from the following options:</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className={'sk-p'}>
|
<li className={'sk-p'}>
|
||||||
Enable the Hosted option for this component by opening the Preferences {'>'}{' '}
|
Enable the Hosted option for this component by opening the Preferences {'>'} General {'>'} Advanced
|
||||||
General {'>'} Advanced Settings menu and toggling 'Use hosted when local is
|
Settings menu and toggling 'Use hosted when local is unavailable' under this component's options.
|
||||||
unavailable' under this component's options. Then press Reload.
|
Then press Reload.
|
||||||
</li>
|
</li>
|
||||||
<li className={'sk-p'}>Use the desktop application.</li>
|
<li className={'sk-p'}>Use the desktop application.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -10,13 +10,8 @@ export const UrlMissing: FunctionalComponent<IProps> = ({ componentName }) => {
|
|||||||
<div className={'sk-panel static'}>
|
<div className={'sk-panel static'}>
|
||||||
<div className={'sk-panel-content'}>
|
<div className={'sk-panel-content'}>
|
||||||
<div className={'sk-panel-section stretch'}>
|
<div className={'sk-panel-section stretch'}>
|
||||||
<div className={'sk-panel-section-title'}>
|
<div className={'sk-panel-section-title'}>This extension is missing its URL property.</div>
|
||||||
This extension is missing its URL property.
|
<p>In order to access your note immediately, please switch from {componentName} to the Plain Editor.</p>
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
In order to access your note immediately, please switch from {componentName} to the
|
|
||||||
Plain Editor.
|
|
||||||
</p>
|
|
||||||
<br />
|
<br />
|
||||||
<p>Please contact help@standardnotes.com to remedy this issue.</p>
|
<p>Please contact help@standardnotes.com to remedy this issue.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,9 +43,7 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
|
|||||||
|
|
||||||
const [hasIssueLoading, setHasIssueLoading] = useState(false)
|
const [hasIssueLoading, setHasIssueLoading] = useState(false)
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [featureStatus, setFeatureStatus] = useState<FeatureStatus>(
|
const [featureStatus, setFeatureStatus] = useState<FeatureStatus>(componentViewer.getFeatureStatus())
|
||||||
componentViewer.getFeatureStatus(),
|
|
||||||
)
|
|
||||||
const [isComponentValid, setIsComponentValid] = useState(true)
|
const [isComponentValid, setIsComponentValid] = useState(true)
|
||||||
const [error, setError] = useState<ComponentViewerError | undefined>(undefined)
|
const [error, setError] = useState<ComponentViewerError | undefined>(undefined)
|
||||||
const [deprecationMessage, setDeprecationMessage] = useState<string | undefined>(undefined)
|
const [deprecationMessage, setDeprecationMessage] = useState<string | undefined>(undefined)
|
||||||
@@ -199,10 +197,7 @@ export const ComponentView: FunctionalComponent<IProps> = observer(
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{deprecationMessage && !isDeprecationMessageDismissed && (
|
{deprecationMessage && !isDeprecationMessageDismissed && (
|
||||||
<IsDeprecated
|
<IsDeprecated deprecationMessage={deprecationMessage} dismissDeprecationMessage={dismissDeprecationMessage} />
|
||||||
deprecationMessage={deprecationMessage}
|
|
||||||
dismissDeprecationMessage={dismissDeprecationMessage}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{error === ComponentViewerError.OfflineRestricted && <OfflineRestricted />}
|
{error === ComponentViewerError.OfflineRestricted && <OfflineRestricted />}
|
||||||
{error === ComponentViewerError.MissingUrl && <UrlMissing componentName={component.name} />}
|
{error === ComponentViewerError.MissingUrl && <UrlMissing componentName={component.name} />}
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import { ListboxArrow, ListboxButton, ListboxInput, ListboxList, ListboxOption, ListboxPopover } from '@reach/listbox'
|
||||||
ListboxArrow,
|
|
||||||
ListboxButton,
|
|
||||||
ListboxInput,
|
|
||||||
ListboxList,
|
|
||||||
ListboxOption,
|
|
||||||
ListboxPopover,
|
|
||||||
} from '@reach/listbox'
|
|
||||||
import VisuallyHidden from '@reach/visually-hidden'
|
import VisuallyHidden from '@reach/visually-hidden'
|
||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { Icon } from '@/Components/Icon'
|
import { Icon } from '@/Components/Icon'
|
||||||
@@ -53,14 +46,7 @@ const CustomDropdownButton: FunctionComponent<ListboxButtonProps> = ({
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const Dropdown: FunctionComponent<DropdownProps> = ({
|
export const Dropdown: FunctionComponent<DropdownProps> = ({ id, label, items, value, onChange, disabled }) => {
|
||||||
id,
|
|
||||||
label,
|
|
||||||
items,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
disabled,
|
|
||||||
}) => {
|
|
||||||
const labelId = `${id}-label`
|
const labelId = `${id}-label`
|
||||||
|
|
||||||
const handleChange = (value: string) => {
|
const handleChange = (value: string) => {
|
||||||
@@ -72,12 +58,7 @@ export const Dropdown: FunctionComponent<DropdownProps> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<VisuallyHidden id={labelId}>{label}</VisuallyHidden>
|
<VisuallyHidden id={labelId}>{label}</VisuallyHidden>
|
||||||
<ListboxInput
|
<ListboxInput value={value} onChange={handleChange} aria-labelledby={labelId} disabled={disabled}>
|
||||||
value={value}
|
|
||||||
onChange={handleChange}
|
|
||||||
aria-labelledby={labelId}
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
<ListboxButton
|
<ListboxButton
|
||||||
className="sn-dropdown-button"
|
className="sn-dropdown-button"
|
||||||
children={({ value, label, isExpanded }) => {
|
children={({ value, label, isExpanded }) => {
|
||||||
@@ -105,10 +86,7 @@ export const Dropdown: FunctionComponent<DropdownProps> = ({
|
|||||||
>
|
>
|
||||||
{item.icon ? (
|
{item.icon ? (
|
||||||
<div className="flex mr-3">
|
<div className="flex mr-3">
|
||||||
<Icon
|
<Icon type={item.icon} className={`sn-icon--small ${item.iconClassName ?? ''}`} />
|
||||||
type={item.icon}
|
|
||||||
className={`sn-icon--small ${item.iconClassName ?? ''}`}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="text-input">{item.label}</div>
|
<div className="text-input">{item.label}</div>
|
||||||
|
|||||||
@@ -2,12 +2,7 @@ import { WebAppEvent, WebApplication } from '@/UIModels/Application'
|
|||||||
import { ApplicationGroup } from '@/UIModels/ApplicationGroup'
|
import { ApplicationGroup } from '@/UIModels/ApplicationGroup'
|
||||||
import { PureComponent } from '@/Components/Abstract/PureComponent'
|
import { PureComponent } from '@/Components/Abstract/PureComponent'
|
||||||
import { preventRefreshing } from '@/Utils'
|
import { preventRefreshing } from '@/Utils'
|
||||||
import {
|
import { ApplicationEvent, ContentType, CollectionSort, ApplicationDescriptor } from '@standardnotes/snjs'
|
||||||
ApplicationEvent,
|
|
||||||
ContentType,
|
|
||||||
CollectionSort,
|
|
||||||
ApplicationDescriptor,
|
|
||||||
} from '@standardnotes/snjs'
|
|
||||||
import {
|
import {
|
||||||
STRING_NEW_UPDATE_READY,
|
STRING_NEW_UPDATE_READY,
|
||||||
STRING_CONFIRM_APP_QUIT_DURING_UPGRADE,
|
STRING_CONFIRM_APP_QUIT_DURING_UPGRADE,
|
||||||
@@ -241,9 +236,7 @@ export class Footer extends PureComponent<Props, State> {
|
|||||||
style: 'percent',
|
style: 'percent',
|
||||||
})
|
})
|
||||||
|
|
||||||
statusManager.setMessage(
|
statusManager.setMessage(`Syncing ${stats.uploadTotalCount} items (${stringPercentage} complete)`)
|
||||||
`Syncing ${stats.uploadTotalCount} items (${stringPercentage} complete)`,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
statusManager.setMessage('')
|
statusManager.setMessage('')
|
||||||
}
|
}
|
||||||
@@ -330,9 +323,7 @@ export class Footer extends PureComponent<Props, State> {
|
|||||||
betaMessageClickHandler = () => {
|
betaMessageClickHandler = () => {
|
||||||
alertDialog({
|
alertDialog({
|
||||||
title: 'You are using a beta version of the app',
|
title: 'You are using a beta version of the app',
|
||||||
text:
|
text: 'If you wish to go back to a stable version, make sure to sign out ' + 'of this beta app first.',
|
||||||
'If you wish to go back to a stable version, make sure to sign out ' +
|
|
||||||
'of this beta app first.',
|
|
||||||
}).catch(console.error)
|
}).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'
|
' w-8 h-full flex items-center justify-center cursor-pointer rounded-full'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div className={this.state.hasError ? 'danger' : (this.user ? 'info' : 'neutral') + ' w-5 h-5'}>
|
||||||
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" />
|
<Icon type="account-circle" className="hover:color-info w-5 h-5 max-h-5" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -382,10 +369,7 @@ export class Footer extends PureComponent<Props, State> {
|
|||||||
<div className="h-5">
|
<div className="h-5">
|
||||||
<Icon
|
<Icon
|
||||||
type="tune"
|
type="tune"
|
||||||
className={
|
className={(this.state.showQuickSettingsMenu ? 'color-info' : '') + ' rounded hover:color-info'}
|
||||||
(this.state.showQuickSettingsMenu ? 'color-info' : '') +
|
|
||||||
' rounded hover:color-info'
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -401,10 +385,7 @@ export class Footer extends PureComponent<Props, State> {
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="sk-app-bar-item border" />
|
<div className="sk-app-bar-item border" />
|
||||||
<div className="sk-app-bar-item">
|
<div className="sk-app-bar-item">
|
||||||
<a
|
<a onClick={this.betaMessageClickHandler} className="no-decoration sk-label title">
|
||||||
onClick={this.betaMessageClickHandler}
|
|
||||||
className="no-decoration sk-label title"
|
|
||||||
>
|
|
||||||
You are using a beta version of the app
|
You are using a beta version of the app
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -439,10 +420,7 @@ export class Footer extends PureComponent<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{this.state.showSyncResolution && (
|
{this.state.showSyncResolution && (
|
||||||
<SyncResolutionMenu
|
<SyncResolutionMenu close={this.syncResolutionClickHandler} application={this.application} />
|
||||||
close={this.syncResolutionClickHandler}
|
|
||||||
application={this.application}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -39,9 +39,7 @@ export const DecoratedInput: FunctionalComponent<DecoratedInputProps> = forwardR
|
|||||||
const classNames = getClassNames(hasLeftDecorations, hasRightDecorations)
|
const classNames = getClassNames(hasLeftDecorations, hasRightDecorations)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={`${classNames.container} ${disabled ? classNames.disabled : ''} ${className}`}>
|
||||||
className={`${classNames.container} ${disabled ? classNames.disabled : ''} ${className}`}
|
|
||||||
>
|
|
||||||
{left && (
|
{left && (
|
||||||
<div className="flex items-center px-2 py-1.5">
|
<div className="flex items-center px-2 py-1.5">
|
||||||
{left.map((leftChild) => (
|
{left.map((leftChild) => (
|
||||||
|
|||||||
@@ -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
|
* 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'>> =
|
export const DecoratedPasswordInput: FunctionComponent<Omit<DecoratedInputProps, 'type'>> = forwardRef(
|
||||||
forwardRef((props, ref: Ref<HTMLInputElement>) => {
|
(props, ref: Ref<HTMLInputElement>) => {
|
||||||
const [isToggled, setIsToggled] = useState(false)
|
const [isToggled, setIsToggled] = useState(false)
|
||||||
|
|
||||||
const rightSideDecorations = props.right ? [...props.right] : []
|
const rightSideDecorations = props.right ? [...props.right] : []
|
||||||
@@ -33,10 +33,8 @@ export const DecoratedPasswordInput: FunctionComponent<Omit<DecoratedInputProps,
|
|||||||
{...props}
|
{...props}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
type={isToggled ? 'text' : 'password'}
|
type={isToggled ? 'text' : 'password'}
|
||||||
right={[
|
right={[...rightSideDecorations, <Toggle isToggled={isToggled} setIsToggled={setIsToggled} />]}
|
||||||
...rightSideDecorations,
|
|
||||||
<Toggle isToggled={isToggled} setIsToggled={setIsToggled} />,
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
import {
|
import { JSX, FunctionComponent, ComponentChildren, VNode, RefCallback, ComponentChild, toChildArray } from 'preact'
|
||||||
JSX,
|
|
||||||
FunctionComponent,
|
|
||||||
ComponentChildren,
|
|
||||||
VNode,
|
|
||||||
RefCallback,
|
|
||||||
ComponentChild,
|
|
||||||
toChildArray,
|
|
||||||
} from 'preact'
|
|
||||||
import { useEffect, useRef } from 'preact/hooks'
|
import { useEffect, useRef } from 'preact/hooks'
|
||||||
import { JSXInternal } from 'preact/src/jsx'
|
import { JSXInternal } from 'preact/src/jsx'
|
||||||
import { MenuItem, MenuItemListElement } from './MenuItem'
|
import { MenuItem, MenuItemListElement } from './MenuItem'
|
||||||
@@ -70,11 +62,7 @@ export const Menu: FunctionComponent<MenuProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapMenuItems = (
|
const mapMenuItems = (child: ComponentChild, index: number, array: ComponentChild[]): ComponentChild => {
|
||||||
child: ComponentChild,
|
|
||||||
index: number,
|
|
||||||
array: ComponentChild[],
|
|
||||||
): ComponentChild => {
|
|
||||||
if (!child || (Array.isArray(child) && child.length < 1)) {
|
if (!child || (Array.isArray(child) && child.length < 1)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -84,8 +72,7 @@ export const Menu: FunctionComponent<MenuProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const _child = child as VNode<unknown>
|
const _child = child as VNode<unknown>
|
||||||
const isFirstMenuItem =
|
const isFirstMenuItem = index === array.findIndex((child) => (child as VNode<unknown>).type === MenuItem)
|
||||||
index === array.findIndex((child) => (child as VNode<unknown>).type === MenuItem)
|
|
||||||
|
|
||||||
const hasMultipleItems = Array.isArray(_child.props.children)
|
const hasMultipleItems = Array.isArray(_child.props.children)
|
||||||
? Array.from(_child.props.children as ComponentChild[]).some(
|
? Array.from(_child.props.children as ComponentChild[]).some(
|
||||||
|
|||||||
@@ -65,15 +65,9 @@ export const MenuItem: FunctionComponent<MenuItemProps> = forwardRef(
|
|||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
{...(type === MenuItemType.RadioButton ? { 'aria-checked': checked } : {})}
|
{...(type === MenuItemType.RadioButton ? { 'aria-checked': checked } : {})}
|
||||||
>
|
>
|
||||||
{type === MenuItemType.IconButton && icon ? (
|
{type === MenuItemType.IconButton && icon ? <Icon type={icon} className={iconClassName} /> : null}
|
||||||
<Icon type={icon} className={iconClassName} />
|
|
||||||
) : null}
|
|
||||||
{type === MenuItemType.RadioButton && typeof checked === 'boolean' ? (
|
{type === MenuItemType.RadioButton && typeof checked === 'boolean' ? (
|
||||||
<div
|
<div className={`pseudo-radio-btn ${checked ? 'pseudo-radio-btn--checked' : ''} mr-2 flex-shrink-0`}></div>
|
||||||
className={`pseudo-radio-btn ${
|
|
||||||
checked ? 'pseudo-radio-btn--checked' : ''
|
|
||||||
} mr-2 flex-shrink-0`}
|
|
||||||
></div>
|
|
||||||
) : null}
|
) : null}
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
@@ -81,9 +75,7 @@ export const MenuItem: FunctionComponent<MenuItemProps> = forwardRef(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export const MenuItemSeparator: FunctionComponent = () => (
|
export const MenuItemSeparator: FunctionComponent = () => <div role="separator" className="h-1px my-2 bg-border"></div>
|
||||||
<div role="separator" className="h-1px my-2 bg-border"></div>
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListElementProps = {
|
type ListElementProps = {
|
||||||
isFirstMenuItem: boolean
|
isFirstMenuItem: boolean
|
||||||
|
|||||||
@@ -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">
|
<div className="flex-grow flex flex-col justify-center items-center w-full max-w-md">
|
||||||
<IlNotesIcon className="block" />
|
<IlNotesIcon className="block" />
|
||||||
<h2 className="text-lg m-0 text-center mt-4">{count} selected notes</h2>
|
<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">
|
<p className="text-sm mt-2 text-center max-w-60">Actions will be performed on all selected notes.</p>
|
||||||
Actions will be performed on all selected notes.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,12 +6,7 @@ import { ApplicationEvent, PrefKey } from '@standardnotes/snjs'
|
|||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
||||||
import {
|
import { PanelSide, ResizeFinishCallback, PanelResizer, PanelResizeType } from '@/Components/PanelResizer'
|
||||||
PanelSide,
|
|
||||||
ResizeFinishCallback,
|
|
||||||
PanelResizer,
|
|
||||||
PanelResizeType,
|
|
||||||
} from '@/Components/PanelResizer'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
|
|||||||
@@ -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 () => {
|
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
|
jest
|
||||||
.spyOn(ctrl, 'getSecondsElapsedSinceLastEdit')
|
.spyOn(ctrl, 'getSecondsElapsedSinceLastEdit')
|
||||||
.mockImplementation(
|
.mockImplementation(() => ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction + 5)
|
||||||
() => ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction + 5,
|
|
||||||
)
|
|
||||||
|
|
||||||
await ctrl.onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
|
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 () => {
|
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 =
|
const secondsElapsedSinceLastEdit = ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - 3
|
||||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - 3
|
|
||||||
|
|
||||||
Object.defineProperty(ctrl.note, 'userModifiedDate', {
|
Object.defineProperty(ctrl.note, 'userModifiedDate', {
|
||||||
value: new Date(Date.now() - secondsElapsedSinceLastEdit * 1000),
|
value: new Date(Date.now() - secondsElapsedSinceLastEdit * 1000),
|
||||||
@@ -95,8 +92,7 @@ describe('editor-view', () => {
|
|||||||
await ctrl.onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
|
await ctrl.onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
|
||||||
|
|
||||||
const secondsAfterWhichTheNoteShouldHide =
|
const secondsAfterWhichTheNoteShouldHide =
|
||||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction -
|
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - secondsElapsedSinceLastEdit
|
||||||
secondsElapsedSinceLastEdit
|
|
||||||
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
|
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
|
||||||
expect(setShowProtectedWarningSpy).not.toHaveBeenCalled()
|
expect(setShowProtectedWarningSpy).not.toHaveBeenCalled()
|
||||||
|
|
||||||
@@ -114,8 +110,7 @@ describe('editor-view', () => {
|
|||||||
await ctrl.onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
|
await ctrl.onAppEvent(ApplicationEvent.UnprotectedSessionExpired)
|
||||||
|
|
||||||
let secondsAfterWhichTheNoteShouldHide =
|
let secondsAfterWhichTheNoteShouldHide =
|
||||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction -
|
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - secondsElapsedSinceLastModification
|
||||||
secondsElapsedSinceLastModification
|
|
||||||
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
|
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
|
||||||
|
|
||||||
// A new modification has just happened
|
// A new modification has just happened
|
||||||
@@ -124,8 +119,7 @@ describe('editor-view', () => {
|
|||||||
configurable: true,
|
configurable: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
secondsAfterWhichTheNoteShouldHide =
|
secondsAfterWhichTheNoteShouldHide = ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction
|
||||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction
|
|
||||||
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
|
jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000)
|
||||||
expect(setShowProtectedWarningSpy).not.toHaveBeenCalled()
|
expect(setShowProtectedWarningSpy).not.toHaveBeenCalled()
|
||||||
|
|
||||||
@@ -152,9 +146,7 @@ describe('editor-view', () => {
|
|||||||
describe('dismissProtectedWarning', () => {
|
describe('dismissProtectedWarning', () => {
|
||||||
describe('the note has protection sources', () => {
|
describe('the note has protection sources', () => {
|
||||||
it('should reveal note contents if the authorization has been passed', async () => {
|
it('should reveal note contents if the authorization has been passed', async () => {
|
||||||
jest
|
jest.spyOn(ctrl['application'], 'authorizeNoteAccess').mockImplementation(async () => Promise.resolve(true))
|
||||||
.spyOn(ctrl['application'], 'authorizeNoteAccess')
|
|
||||||
.mockImplementation(async () => Promise.resolve(true))
|
|
||||||
|
|
||||||
await ctrl.dismissProtectedWarning()
|
await ctrl.dismissProtectedWarning()
|
||||||
|
|
||||||
@@ -162,9 +154,7 @@ describe('editor-view', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should not reveal note contents if the authorization has not been passed', async () => {
|
it('should not reveal note contents if the authorization has not been passed', async () => {
|
||||||
jest
|
jest.spyOn(ctrl['application'], 'authorizeNoteAccess').mockImplementation(async () => Promise.resolve(false))
|
||||||
.spyOn(ctrl['application'], 'authorizeNoteAccess')
|
|
||||||
.mockImplementation(async () => Promise.resolve(false))
|
|
||||||
|
|
||||||
await ctrl.dismissProtectedWarning()
|
await ctrl.dismissProtectedWarning()
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,7 @@ import {
|
|||||||
import { debounce, isDesktopApplication } from '@/Utils'
|
import { debounce, isDesktopApplication } from '@/Utils'
|
||||||
import { KeyboardModifier, KeyboardKey } from '@/Services/IOService'
|
import { KeyboardModifier, KeyboardKey } from '@/Services/IOService'
|
||||||
import { EventSource } from '@/UIModels/AppState'
|
import { EventSource } from '@/UIModels/AppState'
|
||||||
import {
|
import { STRING_DELETE_PLACEHOLDER_ATTEMPT, STRING_DELETE_LOCKED_ATTEMPT, StringDeleteNote } from '@/Strings'
|
||||||
STRING_DELETE_PLACEHOLDER_ATTEMPT,
|
|
||||||
STRING_DELETE_LOCKED_ATTEMPT,
|
|
||||||
StringDeleteNote,
|
|
||||||
} from '@/Strings'
|
|
||||||
import { confirmDialog } from '@/Services/AlertService'
|
import { confirmDialog } from '@/Services/AlertService'
|
||||||
import { PureComponent } from '@/Components/Abstract/PureComponent'
|
import { PureComponent } from '@/Components/Abstract/PureComponent'
|
||||||
import { ProtectedNoteOverlay } from '@/Components/ProtectedNoteOverlay'
|
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))
|
return array.sort((a, b) => (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const transactionForAssociateComponentWithCurrentNote = (
|
export const transactionForAssociateComponentWithCurrentNote = (component: SNComponent, note: SNNote) => {
|
||||||
component: SNComponent,
|
|
||||||
note: SNNote,
|
|
||||||
) => {
|
|
||||||
const transaction: TransactionalMutation = {
|
const transaction: TransactionalMutation = {
|
||||||
itemUuid: component.uuid,
|
itemUuid: component.uuid,
|
||||||
mutate: (m: ItemMutator) => {
|
mutate: (m: ItemMutator) => {
|
||||||
@@ -65,10 +58,7 @@ export const transactionForAssociateComponentWithCurrentNote = (
|
|||||||
return transaction
|
return transaction
|
||||||
}
|
}
|
||||||
|
|
||||||
export const transactionForDisassociateComponentWithCurrentNote = (
|
export const transactionForDisassociateComponentWithCurrentNote = (component: SNComponent, note: SNNote) => {
|
||||||
component: SNComponent,
|
|
||||||
note: SNNote,
|
|
||||||
) => {
|
|
||||||
const transaction: TransactionalMutation = {
|
const transaction: TransactionalMutation = {
|
||||||
itemUuid: component.uuid,
|
itemUuid: component.uuid,
|
||||||
mutate: (m: ItemMutator) => {
|
mutate: (m: ItemMutator) => {
|
||||||
@@ -209,11 +199,9 @@ export class NoteView extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
this.registerKeyboardShortcuts()
|
this.registerKeyboardShortcuts()
|
||||||
|
|
||||||
this.removeInnerNoteObserver = this.controller.addNoteInnerValueChangeObserver(
|
this.removeInnerNoteObserver = this.controller.addNoteInnerValueChangeObserver((note, source) => {
|
||||||
(note, source) => {
|
this.onNoteInnerChange(note, source)
|
||||||
this.onNoteInnerChange(note, source)
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
this.autorun(() => {
|
this.autorun(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -281,18 +269,15 @@ export class NoteView extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
this.reloadSpellcheck().catch(console.error)
|
this.reloadSpellcheck().catch(console.error)
|
||||||
|
|
||||||
const isTemplateNoteInsertedToBeInteractableWithEditor =
|
const isTemplateNoteInsertedToBeInteractableWithEditor = source === PayloadEmitSource.LocalInserted && note.dirty
|
||||||
source === PayloadEmitSource.LocalInserted && note.dirty
|
|
||||||
if (isTemplateNoteInsertedToBeInteractableWithEditor) {
|
if (isTemplateNoteInsertedToBeInteractableWithEditor) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.lastSyncBegan || note.dirty) {
|
if (note.lastSyncBegan || note.dirty) {
|
||||||
if (note.lastSyncEnd) {
|
if (note.lastSyncEnd) {
|
||||||
const shouldShowSavingStatus =
|
const shouldShowSavingStatus = note.lastSyncBegan && note.lastSyncBegan.getTime() > note.lastSyncEnd.getTime()
|
||||||
note.lastSyncBegan && note.lastSyncBegan.getTime() > note.lastSyncEnd.getTime()
|
const shouldShowSavedStatus = note.lastSyncBegan && note.lastSyncEnd.getTime() > note.lastSyncBegan.getTime()
|
||||||
const shouldShowSavedStatus =
|
|
||||||
note.lastSyncBegan && note.lastSyncEnd.getTime() > note.lastSyncBegan.getTime()
|
|
||||||
if (note.dirty || shouldShowSavingStatus) {
|
if (note.dirty || shouldShowSavingStatus) {
|
||||||
this.showSavingStatus()
|
this.showSavingStatus()
|
||||||
} else if (this.state.noteStatus && shouldShowSavedStatus) {
|
} else if (this.state.noteStatus && shouldShowSavedStatus) {
|
||||||
@@ -367,15 +352,11 @@ export class NoteView extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
hideProtectedNoteIfInactive(): void {
|
hideProtectedNoteIfInactive(): void {
|
||||||
const secondsElapsedSinceLastEdit = this.getSecondsElapsedSinceLastEdit()
|
const secondsElapsedSinceLastEdit = this.getSecondsElapsedSinceLastEdit()
|
||||||
if (
|
if (secondsElapsedSinceLastEdit >= ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction) {
|
||||||
secondsElapsedSinceLastEdit >=
|
|
||||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction
|
|
||||||
) {
|
|
||||||
this.setShowProtectedOverlay(true)
|
this.setShowProtectedOverlay(true)
|
||||||
} else {
|
} else {
|
||||||
const secondsUntilTheNextCheck =
|
const secondsUntilTheNextCheck =
|
||||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction -
|
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - secondsElapsedSinceLastEdit
|
||||||
secondsElapsedSinceLastEdit
|
|
||||||
this.startNoteProtectionInactivityTimer(secondsUntilTheNextCheck)
|
this.startNoteProtectionInactivityTimer(secondsUntilTheNextCheck)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -406,36 +387,24 @@ export class NoteView extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
streamItems() {
|
streamItems() {
|
||||||
this.removeComponentStreamObserver = this.application.streamItems(
|
this.removeComponentStreamObserver = this.application.streamItems(ContentType.Component, async ({ source }) => {
|
||||||
ContentType.Component,
|
if (isPayloadSourceInternalChange(source) || source === PayloadEmitSource.InitialObserverRegistrationPush) {
|
||||||
async ({ source }) => {
|
return
|
||||||
if (
|
}
|
||||||
isPayloadSourceInternalChange(source) ||
|
if (!this.note) {
|
||||||
source === PayloadEmitSource.InitialObserverRegistrationPush
|
return
|
||||||
) {
|
}
|
||||||
return
|
await this.reloadStackComponents()
|
||||||
}
|
this.debounceReloadEditorComponent()
|
||||||
if (!this.note) {
|
})
|
||||||
return
|
|
||||||
}
|
|
||||||
await this.reloadStackComponents()
|
|
||||||
this.debounceReloadEditorComponent()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createComponentViewer(component: SNComponent) {
|
private createComponentViewer(component: SNComponent) {
|
||||||
const viewer = this.application.componentManager.createComponentViewer(
|
const viewer = this.application.componentManager.createComponentViewer(component, this.note.uuid)
|
||||||
component,
|
|
||||||
this.note.uuid,
|
|
||||||
)
|
|
||||||
return viewer
|
return viewer
|
||||||
}
|
}
|
||||||
|
|
||||||
public editorComponentViewerRequestsReload = async (
|
public editorComponentViewerRequestsReload = async (viewer: ComponentViewer, force?: boolean): Promise<void> => {
|
||||||
viewer: ComponentViewer,
|
|
||||||
force?: boolean,
|
|
||||||
): Promise<void> => {
|
|
||||||
if (this.state.editorComponentViewerDidAlreadyReload && !force) {
|
if (this.state.editorComponentViewerDidAlreadyReload && !force) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -698,10 +667,7 @@ export class NoteView extends PureComponent<Props, State> {
|
|||||||
async reloadPreferences() {
|
async reloadPreferences() {
|
||||||
const monospaceFont = this.application.getPreference(PrefKey.EditorMonospaceEnabled, true)
|
const monospaceFont = this.application.getPreference(PrefKey.EditorMonospaceEnabled, true)
|
||||||
|
|
||||||
const marginResizersEnabled = this.application.getPreference(
|
const marginResizersEnabled = this.application.getPreference(PrefKey.EditorResizersEnabled, true)
|
||||||
PrefKey.EditorResizersEnabled,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
|
|
||||||
await this.reloadSpellcheck()
|
await this.reloadSpellcheck()
|
||||||
|
|
||||||
@@ -758,9 +724,7 @@ export class NoteView extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
const newViewers: ComponentViewer[] = []
|
const newViewers: ComponentViewer[] = []
|
||||||
for (const component of needsNewViewer) {
|
for (const component of needsNewViewer) {
|
||||||
newViewers.push(
|
newViewers.push(this.application.componentManager.createComponentViewer(component, this.note.uuid))
|
||||||
this.application.componentManager.createComponentViewer(component, this.note.uuid),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const viewer of needsDestroyViewer) {
|
for (const viewer of needsDestroyViewer) {
|
||||||
@@ -773,9 +737,7 @@ export class NoteView extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stackComponentExpanded = (component: SNComponent): boolean => {
|
stackComponentExpanded = (component: SNComponent): boolean => {
|
||||||
return !!this.state.stackComponentViewers.find(
|
return !!this.state.stackComponentViewers.find((viewer) => viewer.componentUuid === component.uuid)
|
||||||
(viewer) => viewer.componentUuid === component.uuid,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleStackComponent = async (component: SNComponent) => {
|
toggleStackComponent = async (component: SNComponent) => {
|
||||||
@@ -942,9 +904,7 @@ export class NoteView extends PureComponent<Props, State> {
|
|||||||
className="sk-app-bar-item"
|
className="sk-app-bar-item"
|
||||||
>
|
>
|
||||||
<div className="sk-label warning flex items-center">
|
<div className="sk-label warning flex items-center">
|
||||||
{this.state.showLockedIcon && (
|
{this.state.showLockedIcon && <Icon type="pencil-off" className="flex fill-current mr-2" />}
|
||||||
<Icon type="pencil-off" className="flex fill-current mr-2" />
|
|
||||||
)}
|
|
||||||
{this.state.lockText}
|
{this.state.lockText}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -984,9 +944,7 @@ export class NoteView extends PureComponent<Props, State> {
|
|||||||
>
|
>
|
||||||
{this.state.noteStatus?.message}
|
{this.state.noteStatus?.message}
|
||||||
</div>
|
</div>
|
||||||
{this.state.noteStatus?.desc && (
|
{this.state.noteStatus?.desc && <div className="desc">{this.state.noteStatus.desc}</div>}
|
||||||
<div className="desc">{this.state.noteStatus.desc}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{this.appState.features.isFilesEnabled && (
|
{this.appState.features.isFilesEnabled && (
|
||||||
@@ -1022,11 +980,7 @@ export class NoteView extends PureComponent<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div id={ElementIds.EditorContent} className={ElementIds.EditorContent} ref={this.editorContentRef}>
|
||||||
id={ElementIds.EditorContent}
|
|
||||||
className={ElementIds.EditorContent}
|
|
||||||
ref={this.editorContentRef}
|
|
||||||
>
|
|
||||||
{this.state.marginResizersEnabled && this.editorContentRef.current ? (
|
{this.state.marginResizersEnabled && this.editorContentRef.current ? (
|
||||||
<PanelResizer
|
<PanelResizer
|
||||||
minWidth={300}
|
minWidth={300}
|
||||||
@@ -1053,22 +1007,20 @@ export class NoteView extends PureComponent<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.state.editorStateDidLoad &&
|
{this.state.editorStateDidLoad && !this.state.editorComponentViewer && !this.state.textareaUnloading && (
|
||||||
!this.state.editorComponentViewer &&
|
<textarea
|
||||||
!this.state.textareaUnloading && (
|
autocomplete="off"
|
||||||
<textarea
|
className="editable font-editor"
|
||||||
autocomplete="off"
|
dir="auto"
|
||||||
className="editable font-editor"
|
id={ElementIds.NoteTextEditor}
|
||||||
dir="auto"
|
onChange={this.onTextAreaChange}
|
||||||
id={ElementIds.NoteTextEditor}
|
value={this.state.editorText}
|
||||||
onChange={this.onTextAreaChange}
|
readonly={this.state.noteLocked}
|
||||||
value={this.state.editorText}
|
onFocus={this.onContentFocus}
|
||||||
readonly={this.state.noteLocked}
|
spellcheck={this.state.spellcheck}
|
||||||
onFocus={this.onContentFocus}
|
ref={(ref) => this.onSystemEditorLoad(ref)}
|
||||||
spellcheck={this.state.spellcheck}
|
></textarea>
|
||||||
ref={(ref) => this.onSystemEditorLoad(ref)}
|
)}
|
||||||
></textarea>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{this.state.marginResizersEnabled && this.editorContentRef.current ? (
|
{this.state.marginResizersEnabled && this.editorContentRef.current ? (
|
||||||
<PanelResizer
|
<PanelResizer
|
||||||
@@ -1101,9 +1053,7 @@ export class NoteView extends PureComponent<Props, State> {
|
|||||||
<div className="sk-app-bar-item-column">
|
<div className="sk-app-bar-item-column">
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
(this.stackComponentExpanded(component) && component.active
|
(this.stackComponentExpanded(component) && component.active ? 'info ' : '') +
|
||||||
? 'info '
|
|
||||||
: '') +
|
|
||||||
(!this.stackComponentExpanded(component) ? 'neutral ' : '') +
|
(!this.stackComponentExpanded(component) ? 'neutral ' : '') +
|
||||||
' sk-circle small'
|
' sk-circle small'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ export const NotesContextMenu = observer(({ application, appState }: Props) => {
|
|||||||
const { contextMenuOpen, contextMenuPosition, contextMenuMaxHeight } = appState.notes
|
const { contextMenuOpen, contextMenuPosition, contextMenuMaxHeight } = appState.notes
|
||||||
|
|
||||||
const contextMenuRef = useRef<HTMLDivElement>(null)
|
const contextMenuRef = useRef<HTMLDivElement>(null)
|
||||||
const [closeOnBlur] = useCloseOnBlur(contextMenuRef, (open: boolean) =>
|
const [closeOnBlur] = useCloseOnBlur(contextMenuRef, (open: boolean) => appState.notes.setContextMenuOpen(open))
|
||||||
appState.notes.setContextMenuOpen(open),
|
|
||||||
)
|
|
||||||
|
|
||||||
useCloseOnClickOutside(contextMenuRef, () => appState.notes.setContextMenuOpen(false))
|
useCloseOnClickOutside(contextMenuRef, () => appState.notes.setContextMenuOpen(false))
|
||||||
|
|
||||||
|
|||||||
@@ -16,30 +16,14 @@ type Props = {
|
|||||||
|
|
||||||
export const NotesListOptionsMenu: FunctionComponent<Props> = observer(
|
export const NotesListOptionsMenu: FunctionComponent<Props> = observer(
|
||||||
({ closeDisplayOptionsMenu, closeOnBlur, application, isOpen }) => {
|
({ closeDisplayOptionsMenu, closeOnBlur, application, isOpen }) => {
|
||||||
const [sortBy, setSortBy] = useState(() =>
|
const [sortBy, setSortBy] = useState(() => application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt))
|
||||||
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 [sortReverse, setSortReverse] = useState(() =>
|
const [hideDate, setHideDate] = useState(() => application.getPreference(PrefKey.NotesHideDate, false))
|
||||||
application.getPreference(PrefKey.SortNotesReverse, false),
|
const [hideTags, setHideTags] = useState(() => application.getPreference(PrefKey.NotesHideTags, true))
|
||||||
)
|
const [hidePinned, setHidePinned] = useState(() => application.getPreference(PrefKey.NotesHidePinned, false))
|
||||||
const [hidePreview, setHidePreview] = useState(() =>
|
const [showArchived, setShowArchived] = useState(() => application.getPreference(PrefKey.NotesShowArchived, false))
|
||||||
application.getPreference(PrefKey.NotesHideNotePreview, false),
|
const [showTrashed, setShowTrashed] = useState(() => application.getPreference(PrefKey.NotesShowTrashed, 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(() =>
|
const [hideProtected, setHideProtected] = useState(() =>
|
||||||
application.getPreference(PrefKey.NotesHideProtected, false),
|
application.getPreference(PrefKey.NotesHideProtected, false),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -76,11 +76,7 @@ type ListedActionsMenuProps = {
|
|||||||
recalculateMenuStyle: () => void
|
recalculateMenuStyle: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListedActionsMenu: FunctionComponent<ListedActionsMenuProps> = ({
|
const ListedActionsMenu: FunctionComponent<ListedActionsMenuProps> = ({ application, note, recalculateMenuStyle }) => {
|
||||||
application,
|
|
||||||
note,
|
|
||||||
recalculateMenuStyle,
|
|
||||||
}) => {
|
|
||||||
const [menuGroups, setMenuGroups] = useState<ListedMenuGroup[]>([])
|
const [menuGroups, setMenuGroups] = useState<ListedMenuGroup[]>([])
|
||||||
const [isFetchingAccounts, setIsFetchingAccounts] = useState(true)
|
const [isFetchingAccounts, setIsFetchingAccounts] = useState(true)
|
||||||
|
|
||||||
@@ -251,11 +247,7 @@ export const ListedActionsOption: FunctionComponent<Props> = ({ application, not
|
|||||||
return (
|
return (
|
||||||
<div ref={menuContainerRef}>
|
<div ref={menuContainerRef}>
|
||||||
<Disclosure open={isMenuOpen} onChange={toggleListedMenu}>
|
<Disclosure open={isMenuOpen} onChange={toggleListedMenu}>
|
||||||
<DisclosureButton
|
<DisclosureButton ref={menuButtonRef} onBlur={closeOnBlur} className="sn-dropdown-item justify-between">
|
||||||
ref={menuButtonRef}
|
|
||||||
onBlur={closeOnBlur}
|
|
||||||
className="sn-dropdown-item justify-between"
|
|
||||||
>
|
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Icon type="listed" className="color-neutral mr-2" />
|
<Icon type="listed" className="color-neutral mr-2" />
|
||||||
Listed actions
|
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"
|
className="sn-dropdown flex flex-col max-h-120 min-w-68 pb-1 fixed overflow-y-auto"
|
||||||
>
|
>
|
||||||
{isMenuOpen && (
|
{isMenuOpen && (
|
||||||
<ListedActionsMenu
|
<ListedActionsMenu application={application} note={note} recalculateMenuStyle={recalculateMenuStyle} />
|
||||||
application={application}
|
|
||||||
note={note}
|
|
||||||
recalculateMenuStyle={recalculateMenuStyle}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</DisclosurePanel>
|
</DisclosurePanel>
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
|
|||||||
@@ -15,76 +15,72 @@ type Props = {
|
|||||||
onClickPreprocessing?: () => Promise<void>
|
onClickPreprocessing?: () => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NotesOptionsPanel = observer(
|
export const NotesOptionsPanel = observer(({ application, appState, onClickPreprocessing }: Props) => {
|
||||||
({ application, appState, onClickPreprocessing }: Props) => {
|
const [open, setOpen] = useState(false)
|
||||||
const [open, setOpen] = useState(false)
|
const [position, setPosition] = useState({
|
||||||
const [position, setPosition] = useState({
|
top: 0,
|
||||||
top: 0,
|
right: 0,
|
||||||
right: 0,
|
})
|
||||||
})
|
const [maxHeight, setMaxHeight] = useState<number | 'auto'>('auto')
|
||||||
const [maxHeight, setMaxHeight] = useState<number | 'auto'>('auto')
|
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
const panelRef = useRef<HTMLDivElement>(null)
|
||||||
const panelRef = useRef<HTMLDivElement>(null)
|
const [closeOnBlur] = useCloseOnBlur(panelRef, setOpen)
|
||||||
const [closeOnBlur] = useCloseOnBlur(panelRef, setOpen)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Disclosure
|
<Disclosure
|
||||||
open={open}
|
open={open}
|
||||||
onChange={async () => {
|
onChange={async () => {
|
||||||
const rect = buttonRef.current?.getBoundingClientRect()
|
const rect = buttonRef.current?.getBoundingClientRect()
|
||||||
if (rect) {
|
if (rect) {
|
||||||
const { clientHeight } = document.documentElement
|
const { clientHeight } = document.documentElement
|
||||||
const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect()
|
const footerElementRect = document.getElementById('footer-bar')?.getBoundingClientRect()
|
||||||
const footerHeightInPx = footerElementRect?.height
|
const footerHeightInPx = footerElementRect?.height
|
||||||
if (footerHeightInPx) {
|
if (footerHeightInPx) {
|
||||||
setMaxHeight(clientHeight - rect.bottom - footerHeightInPx - 2)
|
setMaxHeight(clientHeight - rect.bottom - footerHeightInPx - 2)
|
||||||
}
|
}
|
||||||
setPosition({
|
setPosition({
|
||||||
top: rect.bottom,
|
top: rect.bottom,
|
||||||
right: document.body.clientWidth - rect.right,
|
right: document.body.clientWidth - rect.right,
|
||||||
})
|
})
|
||||||
const newOpenState = !open
|
const newOpenState = !open
|
||||||
if (newOpenState && onClickPreprocessing) {
|
if (newOpenState && onClickPreprocessing) {
|
||||||
await onClickPreprocessing()
|
await onClickPreprocessing()
|
||||||
}
|
}
|
||||||
setOpen(newOpenState)
|
setOpen(newOpenState)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DisclosureButton
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
setOpen(false)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onBlur={closeOnBlur}
|
||||||
|
ref={buttonRef}
|
||||||
|
className="sn-icon-button border-contrast"
|
||||||
>
|
>
|
||||||
<DisclosureButton
|
<VisuallyHidden>Actions</VisuallyHidden>
|
||||||
onKeyDown={(event) => {
|
<Icon type="more" className="block" />
|
||||||
if (event.key === 'Escape') {
|
</DisclosureButton>
|
||||||
setOpen(false)
|
<DisclosurePanel
|
||||||
}
|
onKeyDown={(event) => {
|
||||||
}}
|
if (event.key === 'Escape') {
|
||||||
onBlur={closeOnBlur}
|
setOpen(false)
|
||||||
ref={buttonRef}
|
buttonRef.current?.focus()
|
||||||
className="sn-icon-button border-contrast"
|
}
|
||||||
>
|
}}
|
||||||
<VisuallyHidden>Actions</VisuallyHidden>
|
ref={panelRef}
|
||||||
<Icon type="more" className="block" />
|
style={{
|
||||||
</DisclosureButton>
|
...position,
|
||||||
<DisclosurePanel
|
maxHeight,
|
||||||
onKeyDown={(event) => {
|
}}
|
||||||
if (event.key === 'Escape') {
|
className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col pt-2 overflow-y-auto fixed"
|
||||||
setOpen(false)
|
onBlur={closeOnBlur}
|
||||||
buttonRef.current?.focus()
|
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||||
}
|
>
|
||||||
}}
|
{open && <NotesOptions application={application} appState={appState} closeOnBlur={closeOnBlur} />}
|
||||||
ref={panelRef}
|
</DisclosurePanel>
|
||||||
style={{
|
</Disclosure>
|
||||||
...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>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -10,12 +10,7 @@ import { NoAccountWarning } from '@/Components/NoAccountWarning'
|
|||||||
import { NotesList } from '@/Components/NotesList'
|
import { NotesList } from '@/Components/NotesList'
|
||||||
import { NotesListOptionsMenu } from '@/Components/NotesList/NotesListOptionsMenu'
|
import { NotesListOptionsMenu } from '@/Components/NotesList/NotesListOptionsMenu'
|
||||||
import { SearchOptions } from '@/Components/SearchOptions'
|
import { SearchOptions } from '@/Components/SearchOptions'
|
||||||
import {
|
import { PanelSide, ResizeFinishCallback, PanelResizer, PanelResizeType } from '@/Components/PanelResizer'
|
||||||
PanelSide,
|
|
||||||
ResizeFinishCallback,
|
|
||||||
PanelResizer,
|
|
||||||
PanelResizeType,
|
|
||||||
} from '@/Components/PanelResizer'
|
|
||||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||||
|
|
||||||
@@ -51,10 +46,7 @@ export const NotesView: FunctionComponent<Props> = observer(({ application, appS
|
|||||||
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false)
|
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false)
|
||||||
const [focusedSearch, setFocusedSearch] = useState(false)
|
const [focusedSearch, setFocusedSearch] = useState(false)
|
||||||
|
|
||||||
const [closeDisplayOptMenuOnBlur] = useCloseOnBlur(
|
const [closeDisplayOptMenuOnBlur] = useCloseOnBlur(displayOptionsMenuRef, setShowDisplayOptionsMenu)
|
||||||
displayOptionsMenuRef,
|
|
||||||
setShowDisplayOptionsMenu,
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleFilterTextChanged()
|
handleFilterTextChanged()
|
||||||
@@ -125,12 +117,7 @@ export const NotesView: FunctionComponent<Props> = observer(({ application, appS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const panelResizeFinishCallback: ResizeFinishCallback = (
|
const panelResizeFinishCallback: ResizeFinishCallback = (width, _lastLeft, _isMaxWidth, isCollapsed) => {
|
||||||
width,
|
|
||||||
_lastLeft,
|
|
||||||
_isMaxWidth,
|
|
||||||
isCollapsed,
|
|
||||||
) => {
|
|
||||||
application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error)
|
application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error)
|
||||||
appState.noteTags.reloadTagsContainerMaxWidth()
|
appState.noteTags.reloadTagsContainerMaxWidth()
|
||||||
appState.panelDidResize(PANEL_NAME_NOTES, isCollapsed)
|
appState.panelDidResize(PANEL_NAME_NOTES, isCollapsed)
|
||||||
@@ -229,9 +216,7 @@ export const NotesView: FunctionComponent<Props> = observer(({ application, appS
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{completedFullSync && !renderedNotes.length ? (
|
{completedFullSync && !renderedNotes.length ? <p className="empty-notes-list faded">No notes.</p> : null}
|
||||||
<p className="empty-notes-list faded">No notes.</p>
|
|
||||||
) : null}
|
|
||||||
{!completedFullSync && !renderedNotes.length ? (
|
{!completedFullSync && !renderedNotes.length ? (
|
||||||
<p className="empty-notes-list faded">Loading notes...</p>
|
<p className="empty-notes-list faded">Loading notes...</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ const ConfirmOtherSessionsSignOut = observer(({ application, appState }: Props)
|
|||||||
</AlertDialogLabel>
|
</AlertDialogLabel>
|
||||||
<AlertDialogDescription className="sk-panel-row">
|
<AlertDialogDescription className="sk-panel-row">
|
||||||
<p className="color-foreground">
|
<p className="color-foreground">
|
||||||
This action will sign out all other devices signed into your account, and remove
|
This action will sign out all other devices signed into your account, and remove your data from
|
||||||
your data from those devices when they next regain connection to the internet.
|
those devices when they next regain connection to the internet. You may sign back in on those
|
||||||
You may sign back in on those devices at any time.
|
devices at any time.
|
||||||
</p>
|
</p>
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
<div className="flex my-1 mt-4">
|
<div className="flex my-1 mt-4">
|
||||||
@@ -49,11 +49,7 @@ const ConfirmOtherSessionsSignOut = observer(({ application, appState }: Props)
|
|||||||
application.revokeAllOtherSessions().catch(console.error)
|
application.revokeAllOtherSessions().catch(console.error)
|
||||||
closeDialog()
|
closeDialog()
|
||||||
application.alertService
|
application.alertService
|
||||||
.alert(
|
.alert('You have successfully revoked your sessions from other devices.', undefined, 'Finish')
|
||||||
'You have successfully revoked your sessions from other devices.',
|
|
||||||
undefined,
|
|
||||||
'Finish',
|
|
||||||
)
|
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -107,9 +107,7 @@ export class PanelResizer extends Component<Props, State> {
|
|||||||
|
|
||||||
isAtMaxWidth = () => {
|
isAtMaxWidth = () => {
|
||||||
const marginOfError = 5
|
const marginOfError = 5
|
||||||
const difference = Math.abs(
|
const difference = Math.abs(Math.round(this.lastWidth + this.lastLeft) - Math.round(this.getParentRect().width))
|
||||||
Math.round(this.lastWidth + this.lastLeft) - Math.round(this.getParentRect().width),
|
|
||||||
)
|
|
||||||
return difference < marginOfError
|
return difference < marginOfError
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,12 +157,7 @@ export class PanelResizer extends Component<Props, State> {
|
|||||||
if (finish) {
|
if (finish) {
|
||||||
this.finishSettingWidth()
|
this.finishSettingWidth()
|
||||||
if (this.props.resizeFinishCallback) {
|
if (this.props.resizeFinishCallback) {
|
||||||
this.props.resizeFinishCallback(
|
this.props.resizeFinishCallback(this.lastWidth, this.lastLeft, this.isAtMaxWidth(), this.isCollapsed())
|
||||||
this.lastWidth,
|
|
||||||
this.lastLeft,
|
|
||||||
this.isAtMaxWidth(),
|
|
||||||
this.isCollapsed(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,12 +177,7 @@ export class PanelResizer extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
this.finishSettingWidth()
|
this.finishSettingWidth()
|
||||||
|
|
||||||
this.props.resizeFinishCallback?.(
|
this.props.resizeFinishCallback?.(this.lastWidth, this.lastLeft, this.isAtMaxWidth(), this.isCollapsed())
|
||||||
this.lastWidth,
|
|
||||||
this.lastLeft,
|
|
||||||
this.isAtMaxWidth(),
|
|
||||||
this.isCollapsed(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleWidthEvent(event?: MouseEvent) {
|
handleWidthEvent(event?: MouseEvent) {
|
||||||
|
|||||||
@@ -109,9 +109,7 @@ export class PasswordWizard extends PureComponent<Props, State> {
|
|||||||
const currentPassword = this.state.formData.currentPassword
|
const currentPassword = this.state.formData.currentPassword
|
||||||
const newPass = this.state.formData.newPassword
|
const newPass = this.state.formData.newPassword
|
||||||
if (!currentPassword || currentPassword.length === 0) {
|
if (!currentPassword || currentPassword.length === 0) {
|
||||||
this.application.alertService
|
this.application.alertService.alert('Please enter your current password.').catch(console.error)
|
||||||
.alert('Please enter your current password.')
|
|
||||||
.catch(console.error)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,9 +118,7 @@ export class PasswordWizard extends PureComponent<Props, State> {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (newPass !== this.state.formData.newPasswordConfirmation) {
|
if (newPass !== this.state.formData.newPasswordConfirmation) {
|
||||||
this.application.alertService
|
this.application.alertService.alert('Your new password does not match its confirmation.').catch(console.error)
|
||||||
.alert('Your new password does not match its confirmation.')
|
|
||||||
.catch(console.error)
|
|
||||||
this.setFormDataState({
|
this.setFormDataState({
|
||||||
status: undefined,
|
status: undefined,
|
||||||
}).catch(console.error)
|
}).catch(console.error)
|
||||||
@@ -131,9 +127,7 @@ export class PasswordWizard extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
if (!this.application.getUser()?.email) {
|
if (!this.application.getUser()?.email) {
|
||||||
this.application.alertService
|
this.application.alertService
|
||||||
.alert(
|
.alert("We don't have your email stored. Please sign out then log back in to fix this issue.")
|
||||||
"We don't have your email stored. Please sign out then log back in to fix this issue.",
|
|
||||||
)
|
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
this.setFormDataState({
|
this.setFormDataState({
|
||||||
status: undefined,
|
status: undefined,
|
||||||
@@ -142,9 +136,7 @@ export class PasswordWizard extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Validate current password */
|
/** Validate current password */
|
||||||
const success = await this.application.validateAccountPassword(
|
const success = await this.application.validateAccountPassword(this.state.formData.currentPassword as string)
|
||||||
this.state.formData.currentPassword as string,
|
|
||||||
)
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
this.application.alertService
|
this.application.alertService
|
||||||
.alert('The current password you entered is not correct. Please try again.')
|
.alert('The current password you entered is not correct. Please try again.')
|
||||||
@@ -194,9 +186,7 @@ export class PasswordWizard extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
dismiss = () => {
|
dismiss = () => {
|
||||||
if (this.state.lockContinue) {
|
if (this.state.lockContinue) {
|
||||||
this.application.alertService
|
this.application.alertService.alert('Cannot close window until pending tasks are complete.').catch(console.error)
|
||||||
.alert('Cannot close window until pending tasks are complete.')
|
|
||||||
.catch(console.error)
|
|
||||||
} else {
|
} else {
|
||||||
this.dismissModal()
|
this.dismissModal()
|
||||||
}
|
}
|
||||||
@@ -211,25 +201,19 @@ export class PasswordWizard extends PureComponent<Props, State> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCurrentPasswordInputChange = ({
|
handleCurrentPasswordInputChange = ({ currentTarget }: JSX.TargetedEvent<HTMLInputElement, Event>) => {
|
||||||
currentTarget,
|
|
||||||
}: JSX.TargetedEvent<HTMLInputElement, Event>) => {
|
|
||||||
this.setFormDataState({
|
this.setFormDataState({
|
||||||
currentPassword: currentTarget.value,
|
currentPassword: currentTarget.value,
|
||||||
}).catch(console.error)
|
}).catch(console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewPasswordInputChange = ({
|
handleNewPasswordInputChange = ({ currentTarget }: JSX.TargetedEvent<HTMLInputElement, Event>) => {
|
||||||
currentTarget,
|
|
||||||
}: JSX.TargetedEvent<HTMLInputElement, Event>) => {
|
|
||||||
this.setFormDataState({
|
this.setFormDataState({
|
||||||
newPassword: currentTarget.value,
|
newPassword: currentTarget.value,
|
||||||
}).catch(console.error)
|
}).catch(console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewPasswordConfirmationInputChange = ({
|
handleNewPasswordConfirmationInputChange = ({ currentTarget }: JSX.TargetedEvent<HTMLInputElement, Event>) => {
|
||||||
currentTarget,
|
|
||||||
}: JSX.TargetedEvent<HTMLInputElement, Event>) => {
|
|
||||||
this.setFormDataState({
|
this.setFormDataState({
|
||||||
newPasswordConfirmation: currentTarget.value,
|
newPasswordConfirmation: currentTarget.value,
|
||||||
}).catch(console.error)
|
}).catch(console.error)
|
||||||
@@ -283,10 +267,7 @@ export class PasswordWizard extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
<div className="sk-panel-row" />
|
<div className="sk-panel-row" />
|
||||||
|
|
||||||
<label
|
<label htmlFor="password-wiz-confirm-new-password" className="block mb-1">
|
||||||
htmlFor="password-wiz-confirm-new-password"
|
|
||||||
className="block mb-1"
|
|
||||||
>
|
|
||||||
Confirm New Password
|
Confirm New Password
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -304,12 +285,10 @@ export class PasswordWizard extends PureComponent<Props, State> {
|
|||||||
)}
|
)}
|
||||||
{this.state.step === Steps.FinishStep && (
|
{this.state.step === Steps.FinishStep && (
|
||||||
<div className="sk-panel-section">
|
<div className="sk-panel-section">
|
||||||
<div className="sk-label sk-bold info">
|
<div className="sk-label sk-bold info">Your password has been successfully changed.</div>
|
||||||
Your password has been successfully changed.
|
|
||||||
</div>
|
|
||||||
<p className="sk-p">
|
<p className="sk-p">
|
||||||
Please ensure you are running the latest version of Standard Notes on all
|
Please ensure you are running the latest version of Standard Notes on all platforms to ensure
|
||||||
platforms to ensure maximum compatibility.
|
maximum compatibility.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -77,10 +77,7 @@ export class PermissionsModal extends Component<Props> {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sk-panel-footer">
|
<div className="sk-panel-footer">
|
||||||
<button
|
<button onClick={this.accept} className="sn-button info block w-full text-base py-3">
|
||||||
onClick={this.accept}
|
|
||||||
className="sn-button info block w-full text-base py-3"
|
|
||||||
>
|
|
||||||
Continue
|
Continue
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { FunctionalComponent } from 'preact'
|
import { FunctionalComponent } from 'preact'
|
||||||
import {
|
import { PreferencesGroup, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
|
||||||
PreferencesGroup,
|
|
||||||
PreferencesSegment,
|
|
||||||
} from '@/Components/Preferences/PreferencesComponents'
|
|
||||||
import { OfflineSubscription } from '@/Components/Preferences/Panes/Account/OfflineSubscription'
|
import { OfflineSubscription } from '@/Components/Preferences/Panes/Account/OfflineSubscription'
|
||||||
import { WebApplication } from '@/UIModels/Application'
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
@@ -17,25 +14,23 @@ interface IProps {
|
|||||||
extensionsLatestVersions: ExtensionsLatestVersions
|
extensionsLatestVersions: ExtensionsLatestVersions
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Advanced: FunctionalComponent<IProps> = observer(
|
export const Advanced: FunctionalComponent<IProps> = observer(({ application, appState, extensionsLatestVersions }) => {
|
||||||
({ application, appState, extensionsLatestVersions }) => {
|
return (
|
||||||
return (
|
<PreferencesGroup>
|
||||||
<PreferencesGroup>
|
<PreferencesSegment>
|
||||||
<PreferencesSegment>
|
<AccordionItem title={'Advanced Settings'}>
|
||||||
<AccordionItem title={'Advanced Settings'}>
|
<div className="flex flex-row items-center">
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex-grow flex flex-col">
|
||||||
<div className="flex-grow flex flex-col">
|
<OfflineSubscription application={application} appState={appState} />
|
||||||
<OfflineSubscription application={application} appState={appState} />
|
<Extensions
|
||||||
<Extensions
|
className={'mt-3'}
|
||||||
className={'mt-3'}
|
application={application}
|
||||||
application={application}
|
extensionsLatestVersions={extensionsLatestVersions}
|
||||||
extensionsLatestVersions={extensionsLatestVersions}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</AccordionItem>
|
</div>
|
||||||
</PreferencesSegment>
|
</AccordionItem>
|
||||||
</PreferencesGroup>
|
</PreferencesSegment>
|
||||||
)
|
</PreferencesGroup>
|
||||||
},
|
)
|
||||||
)
|
})
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { AccountMenuPane } from '@/Components/AccountMenu'
|
import { AccountMenuPane } from '@/Components/AccountMenu'
|
||||||
import { Button } from '@/Components/Button/Button'
|
import { Button } from '@/Components/Button/Button'
|
||||||
import {
|
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||||
PreferencesGroup,
|
|
||||||
PreferencesSegment,
|
|
||||||
Text,
|
|
||||||
Title,
|
|
||||||
} from '@/Components/Preferences/PreferencesComponents'
|
|
||||||
import { WebApplication } from '@/UIModels/Application'
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
import { AppState } from '@/UIModels/AppState'
|
import { AppState } from '@/UIModels/AppState'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
@@ -35,21 +30,12 @@ export const Authentication: FunctionComponent<{
|
|||||||
<AccountIllustration className="mb-3" />
|
<AccountIllustration className="mb-3" />
|
||||||
<Title>You're not signed in</Title>
|
<Title>You're not signed in</Title>
|
||||||
<Text className="text-center mb-3">
|
<Text className="text-center mb-3">
|
||||||
Sign in to sync your notes and preferences across all your devices and enable end-to-end
|
Sign in to sync your notes and preferences across all your devices and enable end-to-end encryption.
|
||||||
encryption.
|
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button variant="primary" label="Create free account" onClick={clickRegister} className="mb-3" />
|
||||||
variant="primary"
|
|
||||||
label="Create free account"
|
|
||||||
onClick={clickRegister}
|
|
||||||
className="mb-3"
|
|
||||||
/>
|
|
||||||
<div className="text-input">
|
<div className="text-input">
|
||||||
Already have an account?{' '}
|
Already have an account?{' '}
|
||||||
<button
|
<button className="border-0 p-0 bg-default color-info underline cursor-pointer" onClick={clickSignIn}>
|
||||||
className="border-0 p-0 bg-default color-info underline cursor-pointer"
|
|
||||||
onClick={clickSignIn}
|
|
||||||
>
|
|
||||||
Sign in
|
Sign in
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ const labelClassName = 'block mb-1'
|
|||||||
|
|
||||||
const inputClassName = 'sk-input contrast'
|
const inputClassName = 'sk-input contrast'
|
||||||
|
|
||||||
export const ChangeEmailForm: FunctionalComponent<Props> = ({
|
export const ChangeEmailForm: FunctionalComponent<Props> = ({ setNewEmail, setCurrentPassword }) => {
|
||||||
setNewEmail,
|
|
||||||
setCurrentPassword,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col">
|
<div className="w-full flex flex-col">
|
||||||
<div className="mt-2 mb-3">
|
<div className="mt-2 mb-3">
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ export const ChangeEmailSuccess: FunctionalComponent = () => {
|
|||||||
<div>
|
<div>
|
||||||
<div className={'sk-label sk-bold info mt-2'}>Your email has been successfully changed.</div>
|
<div className={'sk-label sk-bold info mt-2'}>Your email has been successfully changed.</div>
|
||||||
<p className={'sk-p'}>
|
<p className={'sk-p'}>
|
||||||
Please ensure you are running the latest version of Standard Notes on all platforms to
|
Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum
|
||||||
ensure maximum compatibility.
|
compatibility.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -63,9 +63,7 @@ export const ChangeEmail: FunctionalComponent<Props> = ({ onCloseDialog, applica
|
|||||||
const validateNewEmail = async () => {
|
const validateNewEmail = async () => {
|
||||||
if (!isEmailValid(newEmail)) {
|
if (!isEmailValid(newEmail)) {
|
||||||
applicationAlertService
|
applicationAlertService
|
||||||
.alert(
|
.alert('The email you entered has an invalid format. Please review your input and try again.')
|
||||||
'The email you entered has an invalid format. Please review your input and try again.',
|
|
||||||
)
|
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -95,9 +93,7 @@ export const ChangeEmail: FunctionalComponent<Props> = ({ onCloseDialog, applica
|
|||||||
|
|
||||||
const dismiss = () => {
|
const dismiss = () => {
|
||||||
if (lockContinue) {
|
if (lockContinue) {
|
||||||
applicationAlertService
|
applicationAlertService.alert('Cannot close window until pending tasks are complete.').catch(console.error)
|
||||||
.alert('Cannot close window until pending tasks are complete.')
|
|
||||||
.catch(console.error)
|
|
||||||
} else {
|
} else {
|
||||||
onCloseDialog()
|
onCloseDialog()
|
||||||
}
|
}
|
||||||
@@ -139,9 +135,7 @@ export const ChangeEmail: FunctionalComponent<Props> = ({ onCloseDialog, applica
|
|||||||
|
|
||||||
const handleDialogClose = () => {
|
const handleDialogClose = () => {
|
||||||
if (lockContinue) {
|
if (lockContinue) {
|
||||||
applicationAlertService
|
applicationAlertService.alert('Cannot close window until pending tasks are complete.').catch(console.error)
|
||||||
.alert('Cannot close window until pending tasks are complete.')
|
|
||||||
.catch(console.error)
|
|
||||||
} else {
|
} else {
|
||||||
onCloseDialog()
|
onCloseDialog()
|
||||||
}
|
}
|
||||||
@@ -158,12 +152,7 @@ export const ChangeEmail: FunctionalComponent<Props> = ({ onCloseDialog, applica
|
|||||||
{currentStep === Steps.FinishStep && <ChangeEmailSuccess />}
|
{currentStep === Steps.FinishStep && <ChangeEmailSuccess />}
|
||||||
</ModalDialogDescription>
|
</ModalDialogDescription>
|
||||||
<ModalDialogButtons className="px-4.5">
|
<ModalDialogButtons className="px-4.5">
|
||||||
<Button
|
<Button className="min-w-20" variant="primary" label={submitButtonTitle} onClick={handleSubmit} />
|
||||||
className="min-w-20"
|
|
||||||
variant="primary"
|
|
||||||
label={submitButtonTitle}
|
|
||||||
onClick={handleSubmit}
|
|
||||||
/>
|
|
||||||
</ModalDialogButtons>
|
</ModalDialogButtons>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,10 +30,7 @@ export const Credentials: FunctionComponent<Props> = observer(({ application }:
|
|||||||
const passwordCreatedOn = dateToLocalizedString(passwordCreatedAtTimestamp)
|
const passwordCreatedOn = dateToLocalizedString(passwordCreatedAtTimestamp)
|
||||||
|
|
||||||
const presentPasswordWizard = useCallback(() => {
|
const presentPasswordWizard = useCallback(() => {
|
||||||
render(
|
render(<PasswordWizard application={application} />, document.body.appendChild(document.createElement('div')))
|
||||||
<PasswordWizard application={application} />,
|
|
||||||
document.body.appendChild(document.createElement('div')),
|
|
||||||
)
|
|
||||||
}, [application])
|
}, [application])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -57,17 +54,9 @@ export const Credentials: FunctionComponent<Props> = observer(({ application }:
|
|||||||
<Text>
|
<Text>
|
||||||
Current password was set on <span className="font-bold">{passwordCreatedOn}</span>
|
Current password was set on <span className="font-bold">{passwordCreatedOn}</span>
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button className="min-w-20 mt-3" variant="normal" label="Change password" onClick={presentPasswordWizard} />
|
||||||
className="min-w-20 mt-3"
|
|
||||||
variant="normal"
|
|
||||||
label="Change password"
|
|
||||||
onClick={presentPasswordWizard}
|
|
||||||
/>
|
|
||||||
{isChangeEmailDialogOpen && (
|
{isChangeEmailDialogOpen && (
|
||||||
<ChangeEmail
|
<ChangeEmail onCloseDialog={() => setIsChangeEmailDialogOpen(false)} application={application} />
|
||||||
onCloseDialog={() => setIsChangeEmailDialogOpen(false)}
|
|
||||||
application={application}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
</PreferencesGroup>
|
</PreferencesGroup>
|
||||||
|
|||||||
@@ -28,9 +28,7 @@ export const OfflineSubscription: FunctionalComponent<IProps> = observer(({ appl
|
|||||||
}, [application])
|
}, [application])
|
||||||
|
|
||||||
const shouldShowOfflineSubscription = () => {
|
const shouldShowOfflineSubscription = () => {
|
||||||
return (
|
return !application.hasAccount() || application.isThirdPartyHostUsed() || hasUserPreviouslyStoredCode
|
||||||
!application.hasAccount() || application.isThirdPartyHostUsed() || hasUserPreviouslyStoredCode
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubscriptionCodeSubmit = async (event: Event) => {
|
const handleSubscriptionCodeSubmit = async (event: Event) => {
|
||||||
@@ -98,8 +96,8 @@ export const OfflineSubscription: FunctionalComponent<IProps> = observer(({ appl
|
|||||||
</div>
|
</div>
|
||||||
{(isSuccessfullyActivated || isSuccessfullyRemoved) && (
|
{(isSuccessfullyActivated || isSuccessfullyRemoved) && (
|
||||||
<div className={'mt-3 mb-3 info'}>
|
<div className={'mt-3 mb-3 info'}>
|
||||||
Your offline subscription code has been successfully{' '}
|
Your offline subscription code has been successfully {isSuccessfullyActivated ? 'activated' : 'removed'}
|
||||||
{isSuccessfullyActivated ? 'activated' : 'removed'}.
|
.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hasUserPreviouslyStoredCode && (
|
{hasUserPreviouslyStoredCode && (
|
||||||
|
|||||||
@@ -33,11 +33,7 @@ const SignOutView: FunctionComponent<{
|
|||||||
appState.accountMenu.setOtherSessionsSignOut(true)
|
appState.accountMenu.setOtherSessionsSignOut(true)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button variant="normal" label="Manage sessions" onClick={() => appState.openSessionsModal()} />
|
||||||
variant="normal"
|
|
||||||
label="Manage sessions"
|
|
||||||
onClick={() => appState.openSessionsModal()}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ export const NoSubscription: FunctionalComponent<{
|
|||||||
const [purchaseFlowError, setPurchaseFlowError] = useState<string | undefined>(undefined)
|
const [purchaseFlowError, setPurchaseFlowError] = useState<string | undefined>(undefined)
|
||||||
|
|
||||||
const onPurchaseClick = async () => {
|
const onPurchaseClick = async () => {
|
||||||
const errorMessage =
|
const errorMessage = 'There was an error when attempting to redirect you to the subscription page.'
|
||||||
'There was an error when attempting to redirect you to the subscription page.'
|
|
||||||
setIsLoadingPurchaseFlow(true)
|
setIsLoadingPurchaseFlow(true)
|
||||||
try {
|
try {
|
||||||
if (!(await loadPurchaseFlowUrl(application))) {
|
if (!(await loadPurchaseFlowUrl(application))) {
|
||||||
@@ -32,18 +31,9 @@ export const NoSubscription: FunctionalComponent<{
|
|||||||
{isLoadingPurchaseFlow && <Text>Redirecting you to the subscription page...</Text>}
|
{isLoadingPurchaseFlow && <Text>Redirecting you to the subscription page...</Text>}
|
||||||
{purchaseFlowError && <Text className="color-danger">{purchaseFlowError}</Text>}
|
{purchaseFlowError && <Text className="color-danger">{purchaseFlowError}</Text>}
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<LinkButton
|
<LinkButton className="min-w-20 mt-3 mr-3" label="Learn More" link={window.plansUrl as string} />
|
||||||
className="min-w-20 mt-3 mr-3"
|
|
||||||
label="Learn More"
|
|
||||||
link={window.plansUrl as string}
|
|
||||||
/>
|
|
||||||
{application.hasAccount() && (
|
{application.hasAccount() && (
|
||||||
<Button
|
<Button className="min-w-20 mt-3" variant="primary" label="Subscribe" onClick={onPurchaseClick} />
|
||||||
className="min-w-20 mt-3"
|
|
||||||
variant="primary"
|
|
||||||
label="Subscribe"
|
|
||||||
onClick={onPurchaseClick}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import { PreferencesGroup, PreferencesSegment, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||||
PreferencesGroup,
|
|
||||||
PreferencesSegment,
|
|
||||||
Title,
|
|
||||||
} from '@/Components/Preferences/PreferencesComponents'
|
|
||||||
import { WebApplication } from '@/UIModels/Application'
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
import { SubscriptionInformation } from './SubscriptionInformation'
|
import { SubscriptionInformation } from './SubscriptionInformation'
|
||||||
import { NoSubscription } from './NoSubscription'
|
import { NoSubscription } from './NoSubscription'
|
||||||
@@ -15,31 +11,26 @@ type Props = {
|
|||||||
appState: AppState
|
appState: AppState
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Subscription: FunctionComponent<Props> = observer(
|
export const Subscription: FunctionComponent<Props> = observer(({ application, appState }: Props) => {
|
||||||
({ application, appState }: Props) => {
|
const subscriptionState = appState.subscription
|
||||||
const subscriptionState = appState.subscription
|
const { userSubscription } = subscriptionState
|
||||||
const { userSubscription } = subscriptionState
|
|
||||||
|
|
||||||
const now = new Date().getTime()
|
const now = new Date().getTime()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PreferencesGroup>
|
<PreferencesGroup>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<div className="flex-grow flex flex-col">
|
<div className="flex-grow flex flex-col">
|
||||||
<Title>Subscription</Title>
|
<Title>Subscription</Title>
|
||||||
{userSubscription && userSubscription.endsAt > now ? (
|
{userSubscription && userSubscription.endsAt > now ? (
|
||||||
<SubscriptionInformation
|
<SubscriptionInformation subscriptionState={subscriptionState} application={application} />
|
||||||
subscriptionState={subscriptionState}
|
) : (
|
||||||
application={application}
|
<NoSubscription application={application} />
|
||||||
/>
|
)}
|
||||||
) : (
|
|
||||||
<NoSubscription application={application} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</PreferencesSegment>
|
</div>
|
||||||
</PreferencesGroup>
|
</PreferencesSegment>
|
||||||
)
|
</PreferencesGroup>
|
||||||
},
|
)
|
||||||
)
|
})
|
||||||
|
|||||||
@@ -10,49 +10,16 @@ type Props = {
|
|||||||
application: WebApplication
|
application: WebApplication
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusText = observer(
|
const StatusText = observer(({ subscriptionState }: { subscriptionState: Props['subscriptionState'] }) => {
|
||||||
({ subscriptionState }: { subscriptionState: Props['subscriptionState'] }) => {
|
const {
|
||||||
const {
|
userSubscriptionName,
|
||||||
userSubscriptionName,
|
userSubscriptionExpirationDate,
|
||||||
userSubscriptionExpirationDate,
|
isUserSubscriptionExpired,
|
||||||
isUserSubscriptionExpired,
|
isUserSubscriptionCanceled,
|
||||||
isUserSubscriptionCanceled,
|
} = subscriptionState
|
||||||
} = subscriptionState
|
const expirationDateString = userSubscriptionExpirationDate?.toLocaleString()
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (isUserSubscriptionCanceled) {
|
||||||
return (
|
return (
|
||||||
<Text className="mt-1">
|
<Text className="mt-1">
|
||||||
Your{' '}
|
Your{' '}
|
||||||
@@ -60,11 +27,42 @@ const StatusText = observer(
|
|||||||
Standard Notes{userSubscriptionName ? ' ' : ''}
|
Standard Notes{userSubscriptionName ? ' ' : ''}
|
||||||
{userSubscriptionName}
|
{userSubscriptionName}
|
||||||
</span>{' '}
|
</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>
|
</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) => {
|
export const SubscriptionInformation = observer(({ subscriptionState, application }: Props) => {
|
||||||
const manageSubscription = async () => {
|
const manageSubscription = async () => {
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import {
|
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||||
PreferencesGroup,
|
|
||||||
PreferencesSegment,
|
|
||||||
Text,
|
|
||||||
Title,
|
|
||||||
} from '@/Components/Preferences/PreferencesComponents'
|
|
||||||
import { Button } from '@/Components/Button/Button'
|
import { Button } from '@/Components/Button/Button'
|
||||||
import { SyncQueueStrategy, dateToLocalizedString } from '@standardnotes/snjs'
|
import { SyncQueueStrategy, dateToLocalizedString } from '@standardnotes/snjs'
|
||||||
import { STRING_GENERIC_SYNC_ERROR } from '@/Strings'
|
import { STRING_GENERIC_SYNC_ERROR } from '@/Strings'
|
||||||
@@ -22,9 +17,7 @@ export const formatLastSyncDate = (lastUpdatedDate: Date) => {
|
|||||||
|
|
||||||
export const Sync: FunctionComponent<Props> = observer(({ application }: Props) => {
|
export const Sync: FunctionComponent<Props> = observer(({ application }: Props) => {
|
||||||
const [isSyncingInProgress, setIsSyncingInProgress] = useState(false)
|
const [isSyncingInProgress, setIsSyncingInProgress] = useState(false)
|
||||||
const [lastSyncDate, setLastSyncDate] = useState(
|
const [lastSyncDate, setLastSyncDate] = useState(formatLastSyncDate(application.sync.getLastSyncDate() as Date))
|
||||||
formatLastSyncDate(application.sync.getLastSyncDate() as Date),
|
|
||||||
)
|
|
||||||
|
|
||||||
const doSynchronization = async () => {
|
const doSynchronization = async () => {
|
||||||
setIsSyncingInProgress(true)
|
setIsSyncingInProgress(true)
|
||||||
|
|||||||
@@ -3,14 +3,7 @@ import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
|||||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch'
|
||||||
import { WebApplication } from '@/UIModels/Application'
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
import {
|
import { ContentType, FeatureIdentifier, FeatureStatus, PrefKey, GetFeatures, SNTheme } from '@standardnotes/snjs'
|
||||||
ContentType,
|
|
||||||
FeatureIdentifier,
|
|
||||||
FeatureStatus,
|
|
||||||
PrefKey,
|
|
||||||
GetFeatures,
|
|
||||||
SNTheme,
|
|
||||||
} from '@standardnotes/snjs'
|
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { useEffect, useState } from 'preact/hooks'
|
import { useEffect, useState } from 'preact/hooks'
|
||||||
@@ -31,8 +24,7 @@ type Props = {
|
|||||||
export const Appearance: FunctionComponent<Props> = observer(({ application }) => {
|
export const Appearance: FunctionComponent<Props> = observer(({ application }) => {
|
||||||
const premiumModal = usePremiumModal()
|
const premiumModal = usePremiumModal()
|
||||||
const isEntitledToMidnightTheme =
|
const isEntitledToMidnightTheme =
|
||||||
application.features.getFeatureStatus(FeatureIdentifier.MidnightTheme) ===
|
application.features.getFeatureStatus(FeatureIdentifier.MidnightTheme) === FeatureStatus.Entitled
|
||||||
FeatureStatus.Entitled
|
|
||||||
|
|
||||||
const [themeItems, setThemeItems] = useState<DropdownItem[]>([])
|
const [themeItems, setThemeItems] = useState<DropdownItem[]>([])
|
||||||
const [autoLightTheme, setAutoLightTheme] = useState<string>(
|
const [autoLightTheme, setAutoLightTheme] = useState<string>(
|
||||||
@@ -100,9 +92,7 @@ export const Appearance: FunctionComponent<Props> = observer(({ application }) =
|
|||||||
if (item.icon === 'premium-feature') {
|
if (item.icon === 'premium-feature') {
|
||||||
premiumModal.activate(`${item.label} theme`)
|
premiumModal.activate(`${item.label} theme`)
|
||||||
} else {
|
} else {
|
||||||
application
|
application.setPreference(PrefKey.AutoLightThemeIdentifier, value as FeatureIdentifier).catch(console.error)
|
||||||
.setPreference(PrefKey.AutoLightThemeIdentifier, value as FeatureIdentifier)
|
|
||||||
.catch(console.error)
|
|
||||||
setAutoLightTheme(value)
|
setAutoLightTheme(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,9 +101,7 @@ export const Appearance: FunctionComponent<Props> = observer(({ application }) =
|
|||||||
if (item.icon === 'premium-feature') {
|
if (item.icon === 'premium-feature') {
|
||||||
premiumModal.activate(`${item.label} theme`)
|
premiumModal.activate(`${item.label} theme`)
|
||||||
} else {
|
} else {
|
||||||
application
|
application.setPreference(PrefKey.AutoDarkThemeIdentifier, value as FeatureIdentifier).catch(console.error)
|
||||||
.setPreference(PrefKey.AutoDarkThemeIdentifier, value as FeatureIdentifier)
|
|
||||||
.catch(console.error)
|
|
||||||
setAutoDarkTheme(value)
|
setAutoDarkTheme(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,18 +66,13 @@ export const CloudBackupProvider: FunctionComponent<Props> = ({
|
|||||||
const performBackupNow = async () => {
|
const performBackupNow = async () => {
|
||||||
// A backup is performed anytime the setting is updated with the integration token, so just update it here
|
// A backup is performed anytime the setting is updated with the integration token, so just update it here
|
||||||
try {
|
try {
|
||||||
await application.settings.updateSetting(
|
await application.settings.updateSetting(backupFrequencySettingName, backupFrequency as string)
|
||||||
backupFrequencySettingName,
|
|
||||||
backupFrequency as string,
|
|
||||||
)
|
|
||||||
void application.alertService.alert(
|
void application.alertService.alert(
|
||||||
'A backup has been triggered for this provider. Please allow a couple minutes for your backup to be processed.',
|
'A backup has been triggered for this provider. Please allow a couple minutes for your backup to be processed.',
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
application.alertService
|
application.alertService
|
||||||
.alert(
|
.alert('There was an error while trying to trigger a backup for this provider. Please try again.')
|
||||||
'There was an error while trying to trigger a backup for this provider. Please try again.',
|
|
||||||
)
|
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,8 +179,8 @@ export const CloudBackupProvider: FunctionComponent<Props> = ({
|
|||||||
{authBegan && (
|
{authBegan && (
|
||||||
<div>
|
<div>
|
||||||
<p className="sk-panel-row">
|
<p className="sk-panel-row">
|
||||||
Complete authentication from the newly opened window. Upon completion, a confirmation
|
Complete authentication from the newly opened window. Upon completion, a confirmation code will be
|
||||||
code will be displayed. Enter this code below:
|
displayed. Enter this code below:
|
||||||
</p>
|
</p>
|
||||||
<div className={'mt-1'}>
|
<div className={'mt-1'}>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -22,11 +22,7 @@ import { Switch } from '@/Components/Switch'
|
|||||||
import { convertStringifiedBooleanToBoolean } from '@/Utils'
|
import { convertStringifiedBooleanToBoolean } from '@/Utils'
|
||||||
import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
|
import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
|
||||||
|
|
||||||
const providerData = [
|
const providerData = [{ name: CloudProvider.Dropbox }, { name: CloudProvider.Google }, { name: CloudProvider.OneDrive }]
|
||||||
{ name: CloudProvider.Dropbox },
|
|
||||||
{ name: CloudProvider.Google },
|
|
||||||
{ name: CloudProvider.OneDrive },
|
|
||||||
]
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -62,20 +58,12 @@ export const CloudLink: FunctionComponent<Props> = ({ application }) => {
|
|||||||
}, [application])
|
}, [application])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const dailyDropboxBackupStatus = application.features.getFeatureStatus(
|
const dailyDropboxBackupStatus = application.features.getFeatureStatus(FeatureIdentifier.DailyDropboxBackup)
|
||||||
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)
|
setIsEntitledToCloudBackups(isCloudBackupsAllowed)
|
||||||
loadIsFailedCloudBackupEmailMutedSetting().catch(console.error)
|
loadIsFailedCloudBackupEmailMutedSetting().catch(console.error)
|
||||||
@@ -114,9 +102,8 @@ export const CloudLink: FunctionComponent<Props> = ({ application }) => {
|
|||||||
{!isEntitledToCloudBackups && (
|
{!isEntitledToCloudBackups && (
|
||||||
<>
|
<>
|
||||||
<Text>
|
<Text>
|
||||||
A <span className={'font-bold'}>Plus</span> or{' '}
|
A <span className={'font-bold'}>Plus</span> or <span className={'font-bold'}>Pro</span> subscription plan
|
||||||
<span className={'font-bold'}>Pro</span> subscription plan is required to enable Cloud
|
is required to enable Cloud Backups.{' '}
|
||||||
Backups.{' '}
|
|
||||||
<a target="_blank" href="https://standardnotes.com/features">
|
<a target="_blank" href="https://standardnotes.com/features">
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
@@ -127,8 +114,8 @@ export const CloudLink: FunctionComponent<Props> = ({ application }) => {
|
|||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<Text className={additionalClass}>
|
<Text className={additionalClass}>
|
||||||
Configure the integrations below to enable automatic daily backups of your encrypted
|
Configure the integrations below to enable automatic daily backups of your encrypted data set to your
|
||||||
data set to your third-party cloud provider.
|
third-party cloud provider.
|
||||||
</Text>
|
</Text>
|
||||||
<div>
|
<div>
|
||||||
<HorizontalSeparator classes={`mt-3 mb-3 ${additionalClass}`} />
|
<HorizontalSeparator classes={`mt-3 mb-3 ${additionalClass}`} />
|
||||||
|
|||||||
@@ -160,8 +160,7 @@ export const DataBackups = observer(({ application, appState }: Props) => {
|
|||||||
|
|
||||||
{!isDesktopApplication() && (
|
{!isDesktopApplication() && (
|
||||||
<Text className="mb-3">
|
<Text className="mb-3">
|
||||||
Backups are automatically created on desktop and can be managed via the "Backups"
|
Backups are automatically created on desktop and can be managed via the "Backups" top-level menu.
|
||||||
top-level menu.
|
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -171,43 +170,25 @@ export const DataBackups = observer(({ application, appState }: Props) => {
|
|||||||
<form className="sk-panel-form sk-panel-row">
|
<form className="sk-panel-form sk-panel-row">
|
||||||
<div className="sk-input-group">
|
<div className="sk-input-group">
|
||||||
<label className="sk-horizontal-group tight">
|
<label className="sk-horizontal-group tight">
|
||||||
<input
|
<input type="radio" onChange={() => setIsBackupEncrypted(true)} checked={isBackupEncrypted} />
|
||||||
type="radio"
|
|
||||||
onChange={() => setIsBackupEncrypted(true)}
|
|
||||||
checked={isBackupEncrypted}
|
|
||||||
/>
|
|
||||||
<Subtitle>Encrypted</Subtitle>
|
<Subtitle>Encrypted</Subtitle>
|
||||||
</label>
|
</label>
|
||||||
<label className="sk-horizontal-group tight">
|
<label className="sk-horizontal-group tight">
|
||||||
<input
|
<input type="radio" onChange={() => setIsBackupEncrypted(false)} checked={!isBackupEncrypted} />
|
||||||
type="radio"
|
|
||||||
onChange={() => setIsBackupEncrypted(false)}
|
|
||||||
checked={!isBackupEncrypted}
|
|
||||||
/>
|
|
||||||
<Subtitle>Decrypted</Subtitle>
|
<Subtitle>Decrypted</Subtitle>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button variant="normal" onClick={downloadDataArchive} label="Download backup" className="mt-2" />
|
||||||
variant="normal"
|
|
||||||
onClick={downloadDataArchive}
|
|
||||||
label="Download backup"
|
|
||||||
className="mt-2"
|
|
||||||
/>
|
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Subtitle>Import a previously saved backup file</Subtitle>
|
<Subtitle>Import a previously saved backup file</Subtitle>
|
||||||
|
|
||||||
<div class="flex flex-row items-center mt-3">
|
<div class="flex flex-row items-center mt-3">
|
||||||
<Button variant="normal" label="Import backup" onClick={handleImportFile} />
|
<Button variant="normal" label="Import backup" onClick={handleImportFile} />
|
||||||
<input
|
<input type="file" ref={fileInputRef} onChange={importFileSelected} className="hidden" />
|
||||||
type="file"
|
|
||||||
ref={fileInputRef}
|
|
||||||
onChange={importFileSelected}
|
|
||||||
className="hidden"
|
|
||||||
/>
|
|
||||||
{isImportDataLoading && <div className="sk-spinner normal info ml-4" />}
|
{isImportDataLoading && <div className="sk-spinner normal info ml-4" />}
|
||||||
</div>
|
</div>
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ type Props = {
|
|||||||
|
|
||||||
export const EmailBackups = observer(({ application }: Props) => {
|
export const EmailBackups = observer(({ application }: Props) => {
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [emailFrequency, setEmailFrequency] = useState<EmailBackupFrequency>(
|
const [emailFrequency, setEmailFrequency] = useState<EmailBackupFrequency>(EmailBackupFrequency.Disabled)
|
||||||
EmailBackupFrequency.Disabled,
|
|
||||||
)
|
|
||||||
const [emailFrequencyOptions, setEmailFrequencyOptions] = useState<DropdownItem[]>([])
|
const [emailFrequencyOptions, setEmailFrequencyOptions] = useState<DropdownItem[]>([])
|
||||||
const [isFailedBackupEmailMuted, setIsFailedBackupEmailMuted] = useState(true)
|
const [isFailedBackupEmailMuted, setIsFailedBackupEmailMuted] = useState(true)
|
||||||
const [isEntitledToEmailBackups, setIsEntitledToEmailBackups] = useState(false)
|
const [isEntitledToEmailBackups, setIsEntitledToEmailBackups] = useState(false)
|
||||||
@@ -64,9 +62,7 @@ export const EmailBackups = observer(({ application }: Props) => {
|
|||||||
}, [application])
|
}, [application])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const emailBackupsFeatureStatus = application.features.getFeatureStatus(
|
const emailBackupsFeatureStatus = application.features.getFeatureStatus(FeatureIdentifier.DailyEmailBackup)
|
||||||
FeatureIdentifier.DailyEmailBackup,
|
|
||||||
)
|
|
||||||
setIsEntitledToEmailBackups(emailBackupsFeatureStatus === FeatureStatus.Entitled)
|
setIsEntitledToEmailBackups(emailBackupsFeatureStatus === FeatureStatus.Entitled)
|
||||||
|
|
||||||
const frequencyOptions = []
|
const frequencyOptions = []
|
||||||
@@ -109,10 +105,7 @@ export const EmailBackups = observer(({ application }: Props) => {
|
|||||||
const previousValue = isFailedBackupEmailMuted
|
const previousValue = isFailedBackupEmailMuted
|
||||||
setIsFailedBackupEmailMuted(!isFailedBackupEmailMuted)
|
setIsFailedBackupEmailMuted(!isFailedBackupEmailMuted)
|
||||||
|
|
||||||
const updateResult = await updateSetting(
|
const updateResult = await updateSetting(SettingName.MuteFailedBackupsEmails, `${!isFailedBackupEmailMuted}`)
|
||||||
SettingName.MuteFailedBackupsEmails,
|
|
||||||
`${!isFailedBackupEmailMuted}`,
|
|
||||||
)
|
|
||||||
if (!updateResult) {
|
if (!updateResult) {
|
||||||
setIsFailedBackupEmailMuted(previousValue)
|
setIsFailedBackupEmailMuted(previousValue)
|
||||||
}
|
}
|
||||||
@@ -132,9 +125,8 @@ export const EmailBackups = observer(({ application }: Props) => {
|
|||||||
{!isEntitledToEmailBackups && (
|
{!isEntitledToEmailBackups && (
|
||||||
<>
|
<>
|
||||||
<Text>
|
<Text>
|
||||||
A <span className={'font-bold'}>Plus</span> or{' '}
|
A <span className={'font-bold'}>Plus</span> or <span className={'font-bold'}>Pro</span> subscription plan
|
||||||
<span className={'font-bold'}>Pro</span> subscription plan is required to enable Email
|
is required to enable Email Backups.{' '}
|
||||||
Backups.{' '}
|
|
||||||
<a target="_blank" href="https://standardnotes.com/features">
|
<a target="_blank" href="https://standardnotes.com/features">
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
@@ -146,8 +138,7 @@ export const EmailBackups = observer(({ application }: Props) => {
|
|||||||
<div className={isEntitledToEmailBackups ? '' : 'faded cursor-default pointer-events-none'}>
|
<div className={isEntitledToEmailBackups ? '' : 'faded cursor-default pointer-events-none'}>
|
||||||
{!isDesktopApplication() && (
|
{!isDesktopApplication() && (
|
||||||
<Text className="mb-3">
|
<Text className="mb-3">
|
||||||
Daily encrypted email backups of your entire data set delivered directly to your
|
Daily encrypted email backups of your entire data set delivered directly to your inbox.
|
||||||
inbox.
|
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<Subtitle>Email frequency</Subtitle>
|
<Subtitle>Email frequency</Subtitle>
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ export const CloudLink: FunctionComponent = () => (
|
|||||||
<div className="h-2 w-full" />
|
<div className="h-2 w-full" />
|
||||||
<Subtitle>Who can read my private notes?</Subtitle>
|
<Subtitle>Who can read my private notes?</Subtitle>
|
||||||
<Text>
|
<Text>
|
||||||
Quite simply: no one but you. Not us, not your ISP, not a hacker, and not a government
|
Quite simply: no one but you. Not us, not your ISP, not a hacker, and not a government agency. As long as you
|
||||||
agency. As long as you keep your password safe, and your password is reasonably strong,
|
keep your password safe, and your password is reasonably strong, then you are the only person in the world
|
||||||
then you are the only person in the world with the ability to decrypt your notes. For more
|
with the ability to decrypt your notes. For more on how we handle your privacy and security, check out our
|
||||||
on how we handle your privacy and security, check out our easy to read{' '}
|
easy to read{' '}
|
||||||
<a target="_blank" href="https://standardnotes.com/privacy">
|
<a target="_blank" href="https://standardnotes.com/privacy">
|
||||||
Privacy Manifesto.
|
Privacy Manifesto.
|
||||||
</a>
|
</a>
|
||||||
@@ -29,21 +29,17 @@ export const CloudLink: FunctionComponent = () => (
|
|||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Subtitle>Can I collaborate with others on a note?</Subtitle>
|
<Subtitle>Can I collaborate with others on a note?</Subtitle>
|
||||||
<Text>
|
<Text>
|
||||||
Because of our encrypted architecture, Standard Notes does not currently provide a
|
Because of our encrypted architecture, Standard Notes does not currently provide a real-time collaboration
|
||||||
real-time collaboration solution. Multiple users can share the same account however, but
|
solution. Multiple users can share the same account however, but editing at the same time may result in sync
|
||||||
editing at the same time may result in sync conflicts, which may result in the duplication
|
conflicts, which may result in the duplication of notes.
|
||||||
of notes.
|
|
||||||
</Text>
|
</Text>
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Subtitle>Can I use Standard Notes totally offline?</Subtitle>
|
<Subtitle>Can I use Standard Notes totally offline?</Subtitle>
|
||||||
<Text>
|
<Text>
|
||||||
Standard Notes can be used totally offline without an account, and without an internet
|
Standard Notes can be used totally offline without an account, and without an internet connection. You can
|
||||||
connection. You can find{' '}
|
find{' '}
|
||||||
<a
|
<a target="_blank" href="https://standardnotes.com/help/59/can-i-use-standard-notes-totally-offline">
|
||||||
target="_blank"
|
|
||||||
href="https://standardnotes.com/help/59/can-i-use-standard-notes-totally-offline"
|
|
||||||
>
|
|
||||||
more details here.
|
more details here.
|
||||||
</a>
|
</a>
|
||||||
</Text>
|
</Text>
|
||||||
@@ -57,38 +53,25 @@ export const CloudLink: FunctionComponent = () => (
|
|||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Title>Community forum</Title>
|
<Title>Community forum</Title>
|
||||||
<Text>
|
<Text>
|
||||||
If you have an issue, found a bug or want to suggest a feature, you can browse or post to
|
If you have an issue, found a bug or want to suggest a feature, you can browse or post to the forum. It’s
|
||||||
the forum. It’s recommended for non-account related issues. Please read our{' '}
|
recommended for non-account related issues. Please read our{' '}
|
||||||
<a target="_blank" href="https://standardnotes.com/longevity/">
|
<a target="_blank" href="https://standardnotes.com/longevity/">
|
||||||
Longevity statement
|
Longevity statement
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
before advocating for a feature request.
|
before advocating for a feature request.
|
||||||
</Text>
|
</Text>
|
||||||
<LinkButton
|
<LinkButton className="mt-3" label="Go to the forum" link="https://forum.standardnotes.org/" />
|
||||||
className="mt-3"
|
|
||||||
label="Go to the forum"
|
|
||||||
link="https://forum.standardnotes.org/"
|
|
||||||
/>
|
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
</PreferencesGroup>
|
</PreferencesGroup>
|
||||||
<PreferencesGroup>
|
<PreferencesGroup>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Title>Community groups</Title>
|
<Title>Community groups</Title>
|
||||||
<Text>
|
<Text>
|
||||||
Want to meet other passionate note-takers and privacy enthusiasts? Want to share your
|
Want to meet other passionate note-takers and privacy enthusiasts? Want to share your feedback with us? Join
|
||||||
feedback with us? Join the Standard Notes community groups for discussions on security,
|
the Standard Notes community groups for discussions on security, themes, editors and more.
|
||||||
themes, editors and more.
|
|
||||||
</Text>
|
</Text>
|
||||||
<LinkButton
|
<LinkButton className="mt-3" link="https://standardnotes.com/slack" label="Join our Slack" />
|
||||||
className="mt-3"
|
<LinkButton className="mt-3" link="https://standardnotes.com/discord" label="Join our Discord" />
|
||||||
link="https://standardnotes.com/slack"
|
|
||||||
label="Join our Slack"
|
|
||||||
/>
|
|
||||||
<LinkButton
|
|
||||||
className="mt-3"
|
|
||||||
link="https://standardnotes.com/discord"
|
|
||||||
label="Join our Discord"
|
|
||||||
/>
|
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
</PreferencesGroup>
|
</PreferencesGroup>
|
||||||
<PreferencesGroup>
|
<PreferencesGroup>
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import { DisplayStringForContentType, SNComponent } from '@standardnotes/snjs'
|
import { DisplayStringForContentType, SNComponent } from '@standardnotes/snjs'
|
||||||
import { Button } from '@/Components/Button/Button'
|
import { Button } from '@/Components/Button/Button'
|
||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import {
|
import { Title, Text, Subtitle, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
|
||||||
Title,
|
|
||||||
Text,
|
|
||||||
Subtitle,
|
|
||||||
PreferencesSegment,
|
|
||||||
} from '@/Components/Preferences/PreferencesComponents'
|
|
||||||
|
|
||||||
export const ConfirmCustomExtension: FunctionComponent<{
|
export const ConfirmCustomExtension: FunctionComponent<{
|
||||||
component: SNComponent
|
component: SNComponent
|
||||||
@@ -59,21 +54,11 @@ export const ConfirmCustomExtension: FunctionComponent<{
|
|||||||
<div className="min-h-3" />
|
<div className="min-h-3" />
|
||||||
|
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<Button
|
<Button className="min-w-20" variant="normal" label="Cancel" onClick={() => callback(false)} />
|
||||||
className="min-w-20"
|
|
||||||
variant="normal"
|
|
||||||
label="Cancel"
|
|
||||||
onClick={() => callback(false)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="min-w-3" />
|
<div className="min-w-3" />
|
||||||
|
|
||||||
<Button
|
<Button className="min-w-20" variant="normal" label="Install" onClick={() => callback(true)} />
|
||||||
className="min-w-20"
|
|
||||||
variant="normal"
|
|
||||||
label="Install"
|
|
||||||
onClick={() => callback(true)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { SNComponent } from '@standardnotes/snjs'
|
import { SNComponent } from '@standardnotes/snjs'
|
||||||
import {
|
import { PreferencesSegment, SubtitleLight, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||||
PreferencesSegment,
|
|
||||||
SubtitleLight,
|
|
||||||
Title,
|
|
||||||
} from '@/Components/Preferences/PreferencesComponents'
|
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch'
|
||||||
import { WebApplication } from '@/UIModels/Application'
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
import { useState } from 'preact/hooks'
|
import { useState } from 'preact/hooks'
|
||||||
@@ -30,12 +26,7 @@ export interface ExtensionItemProps {
|
|||||||
toggleActivate?: (extension: SNComponent) => void
|
toggleActivate?: (extension: SNComponent) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({
|
export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({ application, extension, first, uninstall }) => {
|
||||||
application,
|
|
||||||
extension,
|
|
||||||
first,
|
|
||||||
uninstall,
|
|
||||||
}) => {
|
|
||||||
const [offlineOnly, setOfflineOnly] = useState(extension.offlineOnly ?? false)
|
const [offlineOnly, setOfflineOnly] = useState(extension.offlineOnly ?? false)
|
||||||
const [extensionName, setExtensionName] = useState(extension.name)
|
const [extensionName, setExtensionName] = useState(extension.name)
|
||||||
|
|
||||||
@@ -95,12 +86,7 @@ export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({
|
|||||||
<>
|
<>
|
||||||
<div className="min-h-2" />
|
<div className="min-h-2" />
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<Button
|
<Button className="min-w-20" variant="normal" label="Uninstall" onClick={() => uninstall(extension)} />
|
||||||
className="min-w-20"
|
|
||||||
variant="normal"
|
|
||||||
label="Uninstall"
|
|
||||||
onClick={() => uninstall(extension)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
|
|||||||
@@ -29,10 +29,7 @@ export class ExtensionsLatestVersions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectFeatures(
|
function collectFeatures(features: FeatureDescription[] | undefined, versionMap: Map<string, string>) {
|
||||||
features: FeatureDescription[] | undefined,
|
|
||||||
versionMap: Map<string, string>,
|
|
||||||
) {
|
|
||||||
if (features == undefined) {
|
if (features == undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,7 @@ import { ExtensionItem } from './ExtensionItem'
|
|||||||
import { ConfirmCustomExtension } from './ConfirmCustomExtension'
|
import { ConfirmCustomExtension } from './ConfirmCustomExtension'
|
||||||
|
|
||||||
const loadExtensions = (application: WebApplication) =>
|
const loadExtensions = (application: WebApplication) =>
|
||||||
application.items.getItems([
|
application.items.getItems([ContentType.ActionsExtension, ContentType.Component, ContentType.Theme]) as SNComponent[]
|
||||||
ContentType.ActionsExtension,
|
|
||||||
ContentType.Component,
|
|
||||||
ContentType.Theme,
|
|
||||||
]) as SNComponent[]
|
|
||||||
|
|
||||||
export const Extensions: FunctionComponent<{
|
export const Extensions: FunctionComponent<{
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -23,9 +19,7 @@ export const Extensions: FunctionComponent<{
|
|||||||
className?: string
|
className?: string
|
||||||
}> = observer(({ application, extensionsLatestVersions, className = '' }) => {
|
}> = observer(({ application, extensionsLatestVersions, className = '' }) => {
|
||||||
const [customUrl, setCustomUrl] = useState('')
|
const [customUrl, setCustomUrl] = useState('')
|
||||||
const [confirmableExtension, setConfirmableExtension] = useState<SNComponent | undefined>(
|
const [confirmableExtension, setConfirmableExtension] = useState<SNComponent | undefined>(undefined)
|
||||||
undefined,
|
|
||||||
)
|
|
||||||
const [extensions, setExtensions] = useState(loadExtensions(application))
|
const [extensions, setExtensions] = useState(loadExtensions(application))
|
||||||
|
|
||||||
const confirmableEnd = useRef<HTMLDivElement>(null)
|
const confirmableEnd = useRef<HTMLDivElement>(null)
|
||||||
@@ -122,10 +116,7 @@ export const Extensions: FunctionComponent<{
|
|||||||
)}
|
)}
|
||||||
{confirmableExtension && (
|
{confirmableExtension && (
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<ConfirmCustomExtension
|
<ConfirmCustomExtension component={confirmableExtension} callback={handleConfirmExtensionSubmit} />
|
||||||
component={confirmableExtension}
|
|
||||||
callback={handleConfirmExtensionSubmit}
|
|
||||||
/>
|
|
||||||
<div ref={confirmableEnd} />
|
<div ref={confirmableEnd} />
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -29,19 +29,16 @@ export const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
|||||||
const [experimentalFeatures, setExperimentalFeatures] = useState<ExperimentalFeatureItem[]>([])
|
const [experimentalFeatures, setExperimentalFeatures] = useState<ExperimentalFeatureItem[]>([])
|
||||||
|
|
||||||
const reloadExperimentalFeatures = useCallback(() => {
|
const reloadExperimentalFeatures = useCallback(() => {
|
||||||
const experimentalFeatures = application.features
|
const experimentalFeatures = application.features.getExperimentalFeatures().map((featureIdentifier) => {
|
||||||
.getExperimentalFeatures()
|
const feature = FindNativeFeature(featureIdentifier)
|
||||||
.map((featureIdentifier) => {
|
return {
|
||||||
const feature = FindNativeFeature(featureIdentifier)
|
identifier: featureIdentifier,
|
||||||
return {
|
name: feature?.name ?? featureIdentifier,
|
||||||
identifier: featureIdentifier,
|
description: feature?.description ?? '',
|
||||||
name: feature?.name ?? featureIdentifier,
|
isEnabled: application.features.isExperimentalFeatureEnabled(featureIdentifier),
|
||||||
description: feature?.description ?? '',
|
isEntitled: application.features.getFeatureStatus(featureIdentifier) === FeatureStatus.Entitled,
|
||||||
isEnabled: application.features.isExperimentalFeatureEnabled(featureIdentifier),
|
}
|
||||||
isEntitled:
|
})
|
||||||
application.features.getFeatureStatus(featureIdentifier) === FeatureStatus.Entitled,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setExperimentalFeatures(experimentalFeatures)
|
setExperimentalFeatures(experimentalFeatures)
|
||||||
}, [application.features])
|
}, [application.features])
|
||||||
|
|
||||||
@@ -56,35 +53,32 @@ export const LabsPane: FunctionComponent<Props> = ({ application }) => {
|
|||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Title>Labs</Title>
|
<Title>Labs</Title>
|
||||||
<div>
|
<div>
|
||||||
{experimentalFeatures.map(
|
{experimentalFeatures.map(({ identifier, name, description, isEnabled, isEntitled }, index: number) => {
|
||||||
({ identifier, name, description, isEnabled, isEntitled }, index: number) => {
|
const toggleFeature = () => {
|
||||||
const toggleFeature = () => {
|
if (!isEntitled) {
|
||||||
if (!isEntitled) {
|
premiumModal.activate(name)
|
||||||
premiumModal.activate(name)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
application.features.toggleExperimentalFeature(identifier)
|
|
||||||
reloadExperimentalFeatures()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const showHorizontalSeparator =
|
application.features.toggleExperimentalFeature(identifier)
|
||||||
experimentalFeatures.length > 1 && index !== experimentalFeatures.length - 1
|
reloadExperimentalFeatures()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
const showHorizontalSeparator = experimentalFeatures.length > 1 && index !== experimentalFeatures.length - 1
|
||||||
<>
|
|
||||||
<div className="flex items-center justify-between">
|
return (
|
||||||
<div className="flex flex-col">
|
<>
|
||||||
<Subtitle>{name}</Subtitle>
|
<div className="flex items-center justify-between">
|
||||||
<Text>{description}</Text>
|
<div className="flex flex-col">
|
||||||
</div>
|
<Subtitle>{name}</Subtitle>
|
||||||
<Switch onChange={toggleFeature} checked={isEnabled} />
|
<Text>{description}</Text>
|
||||||
</div>
|
</div>
|
||||||
{showHorizontalSeparator && <HorizontalSeparator classes="mt-5 mb-3" />}
|
<Switch onChange={toggleFeature} checked={isEnabled} />
|
||||||
</>
|
</div>
|
||||||
)
|
{showHorizontalSeparator && <HorizontalSeparator classes="mt-5 mb-3" />}
|
||||||
},
|
</>
|
||||||
)}
|
)
|
||||||
|
})}
|
||||||
{experimentalFeatures.length === 0 && (
|
{experimentalFeatures.length === 0 && (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
|
|||||||
@@ -21,11 +21,7 @@ export const General: FunctionComponent<GeneralProps> = observer(
|
|||||||
<Tools application={application} />
|
<Tools application={application} />
|
||||||
<Defaults application={application} />
|
<Defaults application={application} />
|
||||||
<LabsPane application={application} />
|
<LabsPane application={application} />
|
||||||
<Advanced
|
<Advanced application={application} appState={appState} extensionsLatestVersions={extensionsLatestVersions} />
|
||||||
application={application}
|
|
||||||
appState={appState}
|
|
||||||
extensionsLatestVersions={extensionsLatestVersions}
|
|
||||||
/>
|
|
||||||
</PreferencesPane>
|
</PreferencesPane>
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ export const HelpAndFeedback: FunctionComponent = () => (
|
|||||||
<div className="h-2 w-full" />
|
<div className="h-2 w-full" />
|
||||||
<Subtitle>Who can read my private notes?</Subtitle>
|
<Subtitle>Who can read my private notes?</Subtitle>
|
||||||
<Text>
|
<Text>
|
||||||
Quite simply: no one but you. Not us, not your ISP, not a hacker, and not a government
|
Quite simply: no one but you. Not us, not your ISP, not a hacker, and not a government agency. As long as you
|
||||||
agency. As long as you keep your password safe, and your password is reasonably strong,
|
keep your password safe, and your password is reasonably strong, then you are the only person in the world
|
||||||
then you are the only person in the world with the ability to decrypt your notes. For more
|
with the ability to decrypt your notes. For more on how we handle your privacy and security, check out our
|
||||||
on how we handle your privacy and security, check out our easy to read{' '}
|
easy to read{' '}
|
||||||
<a target="_blank" href="https://standardnotes.com/privacy">
|
<a target="_blank" href="https://standardnotes.com/privacy">
|
||||||
Privacy Manifesto.
|
Privacy Manifesto.
|
||||||
</a>
|
</a>
|
||||||
@@ -29,21 +29,17 @@ export const HelpAndFeedback: FunctionComponent = () => (
|
|||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Subtitle>Can I collaborate with others on a note?</Subtitle>
|
<Subtitle>Can I collaborate with others on a note?</Subtitle>
|
||||||
<Text>
|
<Text>
|
||||||
Because of our encrypted architecture, Standard Notes does not currently provide a
|
Because of our encrypted architecture, Standard Notes does not currently provide a real-time collaboration
|
||||||
real-time collaboration solution. Multiple users can share the same account however, but
|
solution. Multiple users can share the same account however, but editing at the same time may result in sync
|
||||||
editing at the same time may result in sync conflicts, which may result in the duplication
|
conflicts, which may result in the duplication of notes.
|
||||||
of notes.
|
|
||||||
</Text>
|
</Text>
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Subtitle>Can I use Standard Notes totally offline?</Subtitle>
|
<Subtitle>Can I use Standard Notes totally offline?</Subtitle>
|
||||||
<Text>
|
<Text>
|
||||||
Standard Notes can be used totally offline without an account, and without an internet
|
Standard Notes can be used totally offline without an account, and without an internet connection. You can
|
||||||
connection. You can find{' '}
|
find{' '}
|
||||||
<a
|
<a target="_blank" href="https://standardnotes.com/help/59/can-i-use-standard-notes-totally-offline">
|
||||||
target="_blank"
|
|
||||||
href="https://standardnotes.com/help/59/can-i-use-standard-notes-totally-offline"
|
|
||||||
>
|
|
||||||
more details here.
|
more details here.
|
||||||
</a>
|
</a>
|
||||||
</Text>
|
</Text>
|
||||||
@@ -57,38 +53,25 @@ export const HelpAndFeedback: FunctionComponent = () => (
|
|||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Title>Community forum</Title>
|
<Title>Community forum</Title>
|
||||||
<Text>
|
<Text>
|
||||||
If you have an issue, found a bug or want to suggest a feature, you can browse or post to
|
If you have an issue, found a bug or want to suggest a feature, you can browse or post to the forum. It’s
|
||||||
the forum. It’s recommended for non-account related issues. Please read our{' '}
|
recommended for non-account related issues. Please read our{' '}
|
||||||
<a target="_blank" href="https://standardnotes.com/longevity/">
|
<a target="_blank" href="https://standardnotes.com/longevity/">
|
||||||
Longevity statement
|
Longevity statement
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
before advocating for a feature request.
|
before advocating for a feature request.
|
||||||
</Text>
|
</Text>
|
||||||
<LinkButton
|
<LinkButton className="mt-3" label="Go to the forum" link="https://forum.standardnotes.org/" />
|
||||||
className="mt-3"
|
|
||||||
label="Go to the forum"
|
|
||||||
link="https://forum.standardnotes.org/"
|
|
||||||
/>
|
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
</PreferencesGroup>
|
</PreferencesGroup>
|
||||||
<PreferencesGroup>
|
<PreferencesGroup>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Title>Community groups</Title>
|
<Title>Community groups</Title>
|
||||||
<Text>
|
<Text>
|
||||||
Want to meet other passionate note-takers and privacy enthusiasts? Want to share your
|
Want to meet other passionate note-takers and privacy enthusiasts? Want to share your feedback with us? Join
|
||||||
feedback with us? Join the Standard Notes community groups for discussions on security,
|
the Standard Notes community groups for discussions on security, themes, editors and more.
|
||||||
themes, editors and more.
|
|
||||||
</Text>
|
</Text>
|
||||||
<LinkButton
|
<LinkButton className="mt-3" link="https://standardnotes.com/slack" label="Join our Slack" />
|
||||||
className="mt-3"
|
<LinkButton className="mt-3" link="https://standardnotes.com/discord" label="Join our Discord" />
|
||||||
link="https://standardnotes.com/slack"
|
|
||||||
label="Join our Slack"
|
|
||||||
/>
|
|
||||||
<LinkButton
|
|
||||||
className="mt-3"
|
|
||||||
link="https://standardnotes.com/discord"
|
|
||||||
label="Join our Discord"
|
|
||||||
/>
|
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
</PreferencesGroup>
|
</PreferencesGroup>
|
||||||
<PreferencesGroup>
|
<PreferencesGroup>
|
||||||
|
|||||||
@@ -11,11 +11,7 @@ type Props = {
|
|||||||
application: WebApplication
|
application: WebApplication
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ListedAccountItem: FunctionalComponent<Props> = ({
|
export const ListedAccountItem: FunctionalComponent<Props> = ({ account, showSeparator, application }) => {
|
||||||
account,
|
|
||||||
showSeparator,
|
|
||||||
application,
|
|
||||||
}) => {
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [accountInfo, setAccountInfo] = useState<ListedAccountInfo>()
|
const [accountInfo, setAccountInfo] = useState<ListedAccountInfo>()
|
||||||
|
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ export const Listed = observer(({ application }: Props) => {
|
|||||||
<div className="h-2 w-full" />
|
<div className="h-2 w-full" />
|
||||||
<Subtitle>What is Listed?</Subtitle>
|
<Subtitle>What is Listed?</Subtitle>
|
||||||
<Text>
|
<Text>
|
||||||
Listed is a free blogging platform that allows you to create a public journal published
|
Listed is a free blogging platform that allows you to create a public journal published directly from your
|
||||||
directly from your notes.{' '}
|
notes.{' '}
|
||||||
<a target="_blank" href="https://listed.to" rel="noreferrer noopener">
|
<a target="_blank" href="https://listed.to" rel="noreferrer noopener">
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -3,12 +3,7 @@ import { STRING_E2E_ENABLED, STRING_ENC_NOT_ENABLED, STRING_LOCAL_ENC_ENABLED }
|
|||||||
import { AppState } from '@/UIModels/AppState'
|
import { AppState } from '@/UIModels/AppState'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { ComponentChild, FunctionComponent } from 'preact'
|
import { ComponentChild, FunctionComponent } from 'preact'
|
||||||
import {
|
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||||
PreferencesGroup,
|
|
||||||
PreferencesSegment,
|
|
||||||
Text,
|
|
||||||
Title,
|
|
||||||
} from '@/Components/Preferences/PreferencesComponents'
|
|
||||||
|
|
||||||
const formatCount = (count: number, itemType: string) => `${count} / ${count} ${itemType}`
|
const formatCount = (count: number, itemType: string) => `${count} / ${count} ${itemType}`
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,7 @@ import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
|||||||
import { ApplicationEvent } from '@standardnotes/snjs'
|
import { ApplicationEvent } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { AppState } from '@/UIModels/AppState'
|
import { AppState } from '@/UIModels/AppState'
|
||||||
import {
|
import { PreferencesSegment, Title, Text, PreferencesGroup } from '@/Components/Preferences/PreferencesComponents'
|
||||||
PreferencesSegment,
|
|
||||||
Title,
|
|
||||||
Text,
|
|
||||||
PreferencesGroup,
|
|
||||||
} from '@/Components/Preferences/PreferencesComponents'
|
|
||||||
import { Button } from '@/Components/Button/Button'
|
import { Button } from '@/Components/Button/Button'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -35,8 +30,7 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
|
|||||||
const keyStorageInfo = StringUtils.keyStorageInfo(application)
|
const keyStorageInfo = StringUtils.keyStorageInfo(application)
|
||||||
const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions()
|
const passcodeAutoLockOptions = application.getAutolockService().getAutoLockIntervalOptions()
|
||||||
|
|
||||||
const { setIsEncryptionEnabled, setIsBackupEncrypted, setEncryptionStatusString } =
|
const { setIsEncryptionEnabled, setIsBackupEncrypted, setEncryptionStatusString } = appState.accountMenu
|
||||||
appState.accountMenu
|
|
||||||
|
|
||||||
const passcodeInputRef = useRef<HTMLInputElement>(null)
|
const passcodeInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
@@ -109,9 +103,7 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
|
|||||||
setPasscodeConfirmation(value)
|
setPasscodeConfirmation(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitPasscodeForm = async (
|
const submitPasscodeForm = async (event: TargetedEvent<HTMLFormElement> | TargetedMouseEvent<HTMLButtonElement>) => {
|
||||||
event: TargetedEvent<HTMLFormElement> | TargetedMouseEvent<HTMLButtonElement>,
|
|
||||||
) => {
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
if (!passcode || passcode.length === 0) {
|
if (!passcode || passcode.length === 0) {
|
||||||
@@ -183,22 +175,18 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
|
|||||||
|
|
||||||
{!hasPasscode && canAddPasscode && (
|
{!hasPasscode && canAddPasscode && (
|
||||||
<>
|
<>
|
||||||
<Text className="mb-3">
|
<Text className="mb-3">Add a passcode to lock the application and encrypt on-device key storage.</Text>
|
||||||
Add a passcode to lock the application and encrypt on-device key storage.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{keyStorageInfo && <Text className="mb-3">{keyStorageInfo}</Text>}
|
{keyStorageInfo && <Text className="mb-3">{keyStorageInfo}</Text>}
|
||||||
|
|
||||||
{!showPasscodeForm && (
|
{!showPasscodeForm && <Button label="Add passcode" onClick={handleAddPassCode} variant="primary" />}
|
||||||
<Button label="Add passcode" onClick={handleAddPassCode} variant="primary" />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!hasPasscode && !canAddPasscode && (
|
{!hasPasscode && !canAddPasscode && (
|
||||||
<Text>
|
<Text>
|
||||||
Adding a passcode is not supported in temporary sessions. Please sign out, then sign
|
Adding a passcode is not supported in temporary sessions. Please sign out, then sign back in with the
|
||||||
back in with the "Stay signed in" option checked.
|
"Stay signed in" option checked.
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -221,12 +209,7 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
|
|||||||
placeholder="Confirm Passcode"
|
placeholder="Confirm Passcode"
|
||||||
/>
|
/>
|
||||||
<div className="min-h-2" />
|
<div className="min-h-2" />
|
||||||
<Button
|
<Button variant="primary" onClick={submitPasscodeForm} label="Set Passcode" className="mr-3" />
|
||||||
variant="primary"
|
|
||||||
onClick={submitPasscodeForm}
|
|
||||||
label="Set Passcode"
|
|
||||||
className="mr-3"
|
|
||||||
/>
|
|
||||||
<Button variant="normal" onClick={() => setShowPasscodeForm(false)} label="Cancel" />
|
<Button variant="normal" onClick={() => setShowPasscodeForm(false)} label="Cancel" />
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
@@ -235,17 +218,8 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
|
|||||||
<>
|
<>
|
||||||
<Text>Passcode lock is enabled.</Text>
|
<Text>Passcode lock is enabled.</Text>
|
||||||
<div className="flex flex-row mt-3">
|
<div className="flex flex-row mt-3">
|
||||||
<Button
|
<Button variant="normal" label="Change Passcode" onClick={changePasscodePressed} className="mr-3" />
|
||||||
variant="normal"
|
<Button dangerStyle={true} label="Remove Passcode" onClick={removePasscodePressed} />
|
||||||
label="Change Passcode"
|
|
||||||
onClick={changePasscodePressed}
|
|
||||||
className="mr-3"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
dangerStyle={true}
|
|
||||||
label="Remove Passcode"
|
|
||||||
onClick={removePasscodePressed}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -258,16 +232,12 @@ export const PasscodeLock = observer(({ application, appState }: Props) => {
|
|||||||
<PreferencesGroup>
|
<PreferencesGroup>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Title>Autolock</Title>
|
<Title>Autolock</Title>
|
||||||
<Text className="mb-3">
|
<Text className="mb-3">The autolock timer begins when the window or tab loses focus.</Text>
|
||||||
The autolock timer begins when the window or tab loses focus.
|
|
||||||
</Text>
|
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
{passcodeAutoLockOptions.map((option) => {
|
{passcodeAutoLockOptions.map((option) => {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
className={`sk-a info mr-3 ${
|
className={`sk-a info mr-3 ${option.value === selectedAutoLockInterval ? 'boxed' : ''}`}
|
||||||
option.value === selectedAutoLockInterval ? 'boxed' : ''
|
|
||||||
}`}
|
|
||||||
onClick={() => selectAutoLockInterval(option.value)}
|
onClick={() => selectAutoLockInterval(option.value)}
|
||||||
>
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
|
|||||||
@@ -71,9 +71,7 @@ export const Privacy: FunctionalComponent<Props> = observer(({ application }: Pr
|
|||||||
const toggleMuteSignInEmails = async () => {
|
const toggleMuteSignInEmails = async () => {
|
||||||
const previousValue = signInEmailsMutedValue
|
const previousValue = signInEmailsMutedValue
|
||||||
const newValue =
|
const newValue =
|
||||||
previousValue === MuteSignInEmailsOption.Muted
|
previousValue === MuteSignInEmailsOption.Muted ? MuteSignInEmailsOption.NotMuted : MuteSignInEmailsOption.Muted
|
||||||
? MuteSignInEmailsOption.NotMuted
|
|
||||||
: MuteSignInEmailsOption.Muted
|
|
||||||
setSignInEmailsMutedValue(newValue)
|
setSignInEmailsMutedValue(newValue)
|
||||||
|
|
||||||
const updateResult = await updateSetting(SettingName.MuteSignInEmails, 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">
|
<div className="flex flex-col">
|
||||||
<Subtitle>Disable sign-in notification emails</Subtitle>
|
<Subtitle>Disable sign-in notification emails</Subtitle>
|
||||||
<Text>
|
<Text>
|
||||||
Disables email notifications when a new sign-in occurs on your account. (Email
|
Disables email notifications when a new sign-in occurs on your account. (Email notifications are
|
||||||
notifications are available to paid subscribers).
|
available to paid subscribers).
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -125,9 +123,9 @@ export const Privacy: FunctionalComponent<Props> = observer(({ application }: Pr
|
|||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Subtitle>Session user agent logging</Subtitle>
|
<Subtitle>Session user agent logging</Subtitle>
|
||||||
<Text>
|
<Text>
|
||||||
User agent logging allows you to identify the devices or browsers signed into your
|
User agent logging allows you to identify the devices or browsers signed into your account. For
|
||||||
account. For increased privacy, you can disable this feature, which will remove all
|
increased privacy, you can disable this feature, which will remove all saved user agent values from our
|
||||||
saved user agent values from our server, and disable future logging of this value.
|
server, and disable future logging of this value.
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
|||||||
@@ -3,12 +3,7 @@ import { FunctionalComponent } from 'preact'
|
|||||||
import { useCallback, useState, useEffect } from 'preact/hooks'
|
import { useCallback, useState, useEffect } from 'preact/hooks'
|
||||||
import { ApplicationEvent } from '@standardnotes/snjs'
|
import { ApplicationEvent } from '@standardnotes/snjs'
|
||||||
import { isSameDay } from '@/Utils'
|
import { isSameDay } from '@/Utils'
|
||||||
import {
|
import { PreferencesGroup, PreferencesSegment, Title, Text } from '@/Components/Preferences/PreferencesComponents'
|
||||||
PreferencesGroup,
|
|
||||||
PreferencesSegment,
|
|
||||||
Title,
|
|
||||||
Text,
|
|
||||||
} from '@/Components/Preferences/PreferencesComponents'
|
|
||||||
import { Button } from '@/Components/Button/Button'
|
import { Button } from '@/Components/Button/Button'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -47,9 +42,7 @@ export const Protections: FunctionalComponent<Props> = ({ application }) => {
|
|||||||
return null
|
return null
|
||||||
}, [application])
|
}, [application])
|
||||||
|
|
||||||
const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(
|
const [protectionsDisabledUntil, setProtectionsDisabledUntil] = useState(getProtectionsDisabledUntil())
|
||||||
getProtectionsDisabledUntil(),
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const removeUnprotectedSessionBeginObserver = application.addEventObserver(async () => {
|
const removeUnprotectedSessionBeginObserver = application.addEventObserver(async () => {
|
||||||
@@ -85,17 +78,11 @@ export const Protections: FunctionalComponent<Props> = ({ application }) => {
|
|||||||
<Text className="info">Protections are enabled.</Text>
|
<Text className="info">Protections are enabled.</Text>
|
||||||
)}
|
)}
|
||||||
<Text className="mt-2">
|
<Text className="mt-2">
|
||||||
Actions like viewing or searching protected notes, exporting decrypted backups, or
|
Actions like viewing or searching protected notes, exporting decrypted backups, or revoking an active session
|
||||||
revoking an active session require additional authentication such as entering your account
|
require additional authentication such as entering your account password or application passcode.
|
||||||
password or application passcode.
|
|
||||||
</Text>
|
</Text>
|
||||||
{protectionsDisabledUntil && (
|
{protectionsDisabledUntil && (
|
||||||
<Button
|
<Button className="mt-3" variant="primary" label="End Unprotected Access" onClick={enableProtections} />
|
||||||
className="mt-3"
|
|
||||||
variant="primary"
|
|
||||||
label="End Unprotected Access"
|
|
||||||
onClick={enableProtections}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</PreferencesSegment>
|
</PreferencesSegment>
|
||||||
</PreferencesGroup>
|
</PreferencesGroup>
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ const DisclosureIconButton: FunctionComponent<{
|
|||||||
<DisclosureButton
|
<DisclosureButton
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
className={`no-border cursor-pointer bg-transparent hover:brightness-130 p-0 ${
|
className={`no-border cursor-pointer bg-transparent hover:brightness-130 p-0 ${className ?? ''}`}
|
||||||
className ?? ''
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<Icon type={icon} />
|
<Icon type={icon} />
|
||||||
</DisclosureButton>
|
</DisclosureButton>
|
||||||
@@ -56,8 +54,8 @@ export const AuthAppInfoTooltip: FunctionComponent = () => {
|
|||||||
className={`bg-inverted-default color-inverted-default text-center rounded shadow-overlay
|
className={`bg-inverted-default color-inverted-default text-center rounded shadow-overlay
|
||||||
py-1.5 px-2 absolute w-103 -top-10 -left-51`}
|
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
|
Some apps, like Google Authenticator, do not back up and restore your secret keys if you lose your device or
|
||||||
lose your device or get a new one.
|
get a new one.
|
||||||
</div>
|
</div>
|
||||||
</DisclosurePanel>
|
</DisclosurePanel>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -76,18 +76,8 @@ export const SaveSecretKey: FunctionComponent<{
|
|||||||
</div>
|
</div>
|
||||||
</ModalDialogDescription>
|
</ModalDialogDescription>
|
||||||
<ModalDialogButtons>
|
<ModalDialogButtons>
|
||||||
<Button
|
<Button className="min-w-20" variant="normal" label="Back" onClick={() => act.openScanQRCode()} />
|
||||||
className="min-w-20"
|
<Button className="min-w-20" variant="primary" label="Next" onClick={() => act.openVerification()} />
|
||||||
variant="normal"
|
|
||||||
label="Back"
|
|
||||||
onClick={() => act.openScanQRCode()}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
className="min-w-20"
|
|
||||||
variant="primary"
|
|
||||||
label="Next"
|
|
||||||
onClick={() => act.openVerification()}
|
|
||||||
/>
|
|
||||||
</ModalDialogButtons>
|
</ModalDialogButtons>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,16 +19,10 @@ export const ScanQRCode: FunctionComponent<{
|
|||||||
}> = observer(({ activation: act }) => {
|
}> = observer(({ activation: act }) => {
|
||||||
return (
|
return (
|
||||||
<ModalDialog>
|
<ModalDialog>
|
||||||
<ModalDialogLabel closeDialog={act.cancelActivation}>
|
<ModalDialogLabel closeDialog={act.cancelActivation}>Step 1 of 3 - Scan QR code</ModalDialogLabel>
|
||||||
Step 1 of 3 - Scan QR code
|
|
||||||
</ModalDialogLabel>
|
|
||||||
<ModalDialogDescription className="h-33">
|
<ModalDialogDescription className="h-33">
|
||||||
<div className="w-25 h-25 flex items-center justify-center bg-info">
|
<div className="w-25 h-25 flex items-center justify-center bg-info">
|
||||||
<QRCode
|
<QRCode className="border-neutral-contrast-bg border-solid border-2" value={act.qrCode} size={100} />
|
||||||
className="border-neutral-contrast-bg border-solid border-2"
|
|
||||||
value={act.qrCode}
|
|
||||||
size={100}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-5" />
|
<div className="min-w-5" />
|
||||||
<div className="flex-grow flex flex-col">
|
<div className="flex-grow flex flex-col">
|
||||||
@@ -59,18 +53,8 @@ export const ScanQRCode: FunctionComponent<{
|
|||||||
</div>
|
</div>
|
||||||
</ModalDialogDescription>
|
</ModalDialogDescription>
|
||||||
<ModalDialogButtons>
|
<ModalDialogButtons>
|
||||||
<Button
|
<Button className="min-w-20" variant="normal" label="Cancel" onClick={() => act.cancelActivation()} />
|
||||||
className="min-w-20"
|
<Button className="min-w-20" variant="primary" label="Next" onClick={() => act.openSaveSecretKey()} />
|
||||||
variant="normal"
|
|
||||||
label="Cancel"
|
|
||||||
onClick={() => act.cancelActivation()}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
className="min-w-20"
|
|
||||||
variant="primary"
|
|
||||||
label="Next"
|
|
||||||
onClick={() => act.openSaveSecretKey()}
|
|
||||||
/>
|
|
||||||
</ModalDialogButtons>
|
</ModalDialogButtons>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,12 +25,7 @@ export class TwoFactorActivation {
|
|||||||
|
|
||||||
makeAutoObservable<
|
makeAutoObservable<
|
||||||
TwoFactorActivation,
|
TwoFactorActivation,
|
||||||
| '_secretKey'
|
'_secretKey' | '_authCode' | '_step' | '_enable2FAVerification' | 'inputOtpToken' | 'inputSecretKey'
|
||||||
| '_authCode'
|
|
||||||
| '_step'
|
|
||||||
| '_enable2FAVerification'
|
|
||||||
| 'inputOtpToken'
|
|
||||||
| 'inputSecretKey'
|
|
||||||
>(
|
>(
|
||||||
this,
|
this,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,23 +10,16 @@ export const is2FADisabled = (status: TwoFactorStatus): status is 'two-factor-di
|
|||||||
export const is2FAActivation = (status: TwoFactorStatus): status is TwoFactorActivation =>
|
export const is2FAActivation = (status: TwoFactorStatus): status is TwoFactorActivation =>
|
||||||
(status as TwoFactorActivation)?.type === 'two-factor-activation'
|
(status as TwoFactorActivation)?.type === 'two-factor-activation'
|
||||||
|
|
||||||
export const is2FAEnabled = (status: TwoFactorStatus): status is 'two-factor-enabled' =>
|
export const is2FAEnabled = (status: TwoFactorStatus): status is 'two-factor-enabled' => status === 'two-factor-enabled'
|
||||||
status === 'two-factor-enabled'
|
|
||||||
|
|
||||||
export class TwoFactorAuth {
|
export class TwoFactorAuth {
|
||||||
private _status: TwoFactorStatus | 'fetching' = 'fetching'
|
private _status: TwoFactorStatus | 'fetching' = 'fetching'
|
||||||
private _errorMessage: string | null
|
private _errorMessage: string | null
|
||||||
|
|
||||||
constructor(
|
constructor(private readonly mfaProvider: MfaProvider, private readonly userProvider: UserProvider) {
|
||||||
private readonly mfaProvider: MfaProvider,
|
|
||||||
private readonly userProvider: UserProvider,
|
|
||||||
) {
|
|
||||||
this._errorMessage = null
|
this._errorMessage = null
|
||||||
|
|
||||||
makeAutoObservable<
|
makeAutoObservable<TwoFactorAuth, '_status' | '_errorMessage' | 'deactivateMfa' | 'startActivation'>(
|
||||||
TwoFactorAuth,
|
|
||||||
'_status' | '_errorMessage' | 'deactivateMfa' | 'startActivation'
|
|
||||||
>(
|
|
||||||
this,
|
this,
|
||||||
{
|
{
|
||||||
_status: observable,
|
_status: observable,
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import {
|
import { Title, Text, PreferencesGroup, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
|
||||||
Title,
|
|
||||||
Text,
|
|
||||||
PreferencesGroup,
|
|
||||||
PreferencesSegment,
|
|
||||||
} from '@/Components/Preferences/PreferencesComponents'
|
|
||||||
import { Switch } from '@/Components/Switch'
|
import { Switch } from '@/Components/Switch'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { is2FAActivation, is2FADisabled, TwoFactorAuth } from './TwoFactorAuth'
|
import { is2FAActivation, is2FADisabled, TwoFactorAuth } from './TwoFactorAuth'
|
||||||
|
|||||||
@@ -16,18 +16,11 @@ export const TwoFactorSuccess: FunctionComponent<{
|
|||||||
<ModalDialogLabel closeDialog={act.finishActivation}>Successfully Enabled</ModalDialogLabel>
|
<ModalDialogLabel closeDialog={act.finishActivation}>Successfully Enabled</ModalDialogLabel>
|
||||||
<ModalDialogDescription>
|
<ModalDialogDescription>
|
||||||
<div className="flex flex-row items-center justify-center pt-2">
|
<div className="flex flex-row items-center justify-center pt-2">
|
||||||
<Subtitle>
|
<Subtitle>Two-factor authentication has been successfully enabled for your account.</Subtitle>
|
||||||
Two-factor authentication has been successfully enabled for your account.
|
|
||||||
</Subtitle>
|
|
||||||
</div>
|
</div>
|
||||||
</ModalDialogDescription>
|
</ModalDialogDescription>
|
||||||
<ModalDialogButtons>
|
<ModalDialogButtons>
|
||||||
<Button
|
<Button className="min-w-20" variant="primary" label="Finish" onClick={act.finishActivation} />
|
||||||
className="min-w-20"
|
|
||||||
variant="primary"
|
|
||||||
label="Finish"
|
|
||||||
onClick={act.finishActivation}
|
|
||||||
/>
|
|
||||||
</ModalDialogButtons>
|
</ModalDialogButtons>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ export const Verification: FunctionComponent<{
|
|||||||
const authTokenClass = act.verificationStatus === 'invalid-auth-code' ? 'border-danger' : ''
|
const authTokenClass = act.verificationStatus === 'invalid-auth-code' ? 'border-danger' : ''
|
||||||
return (
|
return (
|
||||||
<ModalDialog>
|
<ModalDialog>
|
||||||
<ModalDialogLabel closeDialog={act.cancelActivation}>
|
<ModalDialogLabel closeDialog={act.cancelActivation}>Step 3 of 3 - Verification</ModalDialogLabel>
|
||||||
Step 3 of 3 - Verification
|
|
||||||
</ModalDialogLabel>
|
|
||||||
<ModalDialogDescription className="h-33">
|
<ModalDialogDescription className="h-33">
|
||||||
<div className="flex-grow flex flex-col">
|
<div className="flex-grow flex flex-col">
|
||||||
<div className="flex flex-row items-center mb-4">
|
<div className="flex flex-row items-center mb-4">
|
||||||
@@ -45,21 +43,12 @@ export const Verification: FunctionComponent<{
|
|||||||
</ModalDialogDescription>
|
</ModalDialogDescription>
|
||||||
<ModalDialogButtons>
|
<ModalDialogButtons>
|
||||||
{act.verificationStatus === 'invalid-auth-code' && (
|
{act.verificationStatus === 'invalid-auth-code' && (
|
||||||
<div className="text-sm color-danger flex-grow">
|
<div className="text-sm color-danger flex-grow">Incorrect authentication code, please try again.</div>
|
||||||
Incorrect authentication code, please try again.
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{act.verificationStatus === 'invalid-secret' && (
|
{act.verificationStatus === 'invalid-secret' && (
|
||||||
<div className="text-sm color-danger flex-grow">
|
<div className="text-sm color-danger flex-grow">Incorrect secret key, please try again.</div>
|
||||||
Incorrect secret key, please try again.
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button className="min-w-20" variant="normal" label="Back" onClick={act.openSaveSecretKey} />
|
||||||
className="min-w-20"
|
|
||||||
variant="normal"
|
|
||||||
label="Back"
|
|
||||||
onClick={act.openSaveSecretKey}
|
|
||||||
/>
|
|
||||||
<Button className="min-w-20" variant="primary" label="Next" onClick={act.enable2FA} />
|
<Button className="min-w-20" variant="primary" label="Next" onClick={act.enable2FA} />
|
||||||
</ModalDialogButtons>
|
</ModalDialogButtons>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
|
|||||||
@@ -7,15 +7,13 @@ export const Title: FunctionComponent = ({ children }) => (
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const Subtitle: FunctionComponent<{ className?: string }> = ({
|
export const Subtitle: FunctionComponent<{ className?: string }> = ({ children, className = '' }) => (
|
||||||
children,
|
<h4 className={`font-medium text-sm m-0 mb-1 ${className}`}>{children}</h4>
|
||||||
className = '',
|
)
|
||||||
}) => <h4 className={`font-medium text-sm m-0 mb-1 ${className}`}>{children}</h4>
|
|
||||||
|
|
||||||
export const SubtitleLight: FunctionComponent<{ className?: string }> = ({
|
export const SubtitleLight: FunctionComponent<{ className?: string }> = ({ children, className = '' }) => (
|
||||||
children,
|
<h4 className={`font-normal text-sm m-0 mb-1 ${className}`}>{children}</h4>
|
||||||
className = '',
|
)
|
||||||
}) => <h4 className={`font-normal text-sm m-0 mb-1 ${className}`}>{children}</h4>
|
|
||||||
|
|
||||||
export const Text: FunctionComponent<{ className?: string }> = ({ children, className = '' }) => (
|
export const Text: FunctionComponent<{ className?: string }> = ({ children, className = '' }) => (
|
||||||
<p className={`${className} text-xs`}>{children}</p>
|
<p className={`${className} text-xs`}>{children}</p>
|
||||||
|
|||||||
@@ -10,13 +10,7 @@ interface Props {
|
|||||||
onClick: () => void
|
onClick: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MenuItem: FunctionComponent<Props> = ({
|
export const MenuItem: FunctionComponent<Props> = ({ iconType, label, selected, onClick, hasBubble }) => (
|
||||||
iconType,
|
|
||||||
label,
|
|
||||||
selected,
|
|
||||||
onClick,
|
|
||||||
hasBubble,
|
|
||||||
}) => (
|
|
||||||
<div
|
<div
|
||||||
className={`preferences-menu-item select-none ${selected ? 'selected' : ''}`}
|
className={`preferences-menu-item select-none ${selected ? 'selected' : ''}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|||||||
@@ -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="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="flex-grow flex flex-col py-6 items-center">
|
||||||
<div className="w-125 max-w-125 flex flex-col">
|
<div className="w-125 max-w-125 flex flex-col">
|
||||||
{children != undefined && Array.isArray(children)
|
{children != undefined && Array.isArray(children) ? children.filter((child) => child != undefined) : children}
|
||||||
? children.filter((child) => child != undefined)
|
|
||||||
: children}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-basis-55 flex-shrink" />
|
<div className="flex-basis-55 flex-shrink" />
|
||||||
|
|||||||
@@ -59,27 +59,16 @@ const READY_PREFERENCES_MENU_ITEMS: PreferencesMenuItem[] = [
|
|||||||
export class PreferencesMenu {
|
export class PreferencesMenu {
|
||||||
private _selectedPane: PreferenceId = 'account'
|
private _selectedPane: PreferenceId = 'account'
|
||||||
private _menu: PreferencesMenuItem[]
|
private _menu: PreferencesMenuItem[]
|
||||||
private _extensionLatestVersions: ExtensionsLatestVersions = new ExtensionsLatestVersions(
|
private _extensionLatestVersions: ExtensionsLatestVersions = new ExtensionsLatestVersions(new Map())
|
||||||
new Map(),
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor(
|
constructor(private application: WebApplication, private readonly _enableUnfinishedFeatures: boolean) {
|
||||||
private application: WebApplication,
|
this._menu = this._enableUnfinishedFeatures ? PREFERENCES_MENU_ITEMS : READY_PREFERENCES_MENU_ITEMS
|
||||||
private readonly _enableUnfinishedFeatures: boolean,
|
|
||||||
) {
|
|
||||||
this._menu = this._enableUnfinishedFeatures
|
|
||||||
? PREFERENCES_MENU_ITEMS
|
|
||||||
: READY_PREFERENCES_MENU_ITEMS
|
|
||||||
|
|
||||||
this.loadLatestVersions()
|
this.loadLatestVersions()
|
||||||
|
|
||||||
makeAutoObservable<
|
makeAutoObservable<
|
||||||
PreferencesMenu,
|
PreferencesMenu,
|
||||||
| '_selectedPane'
|
'_selectedPane' | '_twoFactorAuth' | '_extensionPanes' | '_extensionLatestVersions' | 'loadLatestVersions'
|
||||||
| '_twoFactorAuth'
|
|
||||||
| '_extensionPanes'
|
|
||||||
| '_extensionLatestVersions'
|
|
||||||
| 'loadLatestVersions'
|
|
||||||
>(this, {
|
>(this, {
|
||||||
_twoFactorAuth: observable,
|
_twoFactorAuth: observable,
|
||||||
_selectedPane: observable,
|
_selectedPane: observable,
|
||||||
|
|||||||
@@ -67,14 +67,12 @@ const PaneSelector: FunctionComponent<PreferencesProps & { menu: PreferencesMenu
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const PreferencesCanvas: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = observer(
|
const PreferencesCanvas: FunctionComponent<PreferencesProps & { menu: PreferencesMenu }> = observer((props) => (
|
||||||
(props) => (
|
<div className="flex flex-row flex-grow min-h-0 justify-between">
|
||||||
<div className="flex flex-row flex-grow min-h-0 justify-between">
|
<PreferencesMenuView menu={props.menu} />
|
||||||
<PreferencesMenuView menu={props.menu} />
|
<PaneSelector {...props} />
|
||||||
<PaneSelector {...props} />
|
</div>
|
||||||
</div>
|
))
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const PreferencesView: FunctionComponent<PreferencesProps> = observer((props) => {
|
export const PreferencesView: FunctionComponent<PreferencesProps> = observer((props) => {
|
||||||
const menu = useMemo(
|
const menu = useMemo(
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ export const PremiumFeaturesModal: FunctionalComponent<Props> = ({
|
|||||||
<div className="text-lg text-center font-bold mb-1">Enable Premium Features</div>
|
<div className="text-lg text-center font-bold mb-1">Enable Premium Features</div>
|
||||||
</AlertDialogLabel>
|
</AlertDialogLabel>
|
||||||
<AlertDialogDescription className="text-sm text-center color-grey-1 px-4.5 mb-2">
|
<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
|
In order to use <span className="font-semibold">{featureName}</span> and other premium features, please
|
||||||
features, please purchase a subscription or upgrade your current plan.
|
purchase a subscription or upgrade your current plan.
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -8,12 +8,7 @@ import { useEffect, useRef, useState } from 'preact/hooks'
|
|||||||
import { FloatingLabelInput } from '@/Components/Input/FloatingLabelInput'
|
import { FloatingLabelInput } from '@/Components/Input/FloatingLabelInput'
|
||||||
import { isEmailValid } from '@/Utils'
|
import { isEmailValid } from '@/Utils'
|
||||||
import { loadPurchaseFlowUrl } from '@/Components/PurchaseFlow/PurchaseFlowWrapper'
|
import { loadPurchaseFlowUrl } from '@/Components/PurchaseFlow/PurchaseFlowWrapper'
|
||||||
import {
|
import { BlueDotIcon, CircleIcon, DiamondIcon, CreateAccountIllustration } from '@standardnotes/stylekit'
|
||||||
BlueDotIcon,
|
|
||||||
CircleIcon,
|
|
||||||
DiamondIcon,
|
|
||||||
CreateAccountIllustration,
|
|
||||||
} from '@standardnotes/stylekit'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appState: AppState
|
appState: AppState
|
||||||
@@ -145,9 +140,7 @@ export const CreateAccount: FunctionComponent<Props> = observer(({ appState, app
|
|||||||
disabled={isCreatingAccount}
|
disabled={isCreatingAccount}
|
||||||
isInvalid={isEmailInvalid}
|
isInvalid={isEmailInvalid}
|
||||||
/>
|
/>
|
||||||
{isEmailInvalid ? (
|
{isEmailInvalid ? <div className="color-dark-red mb-4">Please provide a valid email.</div> : null}
|
||||||
<div className="color-dark-red mb-4">Please provide a valid email.</div>
|
|
||||||
) : null}
|
|
||||||
<FloatingLabelInput
|
<FloatingLabelInput
|
||||||
className="min-w-90 xs:min-w-auto mb-4"
|
className="min-w-90 xs:min-w-auto mb-4"
|
||||||
id="purchase-create-account-password"
|
id="purchase-create-account-password"
|
||||||
|
|||||||
@@ -114,9 +114,7 @@ export const SignIn: FunctionComponent<Props> = observer(({ appState, applicatio
|
|||||||
<form onSubmit={handleSignIn}>
|
<form onSubmit={handleSignIn}>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<FloatingLabelInput
|
<FloatingLabelInput
|
||||||
className={`min-w-90 xs:min-w-auto ${
|
className={`min-w-90 xs:min-w-auto ${isEmailInvalid && !otherErrorMessage ? 'mb-2' : 'mb-4'}`}
|
||||||
isEmailInvalid && !otherErrorMessage ? 'mb-2' : 'mb-4'
|
|
||||||
}`}
|
|
||||||
id="purchase-sign-in-email"
|
id="purchase-sign-in-email"
|
||||||
type="email"
|
type="email"
|
||||||
label="Email"
|
label="Email"
|
||||||
@@ -140,9 +138,7 @@ export const SignIn: FunctionComponent<Props> = observer(({ appState, applicatio
|
|||||||
disabled={isSigningIn}
|
disabled={isSigningIn}
|
||||||
isInvalid={isPasswordInvalid}
|
isInvalid={isPasswordInvalid}
|
||||||
/>
|
/>
|
||||||
{otherErrorMessage ? (
|
{otherErrorMessage ? <div className="color-dark-red mb-4">{otherErrorMessage}</div> : null}
|
||||||
<div className="color-dark-red mb-4">{otherErrorMessage}</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
className={`${isSigningIn ? 'min-w-30' : 'min-w-24'} py-2.5 mb-5`}
|
className={`${isSigningIn ? 'min-w-30' : 'min-w-24'} py-2.5 mb-5`}
|
||||||
|
|||||||
@@ -16,11 +16,7 @@ type PurchaseFlowViewProps = {
|
|||||||
application: WebApplication
|
application: WebApplication
|
||||||
}
|
}
|
||||||
|
|
||||||
const PurchaseFlowPaneSelector: FunctionComponent<PaneSelectorProps> = ({
|
const PurchaseFlowPaneSelector: FunctionComponent<PaneSelectorProps> = ({ currentPane, appState, application }) => {
|
||||||
currentPane,
|
|
||||||
appState,
|
|
||||||
application,
|
|
||||||
}) => {
|
|
||||||
switch (currentPane) {
|
switch (currentPane) {
|
||||||
case PurchaseFlowPane.CreateAccount:
|
case PurchaseFlowPane.CreateAccount:
|
||||||
return <CreateAccount appState={appState} application={application} />
|
return <CreateAccount appState={appState} application={application} />
|
||||||
@@ -29,41 +25,35 @@ const PurchaseFlowPaneSelector: FunctionComponent<PaneSelectorProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PurchaseFlowView: FunctionComponent<PurchaseFlowViewProps> = observer(
|
export const PurchaseFlowView: FunctionComponent<PurchaseFlowViewProps> = observer(({ appState, application }) => {
|
||||||
({ appState, application }) => {
|
const { currentPane } = appState.purchaseFlow
|
||||||
const { currentPane } = appState.purchaseFlow
|
|
||||||
|
|
||||||
return (
|
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="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 fit-content">
|
||||||
<div className="relative p-12 xs:px-8 mb-4 bg-default border-1 border-solid border-main rounded xs:rounded-0">
|
<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" />
|
<SNLogoFull className="mb-5" />
|
||||||
<PurchaseFlowPaneSelector
|
<PurchaseFlowPaneSelector currentPane={currentPane} appState={appState} application={application} />
|
||||||
currentPane={currentPane}
|
</div>
|
||||||
appState={appState}
|
<div className="flex justify-end xs:px-4">
|
||||||
application={application}
|
<a
|
||||||
/>
|
className="mr-3 font-medium color-grey-1"
|
||||||
</div>
|
href="https://standardnotes.com/privacy"
|
||||||
<div className="flex justify-end xs:px-4">
|
target="_blank"
|
||||||
<a
|
rel="noopener noreferrer"
|
||||||
className="mr-3 font-medium color-grey-1"
|
>
|
||||||
href="https://standardnotes.com/privacy"
|
Privacy
|
||||||
target="_blank"
|
</a>
|
||||||
rel="noopener noreferrer"
|
<a
|
||||||
>
|
className="font-medium color-grey-1"
|
||||||
Privacy
|
href="https://standardnotes.com/help"
|
||||||
</a>
|
target="_blank"
|
||||||
<a
|
rel="noopener noreferrer"
|
||||||
className="font-medium color-grey-1"
|
>
|
||||||
href="https://standardnotes.com/help"
|
Help
|
||||||
target="_blank"
|
</a>
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Help
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
</div>
|
||||||
},
|
)
|
||||||
)
|
})
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ export type PurchaseFlowWrapperProps = {
|
|||||||
application: WebApplication
|
application: WebApplication
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPurchaseFlowUrl = async (
|
export const getPurchaseFlowUrl = async (application: WebApplication): Promise<string | undefined> => {
|
||||||
application: WebApplication,
|
|
||||||
): Promise<string | undefined> => {
|
|
||||||
const currentUrl = window.location.origin
|
const currentUrl = window.location.origin
|
||||||
const successUrl = isDesktopApplication() ? 'standardnotes://' : currentUrl
|
const successUrl = isDesktopApplication() ? 'standardnotes://' : currentUrl
|
||||||
if (application.noAccount()) {
|
if (application.noAccount()) {
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ export const quickSettingsKeyDownHandler = (
|
|||||||
themesMenuOpen: boolean,
|
themesMenuOpen: boolean,
|
||||||
) => {
|
) => {
|
||||||
if (quickSettingsMenuRef?.current) {
|
if (quickSettingsMenuRef?.current) {
|
||||||
const items: NodeListOf<HTMLButtonElement> =
|
const items: NodeListOf<HTMLButtonElement> = quickSettingsMenuRef.current.querySelectorAll(':scope > button')
|
||||||
quickSettingsMenuRef.current.querySelectorAll(':scope > button')
|
|
||||||
const currentFocusedIndex = Array.from(items).findIndex((btn) => btn === document.activeElement)
|
const currentFocusedIndex = Array.from(items).findIndex((btn) => btn === document.activeElement)
|
||||||
|
|
||||||
if (!themesMenuOpen) {
|
if (!themesMenuOpen) {
|
||||||
@@ -45,9 +44,7 @@ export const themesMenuKeyDownHandler = (
|
|||||||
) => {
|
) => {
|
||||||
if (themesMenuRef?.current) {
|
if (themesMenuRef?.current) {
|
||||||
const themes = themesMenuRef.current.querySelectorAll('button')
|
const themes = themesMenuRef.current.querySelectorAll('button')
|
||||||
const currentFocusedIndex = Array.from(themes).findIndex(
|
const currentFocusedIndex = Array.from(themes).findIndex((themeBtn) => themeBtn === document.activeElement)
|
||||||
(themeBtn) => themeBtn === document.activeElement,
|
|
||||||
)
|
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
|
|||||||
@@ -14,15 +14,9 @@ type Props = {
|
|||||||
isEnabled: boolean
|
isEnabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FocusModeSwitch: FunctionComponent<Props> = ({
|
export const FocusModeSwitch: FunctionComponent<Props> = ({ application, onToggle, onClose, isEnabled }) => {
|
||||||
application,
|
|
||||||
onToggle,
|
|
||||||
onClose,
|
|
||||||
isEnabled,
|
|
||||||
}) => {
|
|
||||||
const premiumModal = usePremiumModal()
|
const premiumModal = usePremiumModal()
|
||||||
const isEntitled =
|
const isEntitled = application.features.getFeatureStatus(FeatureIdentifier.FocusMode) === FeatureStatus.Entitled
|
||||||
application.features.getFeatureStatus(FeatureIdentifier.FocusMode) === FeatureStatus.Entitled
|
|
||||||
|
|
||||||
const toggle = useCallback(
|
const toggle = useCallback(
|
||||||
(e: JSXInternal.TargetedMouseEvent<HTMLButtonElement>) => {
|
(e: JSXInternal.TargetedMouseEvent<HTMLButtonElement>) => {
|
||||||
@@ -40,10 +34,7 @@ export const FocusModeSwitch: FunctionComponent<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<button className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none justify-between" onClick={toggle}>
|
||||||
className="sn-dropdown-item focus:bg-info-backdrop focus:shadow-none justify-between"
|
|
||||||
onClick={toggle}
|
|
||||||
>
|
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Icon type="menu-close" className="color-neutral mr-2" />
|
<Icon type="menu-close" className="color-neutral mr-2" />
|
||||||
Focused Writing
|
Focused Writing
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ export const ThemesMenuButton: FunctionComponent<Props> = ({ application, item,
|
|||||||
() => application.features.getFeatureStatus(item.identifier) === FeatureStatus.Entitled,
|
() => application.features.getFeatureStatus(item.identifier) === FeatureStatus.Entitled,
|
||||||
[application, item.identifier],
|
[application, item.identifier],
|
||||||
)
|
)
|
||||||
const canActivateTheme = useMemo(
|
const canActivateTheme = useMemo(() => isEntitledToTheme || isThirdPartyTheme, [isEntitledToTheme, isThirdPartyTheme])
|
||||||
() => isEntitledToTheme || isThirdPartyTheme,
|
|
||||||
[isEntitledToTheme, isThirdPartyTheme],
|
|
||||||
)
|
|
||||||
|
|
||||||
const toggleTheme: JSXInternal.MouseEventHandler<HTMLButtonElement> = (e) => {
|
const toggleTheme: JSXInternal.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -61,14 +58,8 @@ export const ThemesMenuButton: FunctionComponent<Props> = ({ application, item,
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div
|
<div className={`pseudo-radio-btn ${item.component?.active ? 'pseudo-radio-btn--checked' : ''} mr-2`}></div>
|
||||||
className={`pseudo-radio-btn ${
|
<span className={item.component?.active ? 'font-semibold' : undefined}>{item.name}</span>
|
||||||
item.component?.active ? 'pseudo-radio-btn--checked' : ''
|
|
||||||
} mr-2`}
|
|
||||||
></div>
|
|
||||||
<span className={item.component?.active ? 'font-semibold' : undefined}>
|
|
||||||
{item.name}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
{item.component && canActivateTheme ? (
|
{item.component && canActivateTheme ? (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
import { WebApplication } from '@/UIModels/Application'
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
import { AppState } from '@/UIModels/AppState'
|
import { AppState } from '@/UIModels/AppState'
|
||||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||||
import {
|
import { ComponentArea, ContentType, FeatureIdentifier, GetFeatures, SNComponent, SNTheme } from '@standardnotes/snjs'
|
||||||
ComponentArea,
|
|
||||||
ContentType,
|
|
||||||
FeatureIdentifier,
|
|
||||||
GetFeatures,
|
|
||||||
SNComponent,
|
|
||||||
SNTheme,
|
|
||||||
} from '@standardnotes/snjs'
|
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
||||||
@@ -25,8 +18,7 @@ import { sortThemes } from '@/Utils/SortThemes'
|
|||||||
|
|
||||||
const focusModeAnimationDuration = 1255
|
const focusModeAnimationDuration = 1255
|
||||||
|
|
||||||
const MENU_CLASSNAME =
|
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'
|
||||||
'sn-menu-border sn-dropdown min-w-80 max-h-120 max-w-xs flex flex-col py-2 overflow-y-auto'
|
|
||||||
|
|
||||||
type MenuProps = {
|
type MenuProps = {
|
||||||
appState: AppState
|
appState: AppState
|
||||||
@@ -48,266 +40,241 @@ const toggleFocusMode = (enabled: boolean) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(
|
export const QuickSettingsMenu: FunctionComponent<MenuProps> = observer(({ application, appState, onClickOutside }) => {
|
||||||
({ application, appState, onClickOutside }) => {
|
const { closeQuickSettingsMenu, shouldAnimateCloseMenu, focusModeEnabled, setFocusModeEnabled } =
|
||||||
const {
|
appState.quickSettingsMenu
|
||||||
closeQuickSettingsMenu,
|
const [themes, setThemes] = useState<ThemeItem[]>([])
|
||||||
shouldAnimateCloseMenu,
|
const [toggleableComponents, setToggleableComponents] = useState<SNComponent[]>([])
|
||||||
focusModeEnabled,
|
const [themesMenuOpen, setThemesMenuOpen] = useState(false)
|
||||||
setFocusModeEnabled,
|
const [themesMenuPosition, setThemesMenuPosition] = useState({})
|
||||||
} = appState.quickSettingsMenu
|
const [defaultThemeOn, setDefaultThemeOn] = useState(false)
|
||||||
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 themesMenuRef = useRef<HTMLDivElement>(null)
|
||||||
const themesButtonRef = useRef<HTMLButtonElement>(null)
|
const themesButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
const prefsButtonRef = useRef<HTMLButtonElement>(null)
|
const prefsButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
const quickSettingsMenuRef = useRef<HTMLDivElement>(null)
|
const quickSettingsMenuRef = useRef<HTMLDivElement>(null)
|
||||||
const defaultThemeButtonRef = useRef<HTMLButtonElement>(null)
|
const defaultThemeButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
const mainRef = useRef<HTMLDivElement>(null)
|
const mainRef = useRef<HTMLDivElement>(null)
|
||||||
useCloseOnClickOutside(mainRef, () => {
|
useCloseOnClickOutside(mainRef, () => {
|
||||||
onClickOutside()
|
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(() => {
|
return () => {
|
||||||
toggleFocusMode(focusModeEnabled)
|
cleanupItemStream()
|
||||||
}, [focusModeEnabled])
|
}
|
||||||
|
}, [application, reloadThemes])
|
||||||
|
|
||||||
const reloadThemes = useCallback(() => {
|
useEffect(() => {
|
||||||
const themes = application.items
|
const cleanupItemStream = application.streamItems(ContentType.Component, () => {
|
||||||
.getDisplayableItems<SNTheme>(ContentType.Theme)
|
reloadToggleableComponents()
|
||||||
.map((item) => {
|
})
|
||||||
return {
|
|
||||||
name: item.name,
|
|
||||||
identifier: item.identifier,
|
|
||||||
component: item,
|
|
||||||
}
|
|
||||||
}) as ThemeItem[]
|
|
||||||
|
|
||||||
GetFeatures()
|
return () => {
|
||||||
.filter((feature) => feature.content_type === ContentType.Theme && !feature.layerable)
|
cleanupItemStream()
|
||||||
.forEach((theme) => {
|
}
|
||||||
if (themes.findIndex((item) => item.identifier === theme.identifier) === -1) {
|
}, [application, reloadToggleableComponents])
|
||||||
themes.push({
|
|
||||||
name: theme.name as string,
|
|
||||||
identifier: theme.identifier,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
setThemes(themes.sort(sortThemes))
|
useEffect(() => {
|
||||||
|
if (themesMenuOpen) {
|
||||||
|
defaultThemeButtonRef.current?.focus()
|
||||||
|
}
|
||||||
|
}, [themesMenuOpen])
|
||||||
|
|
||||||
setDefaultThemeOn(
|
useEffect(() => {
|
||||||
!themes
|
prefsButtonRef.current?.focus()
|
||||||
.map((item) => item?.component)
|
}, [])
|
||||||
.find((theme) => theme?.active && !theme.isLayerable()),
|
|
||||||
)
|
|
||||||
}, [application])
|
|
||||||
|
|
||||||
const reloadToggleableComponents = useCallback(() => {
|
const [closeOnBlur] = useCloseOnBlur(themesMenuRef, setThemesMenuOpen)
|
||||||
const toggleableComponents = application.items
|
|
||||||
.getDisplayableItems<SNComponent>(ContentType.Component)
|
|
||||||
.filter(
|
|
||||||
(component) =>
|
|
||||||
[ComponentArea.EditorStack].includes(component.area) &&
|
|
||||||
component.identifier !== FeatureIdentifier.DeprecatedFoldersComponent,
|
|
||||||
)
|
|
||||||
|
|
||||||
setToggleableComponents(toggleableComponents)
|
const toggleThemesMenu = () => {
|
||||||
}, [application])
|
if (!themesMenuOpen && themesButtonRef.current) {
|
||||||
|
const themesButtonRect = themesButtonRef.current.getBoundingClientRect()
|
||||||
useEffect(() => {
|
setThemesMenuPosition({
|
||||||
if (!themes.length) {
|
left: themesButtonRect.right,
|
||||||
reloadThemes()
|
bottom: document.documentElement.clientHeight - themesButtonRect.bottom,
|
||||||
}
|
|
||||||
}, [reloadThemes, themes.length])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const cleanupItemStream = application.streamItems(ContentType.Theme, () => {
|
|
||||||
reloadThemes()
|
|
||||||
})
|
})
|
||||||
|
setThemesMenuOpen(true)
|
||||||
|
} else {
|
||||||
|
setThemesMenuOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
const openPreferences = () => {
|
||||||
cleanupItemStream()
|
closeQuickSettingsMenu()
|
||||||
}
|
appState.preferences.openPreferences()
|
||||||
}, [application, reloadThemes])
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
const toggleComponent = (component: SNComponent) => {
|
||||||
const cleanupItemStream = application.streamItems(ContentType.Component, () => {
|
if (component.isTheme()) {
|
||||||
reloadToggleableComponents()
|
application.mutator.toggleTheme(component).catch(console.error)
|
||||||
})
|
} else {
|
||||||
|
application.mutator.toggleComponent(component).catch(console.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
const handleBtnKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (event) => {
|
||||||
cleanupItemStream()
|
switch (event.key) {
|
||||||
}
|
case 'Escape':
|
||||||
}, [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 {
|
|
||||||
setThemesMenuOpen(false)
|
setThemesMenuOpen(false)
|
||||||
}
|
themesButtonRef.current?.focus()
|
||||||
|
break
|
||||||
|
case 'ArrowRight':
|
||||||
|
if (!themesMenuOpen) {
|
||||||
|
toggleThemesMenu()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const openPreferences = () => {
|
const handleQuickSettingsKeyDown: JSXInternal.KeyboardEventHandler<HTMLDivElement> = (event) => {
|
||||||
closeQuickSettingsMenu()
|
quickSettingsKeyDownHandler(closeQuickSettingsMenu, event, quickSettingsMenuRef, themesMenuOpen)
|
||||||
appState.preferences.openPreferences()
|
}
|
||||||
|
|
||||||
|
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) => {
|
return (
|
||||||
if (component.isTheme()) {
|
<div ref={mainRef} className="sn-component">
|
||||||
application.mutator.toggleTheme(component).catch(console.error)
|
<div
|
||||||
} else {
|
className={`sn-quick-settings-menu absolute ${MENU_CLASSNAME} ${
|
||||||
application.mutator.toggleComponent(component).catch(console.error)
|
shouldAnimateCloseMenu ? 'slide-up-animation' : 'sn-dropdown--animated'
|
||||||
}
|
}`}
|
||||||
}
|
ref={quickSettingsMenuRef}
|
||||||
|
onKeyDown={handleQuickSettingsKeyDown}
|
||||||
const handleBtnKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (event) => {
|
>
|
||||||
switch (event.key) {
|
<div className="px-3 mt-1 mb-2 font-semibold color-text uppercase">Quick Settings</div>
|
||||||
case 'Escape':
|
<Disclosure open={themesMenuOpen} onChange={toggleThemesMenu}>
|
||||||
setThemesMenuOpen(false)
|
<DisclosureButton
|
||||||
themesButtonRef.current?.focus()
|
onKeyDown={handleBtnKeyDown}
|
||||||
break
|
onBlur={closeOnBlur}
|
||||||
case 'ArrowRight':
|
ref={themesButtonRef}
|
||||||
if (!themesMenuOpen) {
|
className="sn-dropdown-item justify-between focus:bg-info-backdrop focus:shadow-none"
|
||||||
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}
|
|
||||||
>
|
>
|
||||||
<Icon type="more" className="color-neutral mr-2" />
|
<div className="flex items-center">
|
||||||
Open Preferences
|
<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>
|
</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>
|
||||||
)
|
</div>
|
||||||
},
|
)
|
||||||
)
|
})
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import { WebApplication } from '@/UIModels/Application'
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
import {
|
import { Action, ActionVerb, HistoryEntry, NoteHistoryEntry, RevisionListEntry, SNNote } from '@standardnotes/snjs'
|
||||||
Action,
|
|
||||||
ActionVerb,
|
|
||||||
HistoryEntry,
|
|
||||||
NoteHistoryEntry,
|
|
||||||
RevisionListEntry,
|
|
||||||
SNNote,
|
|
||||||
} from '@standardnotes/snjs'
|
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { StateUpdater, useCallback, useState } from 'preact/hooks'
|
import { StateUpdater, useCallback, useState } from 'preact/hooks'
|
||||||
@@ -55,26 +48,19 @@ export const HistoryListContainer: FunctionComponent<Props> = observer(
|
|||||||
const fetchLegacyHistory = async () => {
|
const fetchLegacyHistory = async () => {
|
||||||
const actionExtensions = application.actionsManager.getExtensions()
|
const actionExtensions = application.actionsManager.getExtensions()
|
||||||
actionExtensions.forEach(async (ext) => {
|
actionExtensions.forEach(async (ext) => {
|
||||||
const actionExtension = await application.actionsManager.loadExtensionInContextOfItem(
|
const actionExtension = await application.actionsManager.loadExtensionInContextOfItem(ext, note)
|
||||||
ext,
|
|
||||||
note,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!actionExtension) {
|
if (!actionExtension) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLegacyNoteHistoryExt = actionExtension?.actions.some(
|
const isLegacyNoteHistoryExt = actionExtension?.actions.some((action) => action.verb === ActionVerb.Nested)
|
||||||
(action) => action.verb === ActionVerb.Nested,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!isLegacyNoteHistoryExt) {
|
if (!isLegacyNoteHistoryExt) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const legacyHistoryEntries = actionExtension.actions.filter(
|
const legacyHistoryEntries = actionExtension.actions.filter((action) => action.subactions?.[0])
|
||||||
(action) => action.subactions?.[0],
|
|
||||||
)
|
|
||||||
|
|
||||||
setLegacyHistory(legacyHistoryEntries)
|
setLegacyHistory(legacyHistoryEntries)
|
||||||
})
|
})
|
||||||
@@ -114,10 +100,7 @@ export const HistoryListContainer: FunctionComponent<Props> = observer(
|
|||||||
throw new Error('Could not find revision action url')
|
throw new Error('Could not find revision action url')
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await application.actionsManager.runAction(
|
const response = await application.actionsManager.runAction(revisionListEntry.subactions[0], note)
|
||||||
revisionListEntry.subactions[0],
|
|
||||||
note,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
throw new Error('Could not fetch revision')
|
throw new Error('Could not fetch revision')
|
||||||
@@ -131,13 +114,7 @@ export const HistoryListContainer: FunctionComponent<Props> = observer(
|
|||||||
setIsFetchingSelectedRevision(false)
|
setIsFetchingSelectedRevision(false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[application.actionsManager, note, setIsFetchingSelectedRevision, setSelectedRemoteEntry, setSelectedRevision],
|
||||||
application.actionsManager,
|
|
||||||
note,
|
|
||||||
setIsFetchingSelectedRevision,
|
|
||||||
setSelectedRemoteEntry,
|
|
||||||
setSelectedRevision,
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const fetchAndSetRemoteRevision = useCallback(
|
const fetchAndSetRemoteRevision = useCallback(
|
||||||
@@ -150,10 +127,7 @@ export const HistoryListContainer: FunctionComponent<Props> = observer(
|
|||||||
setSelectedRemoteEntry(undefined)
|
setSelectedRemoteEntry(undefined)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const remoteRevision = await application.historyManager.fetchRemoteRevision(
|
const remoteRevision = await application.historyManager.fetchRemoteRevision(note, revisionListEntry)
|
||||||
note,
|
|
||||||
revisionListEntry,
|
|
||||||
)
|
|
||||||
setSelectedRevision(remoteRevision)
|
setSelectedRevision(remoteRevision)
|
||||||
setSelectedRemoteEntry(revisionListEntry)
|
setSelectedRemoteEntry(revisionListEntry)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -177,17 +151,11 @@ export const HistoryListContainer: FunctionComponent<Props> = observer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={'flex flex-col min-w-60 border-0 border-r-1px border-solid border-main overflow-auto h-full'}>
|
||||||
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">
|
<div className="flex border-0 border-b-1 border-solid border-main">
|
||||||
<TabButton type={RevisionListTabType.Remote} />
|
<TabButton type={RevisionListTabType.Remote} />
|
||||||
<TabButton type={RevisionListTabType.Session} />
|
<TabButton type={RevisionListTabType.Session} />
|
||||||
{legacyHistory && legacyHistory.length > 0 && (
|
{legacyHistory && legacyHistory.length > 0 && <TabButton type={RevisionListTabType.Legacy} />}
|
||||||
<TabButton type={RevisionListTabType.Legacy} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className={'min-h-0 overflow-auto py-1.5 h-full'}>
|
<div className={'min-h-0 overflow-auto py-1.5 h-full'}>
|
||||||
{selectedTab === RevisionListTabType.Session && (
|
{selectedTab === RevisionListTabType.Session && (
|
||||||
|
|||||||
@@ -6,23 +6,15 @@ type HistoryListItemProps = {
|
|||||||
onClick: () => void
|
onClick: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HistoryListItem: FunctionComponent<HistoryListItemProps> = ({
|
export const HistoryListItem: FunctionComponent<HistoryListItemProps> = ({ children, isSelected, onClick }) => {
|
||||||
children,
|
|
||||||
isSelected,
|
|
||||||
onClick,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||||
className={`sn-dropdown-item py-2.5 focus:bg-contrast focus:shadow-none ${
|
className={`sn-dropdown-item py-2.5 focus:bg-contrast focus:shadow-none ${isSelected ? 'bg-info-backdrop' : ''}`}
|
||||||
isSelected ? 'bg-info-backdrop' : ''
|
|
||||||
}`}
|
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
data-selected={isSelected}
|
data-selected={isSelected}
|
||||||
>
|
>
|
||||||
<div
|
<div className={`pseudo-radio-btn ${isSelected ? 'pseudo-radio-btn--checked' : ''} mr-2`}></div>
|
||||||
className={`pseudo-radio-btn ${isSelected ? 'pseudo-radio-btn--checked' : ''} mr-2`}
|
|
||||||
></div>
|
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -68,9 +68,7 @@ export const LegacyHistoryList: FunctionComponent<Props> = ({
|
|||||||
</HistoryListItem>
|
</HistoryListItem>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
{!legacyHistory?.length && (
|
{!legacyHistory?.length && <div className="color-grey-0 select-none">No legacy history found</div>}
|
||||||
<div className="color-grey-0 select-none">No legacy history found</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,13 +43,7 @@ export const RemoteHistoryList: FunctionComponent<RemoteHistoryListProps> = obse
|
|||||||
if (firstEntry && !selectedEntryUuid.length) {
|
if (firstEntry && !selectedEntryUuid.length) {
|
||||||
selectFirstEntry()
|
selectFirstEntry()
|
||||||
}
|
}
|
||||||
}, [
|
}, [fetchAndSetRemoteRevision, firstEntry, remoteHistory, selectFirstEntry, selectedEntryUuid.length])
|
||||||
fetchAndSetRemoteRevision,
|
|
||||||
firstEntry,
|
|
||||||
remoteHistory,
|
|
||||||
selectFirstEntry,
|
|
||||||
selectedEntryUuid.length,
|
|
||||||
])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -76,9 +70,7 @@ export const RemoteHistoryList: FunctionComponent<RemoteHistoryListProps> = obse
|
|||||||
>
|
>
|
||||||
<div className="flex flex-grow items-center justify-between">
|
<div className="flex flex-grow items-center justify-between">
|
||||||
<div>{previewHistoryEntryTitle(entry)}</div>
|
<div>{previewHistoryEntryTitle(entry)}</div>
|
||||||
{!application.features.hasMinimumRole(entry.required_role) && (
|
{!application.features.hasMinimumRole(entry.required_role) && <Icon type="premium-feature" />}
|
||||||
<Icon type="premium-feature" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</HistoryListItem>
|
</HistoryListItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ const getPremiumContentCopy = (planName: string | undefined) => {
|
|||||||
export const RevisionContentLocked: FunctionComponent<{
|
export const RevisionContentLocked: FunctionComponent<{
|
||||||
appState: AppState
|
appState: AppState
|
||||||
}> = observer(({ appState }) => {
|
}> = observer(({ appState }) => {
|
||||||
const { userSubscriptionName, isUserSubscriptionExpired, isUserSubscriptionCanceled } =
|
const { userSubscriptionName, isUserSubscriptionExpired, isUserSubscriptionCanceled } = appState.subscription
|
||||||
appState.subscription
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full h-full items-center justify-center">
|
<div className="flex w-full h-full items-center justify-center">
|
||||||
|
|||||||
@@ -46,13 +46,9 @@ const RevisionContentPlaceholder: FunctionComponent<RevisionContentPlaceholderPr
|
|||||||
: '-z-index-1'
|
: '-z-index-1'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isFetchingSelectedRevision && (
|
{isFetchingSelectedRevision && <div className={`sk-spinner w-5 h-5 spinner-info ${ABSOLUTE_CENTER_CLASSNAME}`} />}
|
||||||
<div className={`sk-spinner w-5 h-5 spinner-info ${ABSOLUTE_CENTER_CLASSNAME}`} />
|
|
||||||
)}
|
|
||||||
{!isFetchingSelectedRevision && !selectedRevision ? (
|
{!isFetchingSelectedRevision && !selectedRevision ? (
|
||||||
<div className={`color-grey-0 select-none ${ABSOLUTE_CENTER_CLASSNAME}`}>
|
<div className={`color-grey-0 select-none ${ABSOLUTE_CENTER_CLASSNAME}`}>No revision selected</div>
|
||||||
No revision selected
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -87,8 +83,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
try {
|
try {
|
||||||
const initialRemoteHistory = await application.historyManager.remoteHistoryForItem(note)
|
const initialRemoteHistory = await application.historyManager.remoteHistoryForItem(note)
|
||||||
|
|
||||||
const remoteHistoryAsGroups =
|
const remoteHistoryAsGroups = sortRevisionListIntoGroups<RevisionListEntry>(initialRemoteHistory)
|
||||||
sortRevisionListIntoGroups<RevisionListEntry>(initialRemoteHistory)
|
|
||||||
|
|
||||||
setRemoteHistory(remoteHistoryAsGroups)
|
setRemoteHistory(remoteHistoryAsGroups)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -240,9 +235,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
isFetchingSelectedRevision={isFetchingSelectedRevision}
|
isFetchingSelectedRevision={isFetchingSelectedRevision}
|
||||||
showContentLockedScreen={showContentLockedScreen}
|
showContentLockedScreen={showContentLockedScreen}
|
||||||
/>
|
/>
|
||||||
{showContentLockedScreen && !selectedRevision && (
|
{showContentLockedScreen && !selectedRevision && <RevisionContentLocked appState={appState} />}
|
||||||
<RevisionContentLocked appState={appState} />
|
|
||||||
)}
|
|
||||||
{selectedRevision && templateNoteForRevision && (
|
{selectedRevision && templateNoteForRevision && (
|
||||||
<SelectedRevisionContent
|
<SelectedRevisionContent
|
||||||
application={application}
|
application={application}
|
||||||
@@ -267,11 +260,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
{selectedRevision && (
|
{selectedRevision && (
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
{selectedRemoteEntry && (
|
{selectedRemoteEntry && (
|
||||||
<Button
|
<Button className="py-1.35 mr-2.5" onClick={deleteSelectedRevision} variant="normal">
|
||||||
className="py-1.35 mr-2.5"
|
|
||||||
onClick={deleteSelectedRevision}
|
|
||||||
variant="normal"
|
|
||||||
>
|
|
||||||
{isDeletingRevision ? (
|
{isDeletingRevision ? (
|
||||||
<div className="sk-spinner my-1 w-3 h-3 spinner-info" />
|
<div className="sk-spinner my-1 w-3 h-3 spinner-info" />
|
||||||
) : (
|
) : (
|
||||||
@@ -285,12 +274,7 @@ export const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps>
|
|||||||
onClick={restoreAsCopy}
|
onClick={restoreAsCopy}
|
||||||
variant="normal"
|
variant="normal"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button className="py-1.35" label="Restore version" onClick={restore} variant="primary" />
|
||||||
className="py-1.35"
|
|
||||||
label="Restore version"
|
|
||||||
onClick={restore}
|
|
||||||
variant="primary"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ export const SelectedRevisionContent: FunctionComponent<SelectedRevisionContentP
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const componentViewer =
|
const componentViewer = application.componentManager.createComponentViewer(editorForCurrentNote)
|
||||||
application.componentManager.createComponentViewer(editorForCurrentNote)
|
|
||||||
componentViewer.setReadonly(true)
|
componentViewer.setReadonly(true)
|
||||||
componentViewer.lockReadonly = true
|
componentViewer.lockReadonly = true
|
||||||
componentViewer.overrideContextItem = templateNoteForRevision
|
componentViewer.overrideContextItem = templateNoteForRevision
|
||||||
|
|||||||
@@ -75,9 +75,7 @@ export const SessionHistoryList: FunctionComponent<Props> = ({
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
) : null),
|
) : null),
|
||||||
)}
|
)}
|
||||||
{!sessionHistoryLength && (
|
{!sessionHistoryLength && <div className="color-grey-0 select-none">No session history found</div>}
|
||||||
<div className="color-grey-0 select-none">No session history found</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,7 @@ export const formatDateAsMonthYearString = (date: Date) =>
|
|||||||
|
|
||||||
export const getGroupIndexForEntry = (entry: RevisionEntry, groups: ListGroup<RevisionEntry>[]) => {
|
export const getGroupIndexForEntry = (entry: RevisionEntry, groups: ListGroup<RevisionEntry>[]) => {
|
||||||
const todayAsDate = new Date()
|
const todayAsDate = new Date()
|
||||||
const entryDate = new Date(
|
const entryDate = new Date((entry as RevisionListEntry).created_at ?? (entry as NoteHistoryEntry).payload.created_at)
|
||||||
(entry as RevisionListEntry).created_at ?? (entry as NoteHistoryEntry).payload.created_at,
|
|
||||||
)
|
|
||||||
|
|
||||||
const differenceBetweenDatesInDays = calculateDifferenceBetweenDatesInDays(todayAsDate, entryDate)
|
const differenceBetweenDatesInDays = calculateDifferenceBetweenDatesInDays(todayAsDate, entryDate)
|
||||||
|
|
||||||
@@ -52,9 +50,7 @@ const GROUP_TITLE_TODAY = 'Today'
|
|||||||
const GROUP_TITLE_WEEK = 'This Week'
|
const GROUP_TITLE_WEEK = 'This Week'
|
||||||
const GROUP_TITLE_YEAR = 'More Than A Year Ago'
|
const GROUP_TITLE_YEAR = 'More Than A Year Ago'
|
||||||
|
|
||||||
export const sortRevisionListIntoGroups = <EntryType extends RevisionEntry>(
|
export const sortRevisionListIntoGroups = <EntryType extends RevisionEntry>(revisionList: EntryType[] | undefined) => {
|
||||||
revisionList: EntryType[] | undefined,
|
|
||||||
) => {
|
|
||||||
const sortedGroups: ListGroup<EntryType>[] = [
|
const sortedGroups: ListGroup<EntryType>[] = [
|
||||||
{
|
{
|
||||||
title: GROUP_TITLE_TODAY,
|
title: GROUP_TITLE_TODAY,
|
||||||
@@ -82,10 +78,7 @@ export const sortRevisionListIntoGroups = <EntryType extends RevisionEntry>(
|
|||||||
} else {
|
} else {
|
||||||
addBeforeLastGroup({
|
addBeforeLastGroup({
|
||||||
title: formatDateAsMonthYearString(
|
title: formatDateAsMonthYearString(
|
||||||
new Date(
|
new Date((entry as RevisionListEntry).created_at ?? (entry as NoteHistoryEntry).payload.created_at),
|
||||||
(entry as RevisionListEntry).created_at ??
|
|
||||||
(entry as NoteHistoryEntry).payload.created_at,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
entries: [entry],
|
entries: [entry],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,28 +18,16 @@ export const SearchOptions = observer(({ appState }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div role="tablist" className="search-options justify-center" onMouseDown={(e) => e.preventDefault()}>
|
||||||
role="tablist"
|
|
||||||
className="search-options justify-center"
|
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
<Bubble
|
<Bubble
|
||||||
label="Protected Contents"
|
label="Protected Contents"
|
||||||
selected={includeProtectedContents}
|
selected={includeProtectedContents}
|
||||||
onSelect={toggleIncludeProtectedContents}
|
onSelect={toggleIncludeProtectedContents}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Bubble
|
<Bubble label="Archived" selected={includeArchived} onSelect={searchOptions.toggleIncludeArchived} />
|
||||||
label="Archived"
|
|
||||||
selected={includeArchived}
|
|
||||||
onSelect={searchOptions.toggleIncludeArchived}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Bubble
|
<Bubble label="Trashed" selected={includeTrashed} onSelect={searchOptions.toggleIncludeTrashed} />
|
||||||
label="Trashed"
|
|
||||||
selected={includeTrashed}
|
|
||||||
onSelect={searchOptions.toggleIncludeTrashed}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import { AppState } from '@/UIModels/AppState'
|
import { AppState } from '@/UIModels/AppState'
|
||||||
import {
|
import { SNApplication, SessionStrings, UuidString, isNullOrUndefined, RemoteSession } from '@standardnotes/snjs'
|
||||||
SNApplication,
|
|
||||||
SessionStrings,
|
|
||||||
UuidString,
|
|
||||||
isNullOrUndefined,
|
|
||||||
RemoteSession,
|
|
||||||
} from '@standardnotes/snjs'
|
|
||||||
import { FunctionComponent } from 'preact'
|
import { FunctionComponent } from 'preact'
|
||||||
import { useState, useEffect, useRef, useMemo } from 'preact/hooks'
|
import { useState, useEffect, useRef, useMemo } from 'preact/hooks'
|
||||||
import { Dialog } from '@reach/dialog'
|
import { Dialog } from '@reach/dialog'
|
||||||
|
|||||||
@@ -28,11 +28,7 @@ export const AccordionItem: FunctionalComponent<Props> = ({ title, className = '
|
|||||||
data-is-expanded={isExpanded}
|
data-is-expanded={isExpanded}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className={'accordion-contents-container cursor-auto'} data-is-expanded={isExpanded} ref={elementRef}>
|
||||||
className={'accordion-contents-container cursor-auto'}
|
|
||||||
data-is-expanded={isExpanded}
|
|
||||||
ref={elementRef}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user