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
const sheet = document.styleSheets[0]
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) {

View File

@@ -58,12 +58,34 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
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 () => {
removeStateServiceListener()
removeBackHandlerServiceListener()
removeColorSchemeServiceListener()
keyboardShowListener.remove()
keyboardHideListener.remove()
keyboardWillChangeFrame.remove()
keyboardDidChangeFrame.remove()
}
}, [webViewRef, stateService, device, androidBackHandlerService, colorSchemeService])
@@ -198,7 +220,7 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
const onFunctionMessage = async (functionName: string, messageId: string, args: any) => {
const returnValue = await (device as any)[functionName](...args)
if (LoggingEnabled) {
if (LoggingEnabled && functionName !== 'consoleLog') {
console.log(`Native device function ${functionName} called`)
}
webViewRef.current?.postMessage(JSON.stringify({ messageId, returnValue, messageType: 'reply' }))
@@ -253,6 +275,12 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
injectedJavaScriptBeforeContentLoaded={injectedJS}
bounces={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>
handleMobileResumingFromBackgroundEvent(): Promise<void>
handleMobileColorSchemeChangeEvent(): void
handleMobileKeyboardWillChangeFrameEvent(frame: { height: number; contentHeight: number }): void
handleMobileKeyboardDidChangeFrameEvent(frame: { height: number; contentHeight: number }): void
isNativeMobileWeb(): boolean
mobileDevice(): MobileDeviceInterface
handleAndroidBackButtonPressed(): void

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@
--sn-stylekit-base-font-size: 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-h5: 0.73125rem;

View File

@@ -38,7 +38,7 @@ import {
import { MobileWebReceiver, NativeMobileEventListener } from '../NativeMobileWeb/MobileWebReceiver'
import { AndroidBackHandler } from '@/NativeMobileWeb/AndroidBackHandler'
import { PrefDefaults } from '@/Constants/PrefDefaults'
import { setViewportHeightWithFallback } from '@/setViewportHeightWithFallback'
import { setCustomViewportHeight, setViewportHeightWithFallback } from '@/setViewportHeightWithFallback'
import { WebServices } from './WebServices'
export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void
@@ -293,6 +293,15 @@ export class WebApplication extends SNApplication implements WebApplicationInter
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> {
const isLocked = await this.isLocked()
if (isLocked) {

View File

@@ -14,6 +14,7 @@ import WorkspaceSwitcherOption from './WorkspaceSwitcher/WorkspaceSwitcherOption
import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { formatLastSyncDate } from '@/Utils/DateUtils'
import Spinner from '@/Components/Spinner/Spinner'
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
type Props = {
viewControllerManager: ViewControllerManager
@@ -23,7 +24,7 @@ type Props = {
closeMenu: () => void
}
const iconClassName = 'text-neutral mr-2'
const iconClassName = `text-neutral mr-2 ${MenuItemIconSize}`
const GeneralAccountMenu: FunctionComponent<Props> = ({
application,
@@ -90,48 +91,48 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
return (
<>
<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}>
<Icon type="close" className="text-neutral" />
</div>
</div>
{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 className="wrap my-0.5 font-bold">{user.email}</div>
<span className="text-neutral">{application.getHost()}</span>
</div>
<div className="mb-2 flex items-start justify-between px-3">
{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" />
Syncing...
</div>
) : (
<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 className="text-sm font-semibold text-success">Last synced:</div>
<div className="text-sm text-text">{lastSyncDate}</div>
<div className="text-base font-semibold text-success lg:text-sm">Last synced:</div>
<div className="text-base text-text lg:text-sm">{lastSyncDate}</div>
</div>
</div>
)}
<div className="flex cursor-pointer text-passive-1" onClick={doSynchronization}>
<Icon type="sync" />
<Icon type="sync" className={`${MenuItemIconSize}`} />
</div>
</div>
</>
) : (
<>
<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
encryption.
</div>
<div className="flex items-center text-passive-1">
<Icon type="cloud-off" className="mr-2" />
<span className="text-sm font-semibold">Offline</span>
<Icon type="cloud-off" className={`mr-2 ${MenuItemIconSize}`} />
<span className="text-lg font-semibold lg:text-sm">Offline</span>
</div>
</div>
</>

View File

@@ -8,6 +8,7 @@ import WorkspaceSwitcherMenu from './WorkspaceSwitcherMenu'
import MenuItem from '@/Components/Menu/MenuItem'
import { MenuItemType } from '@/Components/Menu/MenuItemType'
import Popover from '@/Components/Popover/Popover'
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
type Props = {
mainApplicationGroup: ApplicationGroup
@@ -32,10 +33,10 @@ const WorkspaceSwitcherOption: FunctionComponent<Props> = ({ mainApplicationGrou
className="justify-between"
>
<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
</div>
<Icon type="chevron-right" className="text-neutral" />
<Icon type="chevron-right" className={`text-neutral ${MenuItemIconSize}`} />
</MenuItem>
<Popover
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'
}
>
{message}
<div className="text-base lg:text-xs">{message}</div>
</DialogContent>
</DialogOverlay>
)

View File

@@ -7,7 +7,7 @@ type Props = {
}
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',
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 width = fullWidth ? 'w-full' : 'w-fit'
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' : ''
let colors = primary ? getColorsForPrimaryVariant(style) : getColorsForNormalVariant(style)

View File

@@ -21,7 +21,9 @@ const RoundIconButton = forwardRef(
return (
<button
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,
)}
onClick={click}

View File

@@ -19,6 +19,8 @@ import { ApplicationGroup } from '@/Application/ApplicationGroup'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { ChallengeModalValues } from './ChallengeModalValues'
import { InputValue } from './InputValue'
import { isMobileScreen } from '@/Utils'
import { classNames } from '@/Utils/ConcatenateClassNames'
type Props = {
application: WebApplication
@@ -211,23 +213,24 @@ const ChallengeModal: FunctionComponent<Props> = ({
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 (
<DialogOverlay
className={`sn-component ${
challenge.reason === ChallengeReason.ApplicationUnlock ? 'challenge-modal-overlay' : ''
}`}
className={`sn-component ${isFullScreenBlocker ? 'bg-passive-5' : ''}`}
onDismiss={cancelChallenge}
dangerouslyBypassFocusLock={bypassModalFocusLock}
key={challenge.id}
>
<DialogContent
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'
}`}
>
<DialogContent aria-label="Challenge modal" className={contentClasses}>
{challenge.cancelable && (
<button
onClick={cancelChallenge}

View File

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

View File

@@ -9,6 +9,7 @@ import { ListableContentItem } from '../Types/ListableContentItem'
import { DailyItemsDay } from './DailyItemsDaySection'
import { ListItemTitle } from '../ListItemTitle'
import { EmptyPlaceholderBars } from './EmptyPlaceholderBars'
import { isMobileScreen } from '@/Utils'
type DaySquareProps = {
day: number
@@ -22,7 +23,7 @@ const DaySquare: FunctionComponent<DaySquareProps> = ({ day, hasActivity, weekda
<div
className={`${
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-4xl font-bold">{day}</div>
@@ -72,7 +73,7 @@ export const DailyItemCell = forwardRef(
{!item && (
<div className="w-full">
<div className="break-word mr-2 font-semibold">{formatDateAndTimeForNote(section.date, false)}</div>
<EmptyPlaceholderBars rows={4} />
<EmptyPlaceholderBars rows={isMobileScreen() ? 2 : 4} />
</div>
)}
</div>

View File

@@ -1,5 +1,5 @@
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 { classNames } from '@/Utils/ConcatenateClassNames'
import Popover from '@/Components/Popover/Popover'
@@ -8,6 +8,7 @@ import { NavigationMenuButton } from '@/Components/NavigationMenu/NavigationMenu
import { IconType, isTag } from '@standardnotes/snjs'
import RoundIconButton from '@/Components/Button/RoundIconButton'
import { AnyTag } from '@/Controllers/Navigation/AnyTagType'
import { MediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
type Props = {
application: WebApplication
@@ -34,30 +35,18 @@ const ContentListHeader = ({
const displayOptionsButtonRef = useRef<HTMLButtonElement>(null)
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 toggleDisplayOptionsMenu = useCallback(() => {
setShowDisplayOptionsMenu((show) => !show)
}, [])
return (
<div className="section-title-bar-header items-start gap-1 overflow-hidden">
<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>
const OptionsMenu = useMemo(() => {
return (
<div className="flex">
<div className="relative" ref={displayOptionsContainerRef}>
<RoundIconButton
@@ -83,21 +72,90 @@ const ContentListHeader = ({
/>
</Popover>
</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>
)
}, [
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>
)
}

View File

@@ -23,9 +23,8 @@ import { PreferenceMode } from './PreferenceMode'
import { PremiumFeatureIconClass, PremiumFeatureIconName } from '@/Components/Icon/PremiumFeatureIcon'
import Button from '@/Components/Button/Button'
import { classNames } from '@/Utils/ConcatenateClassNames'
import { isDev } from '@/Utils'
const DailyEntryModeEnabled = isDev
const DailyEntryModeEnabled = true
const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
closeDisplayOptionsMenu,
@@ -180,7 +179,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
return (
<button
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',
)}
onClick={() => {
@@ -209,7 +208,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
</div>
<p className="col-start-1 col-end-3 m-0 mt-1 text-sm">
{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 &&
'Create powerful workflows and organizational layouts with per-tag display preferences.'}
</p>
@@ -226,20 +225,24 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
return (
<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="flex items-center gap-1.5">
<TabButton label="Global" mode="global" />
{!isSystemTag && <TabButton label={selectedTag.title} icon={selectedTag.iconString} mode="tag" />}
</div>
{currentMode === 'tag' && <button onClick={resetTagPreferences}>Reset</button>}
{currentMode === 'tag' && (
<button className="text-base lg:text-sm" onClick={resetTagPreferences}>
Reset
</button>
)}
</div>
{controlsDisabled && <NoSubscriptionBanner />}
<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
disabled={controlsDisabled || isDailyEntry}
className="py-2"
@@ -295,7 +298,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
</div>
</MenuItem>
<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 && (
<MenuItem
disabled={controlsDisabled}
@@ -335,7 +338,7 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
Show icon
</MenuItem>
<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
disabled={controlsDisabled}
type={MenuItemType.SwitchButton}
@@ -384,7 +387,12 @@ const DisplayOptionsMenu: FunctionComponent<DisplayOptionsMenuProps> = ({
onChange={toggleEntryMode}
>
<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>
</MenuItem>

View File

@@ -206,9 +206,9 @@ const NewNotePreferences: FunctionComponent<Props> = ({
return (
<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 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">
<Dropdown
portal={false}
@@ -223,7 +223,7 @@ const NewNotePreferences: FunctionComponent<Props> = ({
</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">
<Dropdown
portal={false}
@@ -249,10 +249,11 @@ const NewNotePreferences: FunctionComponent<Props> = ({
spellCheck={false}
/>
</div>
<div className="mt-2">
<span className="font-bold">Preview:</span> {dayjs().format(customNoteTitleFormat)}
<div className="mt-3 text-neutral">
<span className="font-bold">Preview: </span>
<em>{dayjs().format(customNoteTitleFormat)}</em>
</div>
<div className="mt-1">
<div className="mt-2 text-neutral">
<a
className="underline"
href={HelpPageUrl}
@@ -267,7 +268,7 @@ const NewNotePreferences: FunctionComponent<Props> = ({
>
Options
</a>
. Use <code>[]</code> to escape date-time formatting.
. Use <code>[]</code> to escape formatting.
</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">
{item.locked && (
<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>
)}
{item.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>
)}
{item.archived && (
@@ -33,17 +33,17 @@ const ListItemFlagIcons: FunctionComponent<Props> = ({ item, hasFiles = false })
)}
{item.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>
)}
{hasFiles && (
<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>
)}
{item.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>
)}
</div>

View File

@@ -20,7 +20,7 @@ const ListItemMetadata: FunctionComponent<Props> = ({ item, hideDate, sortBy })
}
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>}
{!hideDate && showModifiedDate && <span>Modified {item.updatedAtString || 'Now'}</span>}
{!hideDate && !showModifiedDate && <span>{item.createdAtString || 'Now'}</span>}

View File

@@ -14,7 +14,7 @@ const ListItemNotePreviewText: FunctionComponent<Props> = ({ item, hidePreview,
}
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 && (
<div
className="my-1"

View File

@@ -13,7 +13,7 @@ const ListItemTags: FunctionComponent<Props> = ({ hideTags, tags }) => {
}
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) => (
<span
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 }) => {
return (
<div className="flex items-start justify-between overflow-hidden text-base font-semibold leading-[1.3]">
<div className={`break-word mr-2 ${item.archived ? 'opacity-60' : ''}`}>{item.title}</div>
<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>
)
}

View File

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

View File

@@ -15,7 +15,7 @@ const UpgradeNow = ({ application, featuresController }: Props) => {
return shouldShowCTA ? (
<div className="flex h-full items-center px-2">
<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={() => {
if (hasAccount) {
void loadPurchaseFlowUrl(application)

View File

@@ -54,7 +54,7 @@ const IconPicker = ({ selectedValue, onIconChange, platform, className }: Props)
return (
<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'
}`}
onClick={() => {
@@ -114,14 +114,18 @@ const IconPicker = ({ selectedValue, onIconChange, platform, className }: Props)
onChange={({ target: input }) => handleEmojiChange((input as HTMLInputElement)?.value)}
/>
</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.
</div>
{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 && (
<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}>
<input
ref={inputRef}
className={`${tags.length > 0 ? 'w-80' : 'mr-10 w-70'} no-border h-7
bg-transparent text-xs text-text focus:border-b-2 focus:border-solid focus:border-info focus:shadow-none focus:outline-none`}
className={classNames(
`${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}
onChange={onSearchQueryChange}
type="text"

View File

@@ -92,7 +92,7 @@ const LinkedItemBubble = ({
return (
<button
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}
onBlur={onBlur}
onClick={onClick}

View File

@@ -22,7 +22,7 @@ const LinkedItemMeta = ({
return (
<>
<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>}
{searchQuery
? splitQueryInString(title, searchQuery).map((substring, index) => (

View File

@@ -326,7 +326,7 @@ const LinkedItemsPanel = ({
onChange={handleFileInputChange}
/>
<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}
>
<Icon type="add" />

View File

@@ -45,8 +45,9 @@ const MenuItem = forwardRef(
disabled={disabled}
ref={ref}
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-mobile-menu-item md:text-tablet-menu-item lg:text-menu-item',
)}
onClick={() => {
onChange(!checked)
@@ -68,9 +69,9 @@ const MenuItem = forwardRef(
role={type === MenuItemType.RadioButton ? 'menuitemradio' : 'menuitem'}
tabIndex={typeof tabIndex === 'number' ? tabIndex : FOCUSABLE_BUT_NOT_TABBABLE}
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',
'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,
)}
onClick={onClick}

View File

@@ -25,8 +25,8 @@ const NoAccountWarningContent = ({ accountMenuController, noAccountWarningContro
return (
<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>
<p className="col-start-1 col-end-3 m-0 mt-1 text-sm">
<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-base lg:text-sm">
Sign in or register to sync your notes to your other devices with end-to-end encryption.
</p>
<Button

View File

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

View File

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

View File

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

View File

@@ -6,14 +6,23 @@ import { NotesController } from '@/Controllers/NotesController'
import { KeyboardKey } from '@standardnotes/ui-services'
import Popover from '../Popover/Popover'
import { LinkingController } from '@/Controllers/LinkingController'
import { IconType } from '@standardnotes/snjs'
type Props = {
navigationController: NavigationController
notesController: NotesController
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 buttonRef = useRef<HTMLButtonElement>(null)
@@ -33,10 +42,10 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
}
}}
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">
<Icon type="hashtag" className="mr-2 text-neutral" />
<Icon type="hashtag" className={`${iconClassName} mr-2 text-neutral`} />
Add tag
</div>
<Icon type="chevron-right" className="text-neutral" />
@@ -52,13 +61,20 @@ const AddTagOption: FunctionComponent<Props> = ({ navigationController, notesCon
{navigationController.tags.map((tag) => (
<button
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={() => {
notesController.isTagInSelectedNotes(tag)
? notesController.removeTagFromSelectedNotes(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
className={`overflow-hidden overflow-ellipsis whitespace-nowrap
${notesController.isTagInSelectedNotes(tag) ? 'font-bold' : ''}`}

View File

@@ -9,9 +9,16 @@ import Popover from '../Popover/Popover'
type ChangeEditorOptionProps = {
application: WebApplication
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 menuContainerRef = useRef<HTMLDivElement>(null)
const buttonRef = useRef<HTMLButtonElement>(null)
@@ -30,10 +37,10 @@ const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({ applic
}
}}
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">
<Icon type="dashboard" className="mr-2 text-neutral" />
<Icon type="dashboard" className={`${iconClassName} mr-2 text-neutral`} />
Change note type
</div>
<Icon type="chevron-right" className="text-neutral" />

View File

@@ -9,9 +9,11 @@ import Popover from '../Popover/Popover'
type Props = {
application: WebApplication
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 buttonRef = useRef<HTMLButtonElement>(null)
@@ -37,10 +39,10 @@ const ListedActionsOption: FunctionComponent<Props> = ({ application, note }) =>
}
}}
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">
<Icon type="listed" className="mr-2 text-neutral" />
<Icon type="listed" className={`mr-2 text-neutral ${iconClassName}`} />
Listed actions
</div>
<Icon type="chevron-right" className="text-neutral" />

View File

@@ -41,17 +41,19 @@ const ListedMenuItem: FunctionComponent<ListedMenuItemProps> = ({
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"
>
<div className="flex flex-col">
<div className="font-semibold">{action.label}</div>
{action.access_type && (
<div className="mt-0.5 text-xs text-passive-0">
{'Uses '}
<strong>{action.access_type}</strong>
{' access to this note.'}
</div>
)}
<div className="flex w-full flex-row items-center justify-between">
<div className="flex flex-col">
<div className="font-semibold">{action.label}</div>
{action.access_type && (
<div className="mt-0.5 text-xs text-passive-0">
{'Uses '}
<strong>{action.access_type}</strong>
{' access to this note.'}
</div>
)}
</div>
{isRunning && <Spinner className="h-3 w-3" />}
</div>
{isRunning && <Spinner className="h-3 w-3" />}
</button>
)
}

View File

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

View File

@@ -40,7 +40,7 @@ const NotesOptionsPanel = ({
return (
<>
<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
application={application}
navigationController={navigationController}

View File

@@ -27,9 +27,8 @@ const PinNoteButton: FunctionComponent<Props> = ({ className = '', notesControll
return (
<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 ${
pinned ? 'toggled' : ''
} ${className}`}
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
md:h-8 md:min-w-8 ${pinned ? 'toggled' : ''} ${className}`}
onClick={togglePinned}
>
<VisuallyHidden>Pin selected notes</VisuallyHidden>

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
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 { ViewControllerManager } from '@/Controllers/ViewControllerManager'
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">
<AccountIllustration className="mb-3" />
<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.
</Text>
</div>
<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?{' '}
<button className="cursor-pointer border-0 bg-default p-0 text-info underline" onClick={clickSignIn}>
Sign in

View File

@@ -19,12 +19,7 @@ const DeleteAccount = ({ application, viewControllerManager }: Props) => {
<PreferencesGroup>
<PreferencesSegment>
<Title>Delete account</Title>
<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>
<Text>This action is irreversible. After deletion completes, you will be signed out on all devices.</Text>
<div className="mt-3 flex flex-row flex-wrap gap-3">
<Button
colorStyle="danger"

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
import Switch from '@/Components/Switch/Switch'
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
import { WebApplication } from '@/Application/Application'
@@ -75,7 +74,6 @@ const Privacy: FunctionComponent<Props> = ({ application }: Props) => {
<PreferencesSegment>
<Title>Privacy</Title>
<div>
<HorizontalSeparator classes="my-4" />
<div className="flex items-center justify-between">
<div className="flex flex-col">
<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 }) => (
<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 }) => (
<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 }) => (
<p className={classNames('text-sm md:text-xs', className)}>{children}</p>
<p className={classNames('text-base lg:text-xs', className)}>{children}</p>
)
const buttonClasses =
'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'
export const LinkButton: FunctionComponent<{

View File

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

View File

@@ -80,7 +80,7 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
>
<div className={'section-title-bar'}>
<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>
</div>
</div>

View File

@@ -105,7 +105,7 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
<div
role="button"
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}
style={{
paddingLeft: `${level * PADDING_PER_LEVEL_PX + PADDING_BASE_PX}px`,
@@ -117,7 +117,7 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
</div>
{isEditing ? (
<input
className={'title editing'}
className={'title editing text-mobile-navigation-list-item lg:text-navigation-list-item'}
id={`react-tag-${view.uuid}`}
onBlur={onBlur}
onInput={onInput}
@@ -127,18 +127,23 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
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}
</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>
{!isSystemView(view) && (
<div className="meta">
{view.conflictOf && (
<div className="danger text-[0.625rem] font-bold">Conflicted Copy {view.conflictOf}</div>
)}
{view.conflictOf && <div className="-mt-1 text-[0.625rem] font-bold text-danger">Conflicted Copy</div>}
{isSelected && (
<div className="menu">

View File

@@ -104,7 +104,7 @@ const TagContextMenu = ({ navigationController, isEntitledToFolders, selectedTag
</MenuItem>
</Menu>
<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">
<span className="font-semibold">Last modified:</span> {tagLastModified}
</div>

View File

@@ -43,7 +43,9 @@ const TagsList: FunctionComponent<Props> = ({ viewControllerManager, type }: Pro
return (
<DndProvider backend={backend}>
{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) => {

View File

@@ -245,7 +245,7 @@ export const TagsListItem: FunctionComponent<Props> = observer(
<div
role="button"
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}
ref={mergeRefs([dragRef, tagRef])}
style={{
@@ -275,7 +275,9 @@ export const TagsListItem: FunctionComponent<Props> = observer(
</div>
{isEditing ? (
<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}`}
onBlur={onBlur}
onInput={onInput}
@@ -286,7 +288,9 @@ export const TagsListItem: FunctionComponent<Props> = observer(
/>
) : (
<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}`}
>
{title}
@@ -303,12 +307,12 @@ export const TagsListItem: FunctionComponent<Props> = observer(
>
<Icon type="more" className="text-neutral" />
</a>
<div className="count">{noteCounts.get()}</div>
<div className="count text-base lg:text-sm">{noteCounts.get()}</div>
</div>
</div>
<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>
{isAddingSubtag && (
@@ -324,7 +328,7 @@ export const TagsListItem: FunctionComponent<Props> = observer(
<Icon type="hashtag" className="mr-1 text-neutral" />
</div>
<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"
ref={subtagInputRef}
onBlur={onSubtagInputBlur}

View File

@@ -57,7 +57,7 @@ const TagsSection: FunctionComponent<Props> = ({ viewControllerManager }) => {
<section>
<div className={'section-title-bar'}>
<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>
</div>
</div>

View File

@@ -22,7 +22,7 @@ const TagsSectionTitle: FunctionComponent<Props> = ({ features, hasMigration, on
if (entitledToFolders) {
return (
<>
<div className="title text-sm">
<div className="title text-base md:text-sm">
<span className="font-bold">Folders</span>
{hasMigration && (
<label className="ml-1 cursor-pointer font-bold text-info" onClick={onClickMigration}>
@@ -36,7 +36,7 @@ const TagsSectionTitle: FunctionComponent<Props> = ({ features, hasMigration, on
return (
<>
<div className="title text-sm">
<div className="title text-base md:text-sm">
<span className="font-bold">Tags</span>
<Tooltip label={TAG_FOLDERS_FEATURE_TOOLTIP}>
<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_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 =
'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
export const MediaQueryBreakpoints = {
sm: '(min-width: 640px)',
sm: '(max-width: 640px)',
md: '(min-width: 768px)',
lg: '(min-width: 1024px)',
xl: '(min-width: 1280px)',

View File

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

View File

@@ -32,11 +32,11 @@ export class MobileWebReceiver {
try {
const parsed = JSON.parse(message)
const { messageType, reactNativeEvent } = parsed
const { messageType, reactNativeEvent, messageData } = parsed
if (messageType === 'event' && reactNativeEvent) {
const nativeEvent = reactNativeEvent as ReactNativeToWebEvent
this.handleNativeEvent(nativeEvent)
this.handleNativeEvent(nativeEvent, messageData)
}
} catch (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) {
case ReactNativeToWebEvent.EnteringBackground:
void this.application.handleMobileEnteringBackgroundEvent()
@@ -71,6 +71,16 @@ export class MobileWebReceiver {
case ReactNativeToWebEvent.ColorSchemeChanged:
void this.application.handleMobileColorSchemeChangeEvent()
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:
break

View File

@@ -204,6 +204,8 @@ export const disableIosTextFieldZoom = () => {
}
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) => {
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 setViewportHeightWithFallback = () => {
const currentHeight = parseInt(document.documentElement.style.getPropertyValue(ViewportHeightKey))
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) {
document.documentElement.style.setProperty(ViewportHeightKey, '100vh')
setCustomViewportHeight('100', 'vh')
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;
overflow: auto;
background-color: var(--editor-header-bar-background-color);
@media screen and (min-width: 768px) {
min-height: 100vh;
height: 100vh;
}
}
$footer-height: 2rem;

View File

@@ -24,9 +24,6 @@ $content-horizontal-padding: 16px;
.no-tags-placeholder {
padding: 0px $content-horizontal-padding;
font-size: 12px;
opacity: 0.4;
margin-top: -5px;
}
.root-drop {
@@ -68,10 +65,19 @@ $content-horizontal-padding: 16px;
}
> .tag-info {
align-items: center;
display: flex;
flex-direction: row;
align-items: center;
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 {
display: block;
@@ -93,9 +99,6 @@ $content-horizontal-padding: 16px;
}
> .title {
font-size: 14px;
line-height: 18px;
width: 80%;
background-color: transparent;
font-weight: 600;

View File

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

View File

@@ -10,6 +10,7 @@ module.exports = {
4.5: '1.125rem',
8.5: '2.125rem',
13: '3.25rem',
15: '3.75rem',
18: '4.5rem',
26: '6.5rem',
30: '7.5rem',
@@ -28,9 +29,11 @@ module.exports = {
3: '0.75rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
8: '2rem',
8.5: '2.125rem',
9: '2.25rem',
10: '2.5rem',
15: '3.75rem',
20: '5rem',
24: '6rem',
@@ -87,7 +90,10 @@ module.exports = {
},
fontSize: {
'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)',
},
screens: {