mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
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:
commit
a6560ac9a8
10 changed files with 234 additions and 81 deletions
|
|
@ -106,8 +106,8 @@ const textVariants = {
|
|||
},
|
||||
shareTitle: {
|
||||
fontSize: '$1',
|
||||
fontWeight: '600',
|
||||
color: '$grayText',
|
||||
fontWeight: '700',
|
||||
color: '$grayTextContrast',
|
||||
},
|
||||
shareSubtitle: {
|
||||
fontSize: '$1',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()} />
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
})
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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()} />
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ export const useReaderSettings = (): ReaderSettings => {
|
|||
{
|
||||
id: 'display_settings',
|
||||
section: 'Article',
|
||||
name: 'Display settings',
|
||||
name: 'Reader Preferences',
|
||||
shortcut: ['d'],
|
||||
perform: () => setShowEditDisplaySettingsModal(true),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue