Skip to content

Commit

Permalink
Merge pull request #5 from xwcoder/feature/keyboard-panel
Browse files Browse the repository at this point in the history
feat(reader): add keyboard shortcuts help panel
  • Loading branch information
xwcoder authored Nov 3, 2023
2 parents 3b6f96b + cd0870f commit 2b8672b
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 22 deletions.
19 changes: 19 additions & 0 deletions src/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@
"subscribe": "Subscribe",
"close": "Close"
}
},

"keyboard": {
"title": "Keyboard shortcuts",
"keys": {
"sw": "Toggle shortcurts help",
"gi": "Go to Unread",
"gs": "Go to Starred",
"ga": "Go to All",
"selectAll": "Select All",
"deselect": "Deselect All",
"x": "Select/Deselect item",
"s": "Toggle star",
"j": "Next item",
"k": "Previous item",
"o": "Open item",
"u": "Close item",
"v": "Open item in browser"
}
}
},

Expand Down
19 changes: 19 additions & 0 deletions src/i18n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@
"subscribe": "订阅",
"close": "关闭"
}
},

"keyboard": {
"title": "快捷键",
"keys": {
"sw": "切换显示快捷键帮助",
"gi": "转到未读列表",
"gs": "转到标星列表",
"ga": "转到全部列表",
"selectAll": "全选",
"deselect": "取消全选",
"x": "选中/取消选中",
"s": "切换标星",
"j": "下一条",
"k": "上一条",
"o": "打开条目",
"u": "关闭条目",
"v": "浏览器中打开"
}
}
},

Expand Down
20 changes: 12 additions & 8 deletions src/renderer/apps/reader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { makeStyles, tokens, shorthands } from '@fluentui/react-components'

import SidePanel from './SidePanel'
import MainPanel from './MainPanel'
import KeyboardShortcutsPanel from './keyboard-shortcuts-panel'

import useKeyboard from './use-keyboard'

Expand All @@ -24,15 +25,18 @@ export default function Reader() {
useKeyboard()

return (
<div className="relative grid grid-cols-[256px_1fr] h-[100%]">
<div className={styles.side}>
<div className="py-5 pr-4">
<SidePanel />
<>
<div className="relative grid grid-cols-[256px_1fr] h-[100%]">
<div className={styles.side}>
<div className="py-5 pr-4">
<SidePanel />
</div>
</div>
<div className={styles.main}>
<MainPanel />
</div>
</div>
<div className={styles.main}>
<MainPanel />
</div>
</div>
<KeyboardShortcutsPanel />
</>
)
}
131 changes: 131 additions & 0 deletions src/renderer/apps/reader/keyboard-shortcuts-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { observer } from 'mobx-react-lite'
import intl from 'react-intl-universal'
import { store } from '@/renderer/store'
import {
Dialog,
DialogSurface,
DialogBody,
DialogTitle,
DialogContent,
makeStyles,
tokens,
} from '@fluentui/react-components'

const { readerStore } = store

const useStyles = makeStyles({
key: {
color: tokens.colorPaletteMarigoldForeground3,
fontWeight: tokens.fontWeightBold,
},
})

function KeyboardShortcutsPanel() {
const { showKeyboardPanel } = readerStore
const styles = useStyles()

const shortcuts = [
{
keys: ['?'],
text: intl.get('reader.keyboard.keys.sw'),
},
{
keys: ['g', 'i'],
text: intl.get('reader.keyboard.keys.gi'),
},
{
keys: ['g', 's'],
text: intl.get('reader.keyboard.keys.gs'),
},
{
keys: ['g', 'a'],
text: intl.get('reader.keyboard.keys.ga'),
},
{
keys: ['*', 'a'],
text: intl.get('reader.keyboard.keys.selectAll'),
},
{
keys: ['*', 'n'],
text: intl.get('reader.keyboard.keys.deselect'),
},
{
keys: ['x'],
text: intl.get('reader.keyboard.keys.x'),
},
{
keys: ['s'],
text: intl.get('reader.keyboard.keys.s'),
},
{
keys: ['j'],
text: intl.get('reader.keyboard.keys.j'),
},
{
keys: ['k'],
text: intl.get('reader.keyboard.keys.k'),
},
{
keys: ['o'],
text: intl.get('reader.keyboard.keys.o'),
},
{
keys: ['u'],
text: intl.get('reader.keyboard.keys.u'),
},
{
keys: ['v'],
text: intl.get('reader.keyboard.keys.v'),
},
].map((item) => {
const keys = item.keys.map((k, index, v) => (
<span
key={k}
>
<span
className={styles.key}
>
{k}
</span>
{index !== v.length - 1 ? <span> then </span> : null}
</span>
))
return (
<div
key={item.keys.join('')}
>
<span
className="inline-block w-14 text-right"
>
{keys}
</span>
{' '}
:
{' '}
{item.text}
</div>
)
})

return (
<Dialog
open={showKeyboardPanel}
onOpenChange={() => readerStore.closeKeyboardPanel()}
>
<DialogSurface>
<DialogBody>
<DialogTitle>
{intl.get('reader.keyboard.title')}
</DialogTitle>
<DialogContent>
<div className="grid grid-cols-2 gap-y-2 gap-x-5">
{shortcuts}
</div>
</DialogContent>
</DialogBody>
</DialogSurface>
</Dialog>
)
}

