More warnings clean up

This commit is contained in:
Jackson Harper 2023-02-27 10:34:12 +08:00
parent 13c01b28f9
commit 9d6a28cc74
54 changed files with 322 additions and 1328 deletions

View file

@ -1,5 +1,4 @@
import { Avatar } from './../elements/Avatar'
import { AngleDownIcon } from './../tokens/icons/AngleDownIcon'
import { HStack } from '../elements/LayoutPrimitives'
type AvatarDropdownProps = {
@ -12,7 +11,7 @@ export function AvatarDropdown(props: AvatarDropdownProps): JSX.Element {
<HStack alignment="center" css={{ gap: '6px' }}>
<Avatar
imageURL={props.profileImageURL}
height='32px'
height="32px"
fallbackText={props.userInitials}
/>
</HStack>

View file

@ -8,8 +8,8 @@ import {
Arrow,
Label,
} from '@radix-ui/react-dropdown-menu'
import { PopperContentProps } from '@radix-ui/react-popover';
import { CSS } from '@stitches/react';
import { PopperContentProps } from '@radix-ui/react-popover'
import { CSS } from '@stitches/react'
import { styled } from './../tokens/stitches.config'
const itemStyles = {
@ -148,7 +148,9 @@ export function DropdownOption(props: DropdownOptionProps): JSX.Element {
)
}
export function Dropdown(props: DropdownProps & PopperContentProps): JSX.Element {
export function Dropdown(
props: DropdownProps & PopperContentProps
): JSX.Element {
const {
children,
align,
@ -159,14 +161,14 @@ export function Dropdown(props: DropdownProps & PopperContentProps): JSX.Element
sideOffset = 0,
alignOffset = 0,
css,
modal
modal,
} = props
return (
<Root modal={modal}>
<DropdownTrigger disabled={disabled}>{triggerElement}</DropdownTrigger>
<DropdownContent
css={css}
onInteractOutside={(event) => {
onInteractOutside={() => {
// remove focus from dropdown
;(document.activeElement as HTMLElement).blur()
}}

View file

@ -9,7 +9,6 @@ import { EdgeIcon } from './images/EdgeIcon'
import { FirefoxIcon } from './images/FirefoxIcon'
import { SafariIcon } from './images/SafariIcon'
import Link from 'next/link'
import { SaveArticleIcon } from './images/SaveArticleIcon'
const icons = {
'Google Chrome': <ChromeIcon />,

View file

@ -1,6 +1,5 @@
import Link from 'next/link'
import { Info } from 'phosphor-react'
import { Box, VStack } from '../elements/LayoutPrimitives'
import { VStack } from '../elements/LayoutPrimitives'
import { theme } from '../tokens/stitches.config'
import { TooltipWrapped } from './Tooltip'

View file

@ -1,4 +1,4 @@
import { getLuminance, lighten, toHsla } from 'color2k'
import { getLuminance, lighten } from 'color2k'
import { useRouter } from 'next/router'
import { Button } from './Button'
import { SpanBox } from './LayoutPrimitives'

View file

@ -5,10 +5,9 @@ import {
DeviceMobileCamera,
} from 'phosphor-react'
import { Box, HStack } from '../elements/LayoutPrimitives'
import { StyledText, StyledImg, StyledAnchor } from '../elements/StyledText'
import { StyledText, StyledAnchor } from '../elements/StyledText'
import { TooltipWrapped } from './Tooltip'
import Link from 'next/link'
import { InstallationIcon } from './images/InstallationIcon'
const TooltipStyle = {
backgroundColor: '#F9D354',

View file

@ -6,8 +6,6 @@ import {
import { VStack, HStack } from '../elements/LayoutPrimitives'
import { Button } from '../elements/Button'
import { StyledText } from '../elements/StyledText'
import { useConfirmListener } from '../../lib/keyboardShortcuts/useKeyboardShortcuts'
import { useEffect, useRef } from 'react'
type ConfirmationModalProps = {
message?: string

View file

@ -1,18 +1,10 @@
import { PageMetaData, PageMetaDataProps } from '../patterns/PageMetaData'
import { Box } from '../elements/LayoutPrimitives'
import {
ReactNode,
MutableRefObject,
useEffect,
useState,
useCallback,
} from 'react'
import { PrimaryHeader } from './../patterns/PrimaryHeader'
import { ReactNode, useEffect, useState, useCallback } from 'react'
import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery'
import { navigationCommands } from '../../lib/keyboardShortcuts/navigationShortcuts'
import { useKeyboardShortcuts } from '../../lib/keyboardShortcuts/useKeyboardShortcuts'
import { useRouter } from 'next/router'
import { Analytics } from '@segment/analytics-next'
import { ConfirmationModal } from '../patterns/ConfirmationModal'
import { KeyboardShortcutListModal } from './KeyboardShortcutListModal'
import { logoutMutation } from '../../lib/networking/mutations/logoutMutation'

View file

@ -48,28 +48,6 @@ type ActionDropdownProps = {
children: JSX.Element
}
const ActionDropdown = (props: ActionDropdownProps): JSX.Element => {
return (
<Dropdown
css={{
m: '0px',
p: '0px',
overflow: 'hidden',
width: '265px',
maxWidth: '265px',
'@smDown': { width: '230px' },
}}
side={props.layout == 'side' ? 'right' : 'bottom'}
sideOffset={props.layout == 'side' ? 8 : 0}
align={props.layout == 'side' ? 'start' : 'center'}
alignOffset={props.layout == 'side' ? -18 : undefined}
triggerElement={props.triggerElement}
>
{props.children}
</Dropdown>
)
}
export function ArticleActionsMenu(
props: ArticleActionsMenuProps
): JSX.Element {

View file

@ -1,12 +1,7 @@
import { VStack } from '../../elements/LayoutPrimitives'
import {
ModalRoot,
ModalOverlay,
ModalContent,
} from '../../elements/ModalPrimitives'
import { ModalRoot, ModalContent } from '../../elements/ModalPrimitives'
import { ReaderSettingsControl } from './ReaderSettingsControl'
type DisplaySettingsModalProps = {
centerX: boolean
onOpenChange: (open: boolean) => void
@ -14,9 +9,13 @@ type DisplaySettingsModalProps = {
articleActionHandler: (action: string, arg?: number | string) => void
}
export function DisplaySettingsModal(props: DisplaySettingsModalProps): JSX.Element {
const top = props.triggerElementRef?.current?.getBoundingClientRect().bottom ?? 0
const left = props.triggerElementRef?.current?.getBoundingClientRect().left ?? 0
export function DisplaySettingsModal(
props: DisplaySettingsModalProps
): JSX.Element {
const top =
props.triggerElementRef?.current?.getBoundingClientRect().bottom ?? 0
const left =
props.triggerElementRef?.current?.getBoundingClientRect().left ?? 0
return (
<ModalRoot defaultOpen onOpenChange={props.onOpenChange}>
@ -24,8 +23,12 @@ export function DisplaySettingsModal(props: DisplaySettingsModalProps): JSX.Elem
css={{
width: '245px',
top: props.triggerElementRef?.current ? top : '50%',
left: props.triggerElementRef?.current ? (left - (props.centerX ? 265 / 2 : 0)) : '50%',
transform: props.triggerElementRef?.current ? 'unset' : 'translate(-50%, -50%)',
left: props.triggerElementRef?.current
? left - (props.centerX ? 265 / 2 : 0)
: '50%',
transform: props.triggerElementRef?.current
? 'unset'
: 'translate(-50%, -50%)',
}}
onPointerDownOutside={(event) => {
event.preventDefault()

View file

@ -1,7 +1,7 @@
import { HStack, Box } from '../../elements/LayoutPrimitives'
import { StyledText } from '../../elements/StyledText'
import { theme } from '../../tokens/stitches.config'
import { CaretLeft, Check, CheckCircle } from 'phosphor-react'
import { CaretLeft, Check } from 'phosphor-react'
const FONT_FAMILIES = [
'Inter',
@ -12,7 +12,7 @@ const FONT_FAMILIES = [
'Roboto',
'Crimson Text',
'OpenDyslexic',
'Source Serif Pro'
'Source Serif Pro',
]
type FontFamiliesListProps = {
@ -27,18 +27,28 @@ type FontOptionProps = {
onSelect: (value: string) => void
}
function FontOption(props: FontOptionProps):JSX.Element {
function FontOption(props: FontOptionProps): JSX.Element {
const isSelected = props.selected === props.family
return (
<HStack distribution='between' alignment='start' css={{ width: '100%', pt: '14px' }}>
<HStack
distribution="between"
alignment="start"
css={{ width: '100%', pt: '14px' }}
>
<StyledText
css={{ m: '0px', fontSize: 16, fontFamily: props.family, textTransform: 'capitalize', cursor: 'pointer' }}
css={{
m: '0px',
fontSize: 16,
fontFamily: props.family,
textTransform: 'capitalize',
cursor: 'pointer',
}}
onClick={() => props.onSelect(props.family)}
>
{props.family}
</StyledText>
{isSelected && (
<Check color={theme.colors.grayTextContrast.toString()} weight='bold' />
<Check color={theme.colors.grayTextContrast.toString()} weight="bold" />
)}
</HStack>
)
@ -47,22 +57,50 @@ function FontOption(props: FontOptionProps):JSX.Element {
export function FontFamiliesOptions(props: FontFamiliesListProps): JSX.Element {
return (
<>
<Box css={{borderBottom: `1px solid ${theme.colors.grayLine.toString()}`, width: '100%'}}>
<HStack alignment='center' distribution='between' css={{width: '100%', py: 10, px: 15}}>
<Box
css={{
borderBottom: `1px solid ${theme.colors.grayLine.toString()}`,
width: '100%',
}}
>
<HStack
alignment="center"
distribution="between"
css={{ width: '100%', py: 10, px: 15 }}
>
<HStack
alignment='center'
distribution='start'
alignment="center"
distribution="start"
css={{ cursor: 'pointer', py: '4px', width: '100%' }}
onClick={() => props.setShowFontFamilies(false)}
>
<CaretLeft color={theme.colors.utilityTextSubtle.toString()} size={15} />
<StyledText css={{ textAlign: 'center', m: 0, fontSize: 14, fontWeight: 'bold', width: '100%', wordWrap: 'revert' }}>Choose Font</StyledText>
<CaretLeft
color={theme.colors.utilityTextSubtle.toString()}
size={15}
/>
<StyledText
css={{
textAlign: 'center',
m: 0,
fontSize: 14,
fontWeight: 'bold',
width: '100%',
wordWrap: 'revert',
}}
>
Choose Font
</StyledText>
</HStack>
</HStack>
</Box>
<Box css={{ px: 15, pb: 15, width: '100%' }}>
{FONT_FAMILIES.map((family) => (
<FontOption selected={props.selected} family={family} onSelect={props.onSelect} key={`font-${family}`} />
<FontOption
selected={props.selected}
family={family}
onSelect={props.onSelect}
key={`font-${family}`}
/>
))}
</Box>
</>

View file

@ -30,18 +30,6 @@ export function HighlightNoteModal(
props.highlight?.annotation ?? ''
)
useConfirmListener(
() => {
saveNoteChanges()
},
undefined,
true
)
const updatedAtMessage = props.highlight
? readableUpdatedAtMessage(props.highlight?.updatedAt)
: undefined
const handleNoteContentChange = useCallback(
(event: React.ChangeEvent<HTMLTextAreaElement>): void => {
setNoteContent(event.target.value)

View file

@ -224,7 +224,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
setHighlightModalAction(inputs)
}
},
[props.highlightBarDisabled, createHighlightFromSelection]
[props.highlightBarDisabled]
)
const selectionPercentPos = (selection: Selection): number | undefined => {
@ -289,7 +289,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
setSelectionData,
canShareNative,
highlightLocations,
createHighlightFromSelection,
]
)

View file

@ -1,89 +0,0 @@
import React, { useState } from 'react'
import { subscribeMutation } from '../../../lib/networking/mutations/subscribeMutation'
import { VStack } from '../../elements/LayoutPrimitives'
import { OnboardingLayout } from '../OnboardingLayout'
import { SelectOption } from './SelectOption'
const newsletterOptions = [
{
icon: 'AxiosDaily.png',
label: 'Axios Daily Essentials',
description: 'Start and end your day with the stories that matter in your inbox.',
name: 'axios_essentials',
isChecked: false,
},
{
icon: 'MilkRoad.png',
label: 'Milk Road',
description: '5 minute daily newsletter. Used by 100,000+ people to be better crypto investors 💪',
name: 'morning_brew',
isChecked: false,
},
{
icon: 'MoneyStuff.png',
label: 'Money Stuff by Matt Levine',
description: 'A daily take on Wall Street, finance, companies and other stuff.',
name: 'milk_road',
isChecked: false,
},
{
icon: 'OmnivoreBlog.png',
label: 'Omnivore',
description: 'Tips and tricks, plus updates on new features in Omnivore.',
name: 'omnivore_blog',
isChecked: false,
},
]
type OnboardingAddNewslettersProps = {
pageNumber: number
}
export type NewsLetterOption = {
icon: string,
label: string,
name: string,
description: string,
isChecked: boolean,
}
export const OnboardingAddNewsletters = (props: OnboardingAddNewslettersProps) => {
const [newsletters, setNewsletters] = useState<NewsLetterOption[]>(newsletterOptions);
const onCheck = (index: number) => {
const temp = [...newsletters]
temp[index].isChecked = !temp[index].isChecked
setNewsletters(temp)
}
const onNext = () => {
newsletters.map((newsletter) => {
if (newsletter.isChecked) subscribeMutation(newsletter.name)
})
}
return (
<OnboardingLayout
pageNumber={props.pageNumber}
subTitle="Omnivore creates an email address for you to subscribe to newsletters with."
description="Subscribe to some newsletters now"
title="Read all your Newsletters in Omnivore"
onNext={onNext}
>
<VStack css={{
marginTop: '$4',
width: '100%'
}}>
{newsletters.map(({ icon, label, description, isChecked }, idx) => (
<SelectOption
key={idx}
indexNum={idx}
onCheck={onCheck}
{...{ icon, label, description, isChecked}} />
))}
</VStack>
</OnboardingLayout>
)
}

View file

@ -1,43 +0,0 @@
import React from 'react'
import { OnboardingLayout } from '../OnboardingLayout'
import { Box } from '../../elements/LayoutPrimitives'
import { styled } from '../../tokens/stitches.config'
const StyledImage = styled('img', {
position: 'relative',
width: '100%',
height: '100%',
'@smDown': {
width: '160%',
height: 'auto',
}
})
type OnboardingOrganizeInstructionsProps = {
pageNumber: number
}
export const OnboardingHighlightInstructions = (props: OnboardingOrganizeInstructionsProps) => {
return (
<OnboardingLayout
pageNumber={props.pageNumber}
title="Highlight and share"
subTitle="Information about creating and sharing highlights Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint. Velit officia consequat duis enim velit mollit. Exercitation veniam consequat sunt nostrud amet."
>
<Box css={{
width: '100%',
height: '100%',
overflow: 'hidden',
'@smDown': {
width: '100%',
height: 'auto',
}
}}>
<StyledImage
src='/static/images/onboarding/highlight-and-share.svg'
alt="Highlight And Share"
/>
</Box>
</OnboardingLayout>
)
}

View file

@ -1,100 +0,0 @@
import React from 'react'
import { OnboardingLayout } from '../OnboardingLayout'
import MobileInstallHelp from '../../elements/MobileInstallHelp'
import ExtensionsInstallHelp from '../../elements/ExtensionsInstallHelp'
import { Box } from '../../elements/LayoutPrimitives'
import { sendInstallInstructions } from '../../../lib/networking/queries/sendInstallInstructions'
import { Button } from '../../elements/Button'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
type OnboardingInstallInstructionsProps = {
pageNumber: number
}
export const OnboardingInstallInstructions = (props: OnboardingInstallInstructionsProps) => {
const onEmailInstructionsClick = async () => {
const res = await sendInstallInstructions()
if (res !== undefined) {
showSuccessToast('Instructions Email Sent', { position: 'bottom-right' })
}
else {
showErrorToast('Failed to send', { position: 'bottom-right' })
}
}
return (
<OnboardingLayout
pageNumber={props.pageNumber}
title="Save links to read later"
subTitle="Save any link to your library using our apps and browser extensions"
description='Install our apps and browser extensions'
image={
<img
src='/static/images/onboarding/browser-extension.svg'
alt="Browser Extension"
/>
}
>
<Box css={{
"@lgDown": {
marginBottom: 100,
}
}}>
<Box
css={{
margin: '27px 0',
'@lg': {
border: '1px solid rgba(0, 0, 0, 0.06)',
boxShadow: '0px 3px 11px rgba(32, 31, 29, 0.04)',
},
backgroundColor: 'White',
}}
>
<Box
css={{
'@lgDown': {
boxShadow: '0px 3px 11px rgba(32, 31, 29, 0.04)',
border: '1px solid rgba(0, 0, 0, 0.06)',
},
}}
>
<MobileInstallHelp onboarding />
</Box>
<Box
css={{
'@lgDown': {
boxShadow: '0px 3px 11px rgba(32, 31, 29, 0.04)',
border: '1px solid rgba(0, 0, 0, 0.06)',
marginTop: '8px',
},
}}
>
<ExtensionsInstallHelp onboarding />
</Box>
</Box>
<Box
css={{
'@lg': {
marginBottom: '20px',
},
}}
>
<Box
onClick={onEmailInstructionsClick}
css={{
fontWeight: '400',
fontSize: '14px',
lineHeight: '17.5px',
color: 'rgba(10, 8, 6, 0.8)',
textDecoration: 'underline',
cursor: 'pointer',
}}
>
Email me instructions
</Box>
</Box>
</Box>
</OnboardingLayout>
)
}

View file

@ -1,77 +0,0 @@
import React from 'react'
import { OnboardingLayout } from '../OnboardingLayout'
import { Box, HStack, VStack } from '../../elements/LayoutPrimitives'
import { styled } from '../../tokens/stitches.config'
const IconContainer = styled(Box, {width: '30%', justifyContent: 'center', display: 'flex'})
const Icon = styled('img', {width: 100})
const Row = styled(HStack, {
width: '100%',
padding: 30,
borderBottom: '1px solid rgba(0, 0, 0, 0.06)',
'@smDown': {
padding: 20,
}
})
const Text = styled(Box, {
width: '65%',
alignSelf: 'center',
color: '#0A0806CC',
fontSize: 24,
fontWeight: '700',
'@smDown': {
fontSize: 16,
}
})
const Container = styled(Box, {
width: 523,
border: '1px solid #0000000F',
background: 'white',
'@smDown': {
width: '95%',
}
})
type OnboardingJoinCommunityProps = {
pageNumber: number
}
export const OnboardingJoinCommunity = (props: OnboardingJoinCommunityProps) => {
return (
<OnboardingLayout
pageNumber={props.pageNumber}
title="Join our Community"
subTitle='Omnivore is open source and open community, join us.'
nextPage='/home'
reduceSpace
>
<HStack distribution='center' alignment='center' css={{width: '100%', height: '100%'}}>
<Container>
<VStack>
<Row distribution='between'>
<IconContainer>
<Icon css={{height: 60}} src='/static/images/onboarding/github.svg' alt="Github Logo" />
</IconContainer>
<Text>Star us on Github</Text>
</Row>
<Row distribution='between'>
<IconContainer>
<Icon src='/static/images/onboarding/discord.svg' alt="Discrod Logo" />
</IconContainer>
<Text>Join us on Discord</Text>
</Row>
<Row distribution='between'>
<IconContainer>
<Icon css={{width: 74, height: 67}} src='/static/images/onboarding/producthunt.svg' alt="ProductHunt Logo" />
</IconContainer>
<Text>Like us on Product Hunt</Text>
</Row>
</VStack>
</Container>
</HStack>
</OnboardingLayout>
)
}

View file

@ -1,57 +0,0 @@
import React from 'react'
import { OnboardingLayout } from '../OnboardingLayout'
import { Box } from '../../elements/LayoutPrimitives'
import { styled } from '../../tokens/stitches.config'
const StyledImage = styled('img', {
position: 'relative',
})
type OnboardingOrganizeInstructionsProps = {
pageNumber: number
}
export const OnboardingOrganizeInstructions = (props: OnboardingOrganizeInstructionsProps) => {
return (
<OnboardingLayout
pageNumber={props.pageNumber}
title="Organize all your content"
subTitle="Information about archiving and tagging
Amet minim mollit non deserunt ullamco est sit aliqua dolor do amet sint. Velit officia consequat duis enim velit mollit. Exercitation veniam consequat sunt nostrud amet."
>
<Box css={{width: '100%', height: '100%'}}>
<StyledImage
src='/static/images/onboarding/organize-content.svg'
alt="Organize Content Desktop"
css={{
width: '100%',
height: '500px',
'@smDown': {
display: 'none',
},
'@lg': {
height: '75%',
},
'@xl': {
width: 'auto',
height: '95%',
}
}}
/>
<StyledImage
src='/static/images/onboarding/organize-content-mobile.svg'
alt="Organize Content Mobile"
css={{
mt: 10,
right: '1.6vh',
objectFit: 'contain',
width: '100%',
'@sm': {
display: 'none',
}
}}
/>
</Box>
</OnboardingLayout>
)
}

View file

@ -1,83 +0,0 @@
import { SelectionOptionCard } from './SelectOption'
import { OnboardingLayout } from '../OnboardingLayout'
import { Box, HStack, VStack } from '../../elements/LayoutPrimitives'
const articleDetails = [
{
title: 'Winnebago Electric RV Concept',
author: 'Omnivore',
originText: 'wired.com',
description: "An incredible number of lines from William Shakespeare's plays have becomeso ingrained in modern vernacular …",
image: "https://images.hgmsites.net/sml/cadillac_100789665_s.jpg",
labels: []
},
{
title: 'Winnebago Electric RV Concept',
author: 'Omnivore',
originText: 'wired.com',
description: "An incredible number of lines from William Shakespeare's plays have becomeso ingrained in modern vernacular …",
image: "https://images.hgmsites.net/sml/cadillac_100789665_s.jpg",
labels: []
},
{
title: '21 Phrases You Use Without Realizin…',
author: 'Omnivore',
originText: 'wired.com',
description: "An incredible number of lines from William Shakespeare's plays have becomeso ingrained in modern vernacular …",
image: "",
labels: [],
},
{
title: '21 Phrases You Use Without Realizin…',
author: 'Omnivore',
originText: 'wired.com',
description: "An incredible number of lines from William Shakespeare's plays have becomeso ingrained in modern vernacular …",
image: "https://images.hgmsites.net/sml/cadillac_100789665_s.jpg",
labels: []
},
]
type OnboardingReaderPreviewProps = {
pageNumber: number
}
export const OnboardingReaderPreview = (props: OnboardingReaderPreviewProps) => {
return (
<OnboardingLayout
pageNumber={props.pageNumber}
title="Read Distraction Free"
subTitle="Omnivore's distraction free reader gives you an uncluttered reading experience"
description='Add some great reads to your library now:'
image={
<img
src='/static/images/onboarding/expanded-controls.svg'
alt="Expanded control"
/>
}
>
<VStack css={{
marginTop: '$4',
marginBottom: '$6'
}}>
<HStack>
<Box css={{
display: 'grid',
gridTemplateColumns: '100%',
gridColumnGap: '$3',
gridRowGap: '$2',
'@md': {
gridTemplateColumns: '50% 50%',
},
}}>
{articleDetails.map(({ title, author, originText, description, image, labels }, idx) => (
<SelectionOptionCard
key={idx}
{...{ title, author, originText, description, image, labels }}
/>
))}
</Box>
</HStack>
</VStack>
</OnboardingLayout>
)
}

View file

@ -1,244 +0,0 @@
import React from 'react'
import Checkbox from '../../elements/Checkbox'
import { Box, VStack, HStack, SpanBox } from '../../elements/LayoutPrimitives'
import { CoverImage } from '../../elements/CoverImage'
import { StyledText } from '../../elements/StyledText'
import { authoredByText } from '../../patterns/ArticleSubtitle'
import { LabelChip } from '../../elements/LabelChip'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import Image from 'next/image'
export const SelectOption: React.FC<{
icon: string
label: string
description: string
onCheck: (idx: number) => void
indexNum: number
isChecked: boolean
}> = ({ icon, label, description, onCheck, indexNum, isChecked}) => {
const toggleChecked = () => {
onCheck(indexNum)
}
return (
<HStack onClick={toggleChecked} css={{
border: isChecked ? '1px solid #F9D354' : '1px solid #0000000F',
backgroundColor: isChecked ? '#FDFAEC' : '#FFFFFF',
boxShadow: '0px 3px 11px 0px #201F1D0A',
justifyContent: 'flex-start',
alignItems: 'center',
borderRadius: '6px',
cursor: 'pointer',
padding: '$2 $3',
marginBottom: 7,
width: '100%'
}}>
<Checkbox checked={isChecked} setChecked={() => undefined} />
<SpanBox css={{ marginLeft: '$2', width: '32px', height: '32px' }}>
<Image width={32} height={32} layout="fixed"
src={`/static/images/newsletter/${icon}`}
alt={`${icon.slice(0, -4)} logo`}
/>
</SpanBox>
<VStack css={{ border: '1', marginLeft: '$2', color: '#0A0806CC', }}>
<StyledText
css={{
margin: 0,
fontSize: 16,
fontWeight: 700,
}}
>
{label}
</StyledText>
<StyledText
css={{
margin: 0,
fontSize: 16,
fontWeight: 400,
color: '#0A0806CC',
}}
>
{description}
</StyledText>
</VStack>
</HStack>
)
}
export const SelectionOptionCard: React.FC <{
title: string,
author: string,
originText: string,
description: string,
image: string,
labels: Label[]
}> = ({title, author, originText, description, image, labels}) => {
const [checked, setChecked] = React.useState(false)
const toggleChecked = () => setChecked(!checked)
return (
<VStack
css={{
p: '$2',
pr: '8px',
height: '100%',
width: '100%',
maxWidth: '100%',
borderRadius: '6px',
cursor: 'pointer',
wordBreak: 'break-word',
overflow: 'clip',
boxShadow: '0px 3px 11px rgba(32, 31, 29, 0.04)',
position: 'relative',
marginRight: '$3',
border: checked ? '1px solid #F9D354' : '1px solid $grayBorder',
backgroundColor: checked ? '#FDFAEC' : '#FFFFFF',
}}
alignment="start"
distribution="start"
onClick={toggleChecked}
>
<Box
css={{
position: 'absolute',
top: '1px',
left: '1px',
width: 'calc(100% - 2px)',
'& > div': {
borderRadius: '100vmax 100vmax 0 0',
},
}}
>
</Box>
<VStack
distribution="start"
alignment="start"
css={{
px: '0px',
width: '100%',
pl: '$1',
}}
>
<HStack
alignment="start"
distribution="between"
css={{
width: '100%',
p: '0px',
mr: '-12px',
mt: '15px',
display: 'grid',
gridTemplateColumns: '1fr 24px',
gridTemplateRows: '1fr',
}}
>
<HStack css={{
alignItems: 'center'
}}>
<Checkbox {...{checked, setChecked}} />
<HStack css={{marginRight: '$2'}} />
<CardTitle title= {title} />
</HStack>
<Box
css={{ alignSelf: 'end', alignItems: 'start', height: '100%' }}
onClick={(e) => {
// This is here to prevent menu click events from bubbling
// up and causing us to "click" on the link item.
e.stopPropagation()
}}
>
</Box>
</HStack>
<HStack alignment="start" distribution="between">
<StyledText style="caption" css={{ my: '0' }}>
{author && (
<SpanBox css={{ mr: '8px' }}>
{authoredByText(author)}
</SpanBox>
)}
<SpanBox css={{ textDecorationLine: 'underline' }}>
{originText}
</SpanBox>
</StyledText>
</HStack>
</VStack>
<HStack
alignment="start"
distribution="between"
css={{
width: '100%',
pt: '$2',
px: '$1',
pr: '12px',
mt: '7px',
flexGrow: '1',
}}
>
<StyledText
css={{
m: 0,
py: '0px',
mr: '$2',
fontStyle: 'normal',
fontWeight: '400',
fontSize: '14px',
lineHeight: '125%',
color: '$grayTextContrast',
flexGrow: '4',
overflow: 'hidden',
display: '-webkit-box',
WebkitLineClamp: 5,
WebkitBoxOrient: 'vertical',
}}
data-testid="listDesc"
>
{description}
</StyledText>
{image && (
<CoverImage
src={image}
alt="Link Preview Image"
width={135}
height={90}
css={{ ml: '10px', mb: '8px', borderRadius: '3px' }}
onError={(e) => {
;(e.target as HTMLElement).style.display = 'none'
}}
/>
)}
</HStack>
<Box css={{ display: 'block', mt: '8px' }}>
{labels?.map(({ name, color }, index) => (
<LabelChip key={index} text={name || ''} color={color} />
))}
</Box>
</VStack>
)
}
type CardTitleProps = {
title: string
}
function CardTitle(props: CardTitleProps): JSX.Element {
return (
<StyledText
style="listTitle"
data-testid="listTitle"
css={{
mt: '0',
mb: '0',
fontWeight: '700',
textAlign: 'left',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
width: '100%',
overflow: 'hidden',
}}
>
{props.title}
</StyledText>
)
}

View file

@ -1,4 +1,4 @@
import { useRef, useEffect, useState, useCallback } from 'react'
import { useRef, useEffect, useState } from 'react'
type ScrollOffset = {
x: number
@ -12,10 +12,7 @@ export type ScrollOffsetChangeset = {
type Effect = (offset: ScrollOffsetChangeset) => void
export function useScrollWatcher(
effect: Effect,
delay: number
): void {
export function useScrollWatcher(effect: Effect, delay: number): void {
const throttleTimeout = useRef<NodeJS.Timeout | undefined>(undefined)
const [currentOffset, setCurrentOffset] = useState<ScrollOffset>({
x: 0,
@ -40,7 +37,6 @@ export function useScrollWatcher(
}
window.addEventListener('scroll', handleScroll)
return () =>
window.removeEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [currentOffset, delay, effect])
}

View file

@ -54,8 +54,8 @@ export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => {
})
})
})
KBAR_KEYS.map((key) => currentKeys[key.toLowerCase()] = false)
KBAR_KEYS.map((key) => (currentKeys[key.toLowerCase()] = false))
return currentKeys
}, [commands])
@ -126,7 +126,7 @@ export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => {
keydownEvent.preventDefault()
}
},
[applyCommands, keys]
[applyCommands, keys, metaPressed]
)
const keyupListener = useCallback(

View file

@ -1,5 +1,5 @@
import { gql } from 'graphql-request'
import { Label, labelFragment } from './labelFragment'
import { Label } from './labelFragment'
export const highlightFragment = gql`
fragment HighlightFields on Highlight {

View file

@ -1,20 +1,13 @@
import { gqlFetcher } from '../networkHelpers'
import { v4 as uuidv4 } from 'uuid'
type UploadFileInput = {
url: string
contentType: string
contentType: string
createPageEntry?: boolean
clientRequestId?: string
}
type UploadFileOutput = {
jobId?: string
url?: string
clientRequestId?: string
}
type UploadFileResponseData = {
uploadFileRequest?: UploadFileData
errorCodes?: unknown[]

View file

@ -1,5 +1,4 @@
import { gqlFetcher } from '../networkHelpers'
import { v4 as uuidv4 } from 'uuid'
export enum UploadImportFileType {
URL_LIST = 'URL_LIST',

View file

@ -48,7 +48,7 @@ export function useGetApiKeysQuery(): ApiKeysQueryResponse {
}
`
const { data, mutate, error, isValidating } = useSWR(query, publicGqlFetcher)
const { data, mutate, isValidating } = useSWR(query, publicGqlFetcher)
try {
if (data) {

View file

@ -1,8 +1,6 @@
import { gql } from 'graphql-request'
import useSWRImmutable, { Cache } from 'swr'
import useSWRImmutable from 'swr'
import { makeGqlFetcher, RequestContext, ssrFetcher } from '../networkHelpers'
import { ScopedMutator } from 'swr/dist/types'
import { LibraryItems } from './useGetLibraryItemsQuery'
type ArticleQueryInput = {
username?: string
@ -25,10 +23,7 @@ export type ArticleAttributes = {
}
const query = gql`
query GetArticle(
$username: String!
$slug: String!
) {
query GetArticle($username: String!, $slug: String!) {
article(username: $username, slug: $slug) {
... on ArticleSuccess {
article {
@ -58,7 +53,7 @@ export function useGetArticleOriginalHtmlQuery({
)
const resultData: ArticleData | undefined = data as ArticleData
console.log("RESULT", JSON.stringify(data))
console.log('RESULT', JSON.stringify(data))
return resultData?.article.article.originalHtml
}
@ -67,7 +62,12 @@ export async function originalHtmlQuery(
context: RequestContext,
input: ArticleQueryInput
): Promise<string | undefined> {
const resultData = (await ssrFetcher(context, query, input, false)) as ArticleData
const resultData = (await ssrFetcher(
context,
query,
input,
false
)) as ArticleData
console.log(JSON.stringify(resultData))
// if (resultData?.article.article.originalHtml) {
// return resultData?.article.article.originalHtml

View file

@ -105,7 +105,7 @@ export function useGetArticleQuery({
includeFriendsHighlights,
}
const { data, error, mutate } = useSWRImmutable(
const { data, error } = useSWRImmutable(
slug ? [query, username, slug, includeFriendsHighlights] : null,
makeGqlFetcher(variables)
)
@ -158,7 +158,7 @@ export const removeItemFromCache = (
) => {
try {
const mappedCache = cache as Map<string, unknown>
mappedCache.forEach((value: any, key) => {
mappedCache.forEach((value: unknown, key) => {
if (typeof value == 'object' && 'search' in value) {
const search = value.search as LibraryItems
const idx = search.edges.findIndex((edge) => edge.node.id == itemId)

View file

@ -11,8 +11,7 @@ export interface Integration {
updatedAt: Date
}
export type IntegrationType =
| 'READWISE'
export type IntegrationType = 'READWISE'
interface IntegrationsQueryResponse {
isValidating: boolean
@ -49,7 +48,7 @@ export function useGetIntegrationsQuery(): IntegrationsQueryResponse {
}
`
const { data, mutate, error, isValidating } = useSWR(query, publicGqlFetcher)
const { data, mutate, isValidating } = useSWR(query, publicGqlFetcher)
console.log('integrations data', data)
try {

View file

@ -34,7 +34,7 @@ export function useGetLabelsQuery(): LabelsQueryResponse {
${labelFragment}
`
const { data, mutate, error, isValidating } = useSWR(query, publicGqlFetcher)
const { data, mutate, isValidating } = useSWR(query, publicGqlFetcher)
try {
if (data) {

View file

@ -45,7 +45,7 @@ export function useGetNewsletterEmailsQuery(): NewsletterEmailsQueryResponse {
}
`
const { data, mutate, error, isValidating } = useSWR(query, publicGqlFetcher)
const { data, mutate, isValidating } = useSWR(query, publicGqlFetcher)
try {
if (data) {

View file

@ -63,24 +63,13 @@ export function useGetRulesQuery(): RulesQueryResponse {
}
`
const { data, mutate, error, isValidating } = useSWR(query, publicGqlFetcher)
const { data, mutate, isValidating } = useSWR(query, publicGqlFetcher)
try {
if (data) {
const result = data as RulesQueryResponseData
const rules = result.rules.rules as Rule[]
const actions: RuleAction[] = [
{
type: RuleActionType.SendNotification,
params: [] as string[],
},
{
type: RuleActionType.AddLabel,
params: ['2dfd9ce2-cc9f-11ec-b535-3be2782c2107'],
},
]
return {
isValidating,
rules: rules ?? [],

View file

@ -56,7 +56,7 @@ export function useGetSubscriptionsQuery(): SubscriptionsQueryResponse {
}
`
const { data, mutate, error, isValidating } = useSWR(query, publicGqlFetcher)
const { data, mutate, isValidating } = useSWR(query, publicGqlFetcher)
try {
if (data) {

View file

@ -58,7 +58,7 @@ export function useGetWebhooksQuery(): WebhooksQueryResponse {
}
`
const { data, mutate, error, isValidating } = useSWR(query, publicGqlFetcher)
const { data, mutate, isValidating } = useSWR(query, publicGqlFetcher)
console.log('webhooks data', data)
try {

View file

@ -1,98 +0,0 @@
import { GetServerSideProps } from 'next'
import { captureException, flush } from '@sentry/nextjs'
import { ArticleHighlights } from '../../../../components/templates/ArticleHighlights'
import { highlightsBaseURL, webBaseURL } from '../../../../lib/appConfig'
import objectToHash from '../../../../lib/highlights/objectToHash'
import {
PublicArticleAttributes,
publicArticleQuery,
} from '../../../../lib/networking/queries/useGetPublicArticleQuery'
import { useEffect } from 'react'
type PublicHighlightPageProps = {
publicArticle: PublicArticleAttributes
showAllHighlights: boolean
selectedHighlightId?: string
previewImagePath?: string
}
export default function PublicHighlightPage(
props: PublicHighlightPageProps
): JSX.Element {
useEffect(() => {
window.analytics?.track('public_highlight_read', {
link: props.publicArticle.id,
slug: props.publicArticle.slug,
url: props.publicArticle.url,
})
}, [props.publicArticle.url])
return (
<>
<ArticleHighlights
publicArticle={props.publicArticle}
showAllHighlights={false}
selectedHighlightId={props.selectedHighlightId}
previewImagePath={props.previewImagePath}
/>
</>
)
}
export const getServerSideProps: GetServerSideProps<
PublicHighlightPageProps
> = async (ctx) => {
const slug = ctx.query.slug as string
const username = ctx.query.username as string
const selectedHighlightId = ctx.query.highlightId as string
try {
const publicArticle = await publicArticleQuery(ctx, { username, slug })
if (publicArticle) {
const selectedHighlight = await publicArticle.highlights.find(
(h) => h.shortId === selectedHighlightId
)
if (selectedHighlight) {
const previewImageMeta = {
highlightsCount: publicArticle.highlights.length,
annotationsCount: publicArticle.highlights.filter(
(h) => !!h.annotation
).length,
quote: selectedHighlight.quote,
prefix: selectedHighlight.prefix,
suffix: selectedHighlight.suffix,
annotation: selectedHighlight.annotation,
}
const previewImageHash = objectToHash(previewImageMeta)
const previewServiceUrl = `${webBaseURL}/${username}/${slug}/highlights/${selectedHighlightId}/preview?pih=${previewImageHash}`
const previewImagePath = `${highlightsBaseURL}/preview?url=${encodeURIComponent(
previewServiceUrl
)}`
return {
props: {
username,
publicArticle,
selectedHighlightId,
previewImagePath,
showAllHighlights: false,
},
}
} else {
throw new Error(
'public article highlights query failed - no highlights'
)
}
} else {
throw new Error('public article highlights query failed - no article')
}
} catch (error) {
captureException(error)
// Flushing before returning is necessary if deploying to Vercel, see
// https://vercel.com/docs/platform/limits#streaming-responses
await flush(2000)
return { notFound: true }
}
}

View file

@ -1,169 +0,0 @@
import { GetServerSideProps } from 'next'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { useEffect, useMemo } from 'react'
import {
Box,
SpanBox,
} from '../../../../../components/elements/LayoutPrimitives'
import {
HighlightFooter,
PublicHighlightView,
} from '../../../../../components/patterns/HighlightView'
import objectToHash from '../../../../../lib/highlights/objectToHash'
import {
PublicArticleAttributes,
publicArticleQuery,
} from '../../../../../lib/networking/queries/useGetPublicArticleQuery'
import { captureException, flush } from '@sentry/nextjs'
type PublicHighlightPageProps = {
publicArticle: PublicArticleAttributes
showAllHighlights: boolean
selectedHighlightId?: string
previewImageFileName?: string
}
export default function PublicHighlightPage(
props: PublicHighlightPageProps
): JSX.Element {
const router = useRouter()
const selectedHighlight = useMemo(() => {
if (!props.selectedHighlightId) {
return null
}
return props.publicArticle.highlights.find(
(h) => h.shortId === props.selectedHighlightId
)
}, [props.selectedHighlightId, props.publicArticle])
const articleSite = useMemo(() => {
try {
const url = new URL(props.publicArticle.url)
return url.hostname
} catch (e) {
console.log('error ', e)
return ''
}
}, [props.publicArticle.url])
// Adjusting the aspect ratio accordingly to the query parameter
useEffect(() => {
if (router.isReady && router.query.adjustAspectRatio) {
const highlightContainer = document.getElementById(
'selected_highlight_wrapper'
)
const footer = document.getElementById('selected_highlight_footer')
if (!highlightContainer || !footer) return
const getTextY = (): number => footer.getBoundingClientRect().bottom
const getContainerY = (): number =>
highlightContainer.getBoundingClientRect().bottom
let widthPercent = 100
// We are gradually decreasing the width of the container until the text bottom spacing is reduced in the container
while (getContainerY() - getTextY() > 25) {
widthPercent = widthPercent - 2
highlightContainer.style.width = widthPercent + '%'
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (
<>
<Head>
{props.previewImageFileName && (
<meta
name="omnivore:preview_image_destination"
content={encodeURIComponent(props.previewImageFileName || '')}
/>
)}
{props.previewImageFileName && (
<meta
name="omnivore:preview_image_selector"
content={encodeURIComponent('#selected_highlight_wrapper')}
/>
)}
</Head>
{selectedHighlight && (
<Box
id="selected_highlight_wrapper"
css={{
aspectRatio: '1.91',
maxWidth: '100%',
padding: '20px',
}}
>
<PublicHighlightView
key={selectedHighlight.id}
highlight={selectedHighlight}
title={props.publicArticle.title}
author={props.publicArticle.author}
/>
<SpanBox id="selected_highlight_footer">
<HighlightFooter
title={props.publicArticle.title}
author={props.publicArticle.author}
site={articleSite}
fontSize="16px"
/>
</SpanBox>
</Box>
)}
</>
)
}
export const getServerSideProps: GetServerSideProps<
PublicHighlightPageProps
> = async (ctx) => {
const slug = ctx.query.slug as string
const username = ctx.query.username as string
const selectedHighlightId = ctx.query.highlightId as string
try {
const publicArticle = await publicArticleQuery(ctx, { username, slug })
if (publicArticle) {
const selectedHighlight = await publicArticle.highlights.find(
(h) => h.shortId === selectedHighlightId
)
if (!selectedHighlight) {
return { notFound: true }
}
const previewImageMeta = {
highlightsCount: publicArticle.highlights.length,
annotationsCount: publicArticle.highlights.filter((h) => !!h.annotation)
.length,
quote: selectedHighlight?.quote,
prefix: selectedHighlight?.prefix,
suffix: selectedHighlight?.suffix,
annotation: selectedHighlight?.annotation,
}
const previewImageHash = objectToHash(previewImageMeta)
const previewImageFileName = `${username}/${slug}/highlights/${selectedHighlightId}/preview_${previewImageHash}.png`
return {
props: {
username,
publicArticle,
selectedHighlightId,
previewImageFileName,
showAllHighlights: false,
},
}
} else {
throw new Error('public article query failed')
}
} catch (error) {
captureException(error)
// Flushing before returning is necessary if deploying to Vercel, see
// https://vercel.com/docs/platform/limits#streaming-responses
await flush(2000)
return { notFound: true }
}
}

View file

@ -1,69 +0,0 @@
import { GetServerSideProps } from 'next'
import { ArticleHighlights } from '../../../../components/templates/ArticleHighlights'
import {
PublicArticleAttributes,
publicArticleQuery,
} from '../../../../lib/networking/queries/useGetPublicArticleQuery'
import { captureException, flush } from '@sentry/nextjs'
import { useEffect } from 'react'
type PublicHighlightsPageProps = {
publicArticle: PublicArticleAttributes
}
export default function PublicHighlightsPage(
props: PublicHighlightsPageProps
): JSX.Element {
useEffect(() => {
window.analytics?.track('public_link_read', {
link: props.publicArticle.id,
slug: props.publicArticle.slug,
url: props.publicArticle.url
})
}, [props.publicArticle.url])
return (
<ArticleHighlights
showAllHighlights={true}
publicArticle={props.publicArticle}
/>
)
}
export const getServerSideProps: GetServerSideProps<
PublicHighlightsPageProps
> = async (ctx) => {
const slug = ctx.query.slug as string
const username = ctx.query.username as string
try {
const publicArticle = await publicArticleQuery(ctx, { username, slug })
if (publicArticle) {
if (publicArticle.highlights.length === 0) {
return {
redirect: {
destination: publicArticle.url,
permanent: false,
},
}
}
return {
props: {
username,
publicArticle,
},
}
} else {
throw new Error('article highlights request failed')
}
} catch (error) {
captureException(error)
// Flushing before returning is necessary if deploying to Vercel, see
// https://vercel.com/docs/platform/limits#streaming-responses
await flush(2000)
return { notFound: true }
}
}

View file

@ -9,7 +9,7 @@ import { useRouter } from 'next/router'
import { VStack } from './../../../components/elements/LayoutPrimitives'
import { ArticleContainer } from './../../../components/templates/article/ArticleContainer'
import { PdfArticleContainerProps } from './../../../components/templates/article/PdfArticleContainer'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
import {
articleKeyboardCommands,
@ -192,7 +192,7 @@ export default function Home(): JSX.Element {
})
router.push(`/home`)
}
}, [article])
}, [article, cache, mutate, router])
useRegisterActions(
[

View file

@ -1,9 +1,7 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable functional/no-class */
import NextDocument, { Html, Head, Main, NextScript } from 'next/document'
import { useCallback } from 'react'
import { getCssText, globalStyles } from '../components/tokens/stitches.config'
import { setupAnalytics } from '../lib/analytics'
export default class Document extends NextDocument {
render() {
@ -120,6 +118,3 @@ export default class Document extends NextDocument {
)
}
}
function useEffect(arg0: () => () => void, arg1: (() => void)[]) {
throw new Error('Function not implemented.')
}

View file

@ -7,8 +7,6 @@ import { useSWRConfig } from 'swr'
import { cacheArticle } from '../../../../lib/networking/queries/useGetArticleQuery'
import { PrimaryLayout } from '../../../../components/templates/PrimaryLayout'
import { applyStoredTheme } from '../../../../lib/themeUpdater'
import { StyledText } from '../../../../components/elements/StyledText'
import { theme } from '../../../../components/tokens/stitches.config'
export default function LinkRequestPage(): JSX.Element {
applyStoredTheme(false) // false to skip server sync
@ -32,7 +30,9 @@ export default function LinkRequestPage(): JSX.Element {
hideHeader={true}
pageTestId={router.pathname}
>
<Box css={{ bg: '$grayBase', height: '100vh', width: '100vw', px: '16px' }}>
<Box
css={{ bg: '$grayBase', height: '100vh', width: '100vw', px: '16px' }}
>
{requestID && username ? (
<PrimaryContent requestID={requestID} username={username} />
) : (
@ -44,9 +44,7 @@ export default function LinkRequestPage(): JSX.Element {
}
function Loader(): JSX.Element {
return (
<Box />
)
return <Box />
}
type PrimaryContentProps = {

View file

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react'
import { useEffect, useRef } from 'react'
import { useRouter } from 'next/router'
import { HStack } from '../../../components/elements/LayoutPrimitives'
@ -30,7 +30,14 @@ export default function ConfirmEmail(): JSX.Element {
>
<input type="hidden" name="token" value={router.query.token} />
</form>
<HStack css={{ bg: '$grayBg', padding: '24px', width: '100%', height: '100%'}}>
<HStack
css={{
bg: '$grayBg',
padding: '24px',
width: '100%',
height: '100%',
}}
>
<LoadingView />
</HStack>
</ProfileLayout>

View file

@ -1,15 +1,15 @@
import { PageMetaData } from '../../components/patterns/PageMetaData'
import { ProfileLayout } from '../../components/templates/ProfileLayout'
import { EmailForgotPassword } from '../../components/templates/auth/EmailForgotPassword'
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import toast, { Toaster } from 'react-hot-toast'
import { showSuccessToast } from '../../lib/toastHelpers'
import { Toaster } from 'react-hot-toast'
export default function ForgotPassword(): JSX.Element {
return (
<>
<PageMetaData title="Reset your password - Omnivore" path="/auth-forgot-password" />
<PageMetaData
title="Reset your password - Omnivore"
path="/auth-forgot-password"
/>
<Toaster
containerStyle={{
top: '5rem',

View file

@ -1,5 +1,5 @@
/* eslint-disable @next/next/no-img-element */
import { Box, HStack } from '../../components/elements/LayoutPrimitives'
import { Box } from '../../components/elements/LayoutPrimitives'
import { PrimaryLayout } from '../../components/templates/PrimaryLayout'
import Link from 'next/link'
@ -31,56 +31,80 @@ export default function Labels(): JSX.Element {
<hr />
<h2>Introduction</h2>
<p>
Labels allow you to group and search for content in Omnivore. A saved page
can have multiple labels and search results can be filtered by label.
Labels allow you to group and search for content in Omnivore. A saved
page can have multiple labels and search results can be filtered by
label.
</p>
<h2>Adding labels to a page on iOS</h2>
<p>
On iOS you add and remove labels from a page using the Assign Labels modal.
</p>
On iOS you add and remove labels from a page using the Assign Labels
modal.
</p>
<p>
You can open the Assign Labels modal from the home view or the reader view.
In the home view long press on an item and choose Edit Labels from the dropdowm
menu. In the reader view use the top right menu button.
You can open the Assign Labels modal from the home view or the reader
view. In the home view long press on an item and choose Edit Labels
from the dropdowm menu. In the reader view use the top right menu
button.
</p>
<h2>Adding labels to a page on the web</h2>
<p>
On the web you add and remove labels from a page using the Assign Labels dropdown
or modal depending on your screen size. For larger monitors you will see the Labels
button on the left hand side of the reader view. For smaller monitors you will see
the labels dropdown at the top of your screen.
On the web you add and remove labels from a page using the Assign
Labels dropdown or modal depending on your screen size. For larger
monitors you will see the Labels button on the left hand side of the
reader view. For smaller monitors you will see the labels dropdown at
the top of your screen.
</p>
<p>
You can also use keyboard commands to open the assign labels modal. On the reader
view tap the <code>l</code> key. Once open you can use the up/down arrow keys, or the
tab key to navigate the available labels, and the Enter key to toggle a label.
You can also use keyboard commands to open the assign labels modal. On
the reader view tap the <code>l</code> key. Once open you can use the
up/down arrow keys, or the tab key to navigate the available labels,
and the Enter key to toggle a label.
</p>
<h2>Searching by label on iOS</h2>
<p>
On iOS you can use the Labels search chip to search for labels. This will open a modal
allowing you to assign multiple labels to your search. This will become an <code>OR </code>
search, meaning if you add multiple labels to your search, pages that have any of the
labels will be returned.
On iOS you can use the Labels search chip to search for labels. This
will open a modal allowing you to assign multiple labels to your
search. This will become an <code>OR </code>
search, meaning if you add multiple labels to your search, pages that
have any of the labels will be returned.
</p>
<h2>Searching by label with Advanced Search</h2>
<p>
Omnivore&apos;s advanced search syntax supports searching for multiple labels using
<code>AND</code> and <code>OR</code> clauses. You can also negate a label search
to find all pages that do not have a certain label.
Omnivore&apos;s advanced search syntax supports searching for multiple
labels using
<code>AND</code> and <code>OR</code> clauses. You can also negate a
label search to find all pages that do not have a certain label.
</p>
<p>Some examples:</p>
<ul>
<li><code>-label:Newsletter</code> finds all pages that have the label <code>Newsletter</code></li>
<li><code>label:Cooking,Fitness</code> finds all your pages with either the <code>Cooking</code> or <code>Fitness</code> labels</li>
<li><code>label:Newsletter label:Surfing</code> finds all pages with both the <code>Newsletter</code> and <code>Surfing</code> labels</li>
<li><code>label:Coding -label:Newsletter</code> finds all pages with the <code>Coding</code> label that do not have the <code>Newsletter</code> label</li>
<li>
<code>-label:Newsletter</code> finds all pages that have the label{' '}
<code>Newsletter</code>
</li>
<li>
<code>label:Cooking,Fitness</code> finds all your pages with either
the <code>Cooking</code> or <code>Fitness</code> labels
</li>
<li>
<code>label:Newsletter label:Surfing</code> finds all pages with
both the <code>Newsletter</code> and <code>Surfing</code> labels
</li>
<li>
<code>label:Coding -label:Newsletter</code> finds all pages with the{' '}
<code>Coding</code> label that do not have the{' '}
<code>Newsletter</code> label
</li>
</ul>
<h2>Editing your list of labels</h2>
<p>
The <Link href="/settings/labels"><a>labels</a></Link> page allows you to
edit all of your labels. From here you can create new labels, delete existing
labels, or modify the color and description of a label.
The{' '}
<Link href="/settings/labels">
<a>labels</a>
</Link>{' '}
page allows you to edit all of your labels. From here you can create
new labels, delete existing labels, or modify the color and
description of a label.
</p>
</Box>
<Box css={{ height: '120px' }} />

View file

@ -1,10 +1,6 @@
/* eslint-disable @next/next/no-img-element */
import { Box, HStack, SpanBox } from '../../components/elements/LayoutPrimitives'
import { Box } from '../../components/elements/LayoutPrimitives'
import { PrimaryLayout } from '../../components/templates/PrimaryLayout'
import { Button } from '../../components/elements/Button'
import Link from 'next/link'
import { Copy, Plus } from 'phosphor-react'
import { theme } from '../../components/tokens/stitches.config'
export default function Search(): JSX.Element {
return (
@ -33,64 +29,107 @@ export default function Search(): JSX.Element {
<h1>Search</h1>
<hr />
<p>
Omnivore uses search to filter items in your library. You can use a simple
keyword search or our advanced search syntax to find items.
Omnivore uses search to filter items in your library. You can use a
simple keyword search or our advanced search syntax to find items.
</p>
<ul>
<li><a href="#text">Searching for text</a></li>
<li><a href="#label">Filtering by label</a></li>
<li><a href="#in">Filtering by archive status</a></li>
<li><a href="#is">Filtering by read state</a></li>
<li><a href="#type">Filtering by type</a></li>
<li><a href="#has">Finding highlights</a></li>
<li><a href="#dates">Filtering by save/publish dates</a></li>
<li><a href="#sort">Sorting</a></li>
<li>
<a href="#text">Searching for text</a>
</li>
<li>
<a href="#label">Filtering by label</a>
</li>
<li>
<a href="#in">Filtering by archive status</a>
</li>
<li>
<a href="#is">Filtering by read state</a>
</li>
<li>
<a href="#type">Filtering by type</a>
</li>
<li>
<a href="#has">Finding highlights</a>
</li>
<li>
<a href="#dates">Filtering by save/publish dates</a>
</li>
<li>
<a href="#sort">Sorting</a>
</li>
</ul>
<h2 id="text">Searching for text</h2>
<p>
Omnivore will perform full text search across library item&apos;s content, title, description,
and site by default. You can search for specific terms by quoting your terms. By default
all results that match your search will be returned in the order they were saved. To change
your search to relevance use the <code>sort:score</code> parameter.
Omnivore will perform full text search across library item&apos;s
content, title, description, and site by default. You can search for
specific terms by quoting your terms. By default all results that
match your search will be returned in the order they were saved. To
change your search to relevance use the <code>sort:score</code>{' '}
parameter.
</p>
<h2 id="label">Filtering by label</h2>
<p>
You can filter your search based on labels using AND and OR clauses.
You can also negate a label search to find pages that do not have a certain label.
You can also negate a label search to find pages that do not have a
certain label.
</p>
<p>Some examples:</p>
<ul>
<li>label:Newsletter finds all pages that have the label Newsletter</li>
<li>label:Cooking,Fitness finds all your pages with either the Cooking or Fitness labels</li>
<li>label:Newsletter label:Surfing finds all pages with both the Newsletter and Surfing labels</li>
<li>label:Coding -label:News finds all pages with the Coding label that do not have the News label</li>
<li>
label:Newsletter finds all pages that have the label Newsletter
</li>
<li>
label:Cooking,Fitness finds all your pages with either the Cooking
or Fitness labels
</li>
<li>
label:Newsletter label:Surfing finds all pages with both the
Newsletter and Surfing labels
</li>
<li>
label:Coding -label:News finds all pages with the Coding label that
do not have the News label
</li>
</ul>
<h2 id="in">Filtering by archive status</h2>
<p>
The <code>in:</code> filter is used to filter search by archive status.
The options are:
The <code>in:</code> filter is used to filter search by archive
status. The options are:
</p>
<ul>
<li><code>in:inbox</code> (the default): show unarchived items</li>
<li><code>in:archive</code>: show archived items</li>
<li><code>in:all</code>: Show all items regardless of archive state</li>
<li>
<code>in:inbox</code> (the default): show unarchived items
</li>
<li>
<code>in:archive</code>: show archived items
</li>
<li>
<code>in:all</code>: Show all items regardless of archive state
</li>
</ul>
<h2 id="is">Filtering by read state</h2>
<p>
The <code>is:</code> filter is used to filter search by read state. Note
that in Omnivore &apos;read&apos; means fully read, not just opened.
The <code>is:</code> filter is used to filter search by read state.
Note that in Omnivore &apos;read&apos; means fully read, not just
opened.
</p>
<p>
The <code>is:</code> filter options are:{' '}
</p>
<p>The <code>is:</code> filter options are: </p>
<ul>
<li><code>is:read</code>: Show only items that are fully read</li>
<li><code>is:unread</code> (the default): show unread items</li>
<li>
<code>is:read</code>: Show only items that are fully read
</li>
<li>
<code>is:unread</code> (the default): show unread items
</li>
</ul>
<h2 id="type">Filtering by type</h2>
@ -98,41 +137,67 @@ export default function Search(): JSX.Element {
The <code>type:</code> filter is used to filter search by type.
</p>
<ul>
<li><code>type:article</code>: Show only articles</li>
<li><code>type:file</code>: Show only files</li>
<li><code>type:pdf</code>: Show only PDFs</li>
<li><code>type:highlights</code>: Show your highlights</li>
<li>
<code>type:article</code>: Show only articles
</li>
<li>
<code>type:file</code>: Show only files
</li>
<li>
<code>type:pdf</code>: Show only PDFs
</li>
<li>
<code>type:highlights</code>: Show your highlights
</li>
</ul>
<h2 id="has">Finding highlights</h2>
<p>
You can find your highlights by using the <code>type:highlights</code> filter
or find saved items with highlights using the <code>has:highlights</code> filter.
You can find your highlights by using the <code>type:highlights</code>{' '}
filter or find saved items with highlights using the{' '}
<code>has:highlights</code> filter.
</p>
<h2 id="dates">Filtering by save/publish dates</h2>
<p>
You can filter your searches based on the time they were saved or published using the
<code>saved:</code> and <code>published:</code> filters. These filters take two dates
to create a date range. The <code>*</code> wildcard will accept any date.
You can filter your searches based on the time they were saved or
published using the
<code>saved:</code> and <code>published:</code> filters. These filters
take two dates to create a date range. The <code>*</code> wildcard
will accept any date.
</p>
<p>For Example:</p>
<ul>
<li><code>saved:2022-04-21..*</code> All items saved since 2022-04-21</li>
<li><code>published:2020-01-01..2022-02-02</code> All items published between 2020-01-01 and 2022-02-02</li>
<li><code>published:*..2020-01-01</code> All items published before 2020-01-01</li>
<li>
<code>saved:2022-04-21..*</code> All items saved since 2022-04-21
</li>
<li>
<code>published:2020-01-01..2022-02-02</code> All items published
between 2020-01-01 and 2022-02-02
</li>
<li>
<code>published:*..2020-01-01</code> All items published before
2020-01-01
</li>
</ul>
<h2 id="sort">Sorting</h2>
<p>
By default all search results in Omnivore are sorted by saved date. This puts the
most recently saved items at the top of your library. You can use sort options
to change the library order:
By default all search results in Omnivore are sorted by saved date.
This puts the most recently saved items at the top of your library.
You can use sort options to change the library order:
</p>
<ul>
<li><code>sort:saved</code>: Sort by saved date</li>
<li><code>sort:updated</code>: Sort by time the item was updated, for example having a label or highlight added.</li>
<li><code>sort:score</code>: Sort by query term relevance.</li>
<li>
<code>sort:saved</code>: Sort by saved date
</li>
<li>
<code>sort:updated</code>: Sort by time the item was updated, for
example having a label or highlight added.
</li>
<li>
<code>sort:score</code>: Sort by query term relevance.
</li>
</ul>
</Box>
<Box css={{ height: '120px' }} />

View file

@ -1,6 +1,4 @@
import { useEffect, useMemo, useState } from 'react'
import { useRouter } from 'next/router'
import { useMemo, useState } from 'react'
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
import { applyStoredTheme } from '../../lib/themeUpdater'
import { useGetApiKeysQuery } from '../../lib/networking/queries/useGetApiKeysQuery'

View file

@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react'
import { useState } from 'react'
import { useRouter } from 'next/router'
import { Toaster } from 'react-hot-toast'
@ -24,13 +24,13 @@ export default function DeleteMyAccount(): JSX.Element {
async function deleteAccount(): Promise<void> {
const viewerId = viewer.viewerData?.me?.id
if (!viewerId) {
showErrorToast("Error deleting user, no user id found.")
showErrorToast('Error deleting user, no user id found.')
return
}
const result = await deleteAccountMutation(viewerId)
if (result) {
showSuccessToast('Account deleted',)
showSuccessToast('Account deleted')
setTimeout(() => {
window.location.href = '/login'
}, 2000)
@ -55,21 +55,22 @@ export default function DeleteMyAccount(): JSX.Element {
{showConfirm ? (
<ConfirmationModal
message={'Are you sure you want to delete your account? This can not be reversed and all your data (saved articles, highlights, and notes) will be deleted.'}
message={
'Are you sure you want to delete your account? This can not be reversed and all your data (saved articles, highlights, and notes) will be deleted.'
}
onAccept={deleteAccount}
onOpenChange={() => setShowConfirm(false)}
/>
) : null}
<HStack css={{ padding: '24px', width: '100%', height: '100%'}}>
<HStack css={{ padding: '24px', width: '100%', height: '100%' }}>
{viewer && router ? (
<Button
style="ctaDarkYellow"
onClick={() => setShowConfirm(true)}
>
<Button style="ctaDarkYellow" onClick={() => setShowConfirm(true)}>
Delete my Account
</Button>
) : <Loader />}
) : (
<Loader />
)}
</HStack>
</PrimaryLayout>
)

View file

@ -49,7 +49,7 @@ const CopyTextBtnWrapper = styled(Box, {
})
function CopyTextButton(props: CopyTextButtonProps): JSX.Element {
const { copyLink, isLinkCopied } = useCopyLink(
const { copyLink } = useCopyLink(
props.text,
'newsletter_' +
(props.type == TextType.EmailAddress
@ -64,7 +64,7 @@ function CopyTextButton(props: CopyTextButtonProps): JSX.Element {
? 'Email Address Copied'
: 'Confirmation Code Copied'
)
}, [])
}, [copyLink, props.type])
return (
<Button style="plainIcon" onClick={copy}>

View file

@ -1,5 +1,4 @@
import React, { useEffect } from 'react'
import Router from 'next/router'
import React from 'react'
import ExtensionInstallHelp from '../../../components/elements/ExtensionsInstallHelp'
import { SettingsLayout } from '../../../components/templates/SettingsLayout'
import { VStack } from '../../../components/elements/LayoutPrimitives'

View file

@ -1,8 +1,6 @@
import React, { useEffect } from 'react'
import Router from 'next/router'
import React from 'react'
import { SettingsLayout } from '../../../components/templates/SettingsLayout'
import MobileInstallHelp from '../../../components/elements/MobileInstallHelp'
import { VStack } from '../../../components/elements/LayoutPrimitives'
export default function Mobile(): JSX.Element {
return (

View file

@ -122,7 +122,7 @@ export default function Integrations(): JSX.Element {
},
},
])
}, [])
}, [deleteIntegration, readwiseConnected, router, webhooks])
return (
<PrimaryLayout pageTestId={'integrations'}>

View file

@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from 'react'
import { PrimaryLayout } from '../../components/templates/PrimaryLayout'
import { Button } from '../../components/elements/Button'
import { styled, theme } from '../../components/tokens/stitches.config'
import { styled } from '../../components/tokens/stitches.config'
import {
Box,
SpanBox,

View file

@ -1,7 +1,7 @@
import { useCallback, useState } from 'react'
import { applyStoredTheme } from '../../lib/themeUpdater'
import { Box, VStack } from '../../components/elements/LayoutPrimitives'
import { VStack } from '../../components/elements/LayoutPrimitives'
import { StyledText } from '../../components/elements/StyledText'
import { ProfileLayout } from '../../components/templates/ProfileLayout'

View file

@ -69,6 +69,12 @@ export default function ImportUploader(): JSX.Element {
'content-length': `${file.size}`,
},
})
if (!uploadRes) {
setErrorMessage(
'Unable to create file upload. Please ensure you are logged in.'
)
setUploadState('none')
}
setUploadState('completed')
} else {
setErrorMessage(

View file

@ -1,44 +1,6 @@
import { VStack, Box } from '.././../components/elements/LayoutPrimitives'
import {
LandingSectionsContainer,
GetStartedButton,
} from '../../components/templates/landing/LandingSectionsContainer'
import { LandingHeader } from '../../components/templates/landing/LandingHeader'
import { LandingFooter } from '../../components/templates/landing/LandingFooter'
import { PageMetaData } from '../../components/patterns/PageMetaData'
import { About } from '../../components/templates/About'
const containerStyles = {
alignSelf: 'center',
marginTop: 80,
maxWidth: 960,
px: '2vw',
'@md': {
px: '6vw',
},
'@xl': {
px: '120px',
},
}
const headingStyles = {
fontWeight: '700',
color: '#3D3D3D',
fontSize: 45,
lineHeight: '53px',
padding: '10px',
paddingBottom: '0px',
textAlign: 'center',
}
const subHeadingStyles = {
color: 'rgb(125, 125, 125)',
padding: '10px',
textAlign: 'center',
width: '100%',
fontWeight: '600',
}
export default function LandingPage(): JSX.Element {
return (
<>