From 552d76a92948fa43f7fe816c689feb5a7a46fa35 Mon Sep 17 00:00:00 2001 From: gitstart-omnivore Date: Fri, 24 Jun 2022 15:30:48 +0000 Subject: [PATCH 1/7] Added search support for kbar in this PR. --- packages/web/components/elements/KBar.tsx | 85 +++++++++ .../web/components/patterns/PrimaryHeader.tsx | 15 +- .../templates/article/ArticleActionsMenu.tsx | 1 - .../templates/homeFeed/HomeFeedContainer.tsx | 93 ++++++++++ packages/web/lib/hooks/useReaderSettings.tsx | 50 +++++- .../keyboardShortcuts/navigationShortcuts.ts | 168 +++++++++--------- .../keyboardShortcuts/useKeyboardShortcuts.ts | 5 +- .../web/lib/networking/queries/search.tsx | 80 +++++++++ packages/web/package.json | 1 + .../web/pages/[username]/[slug]/index.tsx | 114 +++++++----- packages/web/pages/_app.tsx | 47 ++++- yarn.lock | 60 +++++++ 12 files changed, 565 insertions(+), 154 deletions(-) create mode 100644 packages/web/components/elements/KBar.tsx create mode 100644 packages/web/lib/networking/queries/search.tsx diff --git a/packages/web/components/elements/KBar.tsx b/packages/web/components/elements/KBar.tsx new file mode 100644 index 000000000..81d9c4510 --- /dev/null +++ b/packages/web/components/elements/KBar.tsx @@ -0,0 +1,85 @@ +import { KBarResults, useMatches } from 'kbar' +import { theme } from '../tokens/stitches.config' +import { Box, SpanBox } from './LayoutPrimitives' + +export const searchStyle = { + padding: '14px 16px', + fontSize: '16px', + width: '100%', + outline: 'none', + border: 'none', + boxSizing: 'border-box' as React.CSSProperties['boxSizing'], + backgroundColor: theme.colors.grayBase.toString(), + color: theme.colors.grayTextContrast.toString(), +} + +export const animatorStyle = { + maxWidth: '600px', + width: '100%', + backgroundColor: theme.colors.grayBase.toString(), + color: theme.colors.grayTextContrast.toString(), + borderRadius: '8px', + overflow: 'hidden', + boxShadow: '0px 6px 20px rgba(0, 0, 0, 0.2)', +} + +const groupNameStyle = { + padding: '8px 16px', + fontSize: '10px', + textTransform: 'uppercase' as const, + opacity: 0.5, +} + +export const KBarResultsComponents = () => { + const { results } = useMatches() + + return ( + { + return typeof item === 'string' ? ( + {item.toLocaleUpperCase()} + ) : ( + + {item.name} + + {item.shortcut?.map((st, idx) => ( + + {st} + + ))} + + + ) + }} + /> + ) +} diff --git a/packages/web/components/patterns/PrimaryHeader.tsx b/packages/web/components/patterns/PrimaryHeader.tsx index cf5139e17..10a8a9f4a 100644 --- a/packages/web/components/patterns/PrimaryHeader.tsx +++ b/packages/web/components/patterns/PrimaryHeader.tsx @@ -10,6 +10,7 @@ import { useKeyboardShortcuts } from '../../lib/keyboardShortcuts/useKeyboardSho import { primaryCommands } from '../../lib/keyboardShortcuts/navigationShortcuts' import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery' import { setupAnalytics } from '../../lib/analytics' +import { useRegisterActions } from 'kbar' type HeaderProps = { user?: UserBasicData @@ -30,12 +31,12 @@ export function PrimaryHeader(props: HeaderProps): JSX.Element { useKeyboardShortcuts( primaryCommands((action) => { switch (action) { - case 'themeDarker': - darkenTheme() - break - case 'themeLighter': - lightenTheme() - break + // case 'themeDarker': + // darkenTheme() + // break + // case 'themeLighter': + // lightenTheme() + // break case 'toggleShortcutHelpModalDisplay': props.setShowKeyboardCommandsModal(true) break @@ -43,7 +44,6 @@ export function PrimaryHeader(props: HeaderProps): JSX.Element { }) ) -/* useRegisterActions([ { id: 'lightTheme', @@ -62,7 +62,6 @@ export function PrimaryHeader(props: HeaderProps): JSX.Element { perform: () => darkenTheme(), }, ]) - */ const initAnalytics = useCallback(() => { setupAnalytics(props.user) diff --git a/packages/web/components/templates/article/ArticleActionsMenu.tsx b/packages/web/components/templates/article/ArticleActionsMenu.tsx index 8971ff09c..ba367f44e 100644 --- a/packages/web/components/templates/article/ArticleActionsMenu.tsx +++ b/packages/web/components/templates/article/ArticleActionsMenu.tsx @@ -176,7 +176,6 @@ export function ArticleActionsMenu(props: ArticleActionsMenuProps): JSX.Element )} {/* - */} diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index 25f9121ec..7b49e6cef 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -1,6 +1,7 @@ import { Box, HStack, VStack } from './../../elements/LayoutPrimitives' import type { LibraryItem, + LibraryItemsData, LibraryItemsQueryInput, } from '../../../lib/networking/queries/useGetLibraryItemsQuery' import { useGetLibraryItemsQuery } from '../../../lib/networking/queries/useGetLibraryItemsQuery' @@ -41,8 +42,11 @@ import { State, PageType, } from '../../../lib/networking/fragments/articleFragment' +import { useRegisterActions, createAction, useKBar } from 'kbar' import { EditTitleModal } from './EditTitleModal' import { useGetUserPreferences } from '../../../lib/networking/queries/useGetUserPreferences' +import { searchQuery } from '../../../lib/networking/queries/search' +import debounce from 'lodash/debounce' export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT' @@ -60,11 +64,24 @@ const SAVED_SEARCHES: Record = { Newsletters: `in:inbox label:Newsletter`, } +const fetchSearchResults = async (query: string, cb: any) => { + if (!query.startsWith('#')) return + const res = await searchQuery({ limit: 10, searchQuery: query.substring(1)}) + cb(res); +}; + +const debouncedFetchSearchResults = debounce((query, cb) => { + fetchSearchResults(query, cb); +}, 300); + export function HomeFeedContainer(): JSX.Element { useGetUserPreferences() const { viewerData } = useGetViewerQuery() const router = useRouter() + const { queryValue } = useKBar((state) => ({queryValue: state.searchQuery})); + const [searchResults, setSearchResults] = useState([]); + const defaultQuery = { limit: 10, sortDescending: true, @@ -102,6 +119,16 @@ export function HomeFeedContainer(): JSX.Element { const { itemsPages, size, setSize, isValidating, performActionOnItem } = useGetLibraryItemsQuery(queryInputs) + useEffect(() => { + if (queryValue.startsWith('#')) { + debouncedFetchSearchResults(queryValue, (data: LibraryItemsData) => { + setSearchResults(data?.search.edges || []) + }) + } + else setSearchResults([]) + + }, [queryValue]) + useEffect(() => { if (!router.isReady) return const q = router.query['q'] @@ -421,6 +448,72 @@ export function HomeFeedContainer(): JSX.Element { }) ) + const ARCHIVE_ACTION = !activeItem?.node.isArchived ? + createAction({ + section: 'Library', + name: 'Archive selected item', + shortcut: ['e'], + perform: () => handleCardAction('archive', activeItem), + }) : + createAction({ + section: 'Library', + name: 'UnArchive selected item', + shortcut: ['e'], + perform: () => handleCardAction('unarchive', activeItem), + }) + + const ACTIVE_ACTIONS = [ + ARCHIVE_ACTION, + createAction({ + section: 'Library', + name: 'Remove item', + shortcut: ['r'], + perform: () => handleCardAction('delete', activeItem), + }), + createAction({ + section: 'Library', + name: 'Edit item labels', + shortcut: ['l'], + perform: () => handleCardAction('set-labels', activeItem), + }), + createAction({ + section: 'Library', + name: 'Mark item as read', + shortcut: ['Shift', 'i'], + perform: () => handleCardAction('mark-read', activeItem), + }), + createAction({ + section: 'Library', + name: 'Mark item as unread', + shortcut: ['Shift', 'u'], + perform: () => handleCardAction('mark-unread', activeItem), + }), + ] + + const UNACTIVE_ACTIONS = [ + createAction({ + section: 'Library', + name: 'Sort item in ascending order', + shortcut: ['s', 'o'], + perform: () => setQueryInputs({ ...queryInputs, sortDescending: false }), + }), + createAction({ + section: 'Library', + name: 'Sort item in descending order', + shortcut: ['s', 'n'], + perform: () => setQueryInputs({ ...queryInputs, sortDescending: true }), + }), + ] + + useRegisterActions(searchResults.map(link => ({ + id: link.node.id, + section: 'Search Results', + name: link.node.title, + keywords: '#' + link.node.title, + perform: () => handleCardAction('showDetail', link), + })), [searchResults]) + + useRegisterActions(activeCardId ? [...ACTIVE_ACTIONS, ...UNACTIVE_ACTIONS] : UNACTIVE_ACTIONS, [activeCardId, activeItem]); useFetchMore(handleFetchMore) return ( diff --git a/packages/web/lib/hooks/useReaderSettings.tsx b/packages/web/lib/hooks/useReaderSettings.tsx index a6d254143..8dd7cb8d9 100644 --- a/packages/web/lib/hooks/useReaderSettings.tsx +++ b/packages/web/lib/hooks/useReaderSettings.tsx @@ -1,3 +1,4 @@ +import { useRegisterActions } from "kbar" import { useCallback, useState } from "react" import { userPersonalizationMutation } from "../networking/mutations/userPersonalizationMutation" import { useGetUserPreferences, UserPreferences } from "../networking/queries/useGetUserPreferences" @@ -36,12 +37,12 @@ export const useReaderSettings = (): ReaderSettings => { const [showSetLabelsModal, setShowSetLabelsModal] = useState(false) const [showEditDisplaySettingsModal, setShowEditDisplaySettingsModal] = useState(false) - const actionHandler = useCallback(async(action: string, arg?: unknown) => { - const updateFontSize = async(newFontSize: number) => { - setFontSize(newFontSize) - await userPersonalizationMutation({ fontSize: newFontSize }) - } + const updateFontSize = async (newFontSize: number) => { + setFontSize(newFontSize) + await userPersonalizationMutation({ fontSize: newFontSize }) + } + const actionHandler = useCallback(async(action: string, arg?: unknown) => { switch (action) { case 'incrementFontSize': await updateFontSize(Math.min(fontSize + 2, 28)) @@ -91,6 +92,45 @@ export const useReaderSettings = (): ReaderSettings => { } }, [fontSize, setFontSize, lineHeight, fontFamily, setLineHeight, marginWidth, setMarginWidth, setFontFamily]) + + + useRegisterActions([ + { + id: 'increaseFont', + section: 'Article', + name: 'Increase font size', + shortcut: ['+'], + perform: () => actionHandler('incrementFontSize'), + }, + { + id: 'decreaseFont', + section: 'Article', + name: 'Decrease font size', + shortcut: ['-'], + perform: () => actionHandler('decrementFontSize'), + }, + { + id: 'increaseMargin', + section: 'Article', + name: 'Increase margin width', + shortcut: [']'], + perform: () => actionHandler('incrementMarginWidth'), + }, + { + id: 'decreaseMargin', + section: 'Article', + name: 'Decrease margin width', + shortcut: ['['], + perform: () => actionHandler('decrementMarginWidth'), + }, + { + id: 'edit_a', + section: 'Article', + name: 'Edit labels', + shortcut: ['l'], + perform: () => setShowSetLabelsModal(true), + }, + ], [fontSize, marginWidth, setFontSize, setMarginWidth]) return { preferencesData, diff --git a/packages/web/lib/keyboardShortcuts/navigationShortcuts.ts b/packages/web/lib/keyboardShortcuts/navigationShortcuts.ts index 007be1a36..a00fcbcba 100644 --- a/packages/web/lib/keyboardShortcuts/navigationShortcuts.ts +++ b/packages/web/lib/keyboardShortcuts/navigationShortcuts.ts @@ -123,36 +123,36 @@ export function libraryListCommands( shortcutKeyDescription: 'k or left arrow', callback: () => actionHandler('moveFocusToPreviousListItem'), }, - { - shortcutKeys: ['e'], - actionDescription: 'Archive item', - shortcutKeyDescription: 'e', - callback: () => actionHandler('archiveItem'), - }, - { - shortcutKeys: ['r'], - actionDescription: 'Remove item', - shortcutKeyDescription: 'r', - callback: () => actionHandler('removeItem'), - }, - { - shortcutKeys: ['l'], - actionDescription: 'Edit item labels', - shortcutKeyDescription: 'l', - callback: () => actionHandler('showEditLabelsModal'), - }, - { - shortcutKeys: ['shift', 'i'], - actionDescription: 'Mark item as read', - shortcutKeyDescription: 'shift + i', - callback: () => actionHandler('markItemAsRead'), - }, - { - shortcutKeys: ['shift', 'u'], - actionDescription: 'Mark item as unread', - shortcutKeyDescription: 'shift + u', - callback: () => actionHandler('markItemAsUnread'), - }, + // { + // shortcutKeys: ['e'], + // actionDescription: 'Archive item', + // shortcutKeyDescription: 'e', + // callback: () => actionHandler('archiveItem'), + // }, + // { + // shortcutKeys: ['r'], + // actionDescription: 'Remove item', + // shortcutKeyDescription: 'r', + // callback: () => actionHandler('removeItem'), + // }, + // { + // shortcutKeys: ['l'], + // actionDescription: 'Edit item labels', + // shortcutKeyDescription: 'l', + // callback: () => actionHandler('showEditLabelsModal'), + // }, + // { + // shortcutKeys: ['shift', 'i'], + // actionDescription: 'Mark item as read', + // shortcutKeyDescription: 'shift + i', + // callback: () => actionHandler('markItemAsRead'), + // }, + // { + // shortcutKeys: ['shift', 'u'], + // actionDescription: 'Mark item as unread', + // shortcutKeyDescription: 'shift + u', + // callback: () => actionHandler('markItemAsUnread'), + // }, // Commented out until we re-enable highlight sharing // { // shortcutKeys: ['shift', 's'], @@ -160,18 +160,18 @@ export function libraryListCommands( // shortcutKeyDescription: 'shift + s', // callback: () => actionHandler('shareItem'), // }, - { - shortcutKeys: ['s', 'o'], - actionDescription: 'Sort item in ascending order', - shortcutKeyDescription: 's then o', - callback: () => actionHandler('sortAscending'), - }, - { - shortcutKeys: ['s', 'n'], - actionDescription: 'Sort item in descending order', - shortcutKeyDescription: 's then n', - callback: () => actionHandler('sortDescending'), - }, + // { + // shortcutKeys: ['s', 'o'], + // actionDescription: 'Sort item in ascending order', + // shortcutKeyDescription: 's then o', + // callback: () => actionHandler('sortAscending'), + // }, + // { + // shortcutKeys: ['s', 'n'], + // actionDescription: 'Sort item in descending order', + // shortcutKeyDescription: 's then n', + // callback: () => actionHandler('sortDescending'), + // }, { shortcutKeys: ['arrowdown'], actionDescription: 'Move cursor to the next row', @@ -231,53 +231,53 @@ export function articleKeyboardCommands( actionHandler: (action: ArticleKeyboardAction) => void ): KeyboardCommand[] { return [ - { - shortcutKeys: ['o'], - actionDescription: 'Open original article page', - shortcutKeyDescription: 'o', - callback: () => actionHandler('openOriginalArticle'), - }, - { - shortcutKeys: ['u'], - actionDescription: 'Back to library', - shortcutKeyDescription: 'u', - callback: () => router?.push('/home'), - }, - { - shortcutKeys: ['+'], - actionDescription: 'Increase font size', - shortcutKeyDescription: '+', - callback: () => actionHandler('incrementFontSize'), - }, - { - shortcutKeys: ['-'], - actionDescription: 'Decrease font size', - shortcutKeyDescription: '-', - callback: () => actionHandler('decrementFontSize'), - }, - { - shortcutKeys: [']'], - actionDescription: 'Increase margin width', - shortcutKeyDescription: ']', - callback: () => actionHandler('incrementMarginWidth'), - }, - { - shortcutKeys: ['['], - actionDescription: 'Decrease margin width', - shortcutKeyDescription: '[', - callback: () => actionHandler('decrementMarginWidth'), - }, + // { + // shortcutKeys: ['o'], + // actionDescription: 'Open original article page', + // shortcutKeyDescription: 'o', + // callback: () => actionHandler('openOriginalArticle'), + // }, + // { + // shortcutKeys: ['u'], + // actionDescription: 'Back to library', + // shortcutKeyDescription: 'u', + // callback: () => router?.push('/home'), + // }, + // { + // shortcutKeys: ['+'], + // actionDescription: 'Increase font size', + // shortcutKeyDescription: '+', + // callback: () => actionHandler('incrementFontSize'), + // }, + // { + // shortcutKeys: ['-'], + // actionDescription: 'Decrease font size', + // shortcutKeyDescription: '-', + // callback: () => actionHandler('decrementFontSize'), + // }, + // { + // shortcutKeys: [']'], + // actionDescription: 'Increase margin width', + // shortcutKeyDescription: ']', + // callback: () => actionHandler('incrementMarginWidth'), + // }, + // { + // shortcutKeys: ['['], + // actionDescription: 'Decrease margin width', + // shortcutKeyDescription: '[', + // callback: () => actionHandler('decrementMarginWidth'), + // }, { shortcutKeys: ['d'], actionDescription: 'Edit Display Settings', shortcutKeyDescription: 'd', callback: () => actionHandler('editDisplaySettings'), }, - { - shortcutKeys: ['l'], - actionDescription: 'Edit labels', - shortcutKeyDescription: 'l', - callback: () => actionHandler('setLabels'), - }, + // { + // shortcutKeys: ['l'], + // actionDescription: 'Edit labels', + // shortcutKeyDescription: 'l', + // callback: () => actionHandler('setLabels'), + // }, ] } diff --git a/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts b/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts index 9808cf44d..da7beeac4 100644 --- a/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts +++ b/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts @@ -10,6 +10,8 @@ const disabledTargets = ['INPUT', 'TEXTAREA'] ] */ +const KBAR_KEYS = ['control', 'meta', 'v', 'i', 's', 'n', '[', ']'] + type KeyPressed = { [name: string]: boolean } @@ -52,7 +54,8 @@ export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => { }) }) }) - + + KBAR_KEYS.map((key) => currentKeys[key.toLowerCase()] = false) return currentKeys }, [commands]) diff --git a/packages/web/lib/networking/queries/search.tsx b/packages/web/lib/networking/queries/search.tsx new file mode 100644 index 000000000..7d3a780e3 --- /dev/null +++ b/packages/web/lib/networking/queries/search.tsx @@ -0,0 +1,80 @@ +import { gql } from 'graphql-request' +import { gqlFetcher } from '../networkHelpers' +import { LibraryItemsData } from './useGetLibraryItemsQuery' + +export type LibraryItemsQueryInput = { + limit?: number + searchQuery?: string +} + +export async function searchQuery({ + limit = 10, + searchQuery, +}: LibraryItemsQueryInput): Promise { + const query = gql` + query Search($after: String, $first: Int, $query: String) { + search(first: $first, after: $after, query: $query) { + ... on SearchSuccess { + edges { + cursor + node { + id + title + slug + url + pageType + contentReader + createdAt + isArchived + readingProgressPercent + readingProgressAnchorIndex + author + image + description + publishedAt + ownedByViewer + originalArticleUrl + uploadFileId + labels { + id + name + color + } + pageId + shortId + quote + annotation + state + siteName + subscription + readAt + } + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + totalCount + } + } + ... on SearchError { + errorCodes + } + } + } + ` + + const variables = { + first: limit, + query: searchQuery, + } + + try { + const data = (await gqlFetcher(query, {...variables})) + return data as LibraryItemsData || undefined; + } catch (error) { + console.log('search error', error) + return undefined + } +} diff --git a/packages/web/package.json b/packages/web/package.json index 5c0eafa45..bb5f1e949 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -32,6 +32,7 @@ "cookie": "^0.5.0", "diff-match-patch": "^1.0.5", "graphql-request": "^3.6.1", + "kbar": "^0.1.0-beta.35", "nanoid": "^3.1.29", "next": "^12.1.0", "phosphor-react": "^1.4.0", diff --git a/packages/web/pages/[username]/[slug]/index.tsx b/packages/web/pages/[username]/[slug]/index.tsx index eb4f31488..04cd26599 100644 --- a/packages/web/pages/[username]/[slug]/index.tsx +++ b/packages/web/pages/[username]/[slug]/index.tsx @@ -29,6 +29,7 @@ import { SetLabelsModal } from '../../../components/templates/article/SetLabelsM import { DisplaySettingsModal } from '../../../components/templates/article/DisplaySettingsModal' import { useReaderSettings } from '../../../lib/hooks/useReaderSettings' import { SkeletonArticleContainer } from '../../../components/templates/article/SkeletonArticleContainer' +import { useRegisterActions } from 'kbar' const PdfArticleContainerNoSSR = dynamic( @@ -137,6 +138,23 @@ export default function Home(): JSX.Element { }) } }, [article, viewerData]) + + useRegisterActions([ + { + id: 'open', + section: 'Article', + name: 'Open original article page', + shortcut: ['o'], + perform: () => actionHandler('openOriginalArticle') + }, + { + id: 'back', + section: 'Article', + name: 'Back to library', + shortcut: ['u'], + perform: () => router.push(`/home`), + }, + ], [article]) if (articleFetchError && articleFetchError.indexOf('NOT_FOUND') > -1) { router.push('/404') @@ -190,55 +208,55 @@ export default function Home(): JSX.Element { /> ) : null} - {article && viewerData?.me && article.contentReader == 'PDF' ? ( - - ) : ( - - {article && viewerData?.me ? ( - - ) : ( - - )} - + {article && viewerData?.me && article.contentReader == 'PDF' ? ( + + ) : ( + + {article && viewerData?.me ? ( + + ) : ( + )} + + )} {article && readerSettings.showSetLabelsModal && ( { + const defaultActions = [ + { + id: 'home', + section: 'Navigation', + name: 'Go to Home (Library) ', + shortcut: ['g', 'h'], + keywords: 'go home', + perform: () => router?.push('/home'), + }, + ] + + return defaultActions +} function OmnivoreApp({ Component, pageProps }: AppProps): JSX.Element { const router = useRouter() @@ -51,11 +74,21 @@ function OmnivoreApp({ Component, pageProps }: AppProps): JSX.Element { }, [router.events, analytics]) return ( - - - - - + + + + + + + + + + + + + + + ) } diff --git a/yarn.lock b/yarn.lock index cdb56a23f..c6bb65208 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5020,6 +5020,28 @@ dependencies: "@babel/runtime" "^7.13.10" +"@reach/observe-rect@^1.1.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2" + integrity sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ== + +"@reach/portal@^0.16.0": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.16.2.tgz#ca83696215ee03acc2bb25a5ae5d8793eaaf2f64" + integrity sha512-9ur/yxNkuVYTIjAcfi46LdKUvH0uYZPfEp4usWcpt6PIp+WDF57F/5deMe/uGi/B/nfDweQu8VVwuMVrCb97JQ== + dependencies: + "@reach/utils" "0.16.0" + tiny-warning "^1.0.3" + tslib "^2.3.0" + +"@reach/utils@0.16.0": + version "0.16.0" + resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.16.0.tgz#5b0777cf16a7cab1ddd4728d5d02762df0ba84ce" + integrity sha512-PCggBet3qaQmwFNcmQ/GqHSefadAFyNCUekq9RrWoaU9hh/S4iaFgf2MBMdM47eQj5i/Bk0Mm07cP/XPFlkN+Q== + dependencies: + tiny-warning "^1.0.3" + tslib "^2.3.0" + "@rushstack/eslint-patch@^1.0.8": version "1.1.0" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323" @@ -10942,6 +10964,11 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== +command-score@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/command-score/-/command-score-0.1.2.tgz#b986ad7e8c0beba17552a56636c44ae38363d381" + integrity sha512-VtDvQpIJBvBatnONUsPzXYFVKQQAhuf3XTNOAsdBxCNO/QCtUUd8LSgjn0GVarBkCad6aJCZfXgrjYbl/KRr7w== + commander@^2.19.0, commander@^2.20.0, commander@^2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -13357,6 +13384,11 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== +fast-equals@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-2.0.4.tgz#3add9410585e2d7364c2deeb6a707beadb24b927" + integrity sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w== + fast-glob@^2.2.6: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" @@ -17111,6 +17143,17 @@ jws@^4.0.0: jwa "^2.0.0" safe-buffer "^5.0.1" +kbar@^0.1.0-beta.35: + version "0.1.0-beta.35" + resolved "https://registry.yarnpkg.com/kbar/-/kbar-0.1.0-beta.35.tgz#48f6f021fa65793cdf9f5e8876db666f160eee47" + integrity sha512-lXA/AFfmeTBIC0+6wsqHPpU1ULI4MMgzX9cRKkpHh9BVij5t1EAJOFTLzQQBr/4jVzSYNwRogRHHU4oQAlAduQ== + dependencies: + "@reach/portal" "^0.16.0" + command-score "^0.1.2" + fast-equals "^2.0.3" + react-virtual "^2.8.2" + tiny-invariant "^1.2.0" + keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -21240,6 +21283,13 @@ react-twitter-widgets@^1.10.0: dependencies: loadjs "^4.2.0" +react-virtual@^2.8.2: + version "2.10.4" + resolved "https://registry.yarnpkg.com/react-virtual/-/react-virtual-2.10.4.tgz#08712f0acd79d7d6f7c4726f05651a13b24d8704" + integrity sha512-Ir6+oPQZTVHfa6+JL9M7cvMILstFZH/H3jqeYeKI4MSUX+rIruVwFC6nGVXw9wqAw8L0Kg2KvfXxI85OvYQdpQ== + dependencies: + "@reach/observe-rect" "^1.1.0" + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -23527,6 +23577,16 @@ tiny-hashes@^1.0.1: resolved "https://registry.yarnpkg.com/tiny-hashes/-/tiny-hashes-1.0.1.tgz#ddbe9060312ddb4efe0a174bb3a27e1331c425a1" integrity sha512-knIN5zj4fl7kW4EBU5sLP20DWUvi/rVouvJezV0UAym2DkQaqm365Nyc8F3QEiOvunNDMxR8UhcXd1d5g+Wg1g== +tiny-invariant@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" + integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg== + +tiny-warning@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + title-case@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/title-case/-/title-case-2.1.1.tgz#3e127216da58d2bc5becf137ab91dae3a7cd8faa" From 4e27ec97f9ad585fe90f92b3473d3e123bc31c30 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 11 Jul 2022 13:06:24 -0700 Subject: [PATCH 2/7] Use kbar for highlighting shortcuts, cleanup options --- .../web/components/patterns/HighlightBar.tsx | 17 ------------- .../web/components/patterns/PrimaryHeader.tsx | 4 ++-- .../templates/homeFeed/HomeFeedContainer.tsx | 24 +++++++++---------- .../web/pages/[username]/[slug]/index.tsx | 21 +++++++++++++++- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/packages/web/components/patterns/HighlightBar.tsx b/packages/web/components/patterns/HighlightBar.tsx index f8efb3ccc..419b19ca3 100644 --- a/packages/web/components/patterns/HighlightBar.tsx +++ b/packages/web/components/patterns/HighlightBar.tsx @@ -1,5 +1,3 @@ -import { highlightBarKeyboardCommands } from '../../lib/keyboardShortcuts/navigationShortcuts' -import { useKeyboardShortcuts } from '../../lib/keyboardShortcuts/useKeyboardShortcuts' import { isAndroid } from '../../lib/deviceType' import { styled, theme } from '../tokens/stitches.config' @@ -82,21 +80,6 @@ export function HighlightBar(props: HighlightBarProps): JSX.Element { } function BarContent(props: HighlightBarProps): JSX.Element { - useKeyboardShortcuts( - highlightBarKeyboardCommands((action) => { - switch (action) { - case 'createHighlight': - props.handleButtonClick('create') - break - case 'openNoteModal': - props.handleButtonClick('comment') - break - case 'openPostModal': - break - } - }) - ) - const Separator = styled('div', { width: '1px', maxWidth: '1px', diff --git a/packages/web/components/patterns/PrimaryHeader.tsx b/packages/web/components/patterns/PrimaryHeader.tsx index 10a8a9f4a..e15c039e7 100644 --- a/packages/web/components/patterns/PrimaryHeader.tsx +++ b/packages/web/components/patterns/PrimaryHeader.tsx @@ -48,7 +48,7 @@ export function PrimaryHeader(props: HeaderProps): JSX.Element { { id: 'lightTheme', section: 'Preferences', - name: 'Change theme (lighter) ', + name: 'Change theme (light) ', shortcut: ['v', 'l'], keywords: 'light theme', perform: () => lightenTheme(), @@ -56,7 +56,7 @@ export function PrimaryHeader(props: HeaderProps): JSX.Element { { id: 'darkTheme', section: 'Preferences', - name: 'Change theme (darker) ', + name: 'Change theme (dark) ', shortcut: ['v', 'd'], keywords: 'dark theme', perform: () => darkenTheme(), diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index 7b49e6cef..15f98e5c4 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -491,18 +491,18 @@ export function HomeFeedContainer(): JSX.Element { ] const UNACTIVE_ACTIONS = [ - createAction({ - section: 'Library', - name: 'Sort item in ascending order', - shortcut: ['s', 'o'], - perform: () => setQueryInputs({ ...queryInputs, sortDescending: false }), - }), - createAction({ - section: 'Library', - name: 'Sort item in descending order', - shortcut: ['s', 'n'], - perform: () => setQueryInputs({ ...queryInputs, sortDescending: true }), - }), + // createAction({ + // section: 'Library', + // name: 'Sort in ascending order', + // shortcut: ['s', 'o'], + // perform: () => setQueryInputs({ ...queryInputs, sortDescending: false }), + // }), + // createAction({ + // section: 'Library', + // name: 'Sort in descending order', + // shortcut: ['s', 'n'], + // perform: () => setQueryInputs({ ...queryInputs, sortDescending: true }), + // }), ] useRegisterActions(searchResults.map(link => ({ diff --git a/packages/web/pages/[username]/[slug]/index.tsx b/packages/web/pages/[username]/[slug]/index.tsx index 04cd26599..20ea8cf16 100644 --- a/packages/web/pages/[username]/[slug]/index.tsx +++ b/packages/web/pages/[username]/[slug]/index.tsx @@ -17,7 +17,6 @@ import { deleteHighlightMutation } from '../../../lib/networking/mutations/delet import { mergeHighlightMutation } from '../../../lib/networking/mutations/mergeHighlightMutation' import { articleReadingProgressMutation } from '../../../lib/networking/mutations/articleReadingProgressMutation' import { updateHighlightMutation } from '../../../lib/networking/mutations/updateHighlightMutation' -import { userPersonalizationMutation } from '../../../lib/networking/mutations/userPersonalizationMutation' import Script from 'next/script' import { theme } from '../../../components/tokens/stitches.config' import { ArticleActionsMenu } from '../../../components/templates/article/ArticleActionsMenu' @@ -154,6 +153,26 @@ export default function Home(): JSX.Element { shortcut: ['u'], perform: () => router.push(`/home`), }, + { + id: 'highlight', + section: 'Article', + name: 'Highlight selected text', + shortcut: ['h'], + perform: () => { + const event = new Event('highlight'); + document.dispatchEvent(event); + }, + }, + { + id: 'note', + section: 'Article', + name: 'Highlight selected text and add a note', + shortcut: ['n'], + perform: () => { + const event = new Event('annotate'); + document.dispatchEvent(event); + }, + }, ], [article]) if (articleFetchError && articleFetchError.indexOf('NOT_FOUND') > -1) { From bde765c5241a857b63c3406b8b2c88e6d0d152f6 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 11 Jul 2022 14:09:24 -0700 Subject: [PATCH 3/7] Cast unactive actions to correct type now that its an empty list --- .../web/components/templates/homeFeed/HomeFeedContainer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx index 15f98e5c4..92d5ad30c 100644 --- a/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx +++ b/packages/web/components/templates/homeFeed/HomeFeedContainer.tsx @@ -42,7 +42,7 @@ import { State, PageType, } from '../../../lib/networking/fragments/articleFragment' -import { useRegisterActions, createAction, useKBar } from 'kbar' +import { useRegisterActions, createAction, useKBar, Action } from 'kbar' import { EditTitleModal } from './EditTitleModal' import { useGetUserPreferences } from '../../../lib/networking/queries/useGetUserPreferences' import { searchQuery } from '../../../lib/networking/queries/search' @@ -490,7 +490,7 @@ export function HomeFeedContainer(): JSX.Element { }), ] - const UNACTIVE_ACTIONS = [ + const UNACTIVE_ACTIONS: Action[] = [ // createAction({ // section: 'Library', // name: 'Sort in ascending order', From 9f2ab0972929b6fc9e0da4ef538563a850bf2c90 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 11 Jul 2022 14:49:24 -0700 Subject: [PATCH 4/7] Special handle the CMD+K keys so they dont interfere with regular keyboard commands --- .../web/lib/keyboardShortcuts/useKeyboardShortcuts.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts b/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts index da7beeac4..1834b27c3 100644 --- a/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts +++ b/packages/web/lib/keyboardShortcuts/useKeyboardShortcuts.ts @@ -61,6 +61,10 @@ export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => { const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping) + const metaPressed = useCallback(() => { + return keys['meta'] === true + }, [keys]) + const applyCommands = useCallback( (updatedKeys: KeyPressed, disabled?: boolean) => { let commandApplied = false @@ -69,6 +73,7 @@ export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => { return } const tempState = { ...updatedKeys } + const arePressed = command.shortcutKeys.every((key) => { const aliases = key.split('|') for (const k of aliases) { @@ -107,6 +112,10 @@ export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => { if (keys[key] === undefined) return if (keys[key] === false) { + if (key === 'k' && metaPressed()) { + // not setting the K value because meta is already pressed + return + } setKeys({ type: ActionType.SET_KEY_DOWN, key }) } const commandApplied = applyCommands( From 808e3ec8d65e9317879db8227adb4cf0b6c27c45 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 11 Jul 2022 14:58:04 -0700 Subject: [PATCH 5/7] Add archive to command bar --- packages/web/pages/[username]/[slug]/index.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/web/pages/[username]/[slug]/index.tsx b/packages/web/pages/[username]/[slug]/index.tsx index 20ea8cf16..8320fcd16 100644 --- a/packages/web/pages/[username]/[slug]/index.tsx +++ b/packages/web/pages/[username]/[slug]/index.tsx @@ -142,17 +142,24 @@ export default function Home(): JSX.Element { { id: 'open', section: 'Article', - name: 'Open original article page', + name: 'Open original article', shortcut: ['o'], perform: () => actionHandler('openOriginalArticle') }, { id: 'back', - section: 'Article', - name: 'Back to library', + section: 'Navigation', + name: 'Return to library', shortcut: ['u'], perform: () => router.push(`/home`), }, + { + id: 'archive', + section: 'Article', + name: 'Archive current item', + shortcut: ['e'], + perform: () => actionHandler('archive'), + }, { id: 'highlight', section: 'Article', From ff6fcd15c476cdec98c580236252f5170a1c6ebb Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 11 Jul 2022 15:47:42 -0700 Subject: [PATCH 6/7] Reduce rerenders of the kbar items --- .../web/components/patterns/PrimaryHeader.tsx | 22 +------------------ packages/web/lib/hooks/useReaderSettings.tsx | 2 +- .../web/pages/[username]/[slug]/index.tsx | 12 +++++----- packages/web/pages/_app.tsx | 17 ++++++++++++++ 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/packages/web/components/patterns/PrimaryHeader.tsx b/packages/web/components/patterns/PrimaryHeader.tsx index e15c039e7..8a0cd80b8 100644 --- a/packages/web/components/patterns/PrimaryHeader.tsx +++ b/packages/web/components/patterns/PrimaryHeader.tsx @@ -1,7 +1,7 @@ import { Box, HStack } from '../elements/LayoutPrimitives' import { OmnivoreNameLogo } from './../elements/images/OmnivoreNameLogo' import { DropdownMenu, HeaderDropdownAction } from './../patterns/DropdownMenu' -import { darkenTheme, lightenTheme, updateTheme } from '../../lib/themeUpdater' +import { updateTheme } from '../../lib/themeUpdater' import { AvatarDropdown } from './../elements/AvatarDropdown' import { ThemeId } from './../tokens/stitches.config' import { useCallback, useEffect, useState } from 'react' @@ -10,7 +10,6 @@ import { useKeyboardShortcuts } from '../../lib/keyboardShortcuts/useKeyboardSho import { primaryCommands } from '../../lib/keyboardShortcuts/navigationShortcuts' import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery' import { setupAnalytics } from '../../lib/analytics' -import { useRegisterActions } from 'kbar' type HeaderProps = { user?: UserBasicData @@ -44,25 +43,6 @@ export function PrimaryHeader(props: HeaderProps): JSX.Element { }) ) - useRegisterActions([ - { - id: 'lightTheme', - section: 'Preferences', - name: 'Change theme (light) ', - shortcut: ['v', 'l'], - keywords: 'light theme', - perform: () => lightenTheme(), - }, - { - id: 'darkTheme', - section: 'Preferences', - name: 'Change theme (dark) ', - shortcut: ['v', 'd'], - keywords: 'dark theme', - perform: () => darkenTheme(), - }, - ]) - const initAnalytics = useCallback(() => { setupAnalytics(props.user) }, [props.user]) diff --git a/packages/web/lib/hooks/useReaderSettings.tsx b/packages/web/lib/hooks/useReaderSettings.tsx index 8dd7cb8d9..7af7745f5 100644 --- a/packages/web/lib/hooks/useReaderSettings.tsx +++ b/packages/web/lib/hooks/useReaderSettings.tsx @@ -130,7 +130,7 @@ export const useReaderSettings = (): ReaderSettings => { shortcut: ['l'], perform: () => setShowSetLabelsModal(true), }, - ], [fontSize, marginWidth, setFontSize, setMarginWidth]) + ], []) return { preferencesData, diff --git a/packages/web/pages/[username]/[slug]/index.tsx b/packages/web/pages/[username]/[slug]/index.tsx index 8320fcd16..a4cbe75d6 100644 --- a/packages/web/pages/[username]/[slug]/index.tsx +++ b/packages/web/pages/[username]/[slug]/index.tsx @@ -147,8 +147,8 @@ export default function Home(): JSX.Element { perform: () => actionHandler('openOriginalArticle') }, { - id: 'back', - section: 'Navigation', + id: 'back_home', + section: 'Article', name: 'Return to library', shortcut: ['u'], perform: () => router.push(`/home`), @@ -166,8 +166,7 @@ export default function Home(): JSX.Element { name: 'Highlight selected text', shortcut: ['h'], perform: () => { - const event = new Event('highlight'); - document.dispatchEvent(event); + document.dispatchEvent(new Event('highlight')); }, }, { @@ -176,11 +175,10 @@ export default function Home(): JSX.Element { name: 'Highlight selected text and add a note', shortcut: ['n'], perform: () => { - const event = new Event('annotate'); - document.dispatchEvent(event); + document.dispatchEvent(new Event('annotate')); }, }, - ], [article]) + ], []) if (articleFetchError && articleFetchError.indexOf('NOT_FOUND') > -1) { router.push('/404') diff --git a/packages/web/pages/_app.tsx b/packages/web/pages/_app.tsx index c5eb717b8..1c819a8e9 100644 --- a/packages/web/pages/_app.tsx +++ b/packages/web/pages/_app.tsx @@ -16,6 +16,7 @@ import { KBarSearch, } from 'kbar' import { animatorStyle, KBarResultsComponents, searchStyle } from '../components/elements/KBar' +import { darkenTheme, lightenTheme } from '../lib/themeUpdater' TopBarProgress.config({ barColors: { @@ -36,6 +37,22 @@ const generateActions = (router: NextRouter) => { keywords: 'go home', perform: () => router?.push('/home'), }, + { + id: 'lightTheme', + section: 'Preferences', + name: 'Change theme (light) ', + shortcut: ['v', 'l'], + keywords: 'light theme', + perform: () => lightenTheme(), + }, + { + id: 'darkTheme', + section: 'Preferences', + name: 'Change theme (dark) ', + shortcut: ['v', 'd'], + keywords: 'dark theme', + perform: () => darkenTheme(), + }, ] return defaultActions From 1965146e9fec358002632ad6f6ce9e897baab14b Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 11 Jul 2022 16:01:36 -0700 Subject: [PATCH 7/7] Clean up highlight bar trash icon --- .../web/components/patterns/HighlightBar.tsx | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/web/components/patterns/HighlightBar.tsx b/packages/web/components/patterns/HighlightBar.tsx index 419b19ca3..b454cade3 100644 --- a/packages/web/components/patterns/HighlightBar.tsx +++ b/packages/web/components/patterns/HighlightBar.tsx @@ -8,7 +8,7 @@ import { Button } from '../elements/Button' import { HStack, Box } from '../elements/LayoutPrimitives' import { TrashIcon } from '../elements/images/TrashIcon' import { PenWithColorIcon } from '../elements/images/PenWithColorIcon' -import { Note } from 'phosphor-react' +import { Note, Trash, TrashSimple } from 'phosphor-react' type PageCoordinates = { pageX: number @@ -131,12 +131,26 @@ function BarContent(props: HighlightBarProps): JSX.Element { style="plainIcon" title="Remove Highlight" onClick={() => props.handleButtonClick('delete')} - css={{ color: '$readerFont', height: '100%', m: 0, p: 0, pt: '6px'}} + css={{ color: '$readerFont', height: '100%', m: 0, p: 0 }} > - + + + + Delete + + )}