fix(mobile): increase font sizes and other mobile-centric improvements (#1907)
This commit is contained in:
@@ -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">
|
||||
You’re offline. Sign in to sync your notes and preferences across all your devices and enable end-to-end
|
||||
encryption.
|
||||
</div>
|
||||
<div className="flex items-center 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>
|
||||
</>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -29,6 +29,7 @@ const MobileItemsListButton = () => {
|
||||
}}
|
||||
label={label}
|
||||
icon={iconType}
|
||||
iconClassName={'h-6 w-6'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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' : ''}`}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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. It’s
|
||||
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"
|
||||
|
||||
@@ -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() && (
|
||||
<>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
Reference in New Issue
Block a user