From 54b060992a7edf16038597fcb3a763d22d08d118 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 28 Aug 2023 09:26:56 +0800 Subject: [PATCH 1/9] Improve the empty search message --- .../templates/homeFeed/EmptyLibrary.tsx | 106 ++++++++++++++---- 1 file changed, 86 insertions(+), 20 deletions(-) diff --git a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx index 95b4ff530..cd38758ba 100644 --- a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx +++ b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx @@ -4,12 +4,95 @@ import { Button } from '../../elements/Button' import { VStack } from '../../elements/LayoutPrimitives' import { StyledText } from '../../elements/StyledText' import { theme } from '../../tokens/stitches.config' +import { useMemo } from 'react' +import { searchQuery } from '../../../lib/networking/queries/search' type EmptyLibraryProps = { + searchTerm: string | undefined onAddLinkClicked: () => void } -export function EmptyLibrary(props: EmptyLibraryProps): JSX.Element { +type MessageType = 'feed' | 'newsletter' | 'library' + +type HelpMessageProps = { + type: MessageType +} + +const HelpMessage = (props: HelpMessageProps) => { + switch (props.type) { + case 'library': + return ( + <> + You can add a link or read more about Omnivore's{' '} + + advanced search + + . + + ) + case 'feed': + return ( + <> + You can subscribe to RSS feeds using the{' '} + feeds page. Learn more about feeds at + 's{' '} + + docs.omnivore.app/using/feeds.html + + . + + ) + case 'newsletter': + return ( + <> + Create email addresses that can be used to subscribe to newsletters on + the emails page. Learn more about + reading newsletters in Omnivore at 's{' '} + + docs.omnivore.app/using/inbox.html + + . + + ) + } + return <> +} + +export const EmptyLibrary = (props: EmptyLibraryProps) => { + const type = useMemo(() => { + if (props.searchTerm) { + switch (props.searchTerm) { + case 'label:RSS': + return 'feed' + case 'label:Newsletter': + return 'newsletter' + } + } + return 'library' + }, [props]) + + const helpTitle = useMemo(() => { + switch (type) { + case 'feed': + return 'You do not have any feed items.' + case 'newsletter': + return 'You do not have any newsletter items.' + } + return 'No results found.' + }, [type]) + return ( - No results found. + {helpTitle} - You can add a link or read more about Omnivore's{' '} - - advanced search - - . + - - ) } From c5031155822ddd2aa03b015625fbe021f1ccd027 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 28 Aug 2023 10:27:16 +0800 Subject: [PATCH 2/9] Rename Read Later to Non-Feed Items to clarify --- .../templates/homeFeed/EmptyLibrary.tsx | 2 +- .../templates/homeFeed/HomeFeedContainer.tsx | 137 ++++++++++-------- .../templates/homeFeed/LibraryFilterMenu.tsx | 15 +- 3 files changed, 92 insertions(+), 62 deletions(-) diff --git a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx index cd38758ba..7f8a37012 100644 --- a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx +++ b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx @@ -86,7 +86,7 @@ export const EmptyLibrary = (props: EmptyLibraryProps) => { const helpTitle = useMemo(() => { switch (type) { case 'feed': - return 'You do not have any feed items.' + return 'You do not have any feed items matching this query.' case 'newsletter': return 'You do not have any newsletter items.' } diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index ae89e8f0f..dbfb16b20 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -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 toast, { 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,8 +48,9 @@ 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" +import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation' +import { articleQuery } from '../../../lib/networking/queries/useGetArticleQuery' +import { searchQuery } from '../../../lib/networking/queries/search' export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT' export type LibraryMode = 'reads' | 'highlights' @@ -70,7 +71,7 @@ const debouncedFetchSearchResults = debounce((query, cb) => { // 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]; +const TIMEOUT_DELAYS = [1000, 2000, 2500, 3500, 5000, 10000, 60000] export function HomeFeedContainer(): JSX.Element { const { viewerData } = useGetViewerQuery() @@ -179,58 +180,69 @@ 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.map(it => ({ ...it, isLoading: it.node.state === 'PROCESSING'})); + return ad.search.edges.map((it) => ({ + ...it, + isLoading: it.node.state === 'PROCESSING', + })) }) || [] return items }, [itemsPages, performActionOnItem]) useEffect(() => { - const timeout : NodeJS.Timeout[] = [] + 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); + 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; + let startIdx = 0 const seeIfUpdated = async () => { if (startIdx > TIMEOUT_DELAYS.length) { - item.node.state = State.FAILED; + item.node.state = State.FAILED return } const username = viewerData?.me?.profile.username - const itemsToUpdate = libraryItems.filter(it => it.isLoading); + const itemsToUpdate = libraryItems.filter((it) => it.isLoading) if (itemsToUpdate.length > 0) { - const link = await articleQuery({ username, slug: item.node.slug, includeFriendsHighlights: false }) + const link = await articleQuery({ + username, + slug: item.node.slug, + includeFriendsHighlights: false, + }) - if (link && link.state != "PROCESSING") { - const updatedArticle = { ...item }; + if (link && link.state != 'PROCESSING') { + const updatedArticle = { ...item } updatedArticle.node = { ...item.node, ...link } - updatedArticle.isLoading = false; + updatedArticle.isLoading = false console.log(`Updating Metadata of ${item.node.slug}.`) - performActionOnItem('update-item', updatedArticle); - return; + performActionOnItem('update-item', updatedArticle) + return } - console.log(`Trying to get the metadata of item ${item.node.slug}... Retry ${startIdx} of 5`); + 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(); - }); + await seeIfUpdated() + }) return () => { - timeout.forEach(clearTimeout); + timeout.forEach(clearTimeout) } }, [itemsPages]) @@ -769,36 +781,39 @@ 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( - () => ( - - Link Saved - - - - ), - { position: 'bottom-right' } - ) - const id = result.url?.match(/[^/]+$/)?.[0] ?? ""; - performActionOnItem('refresh', undefined as unknown as any) - } else { - showErrorToast('Error saving link', { position: 'bottom-right' }) - } - }; + const handleLinkSubmission = async ( + link: string, + timezone: string, + locale: string + ) => { + const result = await saveUrlMutation(link, timezone, locale) + if (result) { + toast( + () => ( + + Link Saved + + + + ), + { 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 ( Promise - handleLinkSubmission: (link: string, timezone: string, locale:string) => Promise, + handleLinkSubmission: ( + link: string, + timezone: string, + locale: string + ) => Promise setIsChecked: (itemId: string, set: boolean) => void itemIsChecked: (itemId: string) => boolean @@ -986,7 +1005,10 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element { )} {props.showAddLinkModal && ( - props.setShowAddLinkModal(false)} /> + props.setShowAddLinkModal(false)} + /> )} @@ -1044,6 +1066,7 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element { > {!props.isValidating && props.items.length == 0 ? ( { props.setShowAddLinkModal(true) }} diff --git a/packages/web/components/templates/homeFeed/LibraryFilterMenu.tsx b/packages/web/components/templates/homeFeed/LibraryFilterMenu.tsx index f3177cbcf..e2c3f4094 100644 --- a/packages/web/components/templates/homeFeed/LibraryFilterMenu.tsx +++ b/packages/web/components/templates/homeFeed/LibraryFilterMenu.tsx @@ -92,7 +92,7 @@ function SavedSearches(props: LibraryFilterMenuProps): JSX.Element { term: 'in:inbox sort:read-desc is:unread', }, { - name: 'Read Later', + name: 'Non-Feed Items', term: 'in:library', }, { @@ -182,7 +182,13 @@ function Subscriptions(props: LibraryFilterMenuProps): JSX.Element { window.location.href = '/settings/subscriptions' }} > - {subscriptions.slice(0, viewAll ? undefined : 4).map((item) => { + + + {(subscriptions ?? []).slice(0, viewAll ? undefined : 4).map((item) => { return ( {props.title} Date: Mon, 28 Aug 2023 10:27:45 +0800 Subject: [PATCH 3/9] Start to add docs for feeds --- packages/web/pages/settings/feeds/index.tsx | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/web/pages/settings/feeds/index.tsx b/packages/web/pages/settings/feeds/index.tsx index a14f7d36c..c94a19b0e 100644 --- a/packages/web/pages/settings/feeds/index.tsx +++ b/packages/web/pages/settings/feeds/index.tsx @@ -8,7 +8,7 @@ import { ConfirmationModal } from '../../../components/patterns/ConfirmationModa import { EmptySettingsRow, SettingsTable, - SettingsTableRow + SettingsTableRow, } from '../../../components/templates/settings/SettingsTable' import { theme } from '../../../components/tokens/stitches.config' import { formattedDateTime } from '../../../lib/dateFormatting' @@ -17,7 +17,7 @@ import { updateSubscriptionMutation } from '../../../lib/networking/mutations/up import { SubscriptionStatus, SubscriptionType, - useGetSubscriptionsQuery + useGetSubscriptionsQuery, } from '../../../lib/networking/queries/useGetSubscriptionsQuery' import { applyStoredTheme } from '../../../lib/themeUpdater' import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers' @@ -90,17 +90,15 @@ export default function Rss(): JSX.Element { return ( { router.push('/settings/feeds/add') }} > {subscriptions.length === 0 ? ( - + ) : ( subscriptions.map((subscription, i) => { return ( @@ -218,9 +216,7 @@ export default function Rss(): JSX.Element { {onDeleteId && ( { await onDelete(onDeleteId) setOnDeleteId('') From 21e81e1c07bb97078b01295097d8692f375b306d Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 28 Aug 2023 11:19:26 +0800 Subject: [PATCH 4/9] Allow people to fully collapse the subscriptions section in the menu --- .../templates/homeFeed/LibraryFilterMenu.tsx | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/packages/web/components/templates/homeFeed/LibraryFilterMenu.tsx b/packages/web/components/templates/homeFeed/LibraryFilterMenu.tsx index e2c3f4094..79fb71724 100644 --- a/packages/web/components/templates/homeFeed/LibraryFilterMenu.tsx +++ b/packages/web/components/templates/homeFeed/LibraryFilterMenu.tsx @@ -170,10 +170,6 @@ function Subscriptions(props: LibraryFilterMenuProps): JSX.Element { [subscriptions] ) - if (!subscriptions || subscriptions.length < 1) { - return <> - } - return ( { window.location.href = '/settings/subscriptions' }} + viewAll={() => { + setViewAll(true) + }} > - - - {(subscriptions ?? []).slice(0, viewAll ? undefined : 4).map((item) => { - return ( + {viewAll ? ( + <> + - ) - })} - + {(subscriptions ?? []).map((item) => { + return ( + + ) + })} + + + ) : ( + + )} ) } @@ -239,6 +244,7 @@ type MenuPanelProps = { editFunc?: () => void editTitle?: string hideBottomBorder?: boolean + viewAll?: () => void } function MenuPanel(props: MenuPanelProps): JSX.Element { @@ -303,6 +309,16 @@ function MenuPanel(props: MenuPanelProps): JSX.Element { } > + {props.viewAll && ( + { + if (props.viewAll) { + props.viewAll() + } + }} + /> + )} { From 748a9b523fc16aac387d422c0c998aaabe9dd91d Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 28 Aug 2023 15:25:43 +0800 Subject: [PATCH 5/9] Update story --- packages/web/stories/EmptyLibrary.stories.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/web/stories/EmptyLibrary.stories.tsx b/packages/web/stories/EmptyLibrary.stories.tsx index 68104a528..196c982fc 100644 --- a/packages/web/stories/EmptyLibrary.stories.tsx +++ b/packages/web/stories/EmptyLibrary.stories.tsx @@ -12,10 +12,15 @@ export default { }, } as ComponentMeta -export const EmptyLibraryStory: ComponentStory = (args: any) => { +export const EmptyLibraryStory: ComponentStory = ( + args: any +) => { return ( - { - console.log('onAddLinkClicked') - }} /> + { + console.log('onAddLinkClicked') + }} + /> ) } From 1eee42f2f31c1ab4cd53fa7e7d72e9488313e95b Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 28 Aug 2023 17:00:06 +0800 Subject: [PATCH 6/9] Use Next Link instead of a for internal links --- .../components/templates/homeFeed/EmptyLibrary.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx index 7f8a37012..afe7b7b1e 100644 --- a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx +++ b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx @@ -38,8 +38,10 @@ const HelpMessage = (props: HelpMessageProps) => { return ( <> You can subscribe to RSS feeds using the{' '} - feeds page. Learn more about feeds at - 's{' '} + + feeds page + + . Learn more about feeds at 's{' '} { return ( <> Create email addresses that can be used to subscribe to newsletters on - the emails page. Learn more about - reading newsletters in Omnivore at 's{' '} + the{' '} + + emails page + + . Learn more about reading newsletters in Omnivore at 's{' '} Date: Mon, 28 Aug 2023 18:24:48 +0800 Subject: [PATCH 7/9] Improve empty state next action suggestions --- .../web/components/templates/ErrorLayout.tsx | 27 +-- .../templates/homeFeed/EmptyLibrary.tsx | 167 +++++++++++++++--- .../templates/homeFeed/HomeFeedContainer.tsx | 1 + 3 files changed, 156 insertions(+), 39 deletions(-) diff --git a/packages/web/components/templates/ErrorLayout.tsx b/packages/web/components/templates/ErrorLayout.tsx index 4c8881d5c..b0837c883 100644 --- a/packages/web/components/templates/ErrorLayout.tsx +++ b/packages/web/components/templates/ErrorLayout.tsx @@ -1,8 +1,4 @@ -import { - VStack, - HStack, - SpanBox, -} from '../elements/LayoutPrimitives' +import { VStack, HStack, SpanBox } from '../elements/LayoutPrimitives' import { StyledText } from '../elements/StyledText' import Link from 'next/link' import { Button } from '../elements/Button' @@ -22,11 +18,14 @@ export function ErrorLayout(props: ErrorLayoutProps): JSX.Element { return ( - + {props.statusCode} @@ -34,9 +33,11 @@ export function ErrorLayout(props: ErrorLayoutProps): JSX.Element { - - + + ) -} \ No newline at end of file +} diff --git a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx index afe7b7b1e..92e04e1da 100644 --- a/packages/web/components/templates/homeFeed/EmptyLibrary.tsx +++ b/packages/web/components/templates/homeFeed/EmptyLibrary.tsx @@ -1,15 +1,20 @@ import Link from 'next/link' import { Book } from 'phosphor-react' import { Button } from '../../elements/Button' -import { VStack } from '../../elements/LayoutPrimitives' +import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' import { StyledText } from '../../elements/StyledText' import { theme } from '../../tokens/stitches.config' import { useMemo } from 'react' import { searchQuery } from '../../../lib/networking/queries/search' +import { LIBRARY_LEFT_MENU_WIDTH } from './LibraryFilterMenu' +import { LayoutType } from './HomeFeedContainer' +import { ArrowRightIcon } from '../../elements/icons/ArrowRightIcon' type EmptyLibraryProps = { searchTerm: string | undefined onAddLinkClicked: () => void + + layoutType: LayoutType } type MessageType = 'feed' | 'newsletter' | 'library' @@ -75,6 +80,108 @@ const HelpMessage = (props: HelpMessageProps) => { return <> } +export const ErrorBox = (props: HelpMessageProps) => { + const errorTitle = useMemo(() => { + switch (props.type) { + case 'feed': + return 'You do not have any feed items matching this query.' + case 'newsletter': + return 'You do not have any newsletter item matching this query.' + } + return 'No results found for this query.' + }, [props.type]) + + return ( + + {errorTitle} + + ) +} + +export const SuggestionBox = (props: HelpMessageProps) => { + const helpMessage = useMemo(() => { + switch (props.type) { + case 'feed': + return 'Want to add an RSS or Atom Subscription?' + case 'newsletter': + return 'Create an Omnivore email address and subscribe to newsletters.' + } + return "Add a link or read more about Omnivore's Advanced Search." + }, [props.type]) + + const helpTarget = useMemo(() => { + switch (props.type) { + case 'feed': + return '/settings/feeds' + case 'newsletter': + return '/settings/emails' + } + return 'https://docs.omnivore.app/' + }, [props.type]) + + return ( + + {helpMessage} + + + + <>Click Here + + + + + + ) +} + export const EmptyLibrary = (props: EmptyLibraryProps) => { const type = useMemo(() => { if (props.searchTerm) { @@ -88,35 +195,43 @@ export const EmptyLibrary = (props: EmptyLibraryProps) => { return 'library' }, [props]) - const helpTitle = useMemo(() => { - switch (type) { - case 'feed': - return 'You do not have any feed items matching this query.' - case 'newsletter': - return 'You do not have any newsletter items.' - } - return 'No results found.' - }, [type]) - return ( - - - - {helpTitle} - - - - - - + + + ) } diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index dbfb16b20..338e3e833 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -1066,6 +1066,7 @@ function LibraryItemsLayout(props: LibraryItemsLayoutProps): JSX.Element { > {!props.isValidating && props.items.length == 0 ? ( { props.setShowAddLinkModal(true) From 878d106338cc5c6a85bcc9636beec60b59fa0bf6 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 29 Aug 2023 10:22:27 +0800 Subject: [PATCH 8/9] Add missing arrow icon --- .../elements/icons/ArrowRightIcon.tsx | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packages/web/components/elements/icons/ArrowRightIcon.tsx diff --git a/packages/web/components/elements/icons/ArrowRightIcon.tsx b/packages/web/components/elements/icons/ArrowRightIcon.tsx new file mode 100644 index 000000000..4077c3b28 --- /dev/null +++ b/packages/web/components/elements/icons/ArrowRightIcon.tsx @@ -0,0 +1,43 @@ +/* eslint-disable functional/no-class */ +/* eslint-disable functional/no-this-expression */ +import { IconProps } from './IconProps' + +import React from 'react' + +export class ArrowRightIcon extends React.Component { + render() { + const size = (this.props.size || 26).toString() + const color = (this.props.color || '#2A2A2A').toString() + + return ( + + + + + + + + ) + } +} From 927fdc8fb8b005af3158321b75f305854cf06555 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 29 Aug 2023 13:35:54 +0800 Subject: [PATCH 9/9] Set empty library layout type in story --- packages/web/stories/EmptyLibrary.stories.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/stories/EmptyLibrary.stories.tsx b/packages/web/stories/EmptyLibrary.stories.tsx index 196c982fc..a690ad1d8 100644 --- a/packages/web/stories/EmptyLibrary.stories.tsx +++ b/packages/web/stories/EmptyLibrary.stories.tsx @@ -17,6 +17,7 @@ export const EmptyLibraryStory: ComponentStory = ( ) => { return ( { console.log('onAddLinkClicked')