export default observer(KeyboardShortcutsPanel)
42 changes: 29 additions & 13 deletions src/renderer/apps/reader/use-keyboard.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { makeKeyboard } from '@/renderer/hooks/keyboard'
import { makeKeyboard, Handler } from '@/renderer/hooks/keyboard'
import { store } from '@/renderer/store'
import { openExternal } from '@/utils/browser/shell'

Expand All @@ -8,22 +8,38 @@ const { useKeyboard, register } = makeKeyboard({ app: 'reader' })

export default useKeyboard

register(['j'], () => readerStore.moveNext())
register(['k'], () => readerStore.movePrev())
const iregister = (k: string[], fn: Handler) => {
const isLocked = () => readerStore.showKeyboardPanel

register(['o'], () => readerStore.open())
register(['u'], () => readerStore.fold())
register(['s'], () => readerStore.toggleStarred())
register(['v'], () => {
const handler: Handler = (event, ks) => {
if (isLocked()) {
return
}

fn(event, ks)
}

register(k, handler)
}

register(['shift', '?'], () => readerStore.toggleKeyboardPanel())

iregister(['j'], () => readerStore.moveNext())
iregister(['k'], () => readerStore.movePrev())

iregister(['o'], () => readerStore.open())
iregister(['u'], () => readerStore.fold())
iregister(['s'], () => readerStore.toggleStarred())
iregister(['v'], () => {
if (readerStore.opened) {
openExternal(readerStore.activeArticle?.url!)
}
})

register(['x'], () => readerStore.toggleSelected())
register(['*', 'a'], () => readerStore.selectAll())
register(['*', 'n'], () => readerStore.deselectAll())
iregister(['x'], () => readerStore.toggleSelected())
iregister(['*', 'a'], () => readerStore.selectAll())
iregister(['*', 'n'], () => readerStore.deselectAll())

register(['g', 's'], () => readerStore.changeTab('starred'))
register(['g', 'a'], () => readerStore.changeTab('all'))
register(['g', 'i'], () => readerStore.changeTab('unread'))
iregister(['g', 's'], () => readerStore.changeTab('starred'))
iregister(['g', 'a'], () => readerStore.changeTab('all'))
iregister(['g', 'i'], () => readerStore.changeTab('unread'))
2 changes: 1 addition & 1 deletion src/renderer/hooks/keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect } from 'react'
import { store } from '@/renderer/store'
import { Apps } from '@/types/common'

type Handler = (event: KeyboardEvent, k: string[]) => any
export type Handler = (event: KeyboardEvent, k: string[]) => any

const equal = (a: string[], b: string[]) => {
if (!a.length || !b.length || a.length !== b.length) {
Expand Down
14 changes: 14 additions & 0 deletions src/renderer/store/reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export class ReaderStore {

opened = false

showKeyboardPanel = false

constructor(rootStore: RootStore) {
makeAutoObservable(this, {
rootStore: false,
Expand Down Expand Up @@ -319,4 +321,16 @@ export class ReaderStore {
this.opened = false
setTimeout(() => this.scrollIntoView())
}

openKeyboardPanel() {
this.showKeyboardPanel = true
}

closeKeyboardPanel() {
this.showKeyboardPanel = false
}

toggleKeyboardPanel() {
this.showKeyboardPanel = !this.showKeyboardPanel
}
}

0 comments on commit 2b8672b

Please sign in to comment.