From 3b6f96b7c79d717bbbceb9b4d87552552b8adfe5 Mon Sep 17 00:00:00 2001 From: creep Date: Fri, 3 Nov 2023 22:09:56 +0800 Subject: [PATCH 01/30] chore: remove tagPrefix of publisher --- forge.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/forge.config.js b/forge.config.js index 2009130..5bc6916 100644 --- a/forge.config.js +++ b/forge.config.js @@ -52,7 +52,6 @@ module.exports = { }, prerelease: true, draft: true, - tagPrefix: '', }, }, ], From cd0870f8447bd5d29bbdcc9195888c003b8b3a2e Mon Sep 17 00:00:00 2001 From: creep Date: Sat, 4 Nov 2023 00:10:39 +0800 Subject: [PATCH 02/30] feat(reader): add keyboard shortcuts help panel --- src/i18n/en-US.json | 19 +++ src/i18n/zh-CN.json | 19 +++ src/renderer/apps/reader/index.tsx | 20 +-- .../apps/reader/keyboard-shortcuts-panel.tsx | 131 ++++++++++++++++++ src/renderer/apps/reader/use-keyboard.ts | 42 ++++-- src/renderer/hooks/keyboard.ts | 2 +- src/renderer/store/reader.ts | 14 ++ 7 files changed, 225 insertions(+), 22 deletions(-) create mode 100644 src/renderer/apps/reader/keyboard-shortcuts-panel.tsx diff --git a/src/i18n/en-US.json b/src/i18n/en-US.json index 1c341ef..71c0457 100644 --- a/src/i18n/en-US.json +++ b/src/i18n/en-US.json @@ -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" + } } }, diff --git a/src/i18n/zh-CN.json b/src/i18n/zh-CN.json index 5592b4f..6eb34da 100644 --- a/src/i18n/zh-CN.json +++ b/src/i18n/zh-CN.json @@ -26,6 +26,25 @@ "subscribe": "订阅", "close": "关闭" } + }, + + "keyboard": { + "title": "快捷键", + "keys": { + "sw": "切换显示快捷键帮助", + "gi": "转到未读列表", + "gs": "转到标星列表", + "ga": "转到全部列表", + "selectAll": "全选", + "deselect": "取消全选", + "x": "选中/取消选中", + "s": "切换标星", + "j": "下一条", + "k": "上一条", + "o": "打开条目", + "u": "关闭条目", + "v": "浏览器中打开" + } } }, diff --git a/src/renderer/apps/reader/index.tsx b/src/renderer/apps/reader/index.tsx index 403c9c5..a8d9472 100644 --- a/src/renderer/apps/reader/index.tsx +++ b/src/renderer/apps/reader/index.tsx @@ -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' @@ -24,15 +25,18 @@ export default function Reader() { useKeyboard() return ( -
-
-
- + <> +
+
+
+ +
+
+
+
-
- -
-
+ + ) } diff --git a/src/renderer/apps/reader/keyboard-shortcuts-panel.tsx b/src/renderer/apps/reader/keyboard-shortcuts-panel.tsx new file mode 100644 index 0000000..4d21dfe --- /dev/null +++ b/src/renderer/apps/reader/keyboard-shortcuts-panel.tsx @@ -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) => ( + + + {k} + + {index !== v.length - 1 ? then : null} + + )) + return ( +
+ + {keys} + + {' '} + : + {' '} + {item.text} +
+ ) + }) + + return ( + readerStore.closeKeyboardPanel()} + > + + + + {intl.get('reader.keyboard.title')} + + +
+ {shortcuts} +
+
+
+
+
+ ) +} + +export default observer(KeyboardShortcutsPanel) diff --git a/src/renderer/apps/reader/use-keyboard.ts b/src/renderer/apps/reader/use-keyboard.ts index 36ad543..d70900a 100644 --- a/src/renderer/apps/reader/use-keyboard.ts +++ b/src/renderer/apps/reader/use-keyboard.ts @@ -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' @@ -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')) diff --git a/src/renderer/hooks/keyboard.ts b/src/renderer/hooks/keyboard.ts index ec44619..55a9f96 100644 --- a/src/renderer/hooks/keyboard.ts +++ b/src/renderer/hooks/keyboard.ts @@ -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) { diff --git a/src/renderer/store/reader.ts b/src/renderer/store/reader.ts index 42f2104..545cea1 100644 --- a/src/renderer/store/reader.ts +++ b/src/renderer/store/reader.ts @@ -25,6 +25,8 @@ export class ReaderStore { opened = false + showKeyboardPanel = false + constructor(rootStore: RootStore) { makeAutoObservable(this, { rootStore: false, @@ -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 + } } From 232dc983ed2211881a433c261849c8497c7620b1 Mon Sep 17 00:00:00 2001 From: creep Date: Sat, 4 Nov 2023 00:15:18 +0800 Subject: [PATCH 03/30] docs: add ? shortcuts --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 12b6168..e868e24 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ I wish to develop **emo** as an **all in one** desktop application. Now, it incl ![Reader screenshot](./docs/images/screenshot.png) ### Keyboard shortcus +- ?: Toggle keyboard shortcus help - g then i: Go to Unread items - g then s: Go to Starred items - g then a: Go to All items From 7b42cb47fc17a27f86f8990c13809b3739887a64 Mon Sep 17 00:00:00 2001 From: creep Date: Sun, 5 Nov 2023 21:01:56 +0800 Subject: [PATCH 04/30] feat(reader): add copy url function --- src/i18n/en-US.json | 6 +- src/i18n/zh-CN.json | 6 +- .../apps/reader/SidePanel/feed-item.tsx | 26 ++++++--- .../reader/components/toolbar/copy-button.tsx | 58 +++++++++++++++++++ .../apps/reader/components/toolbar/index.tsx | 1 + 5 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 src/renderer/apps/reader/components/toolbar/copy-button.tsx diff --git a/src/i18n/en-US.json b/src/i18n/en-US.json index 71c0457..bb44aae 100644 --- a/src/i18n/en-US.json +++ b/src/i18n/en-US.json @@ -11,7 +11,11 @@ "markRead": "Mark as read", "markUnread": "Mark as unread", "refresh": "Refresh", - "openExternal": "Open in browser" + "openExternal": "Open in browser", + "copy": { + "title": "Copy url", + "success": "Copied" + } }, "form": { diff --git a/src/i18n/zh-CN.json b/src/i18n/zh-CN.json index 6eb34da..7f5af7d 100644 --- a/src/i18n/zh-CN.json +++ b/src/i18n/zh-CN.json @@ -11,7 +11,11 @@ "markRead": "标记为已读", "markUnread": "标记为未读", "refresh": "刷新", - "openExternal": "在浏览器中打开" + "openExternal": "在浏览器中打开", + "copy": { + "title": "复制链接", + "success": "已复制到剪贴板" + } }, "form": { diff --git a/src/renderer/apps/reader/SidePanel/feed-item.tsx b/src/renderer/apps/reader/SidePanel/feed-item.tsx index bc980d2..7ad69e2 100644 --- a/src/renderer/apps/reader/SidePanel/feed-item.tsx +++ b/src/renderer/apps/reader/SidePanel/feed-item.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react-lite' import { makeStyles, tokens } from '@fluentui/react-components' import { store } from '@/renderer/store' -import { DeleteButton } from '@/renderer/apps/reader/components/toolbar' +import { DeleteButton, CopyButton } from '@/renderer/apps/reader/components/toolbar' import { Feed } from '@/types/reader' const { readerStore } = store @@ -29,7 +29,12 @@ const useStyles = makeStyles({ }) function Item({ data }: Props) { - const { id, title, unreadCount = 0 } = data + const { + id, + title, + unreadCount = 0, + url, + } = data const active = readerStore.tab === id const styles = useStyles() @@ -63,11 +68,18 @@ function Item({ data }: Props) {
- +
+ + +
diff --git a/src/renderer/apps/reader/components/toolbar/copy-button.tsx b/src/renderer/apps/reader/components/toolbar/copy-button.tsx new file mode 100644 index 0000000..ecfe5c4 --- /dev/null +++ b/src/renderer/apps/reader/components/toolbar/copy-button.tsx @@ -0,0 +1,58 @@ +import intl from 'react-intl-universal' +import { CopyRegular } from '@fluentui/react-icons' +import { + useId, + useToastController, + Toast, + ToastIntent, + ToastTitle, + Toaster, +} from '@fluentui/react-components' +import Button from './button' + +type Props = { + className: string + fontSize: number + content: string +} + +export default function CopyButton(props: Props) { + const { className, fontSize = 18, content } = props + + const toasterId = useId('toaster') + const { dispatchToast } = useToastController(toasterId) + + const notify = (text: string, intent: ToastIntent) => dispatchToast( + + + {text} + + , + { intent }, + ) + + const onClick = async () => { + try { + await navigator.clipboard.writeText(content) + notify(intl.get('reader.action.copy.success'), 'success') + } catch (e) { + notify(`${(e as Error).name}: ${(e as Error).message}`, 'error') + } + } + + return ( + <> +