mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #2617 from Podginator/feat/add-links-in-search
[Feat] Add Links to Search, and show "PROCESSING" Links in the Card/List Views.
This commit is contained in:
commit
755b5fe8c5
9 changed files with 287 additions and 57 deletions
77
packages/web/components/elements/LoadingBar.tsx
Normal file
77
packages/web/components/elements/LoadingBar.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { Box } from './../elements/LayoutPrimitives'
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from "react"
|
||||
|
||||
type LoadingBarProps = {
|
||||
fillColor: string
|
||||
backgroundColor: string
|
||||
borderRadius: string
|
||||
percentFill?: number
|
||||
}
|
||||
|
||||
type AnimationStatus = {
|
||||
position: number,
|
||||
transition: string
|
||||
}
|
||||
|
||||
export function LoadingBar(props: LoadingBarProps): JSX.Element {
|
||||
const [leftOne, setLeftOne] = useState({ position: 0, transition: 'left 0.5s linear' })
|
||||
const [leftTwo, setLeftTwo] = useState({ position: -100, transition: 'left 0.5s linear' })
|
||||
|
||||
const calculateNewValue = (currVal: AnimationStatus, setNextVal: Dispatch<SetStateAction<AnimationStatus>>) => {
|
||||
const position = currVal.position >= 100 ? -100 : currVal.position + 25;
|
||||
const transition = currVal.position >= 100 ? 'left 0s linear' : 'left 0.5s linear';
|
||||
setNextVal({ position, transition })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setTimeout(() => {
|
||||
calculateNewValue(leftOne, setLeftOne)
|
||||
}, 500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(interval)
|
||||
}
|
||||
}, [leftOne])
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setTimeout(() => {
|
||||
calculateNewValue(leftTwo, setLeftTwo)
|
||||
}, 500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(interval)
|
||||
}
|
||||
}, [leftTwo])
|
||||
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
height: '5px',
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: props.backgroundColor,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
css={{
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
width: `${props.percentFill ?? 10}%`,
|
||||
left: `${leftOne.position}%`,
|
||||
transition: leftOne.transition,
|
||||
backgroundColor: props.fillColor,
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
css={{
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
width: `${props.percentFill ?? 10}%`,
|
||||
left: `${leftTwo.position}%`,
|
||||
transition: leftTwo.transition,
|
||||
backgroundColor: props.fillColor,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -30,4 +30,5 @@ export type LinkedItemCardProps = {
|
|||
multiSelectMode: MultiSelectMode
|
||||
|
||||
isHovered?: boolean
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { CoverImage } from '../../elements/CoverImage'
|
|||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { useCallback, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import {
|
||||
AuthorInfoStyle,
|
||||
CardCheckbox,
|
||||
|
|
@ -28,7 +27,7 @@ import {
|
|||
import { CardMenu } from '../CardMenu'
|
||||
import { DotsThree } from 'phosphor-react'
|
||||
import { isTouchScreenDevice } from '../../../lib/deviceType'
|
||||
import { ProgressBarOverlay } from './LibraryListCard'
|
||||
import { LoadingBarOverlay, ProgressBarOverlay } from "./LibraryListCard"
|
||||
import { FallbackImage } from './FallbackImage'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
|
|
@ -88,7 +87,6 @@ export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
|
|||
setIsHovered(false)
|
||||
}}
|
||||
onClick={(event) => {
|
||||
console.log('click event: ', event)
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(
|
||||
`/${props.viewer.profile.username}/${props.item.slug}`,
|
||||
|
|
@ -133,6 +131,7 @@ type GridImageProps = {
|
|||
src?: string
|
||||
title?: string
|
||||
readingProgress?: number
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
const GridImage = (props: GridImageProps): JSX.Element => {
|
||||
|
|
@ -140,7 +139,17 @@ const GridImage = (props: GridImageProps): JSX.Element => {
|
|||
|
||||
return (
|
||||
<>
|
||||
{(props.readingProgress ?? 0) > 0 && (
|
||||
{
|
||||
props.isLoading && (
|
||||
<LoadingBarOverlay
|
||||
width="100%"
|
||||
top={95}
|
||||
bottomRadius={'0px'}
|
||||
fillColor={"rgba(60, 179, 113, 1)"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{(props.readingProgress ?? 0) > 0 && !props.isLoading && (
|
||||
<ProgressBarOverlay
|
||||
width="100%"
|
||||
top={95}
|
||||
|
|
@ -189,6 +198,7 @@ const LibraryGridCardContent = (props: LinkedItemCardProps): JSX.Element => {
|
|||
src={props.item.image}
|
||||
title={props.item.title}
|
||||
readingProgress={item.readingProgressPercent}
|
||||
isLoading={props.isLoading}
|
||||
/>
|
||||
<SpanBox
|
||||
css={{
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { ProgressBar } from '../../elements/ProgressBar'
|
|||
import { theme } from '../../tokens/stitches.config'
|
||||
import { FallbackImage } from './FallbackImage'
|
||||
import { useRouter } from 'next/router'
|
||||
import { LoadingBar } from "../../elements/LoadingBar"
|
||||
|
||||
export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
|
||||
const router = useRouter()
|
||||
|
|
@ -128,11 +129,45 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
|
|||
)
|
||||
}
|
||||
|
||||
type LoadingBarOverlayProps = {
|
||||
top: number
|
||||
width: string
|
||||
bottomRadius: string,
|
||||
fillColor?: string,
|
||||
percentFill?: number
|
||||
}
|
||||
|
||||
|
||||
type ProgressBarOverlayProps = {
|
||||
top: number
|
||||
width: string
|
||||
value: number
|
||||
bottomRadius: string
|
||||
bottomRadius: string,
|
||||
}
|
||||
|
||||
export const LoadingBarOverlay = (
|
||||
props: LoadingBarOverlayProps
|
||||
): JSX.Element => {
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
position: 'absolute',
|
||||
width: props.width,
|
||||
top: props.top,
|
||||
borderBottomLeftRadius: props.bottomRadius,
|
||||
borderBottomRightRadius: props.bottomRadius,
|
||||
overflow: 'clip',
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
<LoadingBar
|
||||
fillColor={props.fillColor ?? theme.colors.thProgressFg.toString()}
|
||||
backgroundColor="rgba(217, 217, 217, 0.65)"
|
||||
borderRadius={'2px'}
|
||||
percentFill={props.percentFill}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export const ProgressBarOverlay = (
|
||||
|
|
@ -164,14 +199,25 @@ type ListImageProps = {
|
|||
src?: string
|
||||
title?: string
|
||||
readingProgress?: number
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
const ListImage = (props: ListImageProps): JSX.Element => {
|
||||
const [displayFallback, setDisplayFallback] = useState(props.src == undefined)
|
||||
|
||||
return (
|
||||
<>
|
||||
{(props.readingProgress ?? 0) > 0 && (
|
||||
<>{
|
||||
props.isLoading && (
|
||||
<LoadingBarOverlay
|
||||
width="55px"
|
||||
top={50}
|
||||
bottomRadius="4px"
|
||||
fillColor={"rgba(60, 179, 113, 1)"}
|
||||
percentFill={30}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{(props.readingProgress ?? 0) > 0 && !props.isLoading && (
|
||||
<ProgressBarOverlay
|
||||
width="55px"
|
||||
top={50}
|
||||
|
|
@ -241,6 +287,7 @@ export function LibraryListCardContent(
|
|||
src={props.item.image}
|
||||
title={props.item.title}
|
||||
readingProgress={item.readingProgressPercent}
|
||||
isLoading={props.isLoading}
|
||||
/>
|
||||
</Box>
|
||||
<VStack
|
||||
|
|
|
|||
|
|
@ -16,42 +16,12 @@ import {
|
|||
|
||||
type AddLinkModalProps = {
|
||||
onOpenChange: (open: boolean) => void
|
||||
handleLinkSubmission: (link: string, timezone: string, locale:string) => Promise<void>,
|
||||
}
|
||||
|
||||
export function AddLinkModal(props: AddLinkModalProps): JSX.Element {
|
||||
const [link, setLink] = useState('')
|
||||
|
||||
const handleLinkSubmission = useCallback(
|
||||
async (link: string, timezone: string, locale: string) => {
|
||||
const result = await saveUrlMutation(link, timezone, locale)
|
||||
if (result) {
|
||||
toast(
|
||||
() => (
|
||||
<Box>
|
||||
Link Saved
|
||||
<span style={{ padding: '16px' }} />
|
||||
<Button
|
||||
style="ctaDarkYellow"
|
||||
autoFocus
|
||||
onClick={() => {
|
||||
window.location.href = `/article?url=${encodeURIComponent(
|
||||
link
|
||||
)}`
|
||||
}}
|
||||
>
|
||||
Read Now
|
||||
</Button>
|
||||
</Box>
|
||||
),
|
||||
{ position: 'bottom-right' }
|
||||
)
|
||||
} else {
|
||||
showErrorToast('Error saving link', { position: 'bottom-right' })
|
||||
}
|
||||
},
|
||||
[link]
|
||||
)
|
||||
|
||||
const validateLink = useCallback(
|
||||
(link: string) => {
|
||||
try {
|
||||
|
|
@ -81,7 +51,7 @@ export function AddLinkModal(props: AddLinkModalProps): JSX.Element {
|
|||
<ModalTitleBar title="Add Link" onOpenChange={props.onOpenChange} />
|
||||
<Box css={{ width: '100%', py: '16px' }}>
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
onSubmit={async (event) => {
|
||||
event.preventDefault()
|
||||
|
||||
let submitLink = link
|
||||
|
|
@ -96,7 +66,7 @@ export function AddLinkModal(props: AddLinkModalProps): JSX.Element {
|
|||
setLink(newLink)
|
||||
submitLink = newLink
|
||||
}
|
||||
handleLinkSubmission(submitLink, timeZone, locale)
|
||||
await props.handleLinkSubmission(submitLink, timeZone, locale)
|
||||
props.onOpenChange(false)
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Action, createAction, useKBar, useRegisterActions } from 'kbar'
|
|||
import debounce from 'lodash/debounce'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
import toast, { Toaster } from "react-hot-toast"
|
||||
import TopBarProgress from 'react-topbar-progress-indicator'
|
||||
import { useFetchMore } from '../../../lib/hooks/useFetchMoreScroll'
|
||||
import { usePersistedState } from '../../../lib/hooks/usePersistedState'
|
||||
|
|
@ -48,6 +48,8 @@ import {
|
|||
} from '../../../lib/toastHelpers'
|
||||
import { SetPageLabelsModalPresenter } from '../article/SetLabelsModalPresenter'
|
||||
import { NotebookPresenter } from '../article/NotebookPresenter'
|
||||
import { saveUrlMutation } from "../../../lib/networking/mutations/saveUrlMutation"
|
||||
import { articleQuery } from "../../../lib/networking/queries/useGetArticleQuery"
|
||||
|
||||
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
|
||||
export type LibraryMode = 'reads' | 'highlights'
|
||||
|
|
@ -65,6 +67,11 @@ const debouncedFetchSearchResults = debounce((query, cb) => {
|
|||
fetchSearchResults(query, cb)
|
||||
}, 300)
|
||||
|
||||
// We set a relatively high delay for the refresh at the end, as it's likely there's an issue
|
||||
// in processing. We give it the best attempt to be able to resolve, but if it doesn't we set
|
||||
// the state as Failed. On refresh it will try again if the backend sends "PROCESSING"
|
||||
const TIMEOUT_DELAYS = [1000, 2000, 2500, 3500, 5000, 10000, 60000];
|
||||
|
||||
export function HomeFeedContainer(): JSX.Element {
|
||||
const { viewerData } = useGetViewerQuery()
|
||||
const router = useRouter()
|
||||
|
|
@ -144,10 +151,11 @@ export function HomeFeedContainer(): JSX.Element {
|
|||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
const q = router.query['q']
|
||||
let qs = ''
|
||||
let qs = 'in:inbox' // Default to in:inbox search term.
|
||||
if (q && typeof q === 'string') {
|
||||
qs = q
|
||||
}
|
||||
|
||||
if (qs !== (queryInputs.searchQuery || '')) {
|
||||
setQueryInputs({ ...queryInputs, searchQuery: qs })
|
||||
performActionOnItem('refresh', undefined as unknown as any)
|
||||
|
|
@ -171,14 +179,61 @@ export function HomeFeedContainer(): JSX.Element {
|
|||
return itemsPages[itemsPages.length - 1].search.pageInfo.hasNextPage
|
||||
}, [itemsPages])
|
||||
|
||||
|
||||
const libraryItems = useMemo(() => {
|
||||
const items =
|
||||
itemsPages?.flatMap((ad) => {
|
||||
return ad.search.edges
|
||||
return ad.search.edges.map(it => ({ ...it, isLoading: it.node.state === 'PROCESSING'}));
|
||||
}) || []
|
||||
return items
|
||||
}, [itemsPages, performActionOnItem])
|
||||
|
||||
useEffect(() => {
|
||||
const timeout : NodeJS.Timeout[] = []
|
||||
|
||||
const items =
|
||||
(itemsPages?.flatMap((ad) => {
|
||||
return ad.search.edges.map(it => ({ ...it, isLoading: it.node.state === 'PROCESSING'}));
|
||||
}) || [])
|
||||
.filter(it => it.isLoading);
|
||||
|
||||
items.map(async (item) => {
|
||||
let startIdx = 0;
|
||||
|
||||
const seeIfUpdated = async () => {
|
||||
if (startIdx > TIMEOUT_DELAYS.length) {
|
||||
item.node.state = State.FAILED;
|
||||
return
|
||||
}
|
||||
|
||||
const username = viewerData?.me?.profile.username
|
||||
const itemsToUpdate = libraryItems.filter(it => it.isLoading);
|
||||
|
||||
if (itemsToUpdate.length > 0) {
|
||||
const link = await articleQuery({ username, slug: item.node.slug, includeFriendsHighlights: false })
|
||||
|
||||
if (link && link.state != "PROCESSING") {
|
||||
const updatedArticle = { ...item };
|
||||
updatedArticle.node = { ...item.node, ...link }
|
||||
updatedArticle.isLoading = false;
|
||||
console.log(`Updating Metadata of ${item.node.slug}.`)
|
||||
performActionOnItem('update-item', updatedArticle);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Trying to get the metadata of item ${item.node.slug}... Retry ${startIdx} of 5`);
|
||||
timeout.push(setTimeout(seeIfUpdated, TIMEOUT_DELAYS[startIdx++]))
|
||||
}
|
||||
}
|
||||
|
||||
await seeIfUpdated();
|
||||
});
|
||||
|
||||
return () => {
|
||||
timeout.forEach(clearTimeout);
|
||||
}
|
||||
}, [itemsPages])
|
||||
|
||||
const handleFetchMore = useCallback(() => {
|
||||
if (isValidating || !hasMore) {
|
||||
return
|
||||
|
|
@ -714,6 +769,37 @@ export function HomeFeedContainer(): JSX.Element {
|
|||
[itemsPages, multiSelectMode, checkedItems]
|
||||
)
|
||||
|
||||
const handleLinkSubmission =
|
||||
async (link: string, timezone: string, locale: string) => {
|
||||
const result = await saveUrlMutation(link, timezone, locale)
|
||||
if (result) {
|
||||
toast(
|
||||
() => (
|
||||
<Box>
|
||||
Link Saved
|
||||
<span style={{ padding: '16px' }} />
|
||||
<Button
|
||||
style="ctaDarkYellow"
|
||||
autoFocus
|
||||
onClick={() => {
|
||||
window.location.href = `/article?url=${encodeURIComponent(
|
||||
link
|
||||
)}`
|
||||
}}
|
||||
>
|
||||
Read Now
|
||||
</Button>
|
||||
</Box>
|
||||
),
|
||||
{ position: 'bottom-right' }
|
||||
)
|
||||
const id = result.url?.match(/[^/]+$/)?.[0] ?? "";
|
||||
performActionOnItem('refresh', undefined as unknown as any)
|
||||
} else {
|
||||
showErrorToast('Error saving link', { position: 'bottom-right' })
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<HomeFeedGrid
|
||||
items={libraryItems}
|
||||
|
|
@ -728,6 +814,7 @@ export function HomeFeedContainer(): JSX.Element {
|
|||
gridContainerRef={gridContainerRef}
|
||||
mode={mode}
|
||||
setMode={setMode}
|
||||
handleLinkSubmission={handleLinkSubmission}
|
||||
applySearchQuery={(searchQuery: string) => {
|
||||
setQueryInputs({
|
||||
...queryInputs,
|
||||
|
|
@ -815,6 +902,8 @@ type HomeFeedContentProps = {
|
|||
item: LibraryItem | undefined
|
||||
) => Promise<void>
|
||||
|
||||
handleLinkSubmission: (link: string, timezone: string, locale:string) => Promise<void>,
|
||||
|
||||
setIsChecked: (itemId: string, set: boolean) => void
|
||||
itemIsChecked: (itemId: string) => boolean
|
||||
|
||||
|
|
@ -857,6 +946,7 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
|
|||
applySearchQuery={(searchQuery: string) => {
|
||||
props.applySearchQuery(searchQuery)
|
||||
}}
|
||||
handleLinkSubmission={props.handleLinkSubmission}
|
||||
allowSelectMultiple={props.mode !== 'highlights'}
|
||||
alwaysShowHeader={props.mode == 'highlights'}
|
||||
showFilterMenu={showFilterMenu}
|
||||
|
|
@ -896,7 +986,7 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
|
|||
)}
|
||||
|
||||
{props.showAddLinkModal && (
|
||||
<AddLinkModal onOpenChange={() => props.setShowAddLinkModal(false)} />
|
||||
<AddLinkModal handleLinkSubmission={props.handleLinkSubmission} onOpenChange={() => props.setShowAddLinkModal(false)} />
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
|
|
@ -1145,6 +1235,7 @@ function LibraryItems(props: LibraryItemsProps): JSX.Element {
|
|||
<LinkedItemCard
|
||||
layout={props.layout}
|
||||
item={linkedItem.node}
|
||||
isLoading={linkedItem.isLoading}
|
||||
viewer={props.viewer}
|
||||
isChecked={props.isChecked(linkedItem.node.id)}
|
||||
setIsChecked={props.setIsChecked}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { theme } from '../../tokens/stitches.config'
|
||||
import { FormInput } from '../../elements/FormElements'
|
||||
import { searchBarCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
|
||||
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
|
||||
import { locale, timeZone } from '../../../lib/dateFormatting'
|
||||
import { Button, IconButton } from '../../elements/Button'
|
||||
import {
|
||||
CaretDown,
|
||||
FunnelSimple,
|
||||
MagnifyingGlass,
|
||||
Prohibit,
|
||||
Plus,
|
||||
X,
|
||||
} from 'phosphor-react'
|
||||
import { LayoutType } from './HomeFeedContainer'
|
||||
|
|
@ -52,6 +53,8 @@ type LibraryHeaderProps = {
|
|||
setMultiSelectMode: (mode: MultiSelectMode) => void
|
||||
|
||||
performMultiSelectAction: (action: BulkAction, labelIds?: string[]) => void
|
||||
|
||||
handleLinkSubmission: (link: string, timezone: string, locale:string) => Promise<void>,
|
||||
}
|
||||
|
||||
export function LibraryHeader(props: LibraryHeaderProps): JSX.Element {
|
||||
|
|
@ -110,6 +113,7 @@ function LargeHeaderLayout(props: LibraryHeaderProps): JSX.Element {
|
|||
searchTerm={props.searchTerm}
|
||||
applySearchQuery={props.applySearchQuery}
|
||||
allowSelectMultiple={props.allowSelectMultiple}
|
||||
handleLinkSubmission={props.handleLinkSubmission}
|
||||
/>
|
||||
</HStack>
|
||||
)
|
||||
|
|
@ -148,6 +152,7 @@ function SmallHeaderLayout(props: LibraryHeaderProps): JSX.Element {
|
|||
<>
|
||||
{props.multiSelectMode === 'off' && <MenuHeaderButton {...props} />}
|
||||
<ControlButtonBox
|
||||
handleLinkSubmission={props.handleLinkSubmission}
|
||||
layout={props.layout}
|
||||
updateLayout={props.updateLayout}
|
||||
setShowInlineSearch={setShowInlineSearch}
|
||||
|
|
@ -215,17 +220,24 @@ export type SearchBoxProps = {
|
|||
|
||||
compact?: boolean
|
||||
onClose?: () => void
|
||||
handleLinkSubmission: (link: string, timezone: string, locale:string) => Promise<void>,
|
||||
}
|
||||
|
||||
export function SearchBox(props: SearchBoxProps): JSX.Element {
|
||||
const inputRef = useRef<HTMLInputElement | null>(null)
|
||||
const [focused, setFocused] = useState(false)
|
||||
const [searchTerm, setSearchTerm] = useState(props.searchTerm ?? '')
|
||||
const [isAddAction, setIsAddAction] = useState(false);
|
||||
const IS_URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
|
||||
|
||||
useEffect(() => {
|
||||
setSearchTerm(props.searchTerm ?? '')
|
||||
}, [props.searchTerm])
|
||||
|
||||
useEffect(() => {
|
||||
setIsAddAction(IS_URL_REGEX.test(searchTerm))
|
||||
}, [searchTerm, props.searchTerm])
|
||||
|
||||
useKeyboardShortcuts(
|
||||
searchBarCommands((action) => {
|
||||
if (action === 'focusSearchBar' && inputRef.current) {
|
||||
|
|
@ -272,15 +284,34 @@ export function SearchBox(props: SearchBoxProps): JSX.Element {
|
|||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
<MagnifyingGlass
|
||||
size={props.compact ? 15 : 20}
|
||||
color={theme.colors.graySolid.toString()}
|
||||
/>
|
||||
{
|
||||
(() => {
|
||||
if (isAddAction) {
|
||||
return <Plus
|
||||
size={props.compact ? 15 : 20}
|
||||
color={theme.colors.graySolid.toString()}
|
||||
/>
|
||||
}
|
||||
|
||||
return <MagnifyingGlass
|
||||
size={props.compact ? 15 : 20}
|
||||
color={theme.colors.graySolid.toString()}
|
||||
/>
|
||||
})()
|
||||
}
|
||||
|
||||
</HStack>
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
onSubmit={async (event) => {
|
||||
event.preventDefault()
|
||||
props.applySearchQuery(searchTerm || '')
|
||||
|
||||
if (!isAddAction) {
|
||||
props.applySearchQuery(searchTerm || '')
|
||||
} else {
|
||||
await props.handleLinkSubmission(searchTerm, timeZone, locale)
|
||||
setSearchTerm(props.searchTerm ?? "")
|
||||
props.applySearchQuery(props.searchTerm ?? "")
|
||||
}
|
||||
inputRef.current?.blur()
|
||||
if (props.onClose) {
|
||||
props.onClose()
|
||||
|
|
@ -376,6 +407,8 @@ type ControlButtonBoxProps = {
|
|||
|
||||
searchTerm: string | undefined
|
||||
applySearchQuery: (searchQuery: string) => void
|
||||
|
||||
handleLinkSubmission: (link: string, timezone: string, locale:string) => Promise<void>,
|
||||
}
|
||||
|
||||
function MultiSelectControls(props: ControlButtonBoxProps): JSX.Element {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { gql } from 'graphql-request'
|
||||
import useSWRImmutable, { Cache } from 'swr'
|
||||
import { makeGqlFetcher, RequestContext, ssrFetcher } from '../networkHelpers'
|
||||
import { gqlFetcher, makeGqlFetcher, RequestContext, ssrFetcher } from "../networkHelpers"
|
||||
import {
|
||||
articleFragment,
|
||||
ContentReader,
|
||||
|
|
@ -135,15 +135,15 @@ export function useGetArticleQuery({
|
|||
}
|
||||
|
||||
export async function articleQuery(
|
||||
context: RequestContext,
|
||||
input: ArticleQueryInput
|
||||
): Promise<ArticleAttributes> {
|
||||
const result = (await ssrFetcher(context, query, input)) as ArticleData
|
||||
): Promise<ArticleAttributes | undefined> {
|
||||
|
||||
const result = (await gqlFetcher(query, input)) as ArticleData
|
||||
if (result.article) {
|
||||
return result.article.article
|
||||
}
|
||||
|
||||
return Promise.reject()
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const cacheArticle = (
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ export type LibraryItems = {
|
|||
export type LibraryItem = {
|
||||
cursor: string
|
||||
node: LibraryItemNode
|
||||
isLoading?: boolean | undefined
|
||||
}
|
||||
|
||||
export type LibraryItemNode = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue