Merge pull request #2043 from omnivore-app/fix/web-reader-labels

Clean up keyboard commands functionalities and modal, add tooltips
This commit is contained in:
Jackson Harper 2023-04-14 12:16:44 +08:00 committed by GitHub
commit a6560ac9a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 234 additions and 81 deletions

View file

@ -106,8 +106,8 @@ const textVariants = {
},
shareTitle: {
fontSize: '$1',
fontWeight: '600',
color: '$grayText',
fontWeight: '700',
color: '$grayTextContrast',
},
shareSubtitle: {
fontSize: '$1',

View file

@ -15,19 +15,19 @@ export function ReaderDropdownMenu(props: DropdownMenuProps): JSX.Element {
<Dropdown triggerElement={props.triggerElement}>
<DropdownOption
onSelect={() => props.articleActionHandler('archive')}
title="Archive"
title="Archive (e)"
/>
<DropdownOption
onSelect={() => props.articleActionHandler('setLabels')}
title="Edit Labels"
title="Edit Labels (l)"
/>
<DropdownOption
onSelect={() => props.articleActionHandler('showEditModal')}
title="Edit Info"
title="Edit Info (i)"
/>
<DropdownOption
onSelect={() => props.articleActionHandler('delete')}
title="Delete"
title="Remove (#)"
/>
<DropdownSeparator />
<DropdownOption

View file

@ -3,6 +3,7 @@ import {
ModalRoot,
ModalContent,
ModalOverlay,
ModalTitleBar,
} from '../elements/ModalPrimitives'
import type { KeyboardCommand } from '../../lib/keyboardShortcuts/useKeyboardShortcuts'
import { HStack, VStack, Box } from '../elements/LayoutPrimitives'
@ -22,56 +23,173 @@ type KeyboardShortcutListModalProps = {
onOpenChange: (open: boolean) => void
}
const libraryItemCommands = () => {
return [
{
shortcutKeys: ['e'],
actionDescription: 'Toggle archive status',
shortcutKeyDescription: 'e',
callback: () => {},
},
{
actionDescription: 'Remove item',
shortcutKeys: ['#'],
shortcutKeyDescription: '#',
callback: () => {},
},
{
actionDescription: 'Edit item labels',
shortcutKeys: ['l'],
shortcutKeyDescription: 'l',
callback: () => {},
},
{
actionDescription: 'Mark item as read',
shortcutKeys: ['m', 'r'],
shortcutKeyDescription: 'm then r',
callback: () => {},
},
{
actionDescription: 'Mark item as unread',
shortcutKeys: ['m', 'u'],
shortcutKeyDescription: 'm then u',
callback: () => {},
},
]
}
const readerCommands = () => {
return [
{
shortcutKeys: ['e'],
actionDescription: 'Toggle archive status',
shortcutKeyDescription: 'e',
callback: () => {},
},
{
actionDescription: 'Open original article',
shortcutKeys: ['o'],
shortcutKeyDescription: 'o',
callback: () => {},
},
{
actionDescription: 'Return to library',
shortcutKeys: ['u'],
shortcutKeyDescription: 'u',
callback: () => {},
},
{
actionDescription: 'Archive current item',
shortcutKeys: ['e'],
shortcutKeyDescription: 'e',
callback: () => {},
},
{
actionDescription: 'Mark current item as read',
shortcutKeys: ['m', 'r'],
shortcutKeyDescription: 'm then r',
callback: () => {},
},
{
actionDescription: 'Delete current item',
shortcutKeys: ['#'],
shortcutKeyDescription: '#',
callback: () => {},
},
{
actionDescription: 'Highlight selected text',
shortcutKeys: ['h'],
shortcutKeyDescription: 'h',
callback: () => {},
},
{
actionDescription: 'Scroll to next highlight',
shortcutKeys: ['j'],
shortcutKeyDescription: 'j',
callback: () => {},
},
{
actionDescription: 'Scroll to previous highlight',
shortcutKeys: ['k'],
shortcutKeyDescription: 'k',
callback: () => {},
},
{
actionDescription: 'Open Notebook',
shortcutKeys: ['t'],
shortcutKeyDescription: 't',
callback: () => {},
},
{
actionDescription: 'Edit Info',
shortcutKeys: ['i'],
shortcutKeyDescription: 'i',
callback: () => {},
},
]
}
export function KeyboardShortcutListModal(
props: KeyboardShortcutListModalProps
): JSX.Element {
return (
<ModalRoot defaultOpen onOpenChange={props.onOpenChange}>
<ModalOverlay />
<ModalContent css={{ overflow: 'auto', bg: '$grayBase' }}>
<VStack>
<HStack
distribution="between"
<ModalContent
css={{
bg: '$grayBg',
px: '20px',
minWidth: '650px',
minHeight: '430px',
height: '430px',
}}
onInteractOutside={() => {
// remove focus from modal
;(document.activeElement as HTMLElement).blur()
}}
>
<VStack
distribution="start"
css={{
py: '20px',
gap: '10px',
width: '100%',
height: '100%',
}}
>
<ModalTitleBar
title="Keyboard Shortcuts"
onOpenChange={props.onOpenChange}
/>
<VStack
distribution="start"
css={{
pt: '$2',
px: '$1',
width: '100%',
maxHeight: '100%',
overflow: 'scroll',
}}
>
<StyledText css={{ my: '$2', mx: 0 }} style="modalHeadline">
Keyboard Shortcuts
</StyledText>
<Button
css={{ pt: '$2' }}
style="ghost"
onClick={() => {
props.onOpenChange(false)
}}
>
<CrossIcon
size={20}
strokeColor={theme.colors.grayTextContrast.toString()}
/>
</Button>
</HStack>
<ShortcutListSection
title="Navigation"
commands={navigationCommands(undefined)}
/>
<ShortcutListSection
title="Preferences"
commands={primaryCommands(() => {})}
/>
<ShortcutListSection
title="Library"
commands={searchBarCommands(() => {}).concat(
libraryListCommands(() => {})
)}
/>
<ShortcutListSection
title="Highlight Bar"
commands={highlightBarKeyboardCommands(() => {})}
/>
<ShortcutListSection
title="Navigation"
commands={navigationCommands(undefined)}
/>
<ShortcutListSection
title="Preferences"
commands={primaryCommands(() => {})}
/>
<ShortcutListSection
title="Library"
commands={searchBarCommands(() => {})
.concat(libraryListCommands(() => {}))
.concat(libraryItemCommands())}
/>
<ShortcutListSection title="Reader" commands={readerCommands()} />
<ShortcutListSection
title="Highlight Bar"
commands={highlightBarKeyboardCommands(() => {})}
/>
</VStack>
</VStack>
</ModalContent>
</ModalRoot>
@ -89,11 +207,10 @@ function ShortcutListSection(props: ShortcutListSectionProps): JSX.Element {
<StyledText
style="shareTitle"
css={{
pl: '$2',
pt: '$3',
pt: '15px',
borderTop: '1px solid $grayBorder',
width: '100%',
my: '$2',
my: '15px',
}}
>
{props.title}
@ -102,16 +219,16 @@ function ShortcutListSection(props: ShortcutListSectionProps): JSX.Element {
<Box
css={{
width: '100%',
px: '$3',
px: '15px',
display: 'grid',
gridTemplateColumns: '1fr 1fr',
}}
key={index}
>
<StyledText css={{ my: '$2' }}>
<StyledText css={{ my: '10px' }}>
{command.actionDescription}
</StyledText>
<StyledText css={{ my: '$2' }}>
<StyledText css={{ my: '10px' }}>
{command.shortcutKeyDescription}
</StyledText>
</Box>

View file

@ -75,7 +75,7 @@ export function ArticleActionsMenu(
onClick={() => readerSettings.setShowSetLabelsModal(true)}
>
<TooltipWrapped
tooltipContent="Edit labels"
tooltipContent="Edit labels (l)"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<SpanBox ref={displaySettingsButtonRef}>
@ -128,7 +128,7 @@ export function ArticleActionsMenu(
}}
>
<TooltipWrapped
tooltipContent="View Highlights"
tooltipContent="View Notebook (t)"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<Notebook
@ -150,7 +150,7 @@ export function ArticleActionsMenu(
}}
>
<TooltipWrapped
tooltipContent="Edit title & description"
tooltipContent="Edit Info (i)"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<Info size={24} color={theme.colors.thHighContrast.toString()} />
@ -173,7 +173,7 @@ export function ArticleActionsMenu(
}}
>
<TooltipWrapped
tooltipContent="Delete"
tooltipContent="Remove (#)"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<Trash size={24} color={theme.colors.thHighContrast.toString()} />
@ -190,7 +190,7 @@ export function ArticleActionsMenu(
}}
>
<TooltipWrapped
tooltipContent="Archive"
tooltipContent="Archive (e)"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<ArchiveBox
@ -205,7 +205,7 @@ export function ArticleActionsMenu(
onClick={() => props.articleActionHandler('unarchive')}
>
<TooltipWrapped
tooltipContent="Unarchive"
tooltipContent="Unarchive (u)"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<Tray size={24} color={theme.colors.thHighContrast.toString()} />

View file

@ -228,7 +228,7 @@ function BasicSettings(props: SettingsProps): JSX.Element {
props.readerSettings.setMarginWidth(290)
props.readerSettings.setLineHeight(150)
props.readerSettings.actionHandler('resetReaderSettings')
showSuccessToast('Display settings reset', {
showSuccessToast('Reader Preferences Reset', {
position: 'bottom-right',
})
}}

View file

@ -57,7 +57,7 @@ export function VerticalArticleActionsMenu(
}}
>
<TooltipWrapped
tooltipContent="View Highlights"
tooltipContent="Open Notebook (t)"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<Notebook
@ -79,7 +79,7 @@ export function VerticalArticleActionsMenu(
}}
>
<TooltipWrapped
tooltipContent="Edit title & description"
tooltipContent="Edit Info (i)"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<Info size={24} color={theme.colors.thHighContrast.toString()} />
@ -100,7 +100,7 @@ export function VerticalArticleActionsMenu(
}}
>
<TooltipWrapped
tooltipContent="Delete"
tooltipContent="Remove (#)"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<Trash size={24} color={theme.colors.thHighContrast.toString()} />
@ -117,7 +117,7 @@ export function VerticalArticleActionsMenu(
}}
>
<TooltipWrapped
tooltipContent="Archive"
tooltipContent="Archive (e)"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<ArchiveBox
@ -136,7 +136,7 @@ export function VerticalArticleActionsMenu(
}}
>
<TooltipWrapped
tooltipContent="Unarchive"
tooltipContent="Unarchive (e)"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<Tray size={24} color={theme.colors.thHighContrast.toString()} />
@ -152,7 +152,7 @@ export function VerticalArticleActionsMenu(
}}
>
<TooltipWrapped
tooltipContent="Edit title & description"
tooltipContent="Edit Info (i)"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<TextAa size={24} color={theme.colors.thHighContrast.toString()} />

