2020-06-12 10:18:44 +08:00

200 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as React from "react"
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 { RSSSource, SourceOpenTarget } from "../scripts/models/source"
import { store } from "../scripts/settings"
import { clipboard } from "electron"
const FONT_SIZE_STORE_KEY = "fontSize"
const FONT_SIZE_OPTIONS = [12, 13, 14, 15, 16, 17, 18, 19, 20]
type ArticleProps = {
item: RSSItem
source: RSSSource
locale: string
dismiss: () => void
toggleHasRead: (item: RSSItem) => void
toggleStarred: (item: RSSItem) => void
toggleHidden: (item: RSSItem) => void
textMenu: (text: string, position: [number, number]) => void
}
type ArticleState = {
fontSize: number
loadWebpage: boolean
}
class Article extends React.Component<ArticleProps, ArticleState> {
webview: HTMLWebViewElement
constructor(props) {
super(props)
this.state = {
fontSize: this.getFontSize(),
loadWebpage: this.props.source.openTarget === SourceOpenTarget.Webpage
}
}
getFontSize = () => {
return store.get(FONT_SIZE_STORE_KEY, 16)
}
setFontSize = (size: number) => {
store.set(FONT_SIZE_STORE_KEY, size)
this.setState({fontSize: size})
}
fontMenuProps = (): IContextualMenuProps => ({
items: FONT_SIZE_OPTIONS.map(size => ({
key: String(size),
text: String(size),
canCheck: true,
checked: size === this.state.fontSize,
onClick: () => this.setFontSize(size)
}))
})
moreMenuProps = (): IContextualMenuProps => ({
items: [
{
key: "openInBrowser",
text: intl.get("openExternal"),
iconProps: { iconName: "NavigateExternalInline" },
onClick: this.openInBrowser
},
{
key: "copyURL",
text: intl.get("context.copyURL"),
iconProps: { iconName: "Link" },
onClick: () => { clipboard.writeText(this.props.item.link) }
},
{
key: "toggleHidden",
text: this.props.item.hidden ? intl.get("article.unhide") : intl.get("article.hide"),
iconProps: { iconName: this.props.item.hidden ? "View" : "Hide3" },
onClick: () => { this.props.toggleHidden(this.props.item) }
}
]
})
ipcHandler = event => {
switch (event.channel) {
case "request-navigation": {
openExternal(event.args[0])
break
}
case "context-menu": {
let articlePos = document.getElementById("article").getBoundingClientRect()
let [x, y] = event.args[0]
this.props.textMenu(event.args[1], [x + articlePos.x, y + articlePos.y])
break
}
}
}
popUpHandler = event => {
openExternal(event.url)
}
navigationHandler = event => {
openExternal(event.url)
this.props.dismiss()
}
componentDidMount = () => {
let webview = document.getElementById("article")
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)
this.webview = webview
}
}
componentDidUpdate = (prevProps: ArticleProps) => {
if (prevProps.item._id != this.props.item._id) {
this.setState({loadWebpage: this.props.source.openTarget === SourceOpenTarget.Webpage})
}
this.componentDidMount()
}
componentWillUnmount = () => {
this.webview.removeEventListener("ipc-message", this.ipcHandler)
this.webview.removeEventListener("new-window", this.popUpHandler)
this.webview.removeEventListener("will-navigate", this.navigationHandler)
}
openInBrowser = () => {
openExternal(this.props.item.link)
}
toggleWebpage = () => {
if (this.state.loadWebpage) {
this.setState({loadWebpage: false})
} else if (this.props.item.link.startsWith("https://") || this.props.item.link.startsWith("http://")) {
this.setState({loadWebpage: true})
}
}
articleView = () => "article/article.html?h=" + window.btoa(encodeURIComponent(renderToString(<>
<p className="title">{this.props.item.title}</p>
<p className="date">{this.props.item.date.toLocaleString(this.props.locale, {hour12: !this.props.locale.startsWith("zh")})}</p>
<article dangerouslySetInnerHTML={{__html: this.props.item.content}}></article>
</>))) + `&s=${this.state.fontSize}&u=${this.props.item.link}`
render = () => (
<div className="article">
<Stack horizontal style={{height: 36}}>
<span style={{width: 96}}></span>
<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.props.source.name}
</span>
</Stack.Item>
<CommandBarButton
title={this.props.item.hasRead ? intl.get("article.markUnread") : intl.get("article.markRead")}
iconProps={this.props.item.hasRead
? {iconName: "StatusCircleRing"}
: {iconName: "RadioBtnOn", style: {fontSize: 14, textAlign: "center"}}}
onClick={() => this.props.toggleHasRead(this.props.item)} />
<CommandBarButton
title={this.props.item.starred ? intl.get("article.unstar") : intl.get("article.star")}
iconProps={{iconName: this.props.item.starred ? "FavoriteStarFill" : "FavoriteStar"}}
onClick={() => this.props.toggleStarred(this.props.item)} />
<CommandBarButton
title={intl.get("article.fontSize")}
disabled={this.state.loadWebpage}
iconProps={{iconName: "FontSize"}}
menuIconProps={{style: {display: "none"}}}
menuProps={this.fontMenuProps()} />
<CommandBarButton
title={intl.get("article.loadWebpage")}
className={this.state.loadWebpage ? "active" : ""}
iconProps={{iconName: "Globe"}}
onClick={this.toggleWebpage} />
<CommandBarButton
title={intl.get("more")}
iconProps={{iconName: "More"}}
menuIconProps={{style: {display: "none"}}}
menuProps={this.moreMenuProps()} />
</Stack>
<Stack horizontal horizontalAlign="end" style={{width: 112}}>
<CommandBarButton
title={intl.get("close")}
iconProps={{iconName: "BackToWindow"}}
onClick={this.props.dismiss} />
</Stack>
</Stack>
<webview
id="article"
key={this.props.item._id + (this.state.loadWebpage ? "_" : "")}
src={this.state.loadWebpage ? this.props.item.link : this.articleView()}
preload={this.state.loadWebpage ? null : "article/preload.js"}
partition="sandbox" />
</div>
)
}
export default Article