fix(mobile): increase font sizes and other mobile-centric improvements (#1907)

This commit is contained in:
Mo
2022-11-01 11:41:40 -05:00
committed by GitHub
parent f54b017f53
commit 994f824757
72 changed files with 543 additions and 283 deletions

View File

@@ -111,7 +111,7 @@ async function configureWindow(remoteBridge: CrossProcessBridge) {
// For Mac inset window // For Mac inset window
const sheet = document.styleSheets[0] const sheet = document.styleSheets[0]
if (isMacOS) { if (isMacOS) {
sheet.insertRule('#navigation { padding-top: 25px !important; }', sheet.cssRules.length) sheet.insertRule('#navigation-content { padding-top: 25px !important; }', sheet.cssRules.length)
} }
if (isMacOS || useSystemMenuBar) { if (isMacOS || useSystemMenuBar) {

View File

@@ -58,12 +58,34 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
device.reloadStatusBarStyle(false) device.reloadStatusBarStyle(false)
}) })
const keyboardWillChangeFrame = Keyboard.addListener('keyboardWillChangeFrame', (e) => {
webViewRef.current?.postMessage(
JSON.stringify({
reactNativeEvent: ReactNativeToWebEvent.KeyboardFrameWillChange,
messageType: 'event',
messageData: { height: e.endCoordinates.height, contentHeight: e.endCoordinates.screenY },
}),
)
})
const keyboardDidChangeFrame = Keyboard.addListener('keyboardDidChangeFrame', (e) => {
webViewRef.current?.postMessage(
JSON.stringify({
reactNativeEvent: ReactNativeToWebEvent.KeyboardFrameDidChange,
messageType: 'event',
messageData: { height: e.endCoordinates.height, contentHeight: e.endCoordinates.screenY },
}),
)
})
return () => { return () => {
removeStateServiceListener() removeStateServiceListener()
removeBackHandlerServiceListener() removeBackHandlerServiceListener()
removeColorSchemeServiceListener() removeColorSchemeServiceListener()
keyboardShowListener.remove() keyboardShowListener.remove()
keyboardHideListener.remove() keyboardHideListener.remove()
keyboardWillChangeFrame.remove()
keyboardDidChangeFrame.remove()
} }
}, [webViewRef, stateService, device, androidBackHandlerService, colorSchemeService]) }, [webViewRef, stateService, device, androidBackHandlerService, colorSchemeService])
@@ -198,7 +220,7 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
const onFunctionMessage = async (functionName: string, messageId: string, args: any) => { const onFunctionMessage = async (functionName: string, messageId: string, args: any) => {
const returnValue = await (device as any)[functionName](...args) const returnValue = await (device as any)[functionName](...args)
if (LoggingEnabled) { if (LoggingEnabled && functionName !== 'consoleLog') {
console.log(`Native device function ${functionName} called`) console.log(`Native device function ${functionName} called`)
} }
webViewRef.current?.postMessage(JSON.stringify({ messageId, returnValue, messageType: 'reply' })) webViewRef.current?.postMessage(JSON.stringify({ messageId, returnValue, messageType: 'reply' }))
@@ -253,6 +275,12 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
injectedJavaScriptBeforeContentLoaded={injectedJS} injectedJavaScriptBeforeContentLoaded={injectedJS}
bounces={false} bounces={false}
keyboardDisplayRequiresUserAction={false} keyboardDisplayRequiresUserAction={false}
scalesPageToFit={true}
/**
* This disables the global window scroll but keeps scroll within div elements like lists and textareas.
* This is needed to prevent the keyboard from pushing the webview up and down when it appears and disappears.
*/
scrollEnabled={false}
/> />
) )
} }

View File

@@ -11,6 +11,8 @@ export interface WebApplicationInterface extends ApplicationInterface {
handleMobileLosingFocusEvent(): Promise<void> handleMobileLosingFocusEvent(): Promise<void>
handleMobileResumingFromBackgroundEvent(): Promise<void> handleMobileResumingFromBackgroundEvent(): Promise<void>
handleMobileColorSchemeChangeEvent(): void handleMobileColorSchemeChangeEvent(): void
handleMobileKeyboardWillChangeFrameEvent(frame: { height: number; contentHeight: number }): void
handleMobileKeyboardDidChangeFrameEvent(frame: { height: number; contentHeight: number }): void
isNativeMobileWeb(): boolean isNativeMobileWeb(): boolean
mobileDevice(): MobileDeviceInterface mobileDevice(): MobileDeviceInterface
handleAndroidBackButtonPressed(): void handleAndroidBackButtonPressed(): void

View File

@@ -6,4 +6,6 @@ export enum WebAppEvent {
PanelResized = 'PanelResized', PanelResized = 'PanelResized',
WindowDidFocus = 'WindowDidFocus', WindowDidFocus = 'WindowDidFocus',
WindowDidBlur = 'WindowDidBlur', WindowDidBlur = 'WindowDidBlur',
MobileKeyboardDidChangeFrame = 'MobileKeyboardDidChangeFrame',
MobileKeyboardWillChangeFrame = 'MobileKeyboardWillChangeFrame',
} }

View File

@@ -5,4 +5,6 @@ export enum ReactNativeToWebEvent {
LosingFocus = 'LosingFocus', LosingFocus = 'LosingFocus',
AndroidBackButtonPressed = 'AndroidBackButtonPressed', AndroidBackButtonPressed = 'AndroidBackButtonPressed',
ColorSchemeChanged = 'ColorSchemeChanged', ColorSchemeChanged = 'ColorSchemeChanged',
KeyboardFrameWillChange = 'KeyboardFrameWillChange',
KeyboardFrameDidChange = 'KeyboardFrameDidChange',
} }

View File