View file

@ -78,8 +78,9 @@ export function HomeFeedContainer(): JSX.Element {
const gridContainerRef = useRef<HTMLDivElement>(null)
const [labelsTarget, setLabelsTarget] =
useState<LibraryItem | undefined>(undefined)
const [labelsTarget, setLabelsTarget] = useState<LibraryItem | undefined>(
undefined
)
const [showAddLinkModal, setShowAddLinkModal] = useState(false)
const [showEditTitleModal, setShowEditTitleModal] = useState(false)
@ -460,7 +461,7 @@ export function HomeFeedContainer(): JSX.Element {
createAction({
section: 'Library',
name: 'Remove item',
shortcut: ['r'],
shortcut: ['#'],
perform: () => handleCardAction('delete', activeItem),
}),
createAction({
@ -472,13 +473,16 @@ export function HomeFeedContainer(): JSX.Element {
createAction({
section: 'Library',
name: 'Mark item as read',
shortcut: ['Shift', 'i'],
perform: () => handleCardAction('mark-read', activeItem),
shortcut: ['m', 'r'],
perform: () => {
console.log('mark read action')
handleCardAction('mark-read', activeItem)
},
}),
createAction({
section: 'Library',
name: 'Mark item as unread',
shortcut: ['Shift', 'u'],
shortcut: ['m', 'u'],
perform: () => handleCardAction('mark-unread', activeItem),
}),
]

View file

@ -103,7 +103,7 @@ function ControlButtonBox(props: ReaderHeaderProps): JSX.Element {
props.showDisplaySettingsModal(true)
}}
>
<TooltipWrapped tooltipContent="Adjust Display Settings">
<TooltipWrapped tooltipContent="Reader Preferences (d)">
<TextAa size={25} color={theme.colors.thHighContrast.toString()} />
</TooltipWrapped>
</Button>

View file

@ -194,7 +194,7 @@ export const useReaderSettings = (): ReaderSettings => {
{
id: 'display_settings',
section: 'Article',
name: 'Display settings',
name: 'Reader Preferences',
shortcut: ['d'],
perform: () => setShowEditDisplaySettingsModal(true),
},

View file

@ -102,17 +102,34 @@ export default function Home(): JSX.Element {
linkId: article.id,
archived: true,
}).then((res) => {
if (res) {
showSuccessToast('Link archived', { position: 'bottom-right' })
} else {
// todo: revalidate or put back in cache?
showErrorToast('Error archiving link', {
if (!res) {
showErrorToast('Error archiving', {
position: 'bottom-right',
})
} else {
router.push(`/home`)
}
})
}
break
case 'mark-read':
console.log('marking read: ', article)
if (article) {
articleReadingProgressMutation({
id: article.id,
readingProgressPercent: 100,
readingProgressTopPercent: 100,
readingProgressAnchorIndex: 0,
}).then((res) => {
if (!res) {
// todo: revalidate or put back in cache?
showErrorToast('Error marking as read', {
position: 'bottom-right',
})
} else {
router.push(`/home`)
}
})
router.push(`/home`)
}
break
case 'delete':
@ -153,12 +170,18 @@ export default function Home(): JSX.Element {
actionHandler('delete')
}
const markRead = () => {
actionHandler('mark-read')
}
document.addEventListener('archive', archive)
document.addEventListener('delete', deletePage)
document.addEventListener('mark-read', markRead)
document.addEventListener('openOriginalArticle', openOriginalArticle)
return () => {
document.removeEventListener('archive', archive)
document.removeEventListener('mark-read', markRead)
document.removeEventListener('openOriginalArticle', openOriginalArticle)
}
}, [actionHandler])
@ -224,6 +247,15 @@ export default function Home(): JSX.Element {
document.dispatchEvent(new Event('archive'))
},
},
{
id: 'mark_read',
section: 'Article',
name: 'Mark current item as read',
shortcut: ['m', 'r'],
perform: () => {
document.dispatchEvent(new Event('mark-read'))
},
},
{
id: 'delete',
section: 'Article',
@ -281,7 +313,7 @@ export default function Home(): JSX.Element {
{
id: 'edit_title',
section: 'Article',
name: 'Edit title and description',
name: 'Edit Info',
shortcut: ['i'],
perform: () => setShowEditModal(true),
},