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