mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-04-13 18:02:14 +02:00
article view optimization
This commit is contained in:
parent
7fde620d00
commit
9f85be51dc
26
dist/styles/feeds.css
vendored
26
dist/styles/feeds.css
vendored
@ -22,11 +22,16 @@
|
|||||||
}
|
}
|
||||||
.article {
|
.article {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
.article webview {
|
.article webview, .article .error-prompt {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% - 36px);
|
height: calc(100% - 36px);
|
||||||
border: none;
|
border: none;
|
||||||
|
color: var(--black);
|
||||||
|
}
|
||||||
|
.article webview.error {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
.article i.ms-Icon {
|
.article i.ms-Icon {
|
||||||
color: var(--neutralDarker);
|
color: var(--neutralDarker);
|
||||||
@ -35,18 +40,31 @@
|
|||||||
color: var(--black);
|
color: var(--black);
|
||||||
border-bottom: 1px solid var(--neutralQuaternaryAlt);
|
border-bottom: 1px solid var(--neutralQuaternaryAlt);
|
||||||
}
|
}
|
||||||
.article .actions .favicon {
|
.article .actions .favicon, .article .actions .ms-Spinner {
|
||||||
margin-right: 8px;
|
margin: 8px 8px 11px 0;
|
||||||
|
}
|
||||||
|
.article .actions .ms-Spinner {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
.article .actions .source-name {
|
.article .actions .source-name {
|
||||||
line-height: 35px;
|
line-height: 35px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
max-width: 280px;
|
max-width: 320px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
display: inline-block;
|
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 {
|
.side-article-wrapper, .side-logo-wrapper {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding-top: var(--navHeight);
|
padding-top: var(--navHeight);
|
||||||
|
3
dist/styles/global.css
vendored
3
dist/styles/global.css
vendored
@ -52,6 +52,9 @@ html, body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ms-Link {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
.ms-ContextualMenu-link, .ms-Button, .ms-ContextualMenu-item button {
|
.ms-ContextualMenu-link, .ms-Button, .ms-ContextualMenu-item button {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
@ -42,6 +42,12 @@ const utilsBridge = {
|
|||||||
await ipcRenderer.invoke("clear-cache")
|
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) => {
|
addWebviewContextListener: (callback: (pos: [number, number], text: string) => any) => {
|
||||||
ipcRenderer.removeAllListeners("webview-context-menu")
|
ipcRenderer.removeAllListeners("webview-context-menu")
|
||||||
ipcRenderer.on("webview-context-menu", (_, pos, text) => {
|
ipcRenderer.on("webview-context-menu", (_, pos, text) => {
|
||||||
|
@ -2,7 +2,7 @@ import * as React from "react"
|
|||||||
import intl from "react-intl-universal"
|
import intl from "react-intl-universal"
|
||||||
import { renderToString } from "react-dom/server"
|
import { renderToString } from "react-dom/server"
|
||||||
import { RSSItem } from "../scripts/models/item"
|
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 { RSSSource, SourceOpenTarget } from "../scripts/models/source"
|
||||||
import { shareSubmenu } from "./context-menu"
|
import { shareSubmenu } from "./context-menu"
|
||||||
|
|
||||||
@ -25,6 +25,8 @@ type ArticleProps = {
|
|||||||
type ArticleState = {
|
type ArticleState = {
|
||||||
fontSize: number
|
fontSize: number
|
||||||
loadWebpage: boolean
|
loadWebpage: boolean
|
||||||
|
loaded: boolean
|
||||||
|
error: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
class Article extends React.Component<ArticleProps, ArticleState> {
|
class Article extends React.Component<ArticleProps, ArticleState> {
|
||||||
@ -34,7 +36,9 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
fontSize: this.getFontSize(),
|
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.addWebviewContextListener(this.contextMenuHandler)
|
||||||
window.utils.addWebviewKeydownListener(this.keyDownHandler)
|
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 = () => {
|
componentDidMount = () => {
|
||||||
let webview = document.getElementById("article") as Electron.WebviewTag
|
let webview = document.getElementById("article") as Electron.WebviewTag
|
||||||
if (webview != this.webview) {
|
if (webview != this.webview) {
|
||||||
this.webview = webview
|
this.webview = webview
|
||||||
webview.focus()
|
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
|
let card = document.querySelector(`#refocus div[data-iid="${this.props.item._id}"]`) as HTMLElement
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (card) card.scrollIntoViewIfNeeded()
|
if (card) card.scrollIntoViewIfNeeded()
|
||||||
@ -172,8 +192,11 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
<Stack className="actions" grow horizontal tokens={{childrenGap: 12}}>
|
<Stack className="actions" grow horizontal tokens={{childrenGap: 12}}>
|
||||||
<Stack.Item grow>
|
<Stack.Item grow>
|
||||||
<span className="source-name">
|
<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.source.name}
|
||||||
|
{this.props.item.creator && <span className="creator">{this.props.item.creator}</span>}
|
||||||
</span>
|
</span>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<CommandBarButton
|
<CommandBarButton
|
||||||
@ -212,10 +235,20 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
</Stack>
|
</Stack>
|
||||||
<webview
|
<webview
|
||||||
id="article"
|
id="article"
|
||||||
|
className={this.state.error ? "error" : ""}
|
||||||
key={this.props.item._id + (this.state.loadWebpage ? "_" : "")}
|
key={this.props.item._id + (this.state.loadWebpage ? "_" : "")}
|
||||||
src={this.state.loadWebpage ? this.props.item.link : this.articleView()}
|
src={this.state.loadWebpage ? this.props.item.link : this.articleView()}
|
||||||
webpreferences="contextIsolation,disableDialogs,autoplayPolicy=document-user-activation-required"
|
webpreferences="contextIsolation,disableDialogs,autoplayPolicy=document-user-activation-required"
|
||||||
partition={this.state.loadWebpage ? "sandbox" : undefined} />
|
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>
|
</FocusZone>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,7 @@ import { RootState } from "../scripts/reducer"
|
|||||||
const Root = ({ locale, dispatch }) => locale && (
|
const Root = ({ locale, dispatch }) => locale && (
|
||||||
<div id="root"
|
<div id="root"
|
||||||
key={locale}
|
key={locale}
|
||||||
onMouseDown={() => dispatch(closeContextMenu())}
|
onMouseDown={() => dispatch(closeContextMenu())}>
|
||||||
onContextMenu={event => {
|
|
||||||
let text = document.getSelection().toString()
|
|
||||||
if (text) dispatch(openTextMenu(text, [event.clientX, event.clientY]))
|
|
||||||
}}>
|
|
||||||
<NavContainer />
|
<NavContainer />
|
||||||
<PageContainer />
|
<PageContainer />
|
||||||
<LogMenuContainer />
|
<LogMenuContainer />
|
||||||
|
@ -8,7 +8,7 @@ import { rootReducer, RootState } from "./scripts/reducer"
|
|||||||
import Root from "./components/root"
|
import Root from "./components/root"
|
||||||
import { AppDispatch } from "./scripts/utils"
|
import { AppDispatch } from "./scripts/utils"
|
||||||
import { applyThemeSettings } from "./scripts/settings"
|
import { applyThemeSettings } from "./scripts/settings"
|
||||||
import { initApp } from "./scripts/models/app"
|
import { initApp, openTextMenu } from "./scripts/models/app"
|
||||||
|
|
||||||
window.settings.setProxy()
|
window.settings.setProxy()
|
||||||
|
|
||||||
@ -22,6 +22,10 @@ const store = createStore(
|
|||||||
|
|
||||||
store.dispatch(initApp())
|
store.dispatch(initApp())
|
||||||
|
|
||||||
|
window.utils.addMainContextListener((pos, text) => {
|
||||||
|
store.dispatch(openTextMenu(text, pos))
|
||||||
|
})
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<Root />
|
<Root />
|
||||||
|
@ -78,6 +78,11 @@ export class WindowManager {
|
|||||||
this.mainWindow.on("unmaximize", () => {
|
this.mainWindow.on("unmaximize", () => {
|
||||||
this.mainWindow.webContents.send("unmaximized")
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,8 @@
|
|||||||
"subscriptions": "Subscriptions"
|
"subscriptions": "Subscriptions"
|
||||||
},
|
},
|
||||||
"article": {
|
"article": {
|
||||||
|
"error": "Failed to load article.",
|
||||||
|
"reload": "Reload?",
|
||||||
"empty": "No articles",
|
"empty": "No articles",
|
||||||
"untitled": "(Untitled)",
|
"untitled": "(Untitled)",
|
||||||
"hide": "Hide article",
|
"hide": "Hide article",
|
||||||
|
@ -49,6 +49,8 @@
|
|||||||
"subscriptions": "订阅源"
|
"subscriptions": "订阅源"
|
||||||
},
|
},
|
||||||
"article": {
|
"article": {
|
||||||
|
"error": "文章加载失败",
|
||||||
|
"reload": "重新加载",
|
||||||
"empty": "无文章",
|
"empty": "无文章",
|
||||||
"untitled": "(无标题)",
|
"untitled": "(无标题)",
|
||||||
"hide": "隐藏文章",
|
"hide": "隐藏文章",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user