fix(mobile): increase font sizes and other mobile-centric improvements (#1907)
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,4 +6,6 @@ export enum WebAppEvent {
|
||||
PanelResized = 'PanelResized',
|
||||
WindowDidFocus = 'WindowDidFocus',
|
||||
WindowDidBlur = 'WindowDidBlur',
|
||||
MobileKeyboardDidChangeFrame = 'MobileKeyboardDidChangeFrame',
|
||||
MobileKeyboardWillChangeFrame = 'MobileKeyboardWillChangeFrame',
|
||||
}
|
||||
|
||||
@@ -5,4 +5,6 @@ export enum ReactNativeToWebEvent {
|
||||
LosingFocus = 'LosingFocus',
|
||||
AndroidBackButtonPressed = 'AndroidBackButtonPressed',
|
||||
ColorSchemeChanged = 'ColorSchemeChanged',
|
||||
KeyboardFrameWillChange = 'KeyboardFrameWillChange',
|
||||
KeyboardFrameDidChange = 'KeyboardFrameDidChange',
|
||||
}
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
|
||||
.sk-panel-content {
|
||||
// padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sk-panel-header {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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.'
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const MenuItemIconSize = 'w-6 h-6 md:w-5 md:h-5'
|
||||
@@ -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)',
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user