article view optimization

This commit is contained in:
刘浩远 2020-07-20 12:18:10 +08:00
parent 7fde620d00
commit 9f85be51dc
9 changed files with 82 additions and 13 deletions

26
dist/styles/feeds.css vendored
View File

@ -22,11 +22,16 @@
}
.article {
height: 100%;
user-select: none;
}
.article webview {
.article webview, .article .error-prompt {
width: 100%;
height: calc(100% - 36px);
border: none;
color: var(--black);
}
.article webview.error {
display: none;
}
.article i.ms-Icon {
color: var(--neutralDarker);
@ -35,18 +40,31 @@
color: var(--black);
border-bottom: 1px solid var(--neutralQuaternaryAlt);
}
.article .actions .favicon {
margin-right: 8px;
.article .actions .favicon, .article .actions .ms-Spinner {
margin: 8px 8px 11px 0;
}
.article .actions .ms-Spinner {
display: inline-block;
vertical-align: middle;
}
.article .actions .source-name {
line-height: 35px;
user-select: none;
max-width: 280px;
max-width: 320px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
}
.article .actions .creator {
color: var(--neutralSecondaryAlt);
user-select: text;
}
.article .actions .creator::before {
display: inline-block;
content: "/";
margin: 0 6px;
}
.side-article-wrapper, .side-logo-wrapper {
flex-grow: 1;
padding-top: var(--navHeight);

View File

@ -52,6 +52,9 @@ html, body {
height: 100%;
}
.ms-Link {
user-select: none;
}
.ms-ContextualMenu-link, .ms-Button, .ms-ContextualMenu-item button {
cursor: default;
font-size: 13px;

View File

@ -42,6 +42,12 @@ const utilsBridge = {
await ipcRenderer.invoke("clear-cache")
},
addMainContextListener: (callback: (pos: [number, number], text: string) => any) => {
ipcRenderer.removeAllListeners("window-context-menu")
ipcRenderer.on("window-context-menu", (_, pos, text) => {
callback(pos, text)
})
},
addWebviewContextListener: (callback: (pos: [number, number], text: string) => any) => {
ipcRenderer.removeAllListeners("webview-context-menu")
ipcRenderer.on("webview-context-menu", (_, pos, text) => {

View File

@ -2,7 +2,7 @@ import * as React from "react"
import intl from "react-intl-universal"
import { renderToString } from "react-dom/server"
import { RSSItem } from "../scripts/models/item"
import { Stack, CommandBarButton, IContextualMenuProps, FocusZone, ContextualMenuItemType } from "@fluentui/react"
import { Stack, CommandBarButton, IContextualMenuProps, FocusZone, ContextualMenuItemType, Spinner, Icon, Link } from "@fluentui/react"
import { RSSSource, SourceOpenTarget } from "../scripts/models/source"
import { shareSubmenu } from "./context-menu"
@ -25,6 +25,8 @@ type ArticleProps = {
type ArticleState = {
fontSize: number
loadWebpage: boolean
loaded: boolean
error: boolean
}
class Article extends React.Component<ArticleProps, ArticleState> {
@ -34,7 +36,9 @@ class Article extends React.Component<ArticleProps, ArticleState> {
super(props)
this.state = {
fontSize: this.getFontSize(),
loadWebpage: this.props.source.openTarget === SourceOpenTarget.Webpage
loadWebpage: this.props.source.openTarget === SourceOpenTarget.Webpage,
loaded: false,
error: false,
}
window.utils.addWebviewContextListener(this.contextMenuHandler)
window.utils.addWebviewKeydownListener(this.keyDownHandler)
@ -125,11 +129,27 @@ class Article extends React.Component<ArticleProps, ArticleState> {
}
}
webviewLoaded = () => {
this.setState({loaded: true})
}
webviewError = () => {
this.setState({error: true})
}
webviewReload = () => {
if (this.webview) {
this.setState({loaded: false, error: false})
this.webview.reload()
}
}
componentDidMount = () => {
let webview = document.getElementById("article") as Electron.WebviewTag
if (webview != this.webview) {
this.webview = webview
webview.focus()
this.setState({loaded: false, error: false})
webview.addEventListener("did-stop-loading", this.webviewLoaded)
webview.addEventListener("did-fail-load", this.webviewError)
let card = document.querySelector(`#refocus div[data-iid="${this.props.item._id}"]`) as HTMLElement
// @ts-ignore
if (card) card.scrollIntoViewIfNeeded()
@ -172,8 +192,11 @@ class Article extends React.Component<ArticleProps, ArticleState> {
<Stack className="actions" grow horizontal tokens={{childrenGap: 12}}>
<Stack.Item grow>
<span className="source-name">
{this.props.source.iconurl && <img className="favicon" src={this.props.source.iconurl} />}
{this.state.loaded
? (this.props.source.iconurl && <img className="favicon" src={this.props.source.iconurl} />)
: <Spinner size={1} />}
{this.props.source.name}
{this.props.item.creator && <span className="creator">{this.props.item.creator}</span>}
</span>
</Stack.Item>
<CommandBarButton
@ -212,10 +235,20 @@ class Article extends React.Component<ArticleProps, ArticleState> {
</Stack>
<webview
id="article"
className={this.state.error ? "error" : ""}
key={this.props.item._id + (this.state.loadWebpage ? "_" : "")}
src={this.state.loadWebpage ? this.props.item.link : this.articleView()}
webpreferences="contextIsolation,disableDialogs,autoplayPolicy=document-user-activation-required"
partition={this.state.loadWebpage ? "sandbox" : undefined} />
{this.state.error && (
<Stack className="error-prompt" verticalAlign="center" horizontalAlign="center" tokens={{childrenGap: 12}}>
<Icon iconName="HeartBroken" style={{fontSize: 32}} />
<Stack horizontal horizontalAlign="center" tokens={{childrenGap: 7}}>
<small>{intl.get("article.error")}</small>
<small><Link onClick={this.webviewReload}>{intl.get("article.reload")}</Link></small>
</Stack>
</Stack>
)}
</FocusZone>
)
}

View File

@ -12,11 +12,7 @@ import { RootState } from "../scripts/reducer"
const Root = ({ locale, dispatch }) => locale && (
<div id="root"
key={locale}
onMouseDown={() => dispatch(closeContextMenu())}
onContextMenu={event => {
let text = document.getSelection().toString()
if (text) dispatch(openTextMenu(text, [event.clientX, event.clientY]))
}}>
onMouseDown={() => dispatch(closeContextMenu())}>
<NavContainer />
<PageContainer />
<LogMenuContainer />

View File

@ -8,7 +8,7 @@ import { rootReducer, RootState } from "./scripts/reducer"
import Root from "./components/root"
import { AppDispatch } from "./scripts/utils"
import { applyThemeSettings } from "./scripts/settings"
import { initApp } from "./scripts/models/app"
import { initApp, openTextMenu } from "./scripts/models/app"
window.settings.setProxy()
@ -22,6 +22,10 @@ const store = createStore(
store.dispatch(initApp())
window.utils.addMainContextListener((pos, text) => {
store.dispatch(openTextMenu(text, pos))
})
ReactDOM.render(
<Provider store={store}>
<Root />

View File

@ -78,6 +78,11 @@ export class WindowManager {
this.mainWindow.on("unmaximize", () => {
this.mainWindow.webContents.send("unmaximized")
})
this.mainWindow.webContents.on("context-menu", (_, params) => {
if (params.selectionText) {
this.mainWindow.webContents.send("window-context-menu", [params.x, params.y], params.selectionText)
}
})
}
}

View File

@ -49,6 +49,8 @@
"subscriptions": "Subscriptions"
},
"article": {
"error": "Failed to load article.",
"reload": "Reload?",
"empty": "No articles",
"untitled": "(Untitled)",
"hide": "Hide article",

View File

@ -49,6 +49,8 @@
"subscriptions": "订阅源"
},
"article": {
"error": "文章加载失败",
"reload": "重新加载",
"empty": "无文章",
"untitled": "(无标题)",
"hide": "隐藏文章",