Skip to content

Commit

Permalink
Merge pull request #59 from yang991178/0.7.1
Browse files Browse the repository at this point in the history
Version 0.7.1
  • Loading branch information
yang991178 authored Aug 6, 2020
2 parents 114d180 + 6cde40f commit 29fe23e
Show file tree
Hide file tree
Showing 32 changed files with 232 additions and 105 deletions.
3 changes: 2 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
dist/article/article.js text eol=lf
dist/article/article.js text eol=lf
dist/article/mercury.web.js text eol=lf
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ npm run package-win
- [Redux](https://github.com/reduxjs/redux)
- [Fluent UI](https://github.com/microsoft/fluentui)
- [NeDB](https://github.com/louischatriot/nedb)
- [Mercury Parser](https://github.com/postlight/mercury-parser)

### License

Expand Down
5 changes: 3 additions & 2 deletions dist/article/article.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src-elem 'sha256-Y47O8EyR7IULmMXvvGsrM43xajwkPmTKvC8AhLDvg/o='; img-src http://* https://*; style-src 'self' 'unsafe-inline'; frame-src http://* https://*; media-src http://* https://*">
content="default-src 'none'; script-src-elem 'sha256-sLDWrq1tUAO8IyyqmUckFqxbXYfZ2/3TEUmtxH8Unf0=' 'sha256-q3VKKMMe+ucICfT8N3WHLJdQovcvvHc0HbJ5N9uA3+w='; img-src http: https: data:; style-src 'self' 'unsafe-inline'; frame-src http: https:; media-src http: https:; connect-src https: http:">
<title>Article</title>
<link rel="stylesheet" href="article.css" />
<script integrity="sha256-sLDWrq1tUAO8IyyqmUckFqxbXYfZ2/3TEUmtxH8Unf0=" src="mercury.web.js"></script>
</head>
<body>
<div id="main"></div>
<script integrity="sha256-Y47O8EyR7IULmMXvvGsrM43xajwkPmTKvC8AhLDvg/o=" src="article.js"></script>
<script integrity="sha256-q3VKKMMe+ucICfT8N3WHLJdQovcvvHc0HbJ5N9uA3+w=" src="article.js"></script>
<!-- Run "cat article.js | openssl dgst -sha256 -binary | openssl enc -base64 -A" for hash -->
</body>
</html>
43 changes: 29 additions & 14 deletions dist/article/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,33 @@ function get(name) {
if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(location.search))
return decodeURIComponent(name[1]);
}
document.documentElement.style.fontSize = get("s") + "px"
let html = decodeURIComponent(window.atob(get("h")))
let domParser = new DOMParser()
let dom = domParser.parseFromString(html, "text/html")
let baseEl = dom.createElement('base')
baseEl.setAttribute('href', get("u").split("/").slice(0, 3).join("/"))
dom.head.append(baseEl)
for (let e of dom.querySelectorAll("*[src]")) {
e.src = e.src
}
for (let s of dom.querySelectorAll("script")) {
s.parentNode.removeChild(s)
async function getArticle(url) {
let article = get("a")
if (get("m") === "1") {
return (await Mercury.parse(url, {html: article})).content || ""
} else {
return article
}
}
let main = document.getElementById("main")
main.innerHTML = dom.body.innerHTML
document.documentElement.style.fontSize = get("s") + "px"
let url = get("u")
getArticle(url).then(article => {
let domParser = new DOMParser()
let dom = domParser.parseFromString(get("h"), "text/html")
dom.getElementsByTagName("article")[0].innerHTML = article
let baseEl = dom.createElement('base')
baseEl.setAttribute('href', url.split("/").slice(0, 3).join("/"))
dom.head.append(baseEl)
for (let s of dom.getElementsByTagName("script")) {
s.parentNode.removeChild(s)
}
for (let e of dom.querySelectorAll("*[src]")) {
e.src = e.src
}
for (let e of dom.querySelectorAll("*[href]")) {
e.href = e.href
}
let main = document.getElementById("main")
main.innerHTML = dom.body.innerHTML
})

1 change: 1 addition & 0 deletions dist/article/mercury.web.js

Large diffs are not rendered by default.

Binary file added dist/icons/fabric-icons-10-c4ded8e4.woff
Binary file not shown.
3 changes: 3 additions & 0 deletions dist/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ div[role="tabpanel"] {
.settings .loading .ms-Spinner {
margin-top: 180px;
}
.settings .loading .ms-Spinner:focus {
outline: none;
}
.tab-body .ms-StackItem {
margin-right: 6px;
margin-bottom: 12px;
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fluent-reader",
"version": "0.7.0",
"version": "0.7.1",
"description": "Modern desktop RSS reader",
"main": "./dist/electron.js",
"scripts": {
Expand Down
6 changes: 3 additions & 3 deletions src/bridges/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ const utilsBridge = {
callback(pos, text)
})
},
addWebviewContextListener: (callback: (pos: [number, number], text: string) => any) => {
addWebviewContextListener: (callback: (pos: [number, number], text: string, url: string) => any) => {
ipcRenderer.removeAllListeners("webview-context-menu")
ipcRenderer.on("webview-context-menu", (_, pos, text) => {
callback(pos, text)
ipcRenderer.on("webview-context-menu", (_, pos, text, url) => {
callback(pos, text, url)
})
},
imageCallback: (type: ImageCallbackTypes) => {
Expand Down
96 changes: 70 additions & 26 deletions src/components/article.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ type ArticleProps = {
toggleHasRead: (item: RSSItem) => void
toggleStarred: (item: RSSItem) => void
toggleHidden: (item: RSSItem) => void
textMenu: (text: string, position: [number, number]) => void
textMenu: (position: [number, number], text: string, url: string) => void
imageMenu: (position: [number, number]) => void
dismissContextMenu: () => void
}

type ArticleState = {
fontSize: number
loadWebpage: boolean
loadFull: boolean
fullContent: string
loaded: boolean
error: boolean
errorDescription: string
Expand All @@ -35,18 +37,21 @@ type ArticleState = {
class Article extends React.Component<ArticleProps, ArticleState> {
webview: Electron.WebviewTag

constructor(props) {
constructor(props: ArticleProps) {
super(props)
this.state = {
fontSize: this.getFontSize(),
loadWebpage: this.props.source.openTarget === SourceOpenTarget.Webpage,
loadWebpage: props.source.openTarget === SourceOpenTarget.Webpage,
loadFull: props.source.openTarget === SourceOpenTarget.FullContent,
fullContent: "",
loaded: false,
error: false,
errorDescription: "",
}
window.utils.addWebviewContextListener(this.contextMenuHandler)
window.utils.addWebviewKeydownListener(this.keyDownHandler)
window.utils.addWebviewErrorListener(this.webviewError)
if (props.source.openTarget === SourceOpenTarget.FullContent) this.loadFull()
}

getFontSize = () => {
Expand Down Expand Up @@ -87,6 +92,13 @@ class Article extends React.Component<ArticleProps, ArticleState> {
iconProps: { iconName: this.props.item.hidden ? "View" : "Hide3" },
onClick: () => { this.props.toggleHidden(this.props.item) }
},
{
key: "fontMenu",
text: intl.get("article.fontSize"),
iconProps: { iconName: "FontSize" },
disabled: this.state.loadWebpage,
subMenuProps: this.fontMenuProps()
},
{
key: "divider_1",
itemType: ContextualMenuItemType.Divider,
Expand All @@ -95,9 +107,9 @@ class Article extends React.Component<ArticleProps, ArticleState> {
]
})

contextMenuHandler = (pos: [number, number], text: string) => {
contextMenuHandler = (pos: [number, number], text: string, url: string) => {
if (pos) {
if (text) this.props.textMenu(text, pos)
if (text || url) this.props.textMenu(pos, text, url)
else this.props.imageMenu(pos)
} else {
this.props.dismissContextMenu()
Expand All @@ -117,6 +129,9 @@ class Article extends React.Component<ArticleProps, ArticleState> {
case "l": case "L":
this.toggleWebpage()
break
case "w": case "W":
this.toggleFull()
break
default:
const keyboardEvent = new KeyboardEvent("keydown", {
code: input.code,
Expand Down Expand Up @@ -145,24 +160,32 @@ class Article extends React.Component<ArticleProps, ArticleState> {
if (this.webview) {
this.setState({loaded: false, error: false})
this.webview.reload()
} else if (this.state.loadFull) {
this.loadFull()
}
}

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)
let card = document.querySelector(`#refocus div[data-iid="${this.props.item._id}"]`) as HTMLElement
// @ts-ignore
if (card) card.scrollIntoViewIfNeeded()
if (webview) {
webview.focus()
this.setState({loaded: false, error: false})
webview.addEventListener("did-stop-loading", this.webviewLoaded)
let card = document.querySelector(`#refocus div[data-iid="${this.props.item._id}"]`) as HTMLElement
// @ts-ignore
if (card) card.scrollIntoViewIfNeeded()
}
}
}
componentDidUpdate = (prevProps: ArticleProps) => {
if (prevProps.item._id != this.props.item._id) {
this.setState({loadWebpage: this.props.source.openTarget === SourceOpenTarget.Webpage})
this.setState({
loadWebpage: this.props.source.openTarget === SourceOpenTarget.Webpage,
loadFull: this.props.source.openTarget === SourceOpenTarget.FullContent,
})
if (this.props.source.openTarget === SourceOpenTarget.FullContent) this.loadFull()
}
this.componentDidMount()
}
Expand All @@ -174,17 +197,39 @@ class Article extends React.Component<ArticleProps, ArticleState> {

toggleWebpage = () => {
if (this.state.loadWebpage) {
this.setState({loadWebpage: false})
this.setState({ loadWebpage: false })
} else if (this.props.item.link.startsWith("https://") || this.props.item.link.startsWith("http://")) {
this.setState({loadWebpage: true})
this.setState({ loadWebpage: true, loadFull: false })
}
}

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}`
toggleFull = () => {
if (this.state.loadFull) {
this.setState({ loadFull: false })
} else if (this.props.item.link.startsWith("https://") || this.props.item.link.startsWith("http://")) {
this.setState({ loadFull: true, loadWebpage: false })
this.loadFull()
}
}
loadFull = async () => {
this.setState({ fullContent: "", loaded: false, error: false })
try {
const html = await (await fetch(this.props.item.link)).text()
this.setState({ fullContent: html })
} catch {
this.setState({ loaded: true, error: true, errorDescription: "MERCURY_PARSER_FAILURE" })
}
}

articleView = () => {
const a = encodeURIComponent(this.state.loadFull ? this.state.fullContent : this.props.item.content)
const h = 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></article>
</>))
return `article/article.html?a=${a}&h=${h}&s=${this.state.fontSize}&u=${this.props.item.link}&m=${this.state.loadFull?1:0}`
}

render = () => (
<FocusZone className="article">
Expand All @@ -211,11 +256,10 @@ class Article extends React.Component<ArticleProps, ArticleState> {
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()} />
title={intl.get("article.loadFull")}
className={this.state.loadFull ? "active" : ""}
iconProps={{iconName: "RawSource"}}
onClick={this.toggleFull} />
<CommandBarButton
title={intl.get("article.loadWebpage")}
className={this.state.loadWebpage ? "active" : ""}
Expand All @@ -234,13 +278,13 @@ class Article extends React.Component<ArticleProps, ArticleState> {
onClick={this.props.dismiss} />
</Stack>
</Stack>
<webview
{(!this.state.loadFull || this.state.fullContent) && <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} />
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}} />
Expand Down
11 changes: 5 additions & 6 deletions src/components/cards/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,15 @@ export namespace Card {
e.preventDefault()
e.stopPropagation()
switch (props.source.openTarget) {
case SourceOpenTarget.Local:
case SourceOpenTarget.Webpage: {
props.markRead(props.item)
props.showItem(props.feedId, props.item)
break
}
case SourceOpenTarget.External: {
openInBrowser(props, e)
break
}
default: {
props.markRead(props.item)
props.showItem(props.feedId, props.item)
break
}
}
}

Expand Down
45 changes: 36 additions & 9 deletions src/components/context-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type ContextMenuProps = ContextReduxProps & {
item?: RSSItem
feedId?: string
text?: string
url?: string
viewType?: ViewType
filter?: FilterType
sids?: number[]
Expand Down Expand Up @@ -143,15 +144,41 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
onClick: () => { window.utils.writeClipboard(this.props.item.link) }
}
]
case ContextMenuType.Text: return [
{
key: "copyText",
text: intl.get("context.copy"),
iconProps: { iconName: "Copy" },
onClick: () => { window.utils.writeClipboard(this.props.text) }
},
getSearchItem(this.props.text)
]
case ContextMenuType.Text: {
const items: IContextualMenuItem[] = this.props.text? [
{
key: "copyText",
text: intl.get("context.copy"),
iconProps: { iconName: "Copy" },
onClick: () => { window.utils.writeClipboard(this.props.text) }
},
getSearchItem(this.props.text)
] : []
if (this.props.url) {
items.push({
key: "urlSection",
itemType: ContextualMenuItemType.Section,
sectionProps: {
topDivider: items.length > 0,
items: [
{
key: "openInBrowser",
text: intl.get("openExternal"),
iconProps: { iconName: "NavigateExternalInline" },
onClick: (e) => { window.utils.openExternal(this.props.url, platformCtrl(e)) }
},
{
key: "copyURL",
text: intl.get("context.copyURL"),
iconProps: { iconName: "Link" },
onClick: () => { window.utils.writeClipboard(this.props.url) }
}
]
}
})
}
return items
}
case ContextMenuType.Image: return [
{
key: "openInBrowser",
Expand Down
Loading

0 comments on commit 29fe23e

Please sign in to comment.