@@ -19,10 +19,6 @@
box-shadow: none; box-shadow: none;
border: none; border: none;
border-radius: 0; border-radius: 0;
.sk-panel-content {
// padding: 0;
}
} }
.sk-panel-header { .sk-panel-header {

View File

@@ -5,7 +5,7 @@
--sn-stylekit-base-font-size: 0.8125rem; --sn-stylekit-base-font-size: 0.8125rem;
--sn-stylekit-font-size-p: 0.8125rem; --sn-stylekit-font-size-p: 0.8125rem;
--sn-stylekit-font-size-editor: 0.983125rem; --sn-stylekit-font-size-editor: 0.9375rem;
--sn-stylekit-font-size-h6: 0.65rem; --sn-stylekit-font-size-h6: 0.65rem;
--sn-stylekit-font-size-h5: 0.73125rem; --sn-stylekit-font-size-h5: 0.73125rem;

View File

@@ -38,7 +38,7 @@ import {
import { MobileWebReceiver, NativeMobileEventListener } from '../NativeMobileWeb/MobileWebReceiver' import { MobileWebReceiver, NativeMobileEventListener } from '../NativeMobileWeb/MobileWebReceiver'
import { AndroidBackHandler } from '@/NativeMobileWeb/AndroidBackHandler' import { AndroidBackHandler } from '@/NativeMobileWeb/AndroidBackHandler'
import { PrefDefaults } from '@/Constants/PrefDefaults' import { PrefDefaults } from '@/Constants/PrefDefaults'
import { setViewportHeightWithFallback } from '@/setViewportHeightWithFallback' import { setCustomViewportHeight, setViewportHeightWithFallback } from '@/setViewportHeightWithFallback'
import { WebServices } from './WebServices' import { WebServices } from './WebServices'
export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void
@@ -293,6 +293,15 @@ export class WebApplication extends SNApplication implements WebApplicationInter
void this.getThemeService().handleMobileColorSchemeChangeEvent() void this.getThemeService().handleMobileColorSchemeChangeEvent()
} }
handleMobileKeyboardWillChangeFrameEvent(frame: { height: number; contentHeight: number }): void {
setCustomViewportHeight(String(frame.contentHeight), 'px', true)
this.notifyWebEvent(WebAppEvent.MobileKeyboardWillChangeFrame, frame)
}
handleMobileKeyboardDidChangeFrameEvent(frame: { height: number; contentHeight: number }): void {
this.notifyWebEvent(WebAppEvent.MobileKeyboardDidChangeFrame, frame)
}
private async lockApplicationAfterMobileEventIfApplicable(): Promise<void> { private async lockApplicationAfterMobileEventIfApplicable(): Promise<void> {
const isLocked = await this.isLocked() const isLocked = await this.isLocked()
if (isLocked) { if (isLocked) {

View File

@@ -14,6 +14,7 @@ import WorkspaceSwitcherOption from './WorkspaceSwitcher/WorkspaceSwitcherOption
import { ApplicationGroup } from '@/Application/ApplicationGroup' import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { formatLastSyncDate } from '@/Utils/DateUtils' import { formatLastSyncDate } from '@/Utils/DateUtils'
import Spinner from '@/Components/Spinner/Spinner' import Spinner from '@/Components/Spinner/Spinner'
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
type Props = { type Props = {
viewControllerManager: ViewControllerManager viewControllerManager: ViewControllerManager
@@ -23,7 +24,7 @@ type Props = {
closeMenu: () => void closeMenu: () => void
} }
const iconClassName = 'text-neutral mr-2' const iconClassName = `text-neutral mr-2 ${MenuItemIconSize}`
const GeneralAccountMenu: FunctionComponent<Props> = ({ const GeneralAccountMenu: FunctionComponent<Props> = ({
application, application,
@@ -90,48 +91,48 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
return ( return (
<> <>
<div className="mt-1 mb-1 flex items-center justify-between px-3"> <div className="mt-1 mb-1 flex items-center justify-between px-3">
<div className="text-base font-bold">Account</div> <div className="text-lg font-bold lg:text-base">Account</div>
<div className="flex cursor-pointer" onClick={closeMenu}> <div className="flex cursor-pointer" onClick={closeMenu}>
<Icon type="close" className="text-neutral" /> <Icon type="close" className="text-neutral" />
</div> </div>
</div> </div>
{user ? ( {user ? (
<> <>
<div className="mb-3 px-3 text-sm text-foreground"> <div className="mb-3 px-3 text-lg text-foreground lg:text-sm">
<div>You're signed in as:</div> <div>You're signed in as:</div>
<div className="wrap my-0.5 font-bold">{user.email}</div> <div className="wrap my-0.5 font-bold">{user.email}</div>
<span className="text-neutral">{application.getHost()}</span> <span className="text-neutral">{application.getHost()}</span>
</div> </div>
<div className="mb-2 flex items-start justify-between px-3"> <div className="mb-2 flex items-start justify-between px-3">
{isSyncingInProgress ? ( {isSyncingInProgress ? (
<div className="flex items-center text-sm font-semibold text-info"> <div className="flex items-center text-base font-semibold text-info lg:text-sm">
<Spinner className="mr-2 h-5 w-5" /> <Spinner className="mr-2 h-5 w-5" />
Syncing... Syncing...
</div> </div>
) : ( ) : (
<div className="flex items-start"> <div className="flex items-start">
<Icon type="check-circle" className="mr-2 text-success" /> <Icon type="check-circle" className={`mr-2 text-success ${MenuItemIconSize}`} />
<div> <div>
<div className="text-sm font-semibold text-success">Last synced:</div> <div className="text-base font-semibold text-success lg:text-sm">Last synced:</div>
<div className="text-sm text-text">{lastSyncDate}</div> <div className="text-base text-text lg:text-sm">{lastSyncDate}</div>
</div> </div>
</div> </div>
)} )}
<div className="flex cursor-pointer text-passive-1" onClick={doSynchronization}> <div className="flex cursor-pointer text-passive-1" onClick={doSynchronization}>
<Icon type="sync" /> <Icon type="sync" className={`${MenuItemIconSize}`} />
</div> </div>
</div> </div>
</> </>
) : ( ) : (
<> <>
<div className="mb-1 px-3"> <div className="mb-1 px-3">
<div className="mb-3 text-sm text-foreground"> <div className="mb-3 text-base text-foreground lg:text-sm">
Youre offline. Sign in to sync your notes and preferences across all your devices and enable end-to-end Youre offline. Sign in to sync your notes and preferences across all your devices and enable end-to-end
encryption. encryption.
</div> </div>
<div className="flex items-center text-passive-1"> <div className="flex items-center text-passive-1">
<Icon type="cloud-off" className="mr-2" /> <Icon type="cloud-off" className={`mr-2 ${MenuItemIconSize}`} />
<span className="text-sm font-semibold">Offline</span> <span className="text-lg font-semibold lg:text-sm">Offline</span>
</div> </div>
</div> </div>
</> </>

View File

@@ -8,6 +8,7 @@ import WorkspaceSwitcherMenu from './WorkspaceSwitcherMenu'
import MenuItem from '@/Components/Menu/MenuItem' import MenuItem from '@/Components/Menu/MenuItem'
import { MenuItemType } from '@/Components/Menu/MenuItemType' import { MenuItemType } from '@/Components/Menu/MenuItemType'
import Popover from '@/Components/Popover/Popover' import Popover from '@/Components/Popover/Popover'
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
type Props = { type Props = {
mainApplicationGroup: ApplicationGroup mainApplicationGroup: ApplicationGroup
@@ -32,10 +33,10 @@ const WorkspaceSwitcherOption: FunctionComponent<Props> = ({ mainApplicationGrou
className="justify-between" className="justify-between"
> >
<div className="flex items-center"> <div className="flex items-center">
<Icon type="user-switch" className="mr-2 text-neutral" /> <Icon type="user-switch" className={`mr-2 text-neutral ${MenuItemIconSize}`} />
Switch workspace Switch workspace
</div> </div>
<Icon type="chevron-right" className="text-neutral" /> <Icon type="chevron-right" className={`text-neutral ${MenuItemIconSize}`} />
</MenuItem> </MenuItem>
<Popover <Popover
align="end" align="end"

View File

@@ -87,7 +87,7 @@ class ApplicationGroupView extends Component<Props, State> {
'challenge-modal shadow-overlay-light relative flex flex-col items-center rounded border border-solid border-border bg-default p-8' 'challenge-modal shadow-overlay-light relative flex flex-col items-center rounded border border-solid border-border bg-default p-8'
} }
> >
{message} <div className="text-base lg:text-xs">{message}</div>
</DialogContent> </DialogContent>
</DialogOverlay> </DialogOverlay>
) )

View File

@@ -7,7 +7,7 @@ type Props = {
} }
const styles = { const styles = {
base: 'active:border-info active:bg-info active:text-neutral-contrast flex-grow cursor-pointer rounded-full border border-solid px-2 py-1 text-center transition', base: 'active:border-info active:bg-info active:text-neutral-contrast flex-grow cursor-pointer rounded-full border border-solid px-2 py-1 text-center transition text-sm',
unselected: 'text-neutral border-secondary-border bg-default', unselected: 'text-neutral border-secondary-border bg-default',
selected: 'text-neutral-contrast border-info bg-info', selected: 'text-neutral-contrast border-info bg-info',
} }

View File

@@ -52,7 +52,7 @@ const getClassName = (
const cursor = disabled ? 'cursor-not-allowed' : 'cursor-pointer' const cursor = disabled ? 'cursor-not-allowed' : 'cursor-pointer'
const width = fullWidth ? 'w-full' : 'w-fit' const width = fullWidth ? 'w-full' : 'w-fit'
const padding = small ? 'px-3 py-1.5' : 'px-4 py-1.5' const padding = small ? 'px-3 py-1.5' : 'px-4 py-1.5'
const textSize = small ? 'text-xs' : 'text-sm' const textSize = small ? 'text-sm lg:text-xs' : 'text-base lg:text-sm'
const rounded = isRounded ? 'rounded' : '' const rounded = isRounded ? 'rounded' : ''
let colors = primary ? getColorsForPrimaryVariant(style) : getColorsForNormalVariant(style) let colors = primary ? getColorsForPrimaryVariant(style) : getColorsForNormalVariant(style)

View File

@@ -21,7 +21,9 @@ const RoundIconButton = forwardRef(
return ( return (
<button <button
className={classNames( className={classNames(
'bg-text-padding m-0 flex h-8.5 min-w-8.5 cursor-pointer items-center justify-center rounded-full border border-solid border-border bg-clip-padding text-neutral hover:bg-contrast hover:text-text focus:bg-contrast focus:text-text focus:outline-none focus:ring-info md:h-8 md:min-w-8', 'bg-text-padding m-0 flex h-10 min-w-10 cursor-pointer items-center justify-center rounded-full border',
'border-solid border-border bg-clip-padding text-neutral hover:bg-contrast hover:text-text focus:bg-contrast',
'focus:text-text focus:outline-none focus:ring-info md:h-8 md:min-w-8',
className, className,
)} )}
onClick={click} onClick={click}

View File

@@ -19,6 +19,8 @@ import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager' import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { ChallengeModalValues } from './ChallengeModalValues' import { ChallengeModalValues } from './ChallengeModalValues'
import { InputValue } from './InputValue' import { InputValue } from './InputValue'
import { isMobileScreen } from '@/Utils'
import { classNames } from '@/Utils/ConcatenateClassNames'
type Props = { type Props = {
application: WebApplication application: WebApplication
@@ -211,23 +213,24 @@ const ChallengeModal: FunctionComponent<Props> = ({
return null return null
} }
const isFullScreenBlocker = challenge.reason === ChallengeReason.ApplicationUnlock
const isMobileOverlay = isMobileScreen() && !isFullScreenBlocker
const contentClasses = classNames(
'challenge-modal relative flex flex-col items-center rounded border-solid border-border p-8 md:border',
!isMobileScreen() && 'shadow-overlay-light',
isMobileOverlay && 'border border-solid border-border shadow-overlay-light',
isFullScreenBlocker && isMobileScreen() ? 'bg-passive-5' : 'bg-default',
)
return ( return (
<DialogOverlay <DialogOverlay
className={`sn-component ${ className={`sn-component ${isFullScreenBlocker ? 'bg-passive-5' : ''}`}
challenge.reason === ChallengeReason.ApplicationUnlock ? 'challenge-modal-overlay' : ''
}`}
onDismiss={cancelChallenge} onDismiss={cancelChallenge}
dangerouslyBypassFocusLock={bypassModalFocusLock} dangerouslyBypassFocusLock={bypassModalFocusLock}
key={challenge.id} key={challenge.id}
> >
<DialogContent <DialogContent aria-label="Challenge modal" className={contentClasses}>
aria-label="Challenge modal"
className={`challenge-modal relative flex flex-col items-center rounded bg-default p-8 ${
challenge.reason !== ChallengeReason.ApplicationUnlock
? 'shadow-overlay-light border border-solid border-border'
: 'focus:shadow-none'
}`}
>
{challenge.cancelable && ( {challenge.cancelable && (
<button <button
onClick={cancelChallenge} onClick={cancelChallenge}

View File

@@ -28,7 +28,7 @@ const PageSize = 2
const InfiniteCalendar = forwardRef<InfiniteCalendarInterface, Props>( const InfiniteCalendar = forwardRef<InfiniteCalendarInterface, Props>(
({ activities, onDateSelect, selectedDay, className }: Props, ref) => { ({ activities, onDateSelect, selectedDay, className }: Props, ref) => {
const [expanded, setExpanded] = useState(true) const [expanded, setExpanded] = useState(isMobileScreen() ? false : true)
const [restoreScrollAfterExpand, setRestoreScrollAfterExpand] = useState(false) const [restoreScrollAfterExpand, setRestoreScrollAfterExpand] = useState(false)
const scrollerRef = useRef<InfiniteScrollerInterface | null>(null) const scrollerRef = useRef<InfiniteScrollerInterface | null>(null)
const previousSelectedDay = usePrevious(selectedDay) const previousSelectedDay = usePrevious(selectedDay)

View File

@@ -9,6 +9,7 @@ import { ListableContentItem } from '../Types/ListableContentItem'
import { DailyItemsDay } from './DailyItemsDaySection' import { DailyItemsDay } from './DailyItemsDaySection'
import { ListItemTitle } from '../ListItemTitle' import { ListItemTitle } from '../ListItemTitle'
import { EmptyPlaceholderBars } from './EmptyPlaceholderBars' import { EmptyPlaceholderBars } from './EmptyPlaceholderBars'
import { isMobileScreen } from '@/Utils'
type DaySquareProps = { type DaySquareProps = {
day: number day: number
@@ -22,7 +23,7 @@ const DaySquare: FunctionComponent<DaySquareProps> = ({ day, hasActivity, weekda
<div <div
className={`${ className={`${
hasActivity ? 'bg-danger text-danger-contrast' : 'bg-neutral text-neutral-contrast' hasActivity ? 'bg-danger text-danger-contrast' : 'bg-neutral text-neutral-contrast'
} h-15 w-18 rounded p-2 text-center`} } h-19 w-18 rounded p-2 text-center`}
> >
<div className="text-sm font-bold">{weekday}</div> <div className="text-sm font-bold">{weekday}</div>
<div className="text-4xl font-bold">{day}</div> <div className="text-4xl font-bold">{day}</div>
@@ -72,7 +73,7 @@ export const DailyItemCell = forwardRef(
{!item && ( {!item && (
<div className="w-full"> <div className="w-full">
<div className="break-word mr-2 font-semibold">{formatDateAndTimeForNote(section.date, false)}</div> <div className="break-word mr-2 font-semibold">{formatDateAndTimeForNote(section.date, false)}</div>
<EmptyPlaceholderBars rows={4} /> <EmptyPlaceholderBars rows={isMobileScreen() ? 2 : 4} />
</div> </div>
)} )}
</div> </div>

View File

@@ -1,5 +1,5 @@
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { memo, useCallback, useRef, useState } from 'react' import { memo, useCallback, useMemo, useRef, useState } from 'react'
import Icon from '../../Icon/Icon' import Icon from '../../Icon/Icon'
import { classNames } from '@/Utils/ConcatenateClassNames' import { classNames } from '@/Utils/ConcatenateClassNames'
import Popover from '@/Components/Popover/Popover' import Popover from '@/Components/Popover/Popover'
@@ -8,6 +8,7 @@ import { NavigationMenuButton } from '@/Components/NavigationMenu/NavigationMenu
import { IconType, isTag } from '@standardnotes/snjs' import { IconType, isTag } from '@standardnotes/snjs'
import RoundIconButton from '@/Components/Button/RoundIconButton' import RoundIconButton from '@/Components/Button/RoundIconButton'
import { AnyTag } from '@/Controllers/Navigation/AnyTagType' import { AnyTag } from '@/Controllers/Navigation/AnyTagType'
import { MediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
type Props = { type Props = {
application: WebApplication application: WebApplication
@@ -34,30 +35,18 @@ const ContentListHeader = ({
const displayOptionsButtonRef = useRef<HTMLButtonElement>(null) const displayOptionsButtonRef = useRef<HTMLButtonElement>(null)
const isDailyEntry = isTag(selectedTag) && selectedTag.isDailyEntry const isDailyEntry = isTag(selectedTag) && selectedTag.isDailyEntry
const matchesMd = useMediaQuery(MediaQueryBreakpoints.md)
const isTouchScreen = !useMediaQuery(MediaQueryBreakpoints.pointerFine)
const isTablet = matchesMd && isTouchScreen
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false) const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false)
const toggleDisplayOptionsMenu = useCallback(() => { const toggleDisplayOptionsMenu = useCallback(() => {
setShowDisplayOptionsMenu((show) => !show) setShowDisplayOptionsMenu((show) => !show)
}, []) }, [])
return ( const OptionsMenu = useMemo(() => {
<div className="section-title-bar-header items-start gap-1 overflow-hidden"> return (
<NavigationMenuButton />
<div className="flex min-w-0 flex-grow flex-col break-words">
<div className={`flex min-w-0 flex-grow flex-row ${!optionsSubtitle ? 'items-center' : ''}`}>
{icon && (
<Icon
type={icon as IconType}
size={'large'}
className={`ml-0.5 mr-1.5 text-neutral ${optionsSubtitle ? 'mt-1' : ''}`}
/>
)}
<div className="flex min-w-0 flex-grow flex-col break-words">
<div className="text-lg font-semibold text-text">{panelTitle}</div>
{optionsSubtitle && <div className="text-xs text-passive-0">{optionsSubtitle}</div>}
</div>
</div>
</div>
<div className="flex"> <div className="flex">
<div className="relative" ref={displayOptionsContainerRef}> <div className="relative" ref={displayOptionsContainerRef}>
<RoundIconButton <RoundIconButton
@@ -83,21 +72,90 @@ const ContentListHeader = ({
/> />
</Popover> </Popover>
</div> </div>
<button
className={classNames(
'absolute bottom-6 right-6 z-editor-title-bar ml-3 flex h-13 w-13 cursor-pointer items-center',
`justify-center rounded-full border border-solid border-transparent ${
isDailyEntry ? 'bg-danger text-danger-contrast' : 'bg-info text-info-contrast'
}`,
'hover:brightness-125 md:static md:h-8 md:w-8',
)}
title={addButtonLabel}
aria-label={addButtonLabel}
onClick={addNewItem}
>
<Icon type="add" size="custom" className="h-6 w-6 md:h-5 md:w-5" />
</button>
</div> </div>
)
}, [
showDisplayOptionsMenu,
toggleDisplayOptionsMenu,
displayOptionsButtonRef,
application,
isFilesSmartView,
selectedTag,
])
const AddButton = useMemo(() => {
return (
<button
className={classNames(
'fixed bottom-6 right-6 z-editor-title-bar ml-3 flex h-15 w-15 cursor-pointer items-center',
`justify-center rounded-full border border-solid border-transparent ${
isDailyEntry ? 'bg-danger text-danger-contrast' : 'bg-info text-info-contrast'
}`,
'hover:brightness-125 md:static md:h-8 md:w-8',
)}
title={addButtonLabel}
aria-label={addButtonLabel}
onClick={addNewItem}
>
<Icon type="add" size="custom" className="h-8 w-8 md:h-5 md:w-5" />
</button>
)
}, [addButtonLabel, addNewItem, isDailyEntry])
const FolderName = useMemo(() => {
return (
<div className="flex min-w-0 flex-grow flex-col break-words pt-1 lg:pt-0">
<div className={`flex min-w-0 flex-grow flex-row ${!optionsSubtitle ? 'items-center' : ''}`}>
{icon && (
<Icon
type={icon as IconType}
size={'custom'}
className={` ml-0.5 mr-1.5 h-7 w-7 text-2xl text-neutral lg:h-6 lg:w-6 lg:text-lg ${
optionsSubtitle ? 'mt-1' : ''
}`}
/>
)}
<div className="flex min-w-0 flex-grow flex-col break-words">
<div className=" text-2xl font-semibold text-text md:text-lg">{panelTitle}</div>
{optionsSubtitle && <div className="text-xs text-passive-0">{optionsSubtitle}</div>}
</div>
</div>
</div>
)
}, [optionsSubtitle, icon, panelTitle])
const PhoneAndDesktopLayout = useMemo(() => {
return (
<div className={'flex w-full justify-between md:flex'}>
<NavigationMenuButton />
{FolderName}
<div className="flex">
{OptionsMenu}
{AddButton}
</div>
</div>
)
}, [OptionsMenu, AddButton, FolderName])
const TabletLayout = useMemo(() => {
return (
<div className={'w-full flex-col'}>
<div className="mb-2 flex justify-between">
<NavigationMenuButton />
<div className="flex">
{OptionsMenu}
{AddButton}
</div>
</div>
{FolderName}
</div>
)
}, [OptionsMenu, AddButton, FolderName])
return (
<div className="section-title-bar-header items-start gap-1 overflow-hidden">
{!isTablet && PhoneAndDesktopLayout}
{isTablet && TabletLayout}
</div> </div>
) )
} }

View File

@@ -23,9 +23,8 @@ import { PreferenceMode } from './PreferenceMode'
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '@/Components/Icon/PremiumFeatureIcon' import { PremiumFeatureIconClass, PremiumFeatureIconName } from '@/Components/Icon/PremiumFeatureIcon'
import Button from '@/Components/Button/Button' import Button from '@/Components/Button/Button'
import { classNames } from '@/Utils/ConcatenateClassNames' import { classNames } from '@/Utils/ConcatenateClassNames'
import { isDev } from '@/Utils'
const DailyEntryModeEnabled = isDev const DailyEntryModeEnabled = true
const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
closeDisplayOptionsMenu, closeDisplayOptionsMenu,
@@ -180,7 +179,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
return ( return (
<button <button
className={classNames( className={classNames(
'relative cursor-pointer rounded-full border-2 border-solid border-transparent px-2 text-sm focus:shadow-none', 'relative cursor-pointer rounded-full border-2 border-solid border-transparent px-2 text-base focus:shadow-none lg:text-sm',
isSelected ? 'bg-info text-info-contrast' : 'bg-transparent text-text hover:bg-info-backdrop', isSelected ? 'bg-info text-info-contrast' : 'bg-transparent text-text hover:bg-info-backdrop',
)} )}
onClick={() => { onClick={() => {
@@ -209,7 +208,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
</div> </div>
<p className="col-start-1 col-end-3 m-0 mt-1 text-sm"> <p className="col-start-1 col-end-3 m-0 mt-1 text-sm">
{DailyEntryModeEnabled && {DailyEntryModeEnabled &&
'Create powerful workflows and organizational layouts with per-tag display preferences and the all-new Daily Notebook feature.'} 'Create powerful workflows and organizational layouts with per-tag display preferences and the all-new Daily Notebook calendar layout.'}
{!DailyEntryModeEnabled && {!DailyEntryModeEnabled &&
'Create powerful workflows and organizational layouts with per-tag display preferences.'} 'Create powerful workflows and organizational layouts with per-tag display preferences.'}
</p> </p>
@@ -226,20 +225,24 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
return ( return (
<Menu className="text-sm" a11yLabel="Notes list options menu" closeMenu={closeDisplayOptionsMenu} isOpen={isOpen}> <Menu className="text-sm" a11yLabel="Notes list options menu" closeMenu={closeDisplayOptionsMenu} isOpen={isOpen}>
<div className="my-1 px-3 text-xs font-semibold uppercase text-text">Preferences for</div> <div className="my-1 px-3 text-base font-semibold uppercase text-text lg:text-xs">Preferences for</div>
<div className={classNames('mt-1.5 flex w-full justify-between px-3', !controlsDisabled && 'mb-3')}> <div className={classNames('mt-1.5 flex w-full justify-between px-3', !controlsDisabled && 'mb-3')}>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<TabButton label="Global" mode="global" /> <TabButton label="Global" mode="global" />
{!isSystemTag && <TabButton label={selectedTag.title} icon={selectedTag.iconString} mode="tag" />} {!isSystemTag && <TabButton label={selectedTag.title} icon={selectedTag.iconString} mode="tag" />}
</div> </div>
{currentMode === 'tag' && <button onClick={resetTagPreferences}>Reset</button>} {currentMode === 'tag' && (
<button className="text-base lg:text-sm" onClick={resetTagPreferences}>
Reset
</button>
)}
</div> </div>
{controlsDisabled && <NoSubscriptionBanner />} {controlsDisabled && <NoSubscriptionBanner />}
<MenuItemSeparator /> <MenuItemSeparator />
<div className="my-1 px-3 text-xs font-semibold uppercase text-text">Sort by</div> <div className="my-1 px-3 text-base font-semibold uppercase text-text lg:text-xs">Sort by</div>
<MenuItem <MenuItem
disabled={controlsDisabled || isDailyEntry} disabled={controlsDisabled || isDailyEntry}
className="py-2" className="py-2"
@@ -295,7 +298,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
</div> </div>
</MenuItem> </MenuItem>
<MenuItemSeparator /> <MenuItemSeparator />
<div className="px-3 py-1 text-xs font-semibold uppercase text-text">View</div> <div className="px-3 py-1 text-base font-semibold uppercase text-text lg:text-xs">View</div>
{!isFilesSmartView && ( {!isFilesSmartView && (
<MenuItem <MenuItem
disabled={controlsDisabled} disabled={controlsDisabled}
@@ -335,7 +338,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
Show icon Show icon
</MenuItem> </MenuItem>
<MenuItemSeparator /> <MenuItemSeparator />
<div className="px-3 py-1 text-xs font-semibold uppercase text-text">Other</div> <div className="px-3 py-1 text-base font-semibold uppercase text-text lg:text-xs">Other</div>
<MenuItem <MenuItem
disabled={controlsDisabled} disabled={controlsDisabled}
type={MenuItemType.SwitchButton} type={MenuItemType.SwitchButton}
@@ -384,7 +387,12 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
onChange={toggleEntryMode} onChange={toggleEntryMode}
> >
<div className="flex flex-col pr-5"> <div className="flex flex-col pr-5">
<div className="text-xs font-semibold uppercase text-text">Daily Notebook</div> <div className="flex flex-row items-center">
<div className="text-base font-semibold uppercase text-text lg:text-xs">Daily Notebook</div>
<div className="ml-2 rounded bg-success px-1.5 py-[1px] text-[10px] font-bold text-success-contrast">
Experimental
</div>
</div>
<div className="mt-1">Capture new notes daily with a calendar-based layout</div> <div className="mt-1">Capture new notes daily with a calendar-based layout</div>
</div> </div>
</MenuItem> </MenuItem>

View File

@@ -206,9 +206,9 @@ const NewNotePreferences: FunctionComponent<Props> = ({
return ( return (
<div className="my-1 px-3 pb-2 pt-1"> <div className="my-1 px-3 pb-2 pt-1">
<div className="text-xs font-semibold uppercase text-text">New Note Defaults</div> <div className="text-base font-semibold uppercase text-text lg:text-xs">New Note Defaults</div>
<div> <div>
<div className="mt-3">Note Type</div> <div className="mt-3 text-mobile-menu-item md:text-menu-item">Note Type</div>
<div className="mt-2"> <div className="mt-2">
<Dropdown <Dropdown
portal={false} portal={false}
@@ -223,7 +223,7 @@ const NewNotePreferences: FunctionComponent<Props> = ({
</div> </div>
</div> </div>
<div> <div>
<div className="mt-3">Title Format</div> <div className="mt-3 text-mobile-menu-item md:text-menu-item">Title Format</div>
<div className="mt-2"> <div className="mt-2">
<Dropdown <Dropdown
portal={false} portal={false}
@@ -249,10 +249,11 @@ const NewNotePreferences: FunctionComponent<Props> = ({
spellCheck={false} spellCheck={false}
/> />
</div> </div>
<div className="mt-2"> <div className="mt-3 text-neutral">
<span className="font-bold">Preview:</span> {dayjs().format(customNoteTitleFormat)} <span className="font-bold">Preview: </span>
<em>{dayjs().format(customNoteTitleFormat)}</em>
</div> </div>
<div className="mt-1"> <div className="mt-2 text-neutral">
<a <a
className="underline" className="underline"
href={HelpPageUrl} href={HelpPageUrl}
@@ -267,7 +268,7 @@ const NewNotePreferences: FunctionComponent<Props> = ({
> >
Options Options
</a> </a>
. Use <code>[]</code> to escape date-time formatting. . Use <code>[]</code> to escape formatting.
</div> </div>
</div> </div>
)} )}

View File

@@ -18,12 +18,12 @@ const ListItemFlagIcons: FunctionComponent<Props> = ({ item, hasFiles = false })
<div className="flex items-start border-b border-solid border-border p-4 pl-0"> <div className="flex items-start border-b border-solid border-border p-4 pl-0">
{item.locked && ( {item.locked && (
<span className="flex items-center" title="Editing Disabled"> <span className="flex items-center" title="Editing Disabled">
<Icon ariaLabel="Editing Disabled" type="pencil-off" className="text-info" size="small" /> <Icon ariaLabel="Editing Disabled" type="pencil-off" className="text-info" size="medium" />
</span> </span>
)} )}
{item.trashed && ( {item.trashed && (
<span className="ml-1.5 flex items-center" title="Trashed"> <span className="ml-1.5 flex items-center" title="Trashed">
<Icon ariaLabel="Trashed" type="trash-filled" className="text-danger" size="small" /> <Icon ariaLabel="Trashed" type="trash-filled" className="text-danger" size="medium" />
</span> </span>
)} )}
{item.archived && ( {item.archived && (
@@ -33,17 +33,17 @@ const ListItemFlagIcons: FunctionComponent<Props> = ({ item, hasFiles = false })
)} )}
{item.pinned && ( {item.pinned && (
<span className="ml-1.5 flex items-center" title="Pinned"> <span className="ml-1.5 flex items-center" title="Pinned">
<Icon ariaLabel="Pinned" type="pin-filled" className="text-info" size="small" /> <Icon ariaLabel="Pinned" type="pin-filled" className="text-info" size="medium" />
</span> </span>
)} )}
{hasFiles && ( {hasFiles && (
<span className="ml-1.5 flex items-center" title="Files"> <span className="ml-1.5 flex items-center" title="Files">
<Icon ariaLabel="Files" type="attachment-file" className="text-info" size="small" /> <Icon ariaLabel="Files" type="attachment-file" className="text-info" size="medium" />
</span> </span>
)} )}
{item.starred && ( {item.starred && (
<span className="ml-1.5 flex items-center" title="Starred"> <span className="ml-1.5 flex items-center" title="Starred">
<Icon ariaLabel="Starred" type="star-filled" className="text-warning" size="small" /> <Icon ariaLabel="Starred" type="star-filled" className="text-warning" size="medium" />
</span> </span>
)} )}
</div> </div>

View File

@@ -20,7 +20,7 @@ const ListItemMetadata: FunctionComponent<Props> = ({ item, hideDate, sortBy })
} }
return ( return (
<div className="leading-1.4 mt-1 text-xs opacity-50"> <div className="leading-1.4 mt-1 text-sm opacity-50 lg:text-xs">
{item.protected && <span>Protected {hideDate ? '' : ' • '}</span>} {item.protected && <span>Protected {hideDate ? '' : ' • '}</span>}
{!hideDate && showModifiedDate && <span>Modified {item.updatedAtString || 'Now'}</span>} {!hideDate && showModifiedDate && <span>Modified {item.updatedAtString || 'Now'}</span>}
{!hideDate && !showModifiedDate && <span>{item.createdAtString || 'Now'}</span>} {!hideDate && !showModifiedDate && <span>{item.createdAtString || 'Now'}</span>}

View File

@@ -14,7 +14,7 @@ const ListItemNotePreviewText: FunctionComponent<Props> = ({ item, hidePreview,
} }
return ( return (
<div className={`overflow-hidden overflow-ellipsis text-sm ${item.archived ? 'opacity-60' : ''}`}> <div className={`overflow-hidden overflow-ellipsis text-base lg:text-sm ${item.archived ? 'opacity-60' : ''}`}>
{item.preview_html && ( {item.preview_html && (
<div <div
className="my-1" className="my-1"

View File

@@ -13,7 +13,7 @@ const ListItemTags: FunctionComponent<Props> = ({ hideTags, tags }) => {
} }
return ( return (
<div className="mt-1.5 flex flex-wrap gap-2 text-xs"> <div className="mt-1.5 flex flex-wrap gap-2 text-sm lg:text-xs">
{tags.map((tag) => ( {tags.map((tag) => (
<span <span
className="inline-flex items-center rounded-sm bg-passive-4-opacity-variant py-1 px-1.5 text-foreground" className="inline-flex items-center rounded-sm bg-passive-4-opacity-variant py-1 px-1.5 text-foreground"

View File

@@ -3,8 +3,12 @@ import { ListableContentItem } from './Types/ListableContentItem'
export const ListItemTitle: FunctionComponent<{ item: ListableContentItem }> = ({ item }) => { export const ListItemTitle: FunctionComponent<{ item: ListableContentItem }> = ({ item }) => {
return ( return (
<div className="flex items-start justify-between overflow-hidden text-base font-semibold leading-[1.3]"> <div
<div className={`break-word mr-2 ${item.archived ? 'opacity-60' : ''}`}>{item.title}</div> className={`break-word mr-2 flex items-start justify-between overflow-hidden text-lg font-semibold leading-[1.3] lg:text-base lg:leading-[1.3] ${
item.archived ? 'opacity-60' : ''
}`}
>
{item.title}
</div> </div>
) )
} }

View File

@@ -36,7 +36,7 @@ const CustomDropdownButton: FunctionComponent<ListboxButtonProps> = ({
<Icon type={icon} className={iconClassName} size="small" /> <Icon type={icon} className={iconClassName} size="small" />
</div> </div>
) : null} ) : null}
<div className="dropdown-selected-label">{label}</div> <div className="text-base lg:text-sm">{label}</div>
</div> </div>
<ListboxArrow className={`flex ${isExpanded ? 'rotate-180' : ''}`}> <ListboxArrow className={`flex ${isExpanded ? 'rotate-180' : ''}`}>
<Icon type="menu-arrow-down" className="text-passive-1" size="small" /> <Icon type="menu-arrow-down" className="text-passive-1" size="small" />
@@ -92,7 +92,7 @@ const Dropdown: FunctionComponent<DropdownProps> = ({
<Icon type={item.icon} className={item.iconClassName ?? ''} size="small" /> <Icon type={item.icon} className={item.iconClassName ?? ''} size="small" />
</div> </div>
) : null} ) : null}
<div className="text-input">{item.label}</div> <div className="text-base lg:text-sm">{item.label}</div>
</StyledListboxOption> </StyledListboxOption>
))} ))}
</ListboxList> </ListboxList>

View File

@@ -15,7 +15,7 @@ const UpgradeNow = ({ application, featuresController }: Props) => {
return shouldShowCTA ? ( return shouldShowCTA ? (
<div className="flex h-full items-center px-2"> <div className="flex h-full items-center px-2">
<button <button
className="rounded bg-info py-0.5 px-1.5 text-xs font-bold uppercase text-info-contrast hover:brightness-125" className="rounded bg-info py-0.5 px-1.5 text-sm font-bold uppercase text-info-contrast hover:brightness-125 lg:text-xs"
onClick={() => { onClick={() => {
if (hasAccount) { if (hasAccount) {
void loadPurchaseFlowUrl(application) void loadPurchaseFlowUrl(application)

View File

@@ -54,7 +54,7 @@ const IconPicker = ({ selectedValue, onIconChange, platform, className }: Props)
return ( return (
<button <button
className={`relative mr-2 cursor-pointer border-0 bg-default pb-1.5 text-sm focus:shadow-none ${ className={`relative mr-2 cursor-pointer border-0 bg-default pb-1.5 text-mobile-menu-item focus:shadow-none md:text-tablet-menu-item lg:text-menu-item ${
isSelected ? 'font-medium text-info' : 'text-text' isSelected ? 'font-medium text-info' : 'text-text'
}`} }`}
onClick={() => { onClick={() => {
@@ -114,14 +114,18 @@ const IconPicker = ({ selectedValue, onIconChange, platform, className }: Props)
onChange={({ target: input }) => handleEmojiChange((input as HTMLInputElement)?.value)} onChange={({ target: input }) => handleEmojiChange((input as HTMLInputElement)?.value)}
/> />
</div> </div>
<div className="mt-2 text-xs text-passive-0"> <div className="mt-2 text-sm text-passive-0 lg:text-xs">
Use your keyboard to enter or paste in an emoji character. Use your keyboard to enter or paste in an emoji character.
</div> </div>
{isMacOS && ( {isMacOS && (
<div className="mt-2 text-xs text-passive-0">On macOS: + + Space bar to bring up emoji picker.</div> <div className="mt-2 text-sm text-passive-0 lg:text-xs">
On macOS: + + Space bar to bring up emoji picker.
</div>
)} )}
{isWindows && ( {isWindows && (
<div className="mt-2 text-xs text-passive-0">On Windows: Windows key + . to bring up emoji picker.</div> <div className="mt-2 text-sm text-passive-0 lg:text-xs">
On Windows: Windows key + . to bring up emoji picker.
</div>
)} )}
</> </>
)} )}

View File

@@ -119,8 +119,11 @@ const ItemLinkAutocompleteInput = ({ linkingController, focusPreviousItem, focus
<Disclosure open={dropdownVisible} onChange={showDropdown}> <Disclosure open={dropdownVisible} onChange={showDropdown}>
<input <input
ref={inputRef} ref={inputRef}
className={`${tags.length > 0 ? 'w-80' : 'mr-10 w-70'} no-border h-7 className={classNames(
bg-transparent text-xs text-text focus:border-b-2 focus:border-solid focus:border-info focus:shadow-none focus:outline-none`} `${tags.length > 0 ? 'w-80' : 'mr-10 w-70'}`,
'bg-transparent text-sm text-text focus:border-b-2 focus:border-solid focus:border-info lg:text-xs',
'no-border h-7 focus:shadow-none focus:outline-none',
)}
value={searchQuery} value={searchQuery}
onChange={onSearchQueryChange} onChange={onSearchQueryChange}
type="text" type="text"

View File

@@ -92,7 +92,7 @@ const LinkedItemBubble = ({
return ( return (
<button <button
ref={ref} ref={ref}
className="group flex h-6 cursor-pointer items-center rounded border-0 bg-passive-4-opacity-variant py-2 pl-1 pr-2 text-xs text-text hover:bg-contrast focus:bg-contrast" className="group flex h-6 cursor-pointer items-center rounded border-0 bg-passive-4-opacity-variant py-2 pl-1 pr-2 text-sm text-text hover:bg-contrast focus:bg-contrast lg:text-xs"
onFocus={handleFocus} onFocus={handleFocus}
onBlur={onBlur} onBlur={onBlur}
onClick={onClick} onClick={onClick}

View File

@@ -22,7 +22,7 @@ const LinkedItemMeta = ({
return ( return (
<> <>
<Icon type={icon} className={classNames('flex-shrink-0', className)} /> <Icon type={icon} className={classNames('flex-shrink-0', className)} />
<div className="min-w-0 flex-grow break-words text-left text-sm"> <div className="min-w-0 flex-grow break-words text-left text-base lg:text-sm">
{tagTitle && <span className="text-passive-1">{tagTitle.titlePrefix}</span>} {tagTitle && <span className="text-passive-1">{tagTitle.titlePrefix}</span>}
{searchQuery {searchQuery
? splitQueryInString(title, searchQuery).map((substring, index) => ( ? splitQueryInString(title, searchQuery).map((substring, index) => (

View File

@@ -326,7 +326,7 @@ const LinkedItemsPanel = ({
onChange={handleFileInputChange} onChange={handleFileInputChange}
/> />
<button <button
className="flex w-full cursor-pointer items-center gap-3 bg-transparent px-3 py-2 text-left text-sm text-text hover:bg-info-backdrop hover:text-foreground focus:bg-info-backdrop focus:shadow-none" className="flex w-full cursor-pointer items-center gap-3 bg-transparent px-3 py-2 text-left text-base text-text hover:bg-info-backdrop hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-sm"
onClick={selectAndUploadFiles} onClick={selectAndUploadFiles}
> >
<Icon type="add" /> <Icon type="add" />

View File

@@ -45,8 +45,9 @@ const MenuItem = forwardRef(
disabled={disabled} disabled={disabled}
ref={ref} ref={ref}
className={classNames( className={classNames(
'flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5', 'flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-2 md:py-1.5',
'text-left text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none', 'text-left text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none',
'text-mobile-menu-item md:text-tablet-menu-item lg:text-menu-item',
)} )}
onClick={() => { onClick={() => {
onChange(!checked) onChange(!checked)
@@ -68,9 +69,9 @@ const MenuItem = forwardRef(
role={type === MenuItemType.RadioButton ? 'menuitemradio' : 'menuitem'} role={type === MenuItemType.RadioButton ? 'menuitemradio' : 'menuitem'}
tabIndex={typeof tabIndex === 'number' ? tabIndex : FOCUSABLE_BUT_NOT_TABBABLE} tabIndex={typeof tabIndex === 'number' ? tabIndex : FOCUSABLE_BUT_NOT_TABBABLE}
className={classNames( className={classNames(
'flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left', 'flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-2 text-left md:py-1.5',
'text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground', 'text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground',
'focus:bg-info-backdrop focus:shadow-none md:text-menu-item', 'focus:bg-info-backdrop focus:shadow-none md:text-tablet-menu-item lg:text-menu-item',
className, className,
)} )}
onClick={onClick} onClick={onClick}

View File

@@ -25,8 +25,8 @@ const NoAccountWarningContent = ({ accountMenuController, noAccountWarningContro
return ( return (
<div className="mt-4 grid grid-cols-1 rounded-md border border-border p-4"> <div className="mt-4 grid grid-cols-1 rounded-md border border-border p-4">
<h1 className="sk-h3 m-0 text-sm font-semibold">Data not backed up</h1> <h1 className="sk-h3 m-0 text-base font-semibold lg:text-sm">Data not backed up</h1>
<p className="col-start-1 col-end-3 m-0 mt-1 text-sm"> <p className="col-start-1 col-end-3 m-0 mt-1 text-base lg:text-sm">
Sign in or register to sync your notes to your other devices with end-to-end encryption. Sign in or register to sync your notes to your other devices with end-to-end encryption.
</p> </p>
<Button <Button

View File

@@ -29,6 +29,7 @@ const MobileItemsListButton = () => {
}} }}
label={label} label={label}
icon={iconType} icon={iconType}
iconClassName={'h-6 w-6'}
/> />
) )
} }

View File

@@ -44,6 +44,7 @@ describe('NoteView', () => {
application.getViewControllerManager = jest.fn().mockReturnValue(viewControllerManager) application.getViewControllerManager = jest.fn().mockReturnValue(viewControllerManager)
application.hasProtectionSources = jest.fn().mockReturnValue(true) application.hasProtectionSources = jest.fn().mockReturnValue(true)
application.authorizeNoteAccess = jest.fn() application.authorizeNoteAccess = jest.fn()
application.addWebEventObserver = jest.fn()
}) })
afterEach(() => { afterEach(() => {

View File

@@ -9,7 +9,7 @@ import { ElementIds } from '@/Constants/ElementIDs'
import { PrefDefaults } from '@/Constants/PrefDefaults' import { PrefDefaults } from '@/Constants/PrefDefaults'
import { StringDeleteNote, STRING_DELETE_LOCKED_ATTEMPT, STRING_DELETE_PLACEHOLDER_ATTEMPT } from '@/Constants/Strings' import { StringDeleteNote, STRING_DELETE_LOCKED_ATTEMPT, STRING_DELETE_PLACEHOLDER_ATTEMPT } from '@/Constants/Strings'
import { log, LoggingDomain } from '@/Logging' import { log, LoggingDomain } from '@/Logging'
import { debounce, isDesktopApplication, isMobileScreen } from '@/Utils' import { debounce, isDesktopApplication, isMobileScreen, isTabletScreen } from '@/Utils'
import { classNames } from '@/Utils/ConcatenateClassNames' import { classNames } from '@/Utils/ConcatenateClassNames'
import { import {
ApplicationEvent, ApplicationEvent,
@@ -91,12 +91,36 @@ type State = {
noteType?: NoteType noteType?: NoteType
} }
const PlaintextFontSizeMapping: Record<EditorFontSize, string> = { const getPlaintextFontSize = (key: EditorFontSize): string => {
ExtraSmall: 'text-xs', const desktopMapping: Record<EditorFontSize, string> = {
Small: 'text-sm', ExtraSmall: 'text-xs',
Normal: 'text-editor', Small: 'text-sm',
Medium: 'text-lg', Normal: 'text-editor',
Large: 'text-xl', Medium: 'text-lg',
Large: 'text-xl',
}
const mobileMapping: Record<EditorFontSize, string> = {
ExtraSmall: 'text-sm',
Small: 'text-editor',
Normal: 'text-lg',
Medium: 'text-xl',
Large: 'text-xl2',
}
const tabletMapping: Record<EditorFontSize, string> = {
ExtraSmall: 'text-sm',
Small: 'text-editor',
Normal: 'text-base',
Medium: 'text-xl',
Large: 'text-xl2',
}
if (isTabletScreen()) {
return tabletMapping[key]
}
return isMobileScreen() ? mobileMapping[key] : desktopMapping[key]
} }
class NoteView extends AbstractComponent<NoteViewProps, State> { class NoteView extends AbstractComponent<NoteViewProps, State> {
@@ -111,6 +135,10 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
private removeComponentStreamObserver?: () => void private removeComponentStreamObserver?: () => void
private removeComponentManagerObserver?: () => void private removeComponentManagerObserver?: () => void
private removeInnerNoteObserver?: () => void private removeInnerNoteObserver?: () => void
private removeWebAppEventObserver: () => void
private needsAdjustMobileCursor = false
private isAdjustingMobileCursor = false
private protectionTimeoutId: ReturnType<typeof setTimeout> | null = null private protectionTimeoutId: ReturnType<typeof setTimeout> | null = null
@@ -133,6 +161,12 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
this.textAreaChangeDebounceSave = debounce(this.textAreaChangeDebounceSave, TextareaDebounce) this.textAreaChangeDebounceSave = debounce(this.textAreaChangeDebounceSave, TextareaDebounce)
this.removeWebAppEventObserver = props.application.addWebEventObserver((event) => {
if (event === WebAppEvent.MobileKeyboardWillChangeFrame) {
this.scrollMobileCursorIntoViewAfterWebviewResize()
}
})
this.state = { this.state = {
availableStackComponents: [], availableStackComponents: [],
editorStateDidLoad: false, editorStateDidLoad: false,
@@ -160,6 +194,16 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
this.editorContentRef = createRef<HTMLDivElement>() this.editorContentRef = createRef<HTMLDivElement>()
} }
scrollMobileCursorIntoViewAfterWebviewResize() {
if (this.needsAdjustMobileCursor) {
this.needsAdjustMobileCursor = false
this.isAdjustingMobileCursor = true
document.getElementById('note-text-editor')?.blur()
document.getElementById('note-text-editor')?.focus()
this.isAdjustingMobileCursor = false
}
}
override deinit() { override deinit() {
super.deinit() super.deinit()
;(this.controller as unknown) = undefined ;(this.controller as unknown) = undefined
@@ -179,6 +223,9 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
this.clearNoteProtectionInactivityTimer() this.clearNoteProtectionInactivityTimer()
;(this.ensureNoteIsInsertedBeforeUIAction as unknown) = undefined ;(this.ensureNoteIsInsertedBeforeUIAction as unknown) = undefined
this.removeWebAppEventObserver?.()
;(this.removeWebAppEventObserver as unknown) = undefined
this.removeTabObserver?.() this.removeTabObserver?.()
this.removeTabObserver = undefined this.removeTabObserver = undefined
this.onEditorComponentLoad = undefined this.onEditorComponentLoad = undefined
@@ -671,9 +718,13 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
} }
onContentFocus = () => { onContentFocus = () => {
if (!this.isAdjustingMobileCursor) {
this.needsAdjustMobileCursor = true
}
if (this.lastEditorFocusEventSource) { if (this.lastEditorFocusEventSource) {
this.application.notifyWebEvent(WebAppEvent.EditorFocused, { eventSource: this.lastEditorFocusEventSource }) this.application.notifyWebEvent(WebAppEvent.EditorFocused, { eventSource: this.lastEditorFocusEventSource })
} }
this.lastEditorFocusEventSource = undefined this.lastEditorFocusEventSource = undefined
this.setState({ plaintextEditorFocused: true }) this.setState({ plaintextEditorFocused: true })
} }
@@ -1114,7 +1165,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
className={classNames( className={classNames(
'editable font-editor flex-grow', 'editable font-editor flex-grow',
this.state.lineHeight && `leading-${this.state.lineHeight.toLowerCase()}`, this.state.lineHeight && `leading-${this.state.lineHeight.toLowerCase()}`,
this.state.fontSize && PlaintextFontSizeMapping[this.state.fontSize], this.state.fontSize && getPlaintextFontSize(this.state.fontSize),
)} )}
></textarea> ></textarea>
)} )}

View File

@@ -6,14 +6,23 @@ import { NotesController } from '@/Controllers/NotesController'
import { KeyboardKey } from '@standardnotes/ui-services' import { KeyboardKey } from '@standardnotes/ui-services'
import Popover from '../Popover/Popover' import Popover from '../Popover/Popover'
import { LinkingController } from '@/Controllers/LinkingController' import { LinkingController } from '@/Controllers/LinkingController'
import { IconType } from '@standardnotes/snjs'
type Props = { type Props = {
navigationController: NavigationController navigationController: NavigationController
notesController: NotesController notesController: NotesController
linkingController: LinkingController linkingController: LinkingController
className: string
iconClassName: string
} }
const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesController, linkingController }) => { const AddTagOption: FunctionComponent<Props> = ({
navigationController,
notesController,
linkingController,
className,
iconClassName,
}) => {
const menuContainerRef = useRef<HTMLDivElement>(null) const menuContainerRef = useRef<HTMLDivElement>(null)
const buttonRef = useRef<HTMLButtonElement>(null) const buttonRef = useRef<HTMLButtonElement>(null)
@@ -33,10 +42,10 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
} }
}} }}
ref={buttonRef} ref={buttonRef}
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={className}
> >
<div className="flex items-center"> <div className="flex items-center">
<Icon type="hashtag" className="mr-2 text-neutral" /> <Icon type="hashtag" className={`${iconClassName} mr-2 text-neutral`} />
Add tag Add tag
</div> </div>
<Icon type="chevron-right" className="text-neutral" /> <Icon type="chevron-right" className="text-neutral" />
@@ -52,13 +61,20 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
{navigationController.tags.map((tag) => ( {navigationController.tags.map((tag) => (
<button <button
key={tag.uuid} key={tag.uuid}
className="max-w-80 flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-2 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={`max-w-80 ${className.replace('justify-between', 'justify-start')}`}
onClick={() => { onClick={() => {
notesController.isTagInSelectedNotes(tag) notesController.isTagInSelectedNotes(tag)
? notesController.removeTagFromSelectedNotes(tag).catch(console.error) ? notesController.removeTagFromSelectedNotes(tag).catch(console.error)
: notesController.addTagToSelectedNotes(tag).catch(console.error) : notesController.addTagToSelectedNotes(tag).catch(console.error)
}} }}
> >
{tag.iconString && (
<Icon
type={tag.iconString as IconType}
size={'custom'}
className={'ml-0.5 mr-1.5 h-7 w-7 text-2xl text-neutral lg:h-6 lg:w-6 lg:text-lg'}
/>
)}
<span <span
className={`overflow-hidden overflow-ellipsis whitespace-nowrap className={`overflow-hidden overflow-ellipsis whitespace-nowrap
${notesController.isTagInSelectedNotes(tag) ? 'font-bold' : ''}`} ${notesController.isTagInSelectedNotes(tag) ? 'font-bold' : ''}`}

View File

@@ -9,9 +9,16 @@ import Popover from '../Popover/Popover'
type ChangeEditorOptionProps = { type ChangeEditorOptionProps = {
application: WebApplication application: WebApplication
note: SNNote note: SNNote
className: string
iconClassName: string
} }
const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({ application, note }) => { const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({
application,
note,
className,
iconClassName,
}) => {
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const menuContainerRef = useRef<HTMLDivElement>(null) const menuContainerRef = useRef<HTMLDivElement>(null)
const buttonRef = useRef<HTMLButtonElement>(null) const buttonRef = useRef<HTMLButtonElement>(null)
@@ -30,10 +37,10 @@ const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({ applic
} }
}} }}
ref={buttonRef} ref={buttonRef}
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={className}
> >
<div className="flex items-center"> <div className="flex items-center">
<Icon type="dashboard" className="mr-2 text-neutral" /> <Icon type="dashboard" className={`${iconClassName} mr-2 text-neutral`} />
Change note type Change note type
</div> </div>
<Icon type="chevron-right" className="text-neutral" /> <Icon type="chevron-right" className="text-neutral" />

View File

@@ -9,9 +9,11 @@ import Popover from '../Popover/Popover'
type Props = { type Props = {
application: WebApplication application: WebApplication
note: SNNote note: SNNote
className: string
iconClassName: string
} }
const ListedActionsOption: FunctionComponent<Props> = ({ application, note }) => { const ListedActionsOption: FunctionComponent<Props> = ({ application, note, className, iconClassName }) => {
const menuContainerRef = useRef<HTMLDivElement>(null) const menuContainerRef = useRef<HTMLDivElement>(null)
const buttonRef = useRef<HTMLButtonElement>(null) const buttonRef = useRef<HTMLButtonElement>(null)
@@ -37,10 +39,10 @@ const ListedActionsOption: FunctionComponent<Props> = ({ application, note }) =>
} }
}} }}
ref={buttonRef} ref={buttonRef}
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={className}
> >
<div className="flex items-center"> <div className="flex items-center">
<Icon type="listed" className="mr-2 text-neutral" /> <Icon type="listed" className={`mr-2 text-neutral ${iconClassName}`} />
Listed actions Listed actions
</div> </div>
<Icon type="chevron-right" className="text-neutral" /> <Icon type="chevron-right" className="text-neutral" />

View File

@@ -41,17 +41,19 @@ const ListedMenuItem: FunctionComponent<ListedMenuItemProps> = ({
onClick={handleClick} onClick={handleClick}
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-2 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none" className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-2 text-left text-sm text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none"
> >
<div className="flex flex-col"> <div className="flex w-full flex-row items-center justify-between">
<div className="font-semibold">{action.label}</div> <div className="flex flex-col">
{action.access_type && ( <div className="font-semibold">{action.label}</div>
<div className="mt-0.5 text-xs text-passive-0"> {action.access_type && (
{'Uses '} <div className="mt-0.5 text-xs text-passive-0">
<strong>{action.access_type}</strong> {'Uses '}
{' access to this note.'} <strong>{action.access_type}</strong>
</div> {' access to this note.'}
)} </div>
)}
</div>
{isRunning && <Spinner className="h-3 w-3" />}
</div> </div>
{isRunning && <Spinner className="h-3 w-3" />}
</button> </button>
) )
} }

View File

@@ -19,6 +19,8 @@ import { getNoteBlob, getNoteFileName } from '@/Utils/NoteExportUtils'
import { shareSelectedNotes } from '@/NativeMobileWeb/ShareSelectedNotes' import { shareSelectedNotes } from '@/NativeMobileWeb/ShareSelectedNotes'
import { downloadSelectedNotesOnAndroid } from '@/NativeMobileWeb/DownloadSelectedNotesOnAndroid' import { downloadSelectedNotesOnAndroid } from '@/NativeMobileWeb/DownloadSelectedNotesOnAndroid'
import ProtectedUnauthorizedLabel from '../ProtectedItemOverlay/ProtectedUnauthorizedLabel' import ProtectedUnauthorizedLabel from '../ProtectedItemOverlay/ProtectedUnauthorizedLabel'
import { classNames } from '@/Utils/ConcatenateClassNames'
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
type DeletePermanentlyButtonProps = { type DeletePermanentlyButtonProps = {
onClick: () => void onClick: () => void
@@ -34,10 +36,11 @@ const DeletePermanentlyButton = ({ onClick }: DeletePermanentlyButtonProps) => (
</button> </button>
) )
const iconClass = 'text-neutral mr-2' const iconSize = MenuItemIconSize
const iconClassDanger = 'text-danger mr-2' const iconClass = `text-neutral mr-2 ${iconSize}`
const iconClassWarning = 'text-warning mr-2' const iconClassDanger = `text-danger mr-2 ${iconSize}`
const iconClassSuccess = 'text-success mr-2' const iconClassWarning = `text-warning mr-2 ${iconSize}`
const iconClassSuccess = `text-success mr-2 ${iconSize}`
const getWordCount = (text: string) => { const getWordCount = (text: string) => {
if (text.trim().length === 0) { if (text.trim().length === 0) {
@@ -99,7 +102,7 @@ const NoteAttributes: FunctionComponent<{
const format = editor?.package_info?.file_type || 'txt' const format = editor?.package_info?.file_type || 'txt'
return ( return (
<div className="select-text px-3 py-1.5 text-xs font-medium text-neutral"> <div className="select-text px-3 py-1.5 text-sm font-medium text-neutral lg:text-xs">
{typeof words === 'number' && (format === 'txt' || format === 'md') ? ( {typeof words === 'number' && (format === 'txt' || format === 'md') ? (
<> <>
<div className="mb-1"> <div className="mb-1">
@@ -127,7 +130,8 @@ const SpellcheckOptions: FunctionComponent<{
editorForNote: SNComponent | undefined editorForNote: SNComponent | undefined
notesController: NotesController notesController: NotesController
note: SNNote note: SNNote
}> = ({ editorForNote, notesController, note }) => { className: string
}> = ({ editorForNote, notesController, note, className }) => {
const spellcheckControllable = Boolean(!editorForNote || editorForNote.package_info.spellcheckControl) const spellcheckControllable = Boolean(!editorForNote || editorForNote.package_info.spellcheckControl)
const noteSpellcheck = !spellcheckControllable const noteSpellcheck = !spellcheckControllable
? true ? true
@@ -138,7 +142,7 @@ const SpellcheckOptions: FunctionComponent<{
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
<button <button
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={className}
onClick={() => { onClick={() => {
notesController.toggleGlobalSpellcheckForNote(note).catch(console.error) notesController.toggleGlobalSpellcheckForNote(note).catch(console.error)
}} }}
@@ -273,14 +277,20 @@ const NotesOptions = ({
return <ProtectedUnauthorizedLabel /> return <ProtectedUnauthorizedLabel />
} }
const textClassNames = 'text-mobile-menu-item md:text-tablet-menu-item lg:text-menu-item'
const defaultClassNames = classNames(
textClassNames,
'flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none',
)
const switchClassNames = classNames(textClassNames, defaultClassNames, 'justify-between')
return ( return (
<> <>
{notes.length === 1 && ( {notes.length === 1 && (
<> <>
<button <button className={defaultClassNames} onClick={openRevisionHistoryModal}>
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item"
onClick={openRevisionHistoryModal}
>
<Icon type="history" className={iconClass} /> <Icon type="history" className={iconClass} />
Note history Note history
</button> </button>
@@ -288,7 +298,7 @@ const NotesOptions = ({
</> </>
)} )}
<button <button
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={switchClassNames}
onClick={() => { onClick={() => {
notesController.setLockSelectedNotes(!locked) notesController.setLockSelectedNotes(!locked)
}} }}
@@ -300,7 +310,7 @@ const NotesOptions = ({
<Switch className="px-0" checked={locked} /> <Switch className="px-0" checked={locked} />
</button> </button>
<button <button
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={switchClassNames}
onClick={() => { onClick={() => {
notesController.setHideSelectedNotePreviews(!hidePreviews) notesController.setHideSelectedNotePreviews(!hidePreviews)
}} }}
@@ -312,7 +322,7 @@ const NotesOptions = ({
<Switch className="px-0" checked={!hidePreviews} /> <Switch className="px-0" checked={!hidePreviews} />
</button> </button>
<button <button
className="flex w-full cursor-pointer items-center justify-between border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={switchClassNames}
onClick={() => { onClick={() => {
notesController.setProtectSelectedNotes(!protect).catch(console.error) notesController.setProtectSelectedNotes(!protect).catch(console.error)
}} }}
@@ -326,12 +336,19 @@ const NotesOptions = ({
{notes.length === 1 && ( {notes.length === 1 && (
<> <>
<HorizontalSeparator classes="my-2" /> <HorizontalSeparator classes="my-2" />
<ChangeEditorOption application={application} note={notes[0]} /> <ChangeEditorOption
iconClassName={iconClass}
className={switchClassNames}
application={application}
note={notes[0]}
/>
</> </>
)} )}
<HorizontalSeparator classes="my-2" /> <HorizontalSeparator classes="my-2" />
{navigationController.tagsCount > 0 && ( {navigationController.tagsCount > 0 && (
<AddTagOption <AddTagOption
iconClassName={iconClass}
className={switchClassNames}
navigationController={navigationController} navigationController={navigationController}
notesController={notesController} notesController={notesController}
linkingController={linkingController} linkingController={linkingController}
@@ -339,7 +356,7 @@ const NotesOptions = ({
)} )}
<button <button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={defaultClassNames}
onClick={() => { onClick={() => {
notesController.setStarSelectedNotes(!starred) notesController.setStarSelectedNotes(!starred)
}} }}
@@ -350,7 +367,7 @@ const NotesOptions = ({
{unpinned && ( {unpinned && (
<button <button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={defaultClassNames}
onClick={() => { onClick={() => {
notesController.setPinSelectedNotes(true) notesController.setPinSelectedNotes(true)
}} }}
@@ -361,7 +378,7 @@ const NotesOptions = ({
)} )}
{pinned && ( {pinned && (
<button <button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={defaultClassNames}
onClick={() => { onClick={() => {
notesController.setPinSelectedNotes(false) notesController.setPinSelectedNotes(false)
}} }}
@@ -371,7 +388,7 @@ const NotesOptions = ({
</button> </button>
)} )}
<button <button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={defaultClassNames}
onClick={() => { onClick={() => {
application.isNativeMobileWeb() ? shareSelectedNotes(application, notes) : downloadSelectedItems() application.isNativeMobileWeb() ? shareSelectedNotes(application, notes) : downloadSelectedItems()
}} }}
@@ -380,24 +397,18 @@ const NotesOptions = ({
{application.platform === Platform.Android ? 'Share' : 'Export'} {application.platform === Platform.Android ? 'Share' : 'Export'}
</button> </button>
{application.platform === Platform.Android && ( {application.platform === Platform.Android && (
<button <button className={defaultClassNames} onClick={() => downloadSelectedNotesOnAndroid(application, notes)}>
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item"
onClick={() => downloadSelectedNotesOnAndroid(application, notes)}
>
<Icon type="download" className={iconClass} /> <Icon type="download" className={iconClass} />
Export Export
</button> </button>
)} )}
<button <button className={defaultClassNames} onClick={duplicateSelectedItems}>
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item"
onClick={duplicateSelectedItems}
>
<Icon type="copy" className={iconClass} /> <Icon type="copy" className={iconClass} />
Duplicate Duplicate
</button> </button>
{unarchived && ( {unarchived && (
<button <button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={defaultClassNames}
onClick={async () => { onClick={async () => {
await notesController.setArchiveSelectedNotes(true).catch(console.error) await notesController.setArchiveSelectedNotes(true).catch(console.error)
closeMenuAndToggleNotesList() closeMenuAndToggleNotesList()
@@ -409,7 +420,7 @@ const NotesOptions = ({
)} )}
{archived && ( {archived && (
<button <button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={defaultClassNames}
onClick={async () => { onClick={async () => {
await notesController.setArchiveSelectedNotes(false).catch(console.error) await notesController.setArchiveSelectedNotes(false).catch(console.error)
closeMenuAndToggleNotesList() closeMenuAndToggleNotesList()
@@ -429,7 +440,7 @@ const NotesOptions = ({
/> />
) : ( ) : (
<button <button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={defaultClassNames}
onClick={async () => { onClick={async () => {
await notesController.setTrashSelectedNotes(true) await notesController.setTrashSelectedNotes(true)
closeMenuAndToggleNotesList() closeMenuAndToggleNotesList()
@@ -442,7 +453,7 @@ const NotesOptions = ({
{trashed && ( {trashed && (
<> <>
<button <button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={defaultClassNames}
onClick={async () => { onClick={async () => {
await notesController.setTrashSelectedNotes(false) await notesController.setTrashSelectedNotes(false)
closeMenuAndToggleNotesList() closeMenuAndToggleNotesList()
@@ -458,7 +469,7 @@ const NotesOptions = ({
}} }}
/> />
<button <button
className="flex w-full cursor-pointer items-center border-0 bg-transparent px-3 py-1.5 text-left text-mobile-menu-item text-text hover:bg-contrast hover:text-foreground focus:bg-info-backdrop focus:shadow-none md:text-menu-item" className={defaultClassNames}
onClick={async () => { onClick={async () => {
await notesController.emptyTrash() await notesController.emptyTrash()
closeMenuAndToggleNotesList() closeMenuAndToggleNotesList()
@@ -474,14 +485,31 @@ const NotesOptions = ({
</button> </button>
</> </>
)} )}
{notes.length === 1 ? ( {notes.length === 1 ? (
<> <>
<HorizontalSeparator classes="my-2" /> <HorizontalSeparator classes="my-2" />
<ListedActionsOption application={application} note={notes[0]} />
<ListedActionsOption
iconClassName={iconClass}
className={switchClassNames}
application={application}
note={notes[0]}
/>
<HorizontalSeparator classes="my-2" /> <HorizontalSeparator classes="my-2" />
<SpellcheckOptions editorForNote={editorForNote} notesController={notesController} note={notes[0]} />
<SpellcheckOptions
className={switchClassNames}
editorForNote={editorForNote}
notesController={notesController}
note={notes[0]}
/>
<HorizontalSeparator classes="my-2" /> <HorizontalSeparator classes="my-2" />
<NoteAttributes application={application} note={notes[0]} /> <NoteAttributes application={application} note={notes[0]} />
<NoteSizeWarning note={notes[0]} /> <NoteSizeWarning note={notes[0]} />
</> </>
) : null} ) : null}

View File

@@ -40,7 +40,7 @@ const NotesOptionsPanel = ({
return ( return (
<> <>
<RoundIconButton label="Note options menu" onClick={toggleMenu} ref={buttonRef} icon="more" /> <RoundIconButton label="Note options menu" onClick={toggleMenu} ref={buttonRef} icon="more" />
<Popover togglePopover={toggleMenu} anchorElement={buttonRef.current} open={isOpen} className="select-none py-2"> <Popover togglePopover={toggleMenu} anchorElement={buttonRef.current} open={isOpen} className="select-none">
<NotesOptions <NotesOptions
application={application} application={application}
navigationController={navigationController} navigationController={navigationController}

View File

@@ -27,9 +27,8 @@ const PinNoteButton: FunctionComponent<Props> = ({ className = '', notesControll
return ( return (
<button <button
className={`sn-icon-button flex h-9 w-9 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast md:h-8 md:w-8 ${ className={`sn-icon-button flex h-10 min-w-10 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast
pinned ? 'toggled' : '' md:h-8 md:min-w-8 ${pinned ? 'toggled' : ''} ${className}`}
} ${className}`}
onClick={togglePinned} onClick={togglePinned}
> >
<VisuallyHidden>Pin selected notes</VisuallyHidden> <VisuallyHidden>Pin selected notes</VisuallyHidden>

View File

@@ -1,7 +1,7 @@
import { useDocumentRect } from '@/Hooks/useDocumentRect' import { useDocumentRect } from '@/Hooks/useDocumentRect'
import { useAutoElementRect } from '@/Hooks/useElementRect' import { useAutoElementRect } from '@/Hooks/useElementRect'
import { classNames } from '@/Utils/ConcatenateClassNames' import { classNames } from '@/Utils/ConcatenateClassNames'
import { useState } from 'react' import { useCallback, useLayoutEffect, useState } from 'react'
import Icon from '../Icon/Icon' import Icon from '../Icon/Icon'
import Portal from '../Portal/Portal' import Portal from '../Portal/Portal'
import HorizontalSeparator from '../Shared/HorizontalSeparator' import HorizontalSeparator from '../Shared/HorizontalSeparator'
@@ -54,6 +54,18 @@ const PositionedPopoverContent = ({
useDisableBodyScrollOnMobile() useDisableBodyScrollOnMobile()
const correctInitialScrollForOverflowedContent = useCallback(() => {
if (popoverElement) {
setTimeout(() => {
popoverElement.scrollTop = 0
})
}
}, [popoverElement])
useLayoutEffect(() => {
correctInitialScrollForOverflowedContent()
}, [popoverElement, correctInitialScrollForOverflowedContent])
return ( return (
<Portal> <Portal>
<div <div
@@ -70,15 +82,13 @@ const PositionedPopoverContent = ({
: '', : '',
top: !isDesktopScreen ? `${document.documentElement.scrollTop}px` : '', top: !isDesktopScreen ? `${document.documentElement.scrollTop}px` : '',
}} }}
ref={(node) => { ref={setPopoverElement}
setPopoverElement(node)
}}
data-popover={id} data-popover={id}
> >
<div className="md:hidden"> <div className="md:hidden">
<div className="flex items-center justify-end px-3 pt-2"> <div className="flex items-center justify-end px-3 pt-2">
<button className="rounded-full border border-border p-1" onClick={togglePopover}> <button className="rounded-full border border-border p-1" onClick={togglePopover}>
<Icon type="close" className="h-4 w-4" /> <Icon type="close" className="h-6 w-6" />
</button> </button>
</div> </div>
<HorizontalSeparator classes="my-2" /> <HorizontalSeparator classes="my-2" />

View File

@@ -32,7 +32,7 @@ const AccountPreferences = ({ application, viewControllerManager }: Props) => (
{application.hasAccount() && viewControllerManager.featuresController.hasFiles && ( {application.hasAccount() && viewControllerManager.featuresController.hasFiles && (
<FilesSection application={application} /> <FilesSection application={application} />
)} )}
<Email application={application} /> {application.hasAccount() && <Email application={application} />}
<SignOutWrapper application={application} viewControllerManager={viewControllerManager} /> <SignOutWrapper application={application} viewControllerManager={viewControllerManager} />
<DeleteAccount application={application} viewControllerManager={viewControllerManager} /> <DeleteAccount application={application} viewControllerManager={viewControllerManager} />
</PreferencesPane> </PreferencesPane>

View File

@@ -1,5 +1,5 @@
import Button from '@/Components/Button/Button' import Button from '@/Components/Button/Button'
import { Text, Title } from '@/Components/Preferences/PreferencesComponents/Content' import { Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager' import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
@@ -33,11 +33,11 @@ const Authentication: FunctionComponent<Props> = ({ viewControllerManager }) =>
<div className="flex flex-col items-center px-4 md:px-12"> <div className="flex flex-col items-center px-4 md:px-12">
<AccountIllustration className="mb-3" /> <AccountIllustration className="mb-3" />
<Title>You're not signed in</Title> <Title>You're not signed in</Title>
<Text className="mb-3 text-center"> <div className="mb-3 text-center text-base lg:text-sm">
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> </div>
<Button primary label="Create free account" onClick={clickRegister} className="mb-3" /> <Button primary label="Create free account" onClick={clickRegister} className="mb-3" />
<div className="text-sm"> <div className="text-base lg:text-sm">
Already have an account?{' '} Already have an account?{' '}
<button className="cursor-pointer border-0 bg-default p-0 text-info underline" onClick={clickSignIn}> <button className="cursor-pointer border-0 bg-default p-0 text-info underline" onClick={clickSignIn}>
Sign in Sign in

View File

@@ -19,12 +19,7 @@ const DeleteAccount = ({ application, viewControllerManager }: Props) => {
<PreferencesGroup> <PreferencesGroup>
<PreferencesSegment> <PreferencesSegment>
<Title>Delete account</Title> <Title>Delete account</Title>
<Text> <Text>This action is irreversible. After deletion completes, you will be signed out on all devices.</Text>
This action is irreversible. After deletion completes, you will be signed out on all devices. If you have an
active paid subscription, cancel the subscription first. Otherwise, if you'd like to keep the subscription,
you can re-register with the same email after deletion, and your subscription will be linked back up with your
account.
</Text>
<div className="mt-3 flex flex-row flex-wrap gap-3"> <div className="mt-3 flex flex-row flex-wrap gap-3">
<Button <Button
colorStyle="danger" colorStyle="danger"

View File

@@ -22,6 +22,7 @@ const EmailBackups = ({ application }: Props) => {
const [emailFrequency, setEmailFrequency] = useState<EmailBackupFrequency>(EmailBackupFrequency.Disabled) const [emailFrequency, setEmailFrequency] = useState<EmailBackupFrequency>(EmailBackupFrequency.Disabled)
const [emailFrequencyOptions, setEmailFrequencyOptions] = useState<DropdownItem[]>([]) const [emailFrequencyOptions, setEmailFrequencyOptions] = useState<DropdownItem[]>([])
const [isFailedBackupEmailMuted, setIsFailedBackupEmailMuted] = useState(true) const [isFailedBackupEmailMuted, setIsFailedBackupEmailMuted] = useState(true)
const hasAccount = application.hasAccount()
const loadEmailFrequencySetting = useCallback(async () => { const loadEmailFrequencySetting = useCallback(async () => {
if (!application.getUser()) { if (!application.getUser()) {
@@ -104,12 +105,13 @@ const EmailBackups = ({ application }: Props) => {
<PreferencesGroup> <PreferencesGroup>
<PreferencesSegment> <PreferencesSegment>
<Title>Email Backups</Title> <Title>Email Backups</Title>
<div> {!isDesktopApplication() && (
{!isDesktopApplication() && ( <Text className="mb-3">
<Text className="mb-3"> Receive daily encrypted email backups of all your notes directly in your email inbox.
Daily encrypted email backups of your entire data set delivered directly to your inbox. </Text>
</Text> )}
)}
<div className={`${!hasAccount ? 'pointer-events-none cursor-default opacity-50' : ''}`}>
<Subtitle>Email frequency</Subtitle> <Subtitle>Email frequency</Subtitle>
<Text>How often to receive backups.</Text> <Text>How often to receive backups.</Text>
<div className="mt-2"> <div className="mt-2">

View File

@@ -78,11 +78,7 @@ const HelpAndFeedback = ({ application }: { application: WebApplication }) => {
<Title>Community forum</Title> <Title>Community forum</Title>
<Text> <Text>
If you have an issue, found a bug or want to suggest a feature, you can browse or post to the forum. Its If you have an issue, found a bug or want to suggest a feature, you can browse or post to the forum. Its
recommended for non-account related issues. Please read our{' '} recommended for non-account related issues.
<a target="_blank" className="underline hover:no-underline" href="https://standardnotes.com/longevity/">
Longevity statement
</a>{' '}
before advocating for a feature request.
</Text> </Text>
<LinkButton <LinkButton
className="mt-3" className="mt-3"
@@ -97,14 +93,8 @@ const HelpAndFeedback = ({ application }: { application: WebApplication }) => {
<Title>Community groups</Title> <Title>Community groups</Title>
<Text> <Text>
Want to meet other passionate note-takers and privacy enthusiasts? Want to share your feedback with us? Join 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. the Standard Notes Discord for discussions on security, themes, editors and more.
</Text> </Text>
<LinkButton
className="mt-3"
link="https://standardnotes.com/slack"
label="Join our Slack"
onClick={handleClick}
/>
<LinkButton <LinkButton
className="mt-3" className="mt-3"
link="https://standardnotes.com/discord" link="https://standardnotes.com/discord"

View File

@@ -19,7 +19,9 @@ const Listed = ({ application }: Props) => {
const [requestingAccount, setRequestingAccount] = useState<boolean>() const [requestingAccount, setRequestingAccount] = useState<boolean>()
const reloadAccounts = useCallback(async () => { const reloadAccounts = useCallback(async () => {
setAccounts(await application.listed.getListedAccounts()) if (application.hasAccount()) {
setAccounts(await application.listed.getListedAccounts())
}
}, [application]) }, [application])
useEffect(() => { useEffect(() => {
@@ -82,11 +84,11 @@ const Listed = ({ application }: Props) => {
<Subtitle>What is Listed?</Subtitle> <Subtitle>What is Listed?</Subtitle>
<Text> <Text>
Listed is a free blogging platform that allows you to create a public journal published directly from your Listed is a free blogging platform that allows you to create a public journal published directly from your
notes.{' '} notes. {!application.getUser() && 'To get started, sign in or register for a Standard Notes account.'}
<a target="_blank" href="https://listed.to" rel="noreferrer noopener">
Learn more
</a>
</Text> </Text>
<a className="mt-2 text-info" target="_blank" href="https://listed.to" rel="noreferrer noopener">
Learn more
</a>
</PreferencesSegment> </PreferencesSegment>
{application.getUser() && ( {application.getUser() && (
<> <>

View File

@@ -1,4 +1,3 @@
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import Switch from '@/Components/Switch/Switch' import Switch from '@/Components/Switch/Switch'
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content' import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { WebApplication } from '@/Application/Application' import { WebApplication } from '@/Application/Application'
@@ -75,7 +74,6 @@ const Privacy: FunctionComponent<Props> = ({ application }: Props) => {
<PreferencesSegment> <PreferencesSegment>
<Title>Privacy</Title> <Title>Privacy</Title>
<div> <div>
<HorizontalSeparator classes="my-4" />
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex flex-col"> <div className="flex flex-col">
<Subtitle>Session user agent logging</Subtitle> <Subtitle>Session user agent logging</Subtitle>

View File

@@ -11,20 +11,20 @@ export const Title: FunctionComponent<Props> = ({ children, className }) => (
) )
export const Subtitle: FunctionComponent<Props> = ({ children, className }) => ( export const Subtitle: FunctionComponent<Props> = ({ children, className }) => (
<h4 className={classNames('m-0 mb-1 text-sm font-medium', className)}>{children}</h4> <h4 className={classNames('m-0 mb-1 text-base font-medium lg:text-sm', className)}>{children}</h4>
) )
export const SubtitleLight: FunctionComponent<Props> = ({ children, className }) => ( export const SubtitleLight: FunctionComponent<Props> = ({ children, className }) => (
<h4 className={classNames('m-0 mb-1 text-sm font-normal', className)}>{children}</h4> <h4 className={classNames('m-0 mb-1 text-base font-normal lg:text-sm', className)}>{children}</h4>
) )
export const Text: FunctionComponent<Props> = ({ children, className }) => ( export const Text: FunctionComponent<Props> = ({ children, className }) => (
<p className={classNames('text-sm md:text-xs', className)}>{children}</p> <p className={classNames('text-base lg:text-xs', className)}>{children}</p>
) )
const buttonClasses = const buttonClasses =
'block bg-default text-text rounded border-solid \ 'block bg-default text-text rounded border-solid \
border px-4 py-1.5 font-bold text-sm w-fit \ border px-4 py-1.5 font-bold text-base lg:text-sm w-fit \
focus:bg-contrast hover:bg-contrast border-border' focus:bg-contrast hover:bg-contrast border-border'
export const LinkButton: FunctionComponent<{ export const LinkButton: FunctionComponent<{

View File

@@ -50,7 +50,7 @@ const SearchBar = ({ itemListController, searchOptionsController }: Props) => {
autocomplete={false} autocomplete={false}
className={{ className={{
container: 'px-1', container: 'px-1',
input: 'placeholder:text-passive-0', input: 'text-base placeholder:text-passive-0 lg:text-sm',
}} }}
placeholder={'Search...'} placeholder={'Search...'}
value={noteFilterText} value={noteFilterText}

View File

@@ -80,7 +80,7 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
> >
<div className={'section-title-bar'}> <div className={'section-title-bar'}>
<div className="section-title-bar-header"> <div className="section-title-bar-header">
<div className="title text-sm"> <div className="title text-base md:text-sm">
<span className="font-bold">Views</span> <span className="font-bold">Views</span>
</div> </div>
</div> </div>

View File

@@ -105,7 +105,7 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
<div <div
role="button" role="button"
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE} tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
className={classNames('tag py-2 px-3.5 md:py-1', isSelected && 'selected', isFaded && 'opacity-50')} className={classNames('tag px-3.5', isSelected && 'selected', isFaded && 'opacity-50')}
onClick={selectCurrentTag} onClick={selectCurrentTag}
style={{ style={{
paddingLeft: `${level * PADDING_PER_LEVEL_PX + PADDING_BASE_PX}px`, paddingLeft: `${level * PADDING_PER_LEVEL_PX + PADDING_BASE_PX}px`,
@@ -117,7 +117,7 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
</div> </div>
{isEditing ? ( {isEditing ? (
<input <input
className={'title editing'} className={'title editing text-mobile-navigation-list-item lg:text-navigation-list-item'}
id={`react-tag-${view.uuid}`} id={`react-tag-${view.uuid}`}
onBlur={onBlur} onBlur={onBlur}
onInput={onInput} onInput={onInput}
@@ -127,18 +127,23 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
ref={inputRef} ref={inputRef}
/> />
) : ( ) : (
<div className={'title overflow-hidden text-left'} id={`react-tag-${view.uuid}`}> <div
className={
'title overflow-hidden text-left text-mobile-navigation-list-item lg:text-navigation-list-item'
}
id={`react-tag-${view.uuid}`}
>
{title} {title}
</div> </div>
)} )}
<div className={'count'}>{view.uuid === SystemViewId.AllNotes && tagsState.allNotesCount}</div> <div className={'count text-base lg:text-sm'}>
{view.uuid === SystemViewId.AllNotes && tagsState.allNotesCount}
</div>
</div> </div>
{!isSystemView(view) && ( {!isSystemView(view) && (
<div className="meta"> <div className="meta">
{view.conflictOf && ( {view.conflictOf && <div className="-mt-1 text-[0.625rem] font-bold text-danger">Conflicted Copy</div>}
<div className="danger text-[0.625rem] font-bold">Conflicted Copy {view.conflictOf}</div>
)}
{isSelected && ( {isSelected && (
<div className="menu"> <div className="menu">

View File

@@ -104,7 +104,7 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag
</MenuItem> </MenuItem>
</Menu> </Menu>
<HorizontalSeparator classes="my-2" /> <HorizontalSeparator classes="my-2" />
<div className="px-3 pt-1 pb-1.5 text-xs font-medium text-neutral"> <div className="px-3 pt-1 pb-1.5 text-sm font-medium text-neutral lg:text-xs">
<div className="mb-1"> <div className="mb-1">
<span className="font-semibold">Last modified:</span> {tagLastModified} <span className="font-semibold">Last modified:</span> {tagLastModified}
</div> </div>

View File

@@ -43,7 +43,9 @@ const TagsList: FunctionComponent<Props> = ({ viewControllerManager, type }: Pro
return ( return (
<DndProvider backend={backend}> <DndProvider backend={backend}>
{allTags.length === 0 ? ( {allTags.length === 0 ? (
<div className="no-tags-placeholder">No tags or folders. Create one using the add button above.</div> <div className="no-tags-placeholder text-base opacity-[0.4] lg:text-sm">
No tags or folders. Create one using the add button above.
</div>
) : ( ) : (
<> <>
{allTags.map((tag) => { {allTags.map((tag) => {

View File

@@ -245,7 +245,7 @@ export const TagsListItem: FunctionComponent<Props> = observer(
<div <div
role="button" role="button"
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE} tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
className={classNames('tag py-2 px-3.5 md:py-1', isSelected && 'selected', readyToDrop && 'is-drag-over')} className={classNames('tag px-3.5', isSelected && 'selected', readyToDrop && 'is-drag-over')}
onClick={selectCurrentTag} onClick={selectCurrentTag}
ref={mergeRefs([dragRef, tagRef])} ref={mergeRefs([dragRef, tagRef])}
style={{ style={{
@@ -275,7 +275,9 @@ export const TagsListItem: FunctionComponent<Props> = observer(
</div> </div>
{isEditing ? ( {isEditing ? (
<input <input
className={'title editing focus:shadow-none focus:outline-none'} className={
'title editing text-mobile-navigation-list-item focus:shadow-none focus:outline-none lg:text-navigation-list-item'
}
id={`react-tag-${tag.uuid}-${type}`} id={`react-tag-${tag.uuid}-${type}`}
onBlur={onBlur} onBlur={onBlur}
onInput={onInput} onInput={onInput}
@@ -286,7 +288,9 @@ export const TagsListItem: FunctionComponent<Props> = observer(
/> />
) : ( ) : (
<div <div
className={'title overflow-hidden text-left focus:shadow-none focus:outline-none'} className={
'title overflow-hidden text-left text-mobile-navigation-list-item focus:shadow-none focus:outline-none lg:text-navigation-list-item'
}
id={`react-tag-${tag.uuid}-${type}`} id={`react-tag-${tag.uuid}-${type}`}
> >
{title} {title}
@@ -303,12 +307,12 @@ export const TagsListItem: FunctionComponent<Props> = observer(
> >
<Icon type="more" className="text-neutral" /> <Icon type="more" className="text-neutral" />
</a> </a>
<div className="count">{noteCounts.get()}</div> <div className="count text-base lg:text-sm">{noteCounts.get()}</div>
</div> </div>
</div> </div>
<div className={`meta ${hasAtLeastOneFolder ? 'with-folders' : ''}`}> <div className={`meta ${hasAtLeastOneFolder ? 'with-folders' : ''}`}>
{tag.conflictOf && <div className="danger text-[0.625rem] font-bold">Conflicted Copy {tag.conflictOf}</div>} {tag.conflictOf && <div className="-mt-1 text-[0.625rem] font-bold text-danger">Conflicted Copy</div>}
</div> </div>
</div> </div>
{isAddingSubtag && ( {isAddingSubtag && (
@@ -324,7 +328,7 @@ export const TagsListItem: FunctionComponent<Props> = observer(
<Icon type="hashtag" className="mr-1 text-neutral" /> <Icon type="hashtag" className="mr-1 text-neutral" />
</div> </div>
<input <input
className="title w-full focus:shadow-none focus:outline-none" className="title w-full text-mobile-navigation-list-item focus:shadow-none focus:outline-none lg:text-navigation-list-item"
type="text" type="text"
ref={subtagInputRef} ref={subtagInputRef}
onBlur={onSubtagInputBlur} onBlur={onSubtagInputBlur}

View File

@@ -57,7 +57,7 @@ const TagsSection: FunctionComponent<Props> = ({ viewControllerManager }) => {
<section> <section>
<div className={'section-title-bar'}> <div className={'section-title-bar'}>
<div className="section-title-bar-header"> <div className="section-title-bar-header">
<div className="title text-sm"> <div className="title text-base md:text-sm">
<span className="font-bold">Favorites</span> <span className="font-bold">Favorites</span>
</div> </div>
</div> </div>

View File

@@ -22,7 +22,7 @@ const TagsSectionTitle: FunctionComponent<Props> = ({ features, hasMigration, on
if (entitledToFolders) { if (entitledToFolders) {
return ( return (
<> <>
<div className="title text-sm"> <div className="title text-base md:text-sm">
<span className="font-bold">Folders</span> <span className="font-bold">Folders</span>
{hasMigration && ( {hasMigration && (
<label className="ml-1 cursor-pointer font-bold text-info" onClick={onClickMigration}> <label className="ml-1 cursor-pointer font-bold text-info" onClick={onClickMigration}>
@@ -36,7 +36,7 @@ const TagsSectionTitle: FunctionComponent<Props> = ({ features, hasMigration, on
return ( return (
<> <>
<div className="title text-sm"> <div className="title text-base md:text-sm">
<span className="font-bold">Tags</span> <span className="font-bold">Tags</span>
<Tooltip label={TAG_FOLDERS_FEATURE_TOOLTIP}> <Tooltip label={TAG_FOLDERS_FEATURE_TOOLTIP}>
<label className="ml-1 cursor-pointer font-bold text-passive-2" onClick={showPremiumAlert}> <label className="ml-1 cursor-pointer font-bold text-passive-2" onClick={showPremiumAlert}>

View File

@@ -99,7 +99,8 @@ export const STRING_UPGRADE_ACCOUNT_CONFIRM_BUTTON = 'Upgrade'
export const STRING_REMOVE_OFFLINE_KEY_CONFIRMATION = 'This will delete the previously saved offline key.' export const STRING_REMOVE_OFFLINE_KEY_CONFIRMATION = 'This will delete the previously saved offline key.'
export const STRING_DELETE_ACCOUNT_CONFIRMATION = 'Are you sure you want to permanently delete your account?' export const STRING_DELETE_ACCOUNT_CONFIRMATION =
"Are you sure you want to permanently delete your account? You will be asked to confirm your account password in the next step. If you have an active paid subscription, cancel the subscription first. Otherwise, if you'd like to keep the subscription, you can re-register with the same email after deletion, and your subscription will be linked back up with your account."
export const STRING_FAILED_TO_UPDATE_USER_SETTING = export const STRING_FAILED_TO_UPDATE_USER_SETTING =
'There was an error while trying to update your settings. Please try again.' 'There was an error while trying to update your settings. Please try again.'

View File

@@ -0,0 +1 @@
export const MenuItemIconSize = 'w-6 h-6 md:w-5 md:h-5'

View File

@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
// Follows https://tailwindcss.com/docs/responsive-design // Follows https://tailwindcss.com/docs/responsive-design
export const MediaQueryBreakpoints = { export const MediaQueryBreakpoints = {
sm: '(min-width: 640px)', sm: '(max-width: 640px)',
md: '(min-width: 768px)', md: '(min-width: 768px)',
lg: '(min-width: 1024px)', lg: '(min-width: 1024px)',
xl: '(min-width: 1280px)', xl: '(min-width: 1280px)',

View File

@@ -6,6 +6,7 @@ export enum LoggingDomain {
NoteView, NoteView,
ItemsList, ItemsList,
NavigationList, NavigationList,
Viewport,
} }
const LoggingStatus: Record<LoggingDomain, boolean> = { const LoggingStatus: Record<LoggingDomain, boolean> = {
@@ -13,6 +14,7 @@ const LoggingStatus: Record<LoggingDomain, boolean> = {
[LoggingDomain.NoteView]: false, [LoggingDomain.NoteView]: false,
[LoggingDomain.ItemsList]: false, [LoggingDomain.ItemsList]: false,
[LoggingDomain.NavigationList]: false, [LoggingDomain.NavigationList]: false,
[LoggingDomain.Viewport]: true,
} }
export function log(domain: LoggingDomain, ...args: any[]): void { export function log(domain: LoggingDomain, ...args: any[]): void {

View File

@@ -32,11 +32,11 @@ export class MobileWebReceiver {
try { try {
const parsed = JSON.parse(message) const parsed = JSON.parse(message)
const { messageType, reactNativeEvent } = parsed const { messageType, reactNativeEvent, messageData } = parsed
if (messageType === 'event' && reactNativeEvent) { if (messageType === 'event' && reactNativeEvent) {
const nativeEvent = reactNativeEvent as ReactNativeToWebEvent const nativeEvent = reactNativeEvent as ReactNativeToWebEvent
this.handleNativeEvent(nativeEvent) this.handleNativeEvent(nativeEvent, messageData)
} }
} catch (error) { } catch (error) {
console.log('[MobileWebReceiver] Error parsing message from React Native', error) console.log('[MobileWebReceiver] Error parsing message from React Native', error)
@@ -51,7 +51,7 @@ export class MobileWebReceiver {
} }
} }
handleNativeEvent(event: ReactNativeToWebEvent) { handleNativeEvent(event: ReactNativeToWebEvent, messageData?: unknown) {
switch (event) { switch (event) {
case ReactNativeToWebEvent.EnteringBackground: case ReactNativeToWebEvent.EnteringBackground:
void this.application.handleMobileEnteringBackgroundEvent() void this.application.handleMobileEnteringBackgroundEvent()
@@ -71,6 +71,16 @@ export class MobileWebReceiver {
case ReactNativeToWebEvent.ColorSchemeChanged: case ReactNativeToWebEvent.ColorSchemeChanged:
void this.application.handleMobileColorSchemeChangeEvent() void this.application.handleMobileColorSchemeChangeEvent()
break break
case ReactNativeToWebEvent.KeyboardFrameWillChange:
void this.application.handleMobileKeyboardWillChangeFrameEvent(
messageData as { height: number; contentHeight: number },
)
break
case ReactNativeToWebEvent.KeyboardFrameDidChange:
void this.application.handleMobileKeyboardDidChangeFrameEvent(
messageData as { height: number; contentHeight: number },
)
break
default: default:
break break

View File

@@ -204,6 +204,8 @@ export const disableIosTextFieldZoom = () => {
} }
export const isMobileScreen = () => !window.matchMedia(MediaQueryBreakpoints.md).matches export const isMobileScreen = () => !window.matchMedia(MediaQueryBreakpoints.md).matches
export const isTabletScreen = () =>
!window.matchMedia(MediaQueryBreakpoints.sm).matches && !window.matchMedia(MediaQueryBreakpoints.lg).matches
export const getBase64FromBlob = (blob: Blob) => { export const getBase64FromBlob = (blob: Blob) => {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {

View File

@@ -1,24 +1,27 @@
import { isDev } from '@/Utils' import { log, LoggingDomain } from './Logging'
export const ViewportHeightKey = '--viewport-height' export const ViewportHeightKey = '--viewport-height'
export const setViewportHeightWithFallback = () => { export const setViewportHeightWithFallback = () => {
const currentHeight = parseInt(document.documentElement.style.getPropertyValue(ViewportHeightKey))
const newValue = visualViewport && visualViewport.height > 0 ? visualViewport.height : window.innerHeight const newValue = visualViewport && visualViewport.height > 0 ? visualViewport.height : window.innerHeight
if (isDev) {
// eslint-disable-next-line no-console
console.log(`currentHeight: ${currentHeight}, newValue: ${newValue}`)
}
if (currentHeight && newValue < currentHeight) {
return
}
if (!newValue) { if (!newValue) {
document.documentElement.style.setProperty(ViewportHeightKey, '100vh') setCustomViewportHeight('100', 'vh')
return return
} }
document.documentElement.style.setProperty(ViewportHeightKey, `${newValue}px`) setCustomViewportHeight(String(newValue), 'px')
}
/**
* @param forceTriggerResizeEvent On iPad at least, setProperty(ViewportHeightKey) does not trigger a resize event
*/
export const setCustomViewportHeight = (height: string, suffix: 'px' | 'vh', forceTriggerResizeEvent = false) => {
log(LoggingDomain.Viewport, `setCustomViewportHeight: ${height}`)
document.documentElement.style.setProperty(ViewportHeightKey, `${height}${suffix}`)
if (forceTriggerResizeEvent) {
window.dispatchEvent(new Event('resize'))
}
} }

View File

@@ -137,11 +137,6 @@ body,
position: relative; position: relative;
overflow: auto; overflow: auto;
background-color: var(--editor-header-bar-background-color); background-color: var(--editor-header-bar-background-color);
@media screen and (min-width: 768px) {
min-height: 100vh;
height: 100vh;
}
} }
$footer-height: 2rem; $footer-height: 2rem;

View File

@@ -24,9 +24,6 @@ $content-horizontal-padding: 16px;
.no-tags-placeholder { .no-tags-placeholder {
padding: 0px $content-horizontal-padding; padding: 0px $content-horizontal-padding;
font-size: 12px;
opacity: 0.4;
margin-top: -5px;
} }
.root-drop { .root-drop {
@@ -68,10 +65,19 @@ $content-horizontal-padding: 16px;
} }
> .tag-info { > .tag-info {
align-items: center;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center;
justify-content: space-between; justify-content: space-between;
min-height: 35px;
@media screen and (min-width: 768px) {
min-height: 31px;
}
@media screen and (min-width: 1024px) {
min-height: 29.5px;
}
.sn-icon { .sn-icon {
display: block; display: block;
@@ -93,9 +99,6 @@ $content-horizontal-padding: 16px;
} }
> .title { > .title {
font-size: 14px;
line-height: 18px;
width: 80%; width: 80%;
background-color: transparent; background-color: transparent;
font-weight: 600; font-weight: 600;

View File

@@ -9,7 +9,7 @@
} }
[data-reach-dialog-overlay]::before { [data-reach-dialog-overlay]::before {
background-color: var(--sn-stylekit-contrast-background-color); background-color: var(--sn-stylekit-passive-color-5);
content: ''; content: '';
position: fixed; position: fixed;
top: 0px; top: 0px;
@@ -19,10 +19,6 @@
opacity: 0.75; opacity: 0.75;
} }
.challenge-modal-overlay::before {
background-color: var(--sn-stylekit-passive-color-5);
}
[data-reach-dialog-content] { [data-reach-dialog-content] {
position: relative; position: relative;
} }

View File

@@ -10,6 +10,7 @@ module.exports = {
4.5: '1.125rem', 4.5: '1.125rem',
8.5: '2.125rem', 8.5: '2.125rem',
13: '3.25rem', 13: '3.25rem',
15: '3.75rem',
18: '4.5rem', 18: '4.5rem',
26: '6.5rem', 26: '6.5rem',
30: '7.5rem', 30: '7.5rem',
@@ -28,9 +29,11 @@ module.exports = {
3: '0.75rem', 3: '0.75rem',
4: '1rem', 4: '1rem',
5: '1.25rem', 5: '1.25rem',
6: '1.5rem',
8: '2rem', 8: '2rem',
8.5: '2.125rem', 8.5: '2.125rem',
9: '2.25rem', 9: '2.25rem',
10: '2.5rem',
15: '3.75rem', 15: '3.75rem',
20: '5rem', 20: '5rem',
24: '6rem', 24: '6rem',
@@ -87,7 +90,10 @@ module.exports = {
}, },
fontSize: { fontSize: {
'menu-item': '0.813rem', 'menu-item': '0.813rem',
'mobile-menu-item': '0.9rem', 'mobile-menu-item': '1.1rem',
'tablet-menu-item': '0.95rem',
'navigation-list-item': '0.88rem',
'mobile-navigation-list-item': '1rem',
editor: 'var(--sn-stylekit-font-size-editor)', editor: 'var(--sn-stylekit-font-size-editor)',
}, },
screens: { screens: {