mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-02-01 01:47:07 +01:00
article navigation with keyboard
This commit is contained in:
parent
76a97852a1
commit
3578721d6c
19
dist/styles.css
vendored
19
dist/styles.css
vendored
@ -631,8 +631,19 @@ img.favicon {
|
||||
cursor: pointer;
|
||||
animation-fill-mode: none;
|
||||
}
|
||||
.card:hover {
|
||||
.card:hover, .card:focus {
|
||||
box-shadow: #0006 0px 5px 40px;
|
||||
outline: none;
|
||||
}
|
||||
.card:focus::after, .list-card:focus::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: calc(100% - 6px);
|
||||
height: calc(100% - 6px);
|
||||
border: 1px solid var(--white);
|
||||
outline: 2px solid #0078d4;
|
||||
}
|
||||
.card:active {
|
||||
transform: scale(.97);
|
||||
@ -663,7 +674,8 @@ img.favicon {
|
||||
.card img.head, .card p, .card h3 {
|
||||
transition: transform ease-out .12s;
|
||||
}
|
||||
.card.transform:hover img.head, .card.transform:hover p, .card.transform:hover h3 {
|
||||
.card.transform:hover img.head, .card.transform:hover p, .card.transform:hover h3,
|
||||
.card.transform:focus img.head, .card.transform:focus p, .card.transform:focus h3 {
|
||||
transform: translateY(-144px);
|
||||
}
|
||||
.card h3.title {
|
||||
@ -717,8 +729,9 @@ img.favicon {
|
||||
cursor: pointer;
|
||||
box-shadow: #0000 0px 5px 15px;
|
||||
}
|
||||
.list-card:hover {
|
||||
.list-card:hover, .list-card:focus {
|
||||
box-shadow: #0004 0px 5px 15px;
|
||||
outline: none;
|
||||
}
|
||||
.list-card:active {
|
||||
box-shadow: #0000 0px 5px 15px, inset #0004 0px 0px 15px;
|
||||
|
@ -3,10 +3,10 @@ import intl = require("react-intl-universal")
|
||||
import { renderToString } from "react-dom/server"
|
||||
import { RSSItem } from "../scripts/models/item"
|
||||
import { openExternal } from "../scripts/utils"
|
||||
import { Stack, CommandBarButton, IContextualMenuProps } from "@fluentui/react"
|
||||
import { Stack, CommandBarButton, IContextualMenuProps, FocusZone } from "@fluentui/react"
|
||||
import { RSSSource, SourceOpenTarget } from "../scripts/models/source"
|
||||
import { store } from "../scripts/settings"
|
||||
import { clipboard } from "electron"
|
||||
import { clipboard, remote } from "electron"
|
||||
|
||||
const FONT_SIZE_STORE_KEY = "fontSize"
|
||||
const FONT_SIZE_OPTIONS = [12, 13, 14, 15, 16, 17, 18, 19, 20]
|
||||
@ -28,7 +28,8 @@ type ArticleState = {
|
||||
}
|
||||
|
||||
class Article extends React.Component<ArticleProps, ArticleState> {
|
||||
webview: HTMLWebViewElement
|
||||
webview: Electron.WebviewTag
|
||||
shouldRefocus = false
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
@ -100,14 +101,23 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
||||
openExternal(event.url)
|
||||
this.props.dismiss()
|
||||
}
|
||||
keyDownHandler = (_, input) => {
|
||||
if (input.type === "keyDown" && input.key === "Escape") {
|
||||
this.shouldRefocus = true
|
||||
this.props.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
let webview = document.getElementById("article")
|
||||
let webview = document.getElementById("article") as Electron.WebviewTag
|
||||
if (webview != this.webview) {
|
||||
if (this.webview) this.componentWillUnmount()
|
||||
webview.addEventListener("ipc-message", this.ipcHandler)
|
||||
webview.addEventListener("new-window", this.popUpHandler)
|
||||
webview.addEventListener("will-navigate", this.navigationHandler)
|
||||
webview.addEventListener("dom-ready", () => {
|
||||
let webContents = remote.webContents.fromId(webview.getWebContentsId())
|
||||
webContents.on("before-input-event", this.keyDownHandler)
|
||||
})
|
||||
this.webview = webview
|
||||
webview.focus()
|
||||
}
|
||||
@ -120,9 +130,10 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
||||
}
|
||||
|
||||
componentWillUnmount = () => {
|
||||
this.webview.removeEventListener("ipc-message", this.ipcHandler)
|
||||
this.webview.removeEventListener("new-window", this.popUpHandler)
|
||||
this.webview.removeEventListener("will-navigate", this.navigationHandler)
|
||||
if (this.shouldRefocus) {
|
||||
let refocus = document.querySelector("#refocus>div[tabindex='0']") as HTMLElement
|
||||
if (refocus) refocus.focus()
|
||||
}
|
||||
}
|
||||
|
||||
openInBrowser = () => {
|
||||
@ -144,7 +155,7 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
||||
</>))) + `&s=${this.state.fontSize}&u=${this.props.item.link}`
|
||||
|
||||
render = () => (
|
||||
<div className="article">
|
||||
<FocusZone className="article">
|
||||
<Stack horizontal style={{height: 36}}>
|
||||
<span style={{width: 96}}></span>
|
||||
<Stack className="actions" grow horizontal tokens={{childrenGap: 12}}>
|
||||
@ -194,7 +205,7 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
||||
src={this.state.loadWebpage ? this.props.item.link : this.articleView()}
|
||||
preload={this.state.loadWebpage ? null : "article/preload.js"}
|
||||
partition="sandbox" />
|
||||
</div>
|
||||
</FocusZone>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -13,8 +13,12 @@ class DefaultCard extends Card {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={this.className()}
|
||||
onClick={this.onClick} onMouseUp={this.onMouseUp} >
|
||||
<div
|
||||
className={this.className()}
|
||||
onClick={this.onClick}
|
||||
onMouseUp={this.onMouseUp}
|
||||
onMouseDown={event => event.preventDefault()}
|
||||
data-is-focusable>
|
||||
{this.props.item.thumb ? (
|
||||
<img className="bg" src={this.props.item.thumb} />
|
||||
) : null}
|
||||
|
@ -12,8 +12,11 @@ class ListCard extends Card {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={this.className()}
|
||||
onClick={this.onClick} onMouseUp={this.onMouseUp} >
|
||||
<div
|
||||
className={this.className()}
|
||||
onClick={this.onClick} onMouseUp={this.onMouseUp}
|
||||
onMouseDown={event => event.preventDefault()}
|
||||
data-is-focusable>
|
||||
{this.props.item.thumb ? (
|
||||
<div className="head"><img src={this.props.item.thumb} /></div>
|
||||
) : null}
|
||||
|
@ -2,7 +2,7 @@ import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { FeedProps } from "./feed"
|
||||
import DefaultCard from "../cards/default-card"
|
||||
import { PrimaryButton } from 'office-ui-fabric-react';
|
||||
import { PrimaryButton, FocusZone } from 'office-ui-fabric-react';
|
||||
|
||||
class CardsFeed extends React.Component<FeedProps> {
|
||||
state = { width: window.innerWidth - 12 }
|
||||
@ -31,7 +31,7 @@ class CardsFeed extends React.Component<FeedProps> {
|
||||
|
||||
render() {
|
||||
return this.props.feed.loaded && (
|
||||
<div className="cards-feed-container">
|
||||
<FocusZone as="div" id="refocus" className="cards-feed-container">
|
||||
{
|
||||
this.props.items.map((item) => (
|
||||
<DefaultCard
|
||||
@ -56,7 +56,7 @@ class CardsFeed extends React.Component<FeedProps> {
|
||||
{ this.props.items.length === 0 && (
|
||||
<div className="empty">{intl.get("article.empty")}</div>
|
||||
)}
|
||||
</div>
|
||||
</FocusZone>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import * as React from "react"
|
||||
import intl = require("react-intl-universal")
|
||||
import { FeedProps } from "./feed"
|
||||
import { DefaultButton } from 'office-ui-fabric-react';
|
||||
import { DefaultButton, FocusZone, FocusZoneDirection } from 'office-ui-fabric-react';
|
||||
import ListCard from "../cards/list-card";
|
||||
|
||||
class ListFeed extends React.Component<FeedProps> {
|
||||
render() {
|
||||
return this.props.feed.loaded && (
|
||||
<div className="list-feed">
|
||||
<FocusZone as="div" id="refocus" direction={FocusZoneDirection.vertical} className="list-feed">
|
||||
{
|
||||
this.props.items.map((item) => (
|
||||
<ListCard
|
||||
@ -31,7 +31,7 @@ class ListFeed extends React.Component<FeedProps> {
|
||||
{ this.props.items.length === 0 && (
|
||||
<div className="empty">{intl.get("article.empty")}</div>
|
||||
)}
|
||||
</div>
|
||||
</FocusZone>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { Nav, INavLink, INavLinkGroup } from "office-ui-fabric-react/lib/Nav"
|
||||
import { SourceGroup } from "../scripts/models/group"
|
||||
import { SourceState, RSSSource } from "../scripts/models/source"
|
||||
import { ALL } from "../scripts/models/feed"
|
||||
import { AnimationClassNames, Stack, FocusZone, FocusZoneDirection } from "@fluentui/react"
|
||||
import { AnimationClassNames, Stack } from "@fluentui/react"
|
||||
|
||||
export type MenuProps = {
|
||||
status: boolean,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from "react"
|
||||
import { FeedContainer } from "../containers/feed-container"
|
||||
import { AnimationClassNames, Icon } from "@fluentui/react"
|
||||
import { AnimationClassNames, Icon, FocusTrapZone } from "@fluentui/react"
|
||||
import ArticleContainer from "../containers/article-container"
|
||||
import { ViewType } from "../scripts/models/page"
|
||||
import ArticleSearch from "./utils/article-search"
|
||||
@ -36,13 +36,17 @@ class Page extends React.Component<PageProps> {
|
||||
))}
|
||||
</div>}
|
||||
{this.props.itemId && (
|
||||
<div className="article-container" onClick={this.props.dismissItem}>
|
||||
<FocusTrapZone
|
||||
ignoreExternalFocusing={true}
|
||||
isClickableOutsideFocusTrap={true}
|
||||
className="article-container"
|
||||
onClick={this.props.dismissItem}>
|
||||
<div className={"article-wrapper " + AnimationClassNames.slideUpIn20} onClick={e => e.stopPropagation()}>
|
||||
<ArticleContainer itemId={this.props.itemId} />
|
||||
</div>
|
||||
<div className="btn-group prev"><a className="btn" onClick={this.prevItem}><Icon iconName="Back" /></a></div>
|
||||
<div className="btn-group next"><a className="btn" onClick={this.nextItem}><Icon iconName="Forward" /></a></div>
|
||||
</div>
|
||||
</FocusTrapZone>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user