mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-02-07 23:38:41 +01:00
keyboard shortcuts
This commit is contained in:
parent
3578721d6c
commit
d4acdfb59c
3
dist/article/article.css
vendored
3
dist/article/article.css
vendored
@ -60,4 +60,7 @@ article figure figcaption {
|
|||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
color: var(--gray);
|
color: var(--gray);
|
||||||
-webkit-user-modify: read-only;
|
-webkit-user-modify: read-only;
|
||||||
|
}
|
||||||
|
article iframe {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
60
dist/icons/logo-outline-dark.svg
vendored
Normal file
60
dist/icons/logo-outline-dark.svg
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="350" height="350" viewBox="0 0 350 350">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--white: #1f1f1f;
|
||||||
|
--primary: #3f3f3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-1 {
|
||||||
|
clip-path: url(#clip-Web_1280_5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2, .cls-3 {
|
||||||
|
fill: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
stroke: var(--primary);
|
||||||
|
stroke-width: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-4 {
|
||||||
|
fill: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-5 {
|
||||||
|
fill: var(--white);
|
||||||
|
font-size: 167px;
|
||||||
|
font-family: Arial-BoldMT, Arial;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-6 {
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-7 {
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<clipPath id="clip-Web_1280_5">
|
||||||
|
<rect width="350" height="350"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g class="cls-1">
|
||||||
|
<rect class="cls-3" width="350" height="350"/>
|
||||||
|
<g class="cls-2" transform="translate(45 25)">
|
||||||
|
<rect class="cls-6" width="280" height="280" rx="5"/>
|
||||||
|
<rect class="cls-7" x="2.5" y="2.5" width="275" height="275" rx="2.5"/>
|
||||||
|
</g>
|
||||||
|
<rect class="cls-3" width="235" height="235" rx="5" transform="translate(30 85)"/>
|
||||||
|
<g class="cls-2" transform="translate(35 90)">
|
||||||
|
<rect class="cls-6" width="225" height="225" rx="5"/>
|
||||||
|
<rect class="cls-7" x="2.5" y="2.5" width="220" height="220" rx="2.5"/>
|
||||||
|
</g>
|
||||||
|
<rect class="cls-3" width="180" height="180" rx="5" transform="translate(20 150)"/>
|
||||||
|
<rect class="cls-4" width="170" height="170" rx="5" transform="translate(25 155)"/>
|
||||||
|
<text id="F" class="cls-5" transform="translate(59 301)"><tspan x="0" y="0">F</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
60
dist/icons/logo-outline.svg
vendored
Normal file
60
dist/icons/logo-outline.svg
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="350" height="350" viewBox="0 0 350 350">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--white: #fff;
|
||||||
|
--primary: #e1dfdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-1 {
|
||||||
|
clip-path: url(#clip-Web_1280_5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2, .cls-3 {
|
||||||
|
fill: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
stroke: var(--primary);
|
||||||
|
stroke-width: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-4 {
|
||||||
|
fill: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-5 {
|
||||||
|
fill: var(--white);
|
||||||
|
font-size: 167px;
|
||||||
|
font-family: Arial-BoldMT, Arial;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-6 {
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-7 {
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<clipPath id="clip-Web_1280_5">
|
||||||
|
<rect width="350" height="350"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g class="cls-1">
|
||||||
|
<rect class="cls-3" width="350" height="350"/>
|
||||||
|
<g class="cls-2" transform="translate(45 25)">
|
||||||
|
<rect class="cls-6" width="280" height="280" rx="5"/>
|
||||||
|
<rect class="cls-7" x="2.5" y="2.5" width="275" height="275" rx="2.5"/>
|
||||||
|
</g>
|
||||||
|
<rect class="cls-3" width="235" height="235" rx="5" transform="translate(30 85)"/>
|
||||||
|
<g class="cls-2" transform="translate(35 90)">
|
||||||
|
<rect class="cls-6" width="225" height="225" rx="5"/>
|
||||||
|
<rect class="cls-7" x="2.5" y="2.5" width="220" height="220" rx="2.5"/>
|
||||||
|
</g>
|
||||||
|
<rect class="cls-3" width="180" height="180" rx="5" transform="translate(20 150)"/>
|
||||||
|
<rect class="cls-4" width="170" height="170" rx="5" transform="translate(25 155)"/>
|
||||||
|
<text id="F" class="cls-5" transform="translate(59 301)"><tspan x="0" y="0">F</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
0
dist/logo.svg → dist/icons/logo.svg
vendored
0
dist/logo.svg → dist/icons/logo.svg
vendored
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
25
dist/styles.css
vendored
25
dist/styles.css
vendored
@ -277,6 +277,7 @@ nav.menu-on .btn-group .btn.system, nav.item-on .btn-group .btn.system {
|
|||||||
height: calc(100% - 64px);
|
height: calc(100% - 64px);
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
box-shadow: 0 6.4px 14.4px 0 rgba(0,0,0,.132), 0 1.2px 3.6px 0 rgba(0,0,0,.108);
|
box-shadow: 0 6.4px 14.4px 0 rgba(0,0,0,.132), 0 1.2px 3.6px 0 rgba(0,0,0,.108);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
div[role="toolbar"] {
|
div[role="toolbar"] {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -469,12 +470,34 @@ img.favicon {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.side-article-wrapper {
|
.side-article-wrapper, .side-logo-wrapper {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding-top: 32px;
|
padding-top: 32px;
|
||||||
height: calc(100% - 32px);
|
height: calc(100% - 32px);
|
||||||
background: var(--white);
|
background: var(--white);
|
||||||
}
|
}
|
||||||
|
.side-logo-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.side-logo-wrapper > img {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
.side-logo-wrapper > img.dark {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.side-logo-wrapper > img.light {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.side-logo-wrapper > img.dark {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
.side-article-wrapper .article {
|
.side-article-wrapper .article {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
|
@ -15,7 +15,9 @@ type ArticleProps = {
|
|||||||
item: RSSItem
|
item: RSSItem
|
||||||
source: RSSSource
|
source: RSSSource
|
||||||
locale: string
|
locale: string
|
||||||
|
shortcuts: (item: RSSItem, key: string) => void
|
||||||
dismiss: () => void
|
dismiss: () => void
|
||||||
|
offsetItem: (offset: number) => void
|
||||||
toggleHasRead: (item: RSSItem) => void
|
toggleHasRead: (item: RSSItem) => void
|
||||||
toggleStarred: (item: RSSItem) => void
|
toggleStarred: (item: RSSItem) => void
|
||||||
toggleHidden: (item: RSSItem) => void
|
toggleHidden: (item: RSSItem) => void
|
||||||
@ -102,9 +104,23 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
this.props.dismiss()
|
this.props.dismiss()
|
||||||
}
|
}
|
||||||
keyDownHandler = (_, input) => {
|
keyDownHandler = (_, input) => {
|
||||||
if (input.type === "keyDown" && input.key === "Escape") {
|
if (input.type === "keyDown") {
|
||||||
this.shouldRefocus = true
|
switch (input.key) {
|
||||||
this.props.dismiss()
|
case "Escape":
|
||||||
|
this.shouldRefocus = true
|
||||||
|
this.props.dismiss()
|
||||||
|
break
|
||||||
|
case "ArrowLeft":
|
||||||
|
case "ArrowRight":
|
||||||
|
this.props.offsetItem(input.key === "ArrowLeft" ? -1 : 1)
|
||||||
|
break
|
||||||
|
case "l":
|
||||||
|
this.toggleWebpage()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
this.props.shortcuts(this.props.item, input.key)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,6 +136,9 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
})
|
})
|
||||||
this.webview = webview
|
this.webview = webview
|
||||||
webview.focus()
|
webview.focus()
|
||||||
|
let card = document.querySelector(`#refocus>div[data-iid="${this.props.item._id}"]`) as HTMLElement
|
||||||
|
// @ts-ignore
|
||||||
|
if (card) card.scrollIntoViewIfNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentDidUpdate = (prevProps: ArticleProps) => {
|
componentDidUpdate = (prevProps: ArticleProps) => {
|
||||||
@ -131,7 +150,7 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
|
|
||||||
componentWillUnmount = () => {
|
componentWillUnmount = () => {
|
||||||
if (this.shouldRefocus) {
|
if (this.shouldRefocus) {
|
||||||
let refocus = document.querySelector("#refocus>div[tabindex='0']") as HTMLElement
|
let refocus = document.querySelector(`#refocus>div[data-iid="${this.props.item._id}"]`) as HTMLElement
|
||||||
if (refocus) refocus.focus()
|
if (refocus) refocus.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ export interface CardProps {
|
|||||||
feedId: string
|
feedId: string
|
||||||
item: RSSItem
|
item: RSSItem
|
||||||
source: RSSSource
|
source: RSSSource
|
||||||
|
shortcuts: (item: RSSItem, key: string) => void
|
||||||
markRead: (item: RSSItem) => void
|
markRead: (item: RSSItem) => void
|
||||||
contextMenu: (feedId: string, item: RSSItem, e) => void
|
contextMenu: (feedId: string, item: RSSItem, e) => void
|
||||||
showItem: (fid: string, item: RSSItem) => void
|
showItem: (fid: string, item: RSSItem) => void
|
||||||
@ -46,4 +47,8 @@ export class Card extends React.Component<CardProps> {
|
|||||||
this.props.contextMenu(this.props.feedId, this.props.item, e)
|
this.props.contextMenu(this.props.feedId, this.props.item, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
this.props.shortcuts(this.props.item, e.key)
|
||||||
|
}
|
||||||
}
|
}
|
@ -18,6 +18,8 @@ class DefaultCard extends Card {
|
|||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
onMouseUp={this.onMouseUp}
|
onMouseUp={this.onMouseUp}
|
||||||
onMouseDown={event => event.preventDefault()}
|
onMouseDown={event => event.preventDefault()}
|
||||||
|
onKeyDown={this.onKeyDown}
|
||||||
|
data-iid={this.props.item._id}
|
||||||
data-is-focusable>
|
data-is-focusable>
|
||||||
{this.props.item.thumb ? (
|
{this.props.item.thumb ? (
|
||||||
<img className="bg" src={this.props.item.thumb} />
|
<img className="bg" src={this.props.item.thumb} />
|
||||||
|
@ -14,8 +14,11 @@ class ListCard extends Card {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={this.className()}
|
className={this.className()}
|
||||||
onClick={this.onClick} onMouseUp={this.onMouseUp}
|
onClick={this.onClick}
|
||||||
|
onMouseUp={this.onMouseUp}
|
||||||
onMouseDown={event => event.preventDefault()}
|
onMouseDown={event => event.preventDefault()}
|
||||||
|
onKeyDown={this.onKeyDown}
|
||||||
|
data-iid={this.props.item._id}
|
||||||
data-is-focusable>
|
data-is-focusable>
|
||||||
{this.props.item.thumb ? (
|
{this.props.item.thumb ? (
|
||||||
<div className="head"><img src={this.props.item.thumb} /></div>
|
<div className="head"><img src={this.props.item.thumb} /></div>
|
||||||
|
@ -39,6 +39,7 @@ class CardsFeed extends React.Component<FeedProps> {
|
|||||||
key={item._id}
|
key={item._id}
|
||||||
item={item}
|
item={item}
|
||||||
source={this.props.sourceMap[item.source]}
|
source={this.props.sourceMap[item.source]}
|
||||||
|
shortcuts={this.props.shortcuts}
|
||||||
markRead={this.props.markRead}
|
markRead={this.props.markRead}
|
||||||
contextMenu={this.props.contextMenu}
|
contextMenu={this.props.contextMenu}
|
||||||
showItem={this.props.showItem} />
|
showItem={this.props.showItem} />
|
||||||
|
@ -11,6 +11,7 @@ export type FeedProps = FeedReduxProps & {
|
|||||||
viewType: ViewType
|
viewType: ViewType
|
||||||
items: RSSItem[]
|
items: RSSItem[]
|
||||||
sourceMap: Object
|
sourceMap: Object
|
||||||
|
shortcuts: (item: RSSItem, key: string) => void
|
||||||
markRead: (item: RSSItem) => void
|
markRead: (item: RSSItem) => void
|
||||||
contextMenu: (feedId: string, item: RSSItem, e) => void
|
contextMenu: (feedId: string, item: RSSItem, e) => void
|
||||||
loadMore: (feed: RSSFeed) => void
|
loadMore: (feed: RSSFeed) => void
|
||||||
|
@ -15,6 +15,7 @@ class ListFeed extends React.Component<FeedProps> {
|
|||||||
key={item._id}
|
key={item._id}
|
||||||
item={item}
|
item={item}
|
||||||
source={this.props.sourceMap[item.source]}
|
source={this.props.sourceMap[item.source]}
|
||||||
|
shortcuts={this.props.shortcuts}
|
||||||
markRead={this.props.markRead}
|
markRead={this.props.markRead}
|
||||||
contextMenu={this.props.contextMenu}
|
contextMenu={this.props.contextMenu}
|
||||||
showItem={this.props.showItem} />
|
showItem={this.props.showItem} />
|
||||||
|
@ -4,6 +4,7 @@ import { remote } from "electron"
|
|||||||
import { Icon } from "@fluentui/react/lib/Icon"
|
import { Icon } from "@fluentui/react/lib/Icon"
|
||||||
import { AppState } from "../scripts/models/app"
|
import { AppState } from "../scripts/models/app"
|
||||||
import { ProgressIndicator } from "@fluentui/react"
|
import { ProgressIndicator } from "@fluentui/react"
|
||||||
|
import { getWindowBreakpoint } from "../scripts/utils"
|
||||||
|
|
||||||
type NavProps = {
|
type NavProps = {
|
||||||
state: AppState,
|
state: AppState,
|
||||||
@ -37,6 +38,40 @@ class Nav extends React.Component<NavProps, NavState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navShortcutsHandler = (e: KeyboardEvent) => {
|
||||||
|
if (!this.props.state.settings.display) {
|
||||||
|
switch (e.key) {
|
||||||
|
case "F1":
|
||||||
|
this.props.menu()
|
||||||
|
break
|
||||||
|
case "F5":
|
||||||
|
this.fetch()
|
||||||
|
break
|
||||||
|
case "F6":
|
||||||
|
this.props.markAllRead()
|
||||||
|
break
|
||||||
|
case "F7":
|
||||||
|
if (!this.props.state.menu || getWindowBreakpoint())
|
||||||
|
this.props.logs()
|
||||||
|
break
|
||||||
|
case "F8":
|
||||||
|
if (!this.props.state.menu || getWindowBreakpoint())
|
||||||
|
this.props.views()
|
||||||
|
break
|
||||||
|
case "F9":
|
||||||
|
this.props.settings()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
document.addEventListener("keydown", this.navShortcutsHandler)
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener("keydown", this.navShortcutsHandler)
|
||||||
|
}
|
||||||
|
|
||||||
minimize = () => {
|
minimize = () => {
|
||||||
this.state.window.minimize()
|
this.state.window.minimize()
|
||||||
}
|
}
|
||||||
|
@ -16,20 +16,18 @@ type PageProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Page extends React.Component<PageProps> {
|
class Page extends React.Component<PageProps> {
|
||||||
prevItem = (event: React.MouseEvent) => {
|
offsetItem = (event: React.MouseEvent, offset: number) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
this.props.offsetItem(-1)
|
this.props.offsetItem(offset)
|
||||||
}
|
|
||||||
nextItem = (event: React.MouseEvent) => {
|
|
||||||
event.stopPropagation()
|
|
||||||
this.props.offsetItem(1)
|
|
||||||
}
|
}
|
||||||
|
prevItem = (event: React.MouseEvent) => this.offsetItem(event, -1)
|
||||||
|
nextItem = (event: React.MouseEvent) => this.offsetItem(event, 1)
|
||||||
|
|
||||||
render = () => this.props.viewType == ViewType.Cards
|
render = () => this.props.viewType == ViewType.Cards
|
||||||
? (
|
? (
|
||||||
<>
|
<>
|
||||||
{this.props.settingsOn ? null :
|
{this.props.settingsOn ? null :
|
||||||
<div className={"main" + (this.props.menuOn ? " menu-on" : "")}>
|
<div key="card" className={"main" + (this.props.menuOn ? " menu-on" : "")}>
|
||||||
<ArticleSearch />
|
<ArticleSearch />
|
||||||
{this.props.feeds.map(fid => (
|
{this.props.feeds.map(fid => (
|
||||||
<FeedContainer viewType={this.props.viewType} feedId={fid} key={fid} />
|
<FeedContainer viewType={this.props.viewType} feedId={fid} key={fid} />
|
||||||
@ -53,17 +51,24 @@ class Page extends React.Component<PageProps> {
|
|||||||
: (
|
: (
|
||||||
<>
|
<>
|
||||||
{this.props.settingsOn ? null :
|
{this.props.settingsOn ? null :
|
||||||
<div className={"list-main" + (this.props.menuOn ? " menu-on" : "")}>
|
<div key="list" className={"list-main" + (this.props.menuOn ? " menu-on" : "")}>
|
||||||
<ArticleSearch />
|
<ArticleSearch />
|
||||||
<div className="list-feed-container">
|
<div className="list-feed-container">
|
||||||
{this.props.feeds.map(fid => (
|
{this.props.feeds.map(fid => (
|
||||||
<FeedContainer viewType={this.props.viewType} feedId={fid} key={fid} />
|
<FeedContainer viewType={this.props.viewType} feedId={fid} key={fid} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{this.props.itemId && (
|
{this.props.itemId
|
||||||
|
? (
|
||||||
<div className="side-article-wrapper">
|
<div className="side-article-wrapper">
|
||||||
<ArticleContainer itemId={this.props.itemId} />
|
<ArticleContainer itemId={this.props.itemId} />
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<div className="side-logo-wrapper">
|
||||||
|
<img className="light" src="icons/logo-outline.svg" />
|
||||||
|
<img className="dark" src="icons/logo-outline-dark.svg" />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>}
|
</div>}
|
||||||
</>
|
</>
|
||||||
|
@ -22,15 +22,15 @@ class Settings extends React.Component<SettingsProps> {
|
|||||||
|
|
||||||
render = () => this.props.display && (
|
render = () => this.props.display && (
|
||||||
<div className="settings-container">
|
<div className="settings-container">
|
||||||
|
<div className="btn-group" style={{position: "absolute", top: 70, left: "calc(50% - 404px)"}}>
|
||||||
|
<a className={"btn" + (this.props.exitting ? " disabled" : "")} title={intl.get("settings.exit")} onClick={this.props.close}>
|
||||||
|
<Icon iconName="Back" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div className={"settings " + AnimationClassNames.slideUpIn20}>
|
<div className={"settings " + AnimationClassNames.slideUpIn20}>
|
||||||
{this.props.blocked && <div className="loading">
|
{this.props.blocked && <div className="loading">
|
||||||
<Spinner label={intl.get("settings.fetching")} />
|
<Spinner label={intl.get("settings.fetching")} />
|
||||||
</div>}
|
</div>}
|
||||||
<div className="btn-group" style={{position: "absolute", top: 6, left: -64}}>
|
|
||||||
<a className={"btn" + (this.props.exitting ? " disabled" : "")} title={intl.get("settings.exit")} onClick={this.props.close}>
|
|
||||||
<Icon iconName="Back" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<Pivot>
|
<Pivot>
|
||||||
<PivotItem headerText={intl.get("settings.sources")} itemIcon="Source">
|
<PivotItem headerText={intl.get("settings.sources")} itemIcon="Source">
|
||||||
<SourcesTabContainer />
|
<SourcesTabContainer />
|
||||||
|
@ -8,7 +8,7 @@ class AboutTab extends React.Component {
|
|||||||
render = () => (
|
render = () => (
|
||||||
<div className="tab-body">
|
<div className="tab-body">
|
||||||
<Stack className="settings-about" horizontalAlign="center">
|
<Stack className="settings-about" horizontalAlign="center">
|
||||||
<img src="logo.svg" style={{width: 120, height: 120}} />
|
<img src="icons/logo.svg" style={{width: 120, height: 120}} />
|
||||||
<h3>Fluent Reader</h3>
|
<h3>Fluent Reader</h3>
|
||||||
<small>{intl.get("settings.version")} {remote.app.getVersion()}</small>
|
<small>{intl.get("settings.version")} {remote.app.getVersion()}</small>
|
||||||
<p className="settings-hint">Copyright © 2020 Haoyuan Liu. All rights reserved.</p>
|
<p className="settings-hint">Copyright © 2020 Haoyuan Liu. All rights reserved.</p>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import { createSelector } from "reselect"
|
import { createSelector } from "reselect"
|
||||||
import { RootState } from "../scripts/reducer"
|
import { RootState } from "../scripts/reducer"
|
||||||
import { RSSItem, markUnread, markRead, toggleStarred, toggleHidden } from "../scripts/models/item"
|
import { RSSItem, markUnread, markRead, toggleStarred, toggleHidden, itemShortcuts } from "../scripts/models/item"
|
||||||
import { AppDispatch } from "../scripts/utils"
|
import { AppDispatch } from "../scripts/utils"
|
||||||
import { dismissItem } from "../scripts/models/page"
|
import { dismissItem, showOffsetItem } from "../scripts/models/page"
|
||||||
import Article from "../components/article"
|
import Article from "../components/article"
|
||||||
import { openTextMenu } from "../scripts/models/app"
|
import { openTextMenu } from "../scripts/models/app"
|
||||||
|
|
||||||
@ -28,7 +28,9 @@ const makeMapStateToProps = () => {
|
|||||||
|
|
||||||
const mapDispatchToProps = (dispatch: AppDispatch) => {
|
const mapDispatchToProps = (dispatch: AppDispatch) => {
|
||||||
return {
|
return {
|
||||||
|
shortcuts: (item: RSSItem, key: string) => dispatch(itemShortcuts(item, key)),
|
||||||
dismiss: () => dispatch(dismissItem()),
|
dismiss: () => dispatch(dismissItem()),
|
||||||
|
offsetItem: (offset: number) => dispatch(showOffsetItem(offset)),
|
||||||
toggleHasRead: (item: RSSItem) => dispatch(item.hasRead ? markUnread(item) : markRead(item)),
|
toggleHasRead: (item: RSSItem) => dispatch(item.hasRead ? markUnread(item) : markRead(item)),
|
||||||
toggleStarred: (item: RSSItem) => dispatch(toggleStarred(item)),
|
toggleStarred: (item: RSSItem) => dispatch(toggleStarred(item)),
|
||||||
toggleHidden: (item: RSSItem) => dispatch(toggleHidden(item)),
|
toggleHidden: (item: RSSItem) => dispatch(toggleHidden(item)),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import { createSelector } from "reselect"
|
import { createSelector } from "reselect"
|
||||||
import { RootState } from "../scripts/reducer"
|
import { RootState } from "../scripts/reducer"
|
||||||
import { markRead, RSSItem } from "../scripts/models/item"
|
import { markRead, RSSItem, itemShortcuts } from "../scripts/models/item"
|
||||||
import { openItemMenu } from "../scripts/models/app"
|
import { openItemMenu } from "../scripts/models/app"
|
||||||
import { loadMore, RSSFeed } from "../scripts/models/feed"
|
import { loadMore, RSSFeed } from "../scripts/models/feed"
|
||||||
import { showItem, ViewType } from "../scripts/models/page"
|
import { showItem, ViewType } from "../scripts/models/page"
|
||||||
@ -30,6 +30,7 @@ const makeMapStateToProps = () => {
|
|||||||
}
|
}
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
|
shortcuts: (item: RSSItem, key: string) => dispatch(itemShortcuts(item, key)),
|
||||||
markRead: (item: RSSItem) => dispatch(markRead(item)),
|
markRead: (item: RSSItem) => dispatch(markRead(item)),
|
||||||
contextMenu: (feedId: string, item: RSSItem, e) => dispatch(openItemMenu(item, feedId, e)),
|
contextMenu: (feedId: string, item: RSSItem, e) => dispatch(openItemMenu(item, feedId, e)),
|
||||||
loadMore: (feed: RSSFeed) => dispatch(loadMore(feed)),
|
loadMore: (feed: RSSFeed) => dispatch(loadMore(feed)),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as db from "../db"
|
import * as db from "../db"
|
||||||
import intl = require("react-intl-universal")
|
import intl = require("react-intl-universal")
|
||||||
import { domParser, htmlDecode, ActionStatus, AppThunk } from "../utils"
|
import { domParser, htmlDecode, ActionStatus, AppThunk, openExternal } from "../utils"
|
||||||
import { RSSSource } from "./source"
|
import { RSSSource } from "./source"
|
||||||
import { FeedActionTypes, INIT_FEED, LOAD_MORE } from "./feed"
|
import { FeedActionTypes, INIT_FEED, LOAD_MORE } from "./feed"
|
||||||
import Parser = require("@yang991178/rss-parser")
|
import Parser = require("@yang991178/rss-parser")
|
||||||
@ -263,6 +263,26 @@ export function toggleHidden(item: RSSItem): AppThunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function itemShortcuts(item: RSSItem, key: string): AppThunk {
|
||||||
|
return (dispatch) => {
|
||||||
|
switch (key) {
|
||||||
|
case "m":
|
||||||
|
if (item.hasRead) dispatch(markUnread(item))
|
||||||
|
else dispatch(markRead(item))
|
||||||
|
break
|
||||||
|
case "b":
|
||||||
|
openExternal(item.link)
|
||||||
|
break
|
||||||
|
case "s":
|
||||||
|
dispatch(toggleStarred(item))
|
||||||
|
break
|
||||||
|
case "h":
|
||||||
|
dispatch(toggleHidden(item))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function applyItemReduction(item: RSSItem, type: string) {
|
export function applyItemReduction(item: RSSItem, type: string) {
|
||||||
let nextItem = { ...item }
|
let nextItem = { ...item }
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -221,18 +221,20 @@ export function pageReducer(
|
|||||||
switch (action.pageType) {
|
switch (action.pageType) {
|
||||||
case PageType.AllArticles: return {
|
case PageType.AllArticles: return {
|
||||||
...state,
|
...state,
|
||||||
feedId: ALL
|
feedId: ALL,
|
||||||
|
itemId: null
|
||||||
}
|
}
|
||||||
case PageType.Sources: return {
|
case PageType.Sources: return {
|
||||||
...state,
|
...state,
|
||||||
feedId: SOURCE
|
feedId: SOURCE,
|
||||||
|
itemId: null
|
||||||
}
|
}
|
||||||
default: return state
|
default: return state
|
||||||
}
|
}
|
||||||
case SWITCH_VIEW: return {
|
case SWITCH_VIEW: return {
|
||||||
...state,
|
...state,
|
||||||
viewType: action.viewType,
|
viewType: action.viewType,
|
||||||
itemId: action.viewType === ViewType.List ? state.itemId : null
|
itemId: null
|
||||||
}
|
}
|
||||||
case APPLY_FILTER: return {
|
case APPLY_FILTER: return {
|
||||||
...state,
|
...state,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user