mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
More warnings clean up
This commit is contained in:
parent
13c01b28f9
commit
9d6a28cc74
54 changed files with 322 additions and 1328 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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 />,
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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[]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { gqlFetcher } from '../networkHelpers'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export enum UploadImportFileType {
|
||||
URL_LIST = 'URL_LIST',
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 ?? [],
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -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.')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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'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'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' }} />
|
||||
|
|
|
|||
|
|
@ -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'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'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 'read' means fully read, not just opened.
|
||||
The <code>is:</code> filter is used to filter search by read state.
|
||||
Note that in Omnivore 'read' 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' }} />
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ export default function Integrations(): JSX.Element {
|
|||
},
|
||||
},
|
||||
])
|
||||
}, [])
|
||||
}, [deleteIntegration, readwiseConnected, router, webhooks])
|
||||
|
||||
return (
|
||||
<PrimaryLayout pageTestId={'integrations'}>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
|||
Loading…
Reference in a new issue