mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-05-01 18:38:45 +02:00
add actions to article
This commit is contained in:
parent
288907fdae
commit
430fdf1fd4
12
dist/article/article.css
vendored
12
dist/article/article.css
vendored
@ -1,7 +1,11 @@
|
|||||||
html, body {
|
html, body {
|
||||||
font-family: "Segoe UI Regular", "Source Han Sans SC Regular", "Microsoft YaHei", sans-serif;
|
font-family: "Segoe UI Regular", "Source Han Sans SC Regular", "Microsoft YaHei", sans-serif;
|
||||||
margin: 16px 48px;
|
}
|
||||||
overflow-x: hidden;
|
html {
|
||||||
|
overflow: hidden scroll;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 12px 96px 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@ -22,8 +26,10 @@ a:hover, a:active {
|
|||||||
article {
|
article {
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
article img {
|
article > * {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
article img {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
article figure {
|
article figure {
|
||||||
|
2
dist/article/article.html
vendored
2
dist/article/article.html
vendored
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="Content-Security-Policy"
|
<meta http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; img-src *; style-src 'self' 'unsafe-inline'; frame-src *">
|
content="default-src 'self'; img-src *; style-src 'self' 'unsafe-inline'; frame-src *; media-src *">
|
||||||
<title>Hello World!</title>
|
<title>Hello World!</title>
|
||||||
<link rel="stylesheet" href="article.css" />
|
<link rel="stylesheet" href="article.css" />
|
||||||
</head>
|
</head>
|
||||||
|
3
dist/article/article.js
vendored
3
dist/article/article.js
vendored
@ -2,9 +2,10 @@ function get(name) {
|
|||||||
if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(location.search))
|
if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(location.search))
|
||||||
return decodeURIComponent(name[1]);
|
return decodeURIComponent(name[1]);
|
||||||
}
|
}
|
||||||
|
document.documentElement.style.fontSize = get("s") + "px"
|
||||||
let main = document.getElementById("main")
|
let main = document.getElementById("main")
|
||||||
main.innerHTML = decodeURIComponent(window.atob(get("h")))
|
main.innerHTML = decodeURIComponent(window.atob(get("h")))
|
||||||
document.addEventListener("click", event => {
|
document.addEventListener("click", event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (event.target.href) ipcRenderer.sendToHost("request-navigation", event.target.href)
|
if (event.target.href) post("request-navigation", event.target.href)
|
||||||
})
|
})
|
2
dist/article/preload.js
vendored
2
dist/article/preload.js
vendored
@ -1 +1 @@
|
|||||||
global.ipcRenderer = require("electron").ipcRenderer
|
global.post = require("electron").ipcRenderer.sendToHost
|
BIN
dist/icons/fabric-icons-5-f95ba260.woff
vendored
Normal file
BIN
dist/icons/fabric-icons-5-f95ba260.woff
vendored
Normal file
Binary file not shown.
20
dist/styles.css
vendored
20
dist/styles.css
vendored
@ -117,7 +117,7 @@ nav.menu-on .btn-group .btn, nav.hide-btns .btn-group .btn {
|
|||||||
nav.menu-on .btn-group .btn.system, nav.hide-btns .btn-group .btn.system {
|
nav.menu-on .btn-group .btn.system, nav.hide-btns .btn-group .btn.system {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
nav.menu-on .btn-group .btn.system {
|
nav.menu-on .btn-group .btn.system, nav.item-on .btn-group .btn.system {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.btn-group .btn:hover {
|
.btn-group .btn:hover {
|
||||||
@ -161,7 +161,6 @@ nav.menu-on .btn-group .btn.system {
|
|||||||
}
|
}
|
||||||
.article-container {
|
.article-container {
|
||||||
z-index: 6;
|
z-index: 6;
|
||||||
background-color: #fff6;
|
|
||||||
}
|
}
|
||||||
.menu-container .menu {
|
.menu-container .menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -273,6 +272,9 @@ img.favicon {
|
|||||||
nav.menu-on .btn-group .btn.system {
|
nav.menu-on .btn-group .btn.system {
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
nav.item-on .btn-group .btn.system {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
.menu-container {
|
.menu-container {
|
||||||
width: 280px;
|
width: 280px;
|
||||||
}
|
}
|
||||||
@ -313,9 +315,21 @@ img.favicon {
|
|||||||
}
|
}
|
||||||
.article webview {
|
.article webview {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100vh - 50px);
|
height: calc(100vh - 86px);
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
.article i.ms-Icon {
|
||||||
|
color: #161514;
|
||||||
|
}
|
||||||
|
.article .actions {
|
||||||
|
border-bottom: 1px solid #e1dfdd;
|
||||||
|
}
|
||||||
|
.article .actions .favicon {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.article .actions .source-name {
|
||||||
|
line-height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
.cards-feed-container {
|
.cards-feed-container {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
@ -2,47 +2,122 @@ import * as React from "react"
|
|||||||
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 { openExternal } from "../scripts/utils"
|
import { openExternal } from "../scripts/utils"
|
||||||
|
import { Stack, CommandBarButton, IContextualMenuProps } from "@fluentui/react"
|
||||||
|
import { RSSSource } from "../scripts/models/source"
|
||||||
|
|
||||||
|
const FONT_SIZE_STORE_KEY = "fontSize"
|
||||||
|
const FONT_SIZE_OPTIONS = [12, 13, 14, 15, 16, 17, 18, 19, 20]
|
||||||
|
|
||||||
type ArticleProps = {
|
type ArticleProps = {
|
||||||
item: RSSItem
|
item: RSSItem
|
||||||
|
source: RSSSource
|
||||||
dismiss: () => void
|
dismiss: () => void
|
||||||
|
toggleHasRead: (item: RSSItem) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
class Article extends React.Component<ArticleProps> {
|
type ArticleState = {
|
||||||
webview: HTMLWebViewElement
|
fontSize: number
|
||||||
|
}
|
||||||
|
|
||||||
|
class Article extends React.Component<ArticleProps, ArticleState> {
|
||||||
|
webview: HTMLWebViewElement
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
fontSize: this.getFontSize()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFontSize = () => {
|
||||||
|
let size = window.localStorage.getItem(FONT_SIZE_STORE_KEY)
|
||||||
|
return size ? parseInt(size) : 16
|
||||||
|
}
|
||||||
|
setFontSize = (size: number) => {
|
||||||
|
window.localStorage.setItem(FONT_SIZE_STORE_KEY, String(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)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
ipcHandler = event => {
|
ipcHandler = event => {
|
||||||
if (event.channel === "request-navigation") {
|
if (event.channel === "request-navigation") {
|
||||||
openExternal(event.args[0])
|
openExternal(event.args[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
popUpHandler = event => {
|
||||||
|
openExternal(event.url)
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount = () => {
|
componentDidMount = () => {
|
||||||
this.webview = document.getElementById("article")
|
this.webview = document.getElementById("article")
|
||||||
this.webview.addEventListener("ipc-message", this.ipcHandler)
|
this.webview.addEventListener("ipc-message", this.ipcHandler)
|
||||||
|
this.webview.addEventListener("new-window", this.popUpHandler)
|
||||||
this.webview.addEventListener("will-navigate", this.props.dismiss)
|
this.webview.addEventListener("will-navigate", this.props.dismiss)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount = () => {
|
componentWillUnmount = () => {
|
||||||
this.webview.removeEventListener("ipc-message", this.ipcHandler)
|
this.webview.removeEventListener("ipc-message", this.ipcHandler)
|
||||||
|
this.webview.removeEventListener("new-window", this.popUpHandler)
|
||||||
this.webview.removeEventListener("will-navigate", this.props.dismiss)
|
this.webview.removeEventListener("will-navigate", this.props.dismiss)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openInBrowser = () => {
|
||||||
|
openExternal(this.props.item.link)
|
||||||
|
}
|
||||||
|
|
||||||
articleView = () => "article/article.html?h=" + window.btoa(encodeURIComponent(renderToString(<>
|
articleView = () => "article/article.html?h=" + window.btoa(encodeURIComponent(renderToString(<>
|
||||||
<p className="title">{this.props.item.title}</p>
|
<p className="title">{this.props.item.title}</p>
|
||||||
<article dangerouslySetInnerHTML={{__html: this.props.item.content}}></article>
|
<article dangerouslySetInnerHTML={{__html: this.props.item.content}}></article>
|
||||||
</>)))
|
</>))) + "&s=" + this.state.fontSize
|
||||||
|
|
||||||
render = () => (
|
render = () => (
|
||||||
<div className="article">
|
<div className="article">
|
||||||
|
<Stack horizontal style={{height: 36}}>
|
||||||
|
<span style={{width: 96}}></span>
|
||||||
|
<Stack className="actions" grow horizontal tokens={{childrenGap: 12}}>
|
||||||
|
<Stack.Item grow>
|
||||||
|
{this.props.source.iconurl && <img className="favicon" src={this.props.source.iconurl} />}
|
||||||
|
<span className="source-name">{this.props.source.name}</span>
|
||||||
|
</Stack.Item>
|
||||||
|
<CommandBarButton
|
||||||
|
title={this.props.item.hasRead ? "标为未读" : "标为已读"}
|
||||||
|
iconProps={this.props.item.hasRead
|
||||||
|
? {iconName: "RadioBtnOn", style: {fontSize: 14, textAlign: "center"}}
|
||||||
|
: {iconName: "StatusCircleRing"}}
|
||||||
|
onClick={() => this.props.toggleHasRead(this.props.item)} />
|
||||||
|
<CommandBarButton
|
||||||
|
iconProps={{iconName: "FavoriteStar"}} />
|
||||||
|
<CommandBarButton
|
||||||
|
title="字体大小"
|
||||||
|
iconProps={{iconName: "FontSize"}}
|
||||||
|
menuIconProps={{style: {display: "none"}}}
|
||||||
|
menuProps={this.fontMenuProps()} />
|
||||||
|
<CommandBarButton
|
||||||
|
title="在浏览器中打开"
|
||||||
|
iconProps={{iconName: "NavigateExternalInline", style: {marginTop: -4}}}
|
||||||
|
onClick={this.openInBrowser} />
|
||||||
|
</Stack>
|
||||||
|
<Stack horizontal horizontalAlign="end" style={{width: 112}}>
|
||||||
|
<CommandBarButton
|
||||||
|
title="关闭"
|
||||||
|
iconProps={{iconName: "Cancel"}}
|
||||||
|
onClick={this.props.dismiss} />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
<webview
|
<webview
|
||||||
id="article"
|
id="article"
|
||||||
src={this.articleView()}
|
src={this.articleView()}
|
||||||
preload="article/preload.js" />
|
preload="article/preload.js"
|
||||||
|
partition="sandbox" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,11 @@ import { FeedIdType } from "../../scripts/models/feed"
|
|||||||
|
|
||||||
export interface CardProps {
|
export interface CardProps {
|
||||||
feedId: FeedIdType
|
feedId: FeedIdType
|
||||||
index: number
|
|
||||||
item: RSSItem
|
item: RSSItem
|
||||||
source: RSSSource
|
source: RSSSource
|
||||||
markRead: (item: RSSItem) => void
|
markRead: (item: RSSItem) => void
|
||||||
contextMenu: (item: RSSItem, e) => void
|
contextMenu: (item: RSSItem, e) => void
|
||||||
showItem: (fid: FeedIdType, index: number) => void
|
showItem: (fid: FeedIdType, item: RSSItem) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Card extends React.Component<CardProps> {
|
export class Card extends React.Component<CardProps> {
|
||||||
@ -24,7 +23,7 @@ export class Card extends React.Component<CardProps> {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
this.props.markRead(this.props.item)
|
this.props.markRead(this.props.item)
|
||||||
this.props.showItem(this.props.feedId, this.props.index)
|
this.props.showItem(this.props.feedId, this.props.item)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseUp = (e: React.MouseEvent) => {
|
onMouseUp = (e: React.MouseEvent) => {
|
||||||
|
@ -32,10 +32,9 @@ class CardsFeed extends Feed {
|
|||||||
return this.props.feed.loaded && (
|
return this.props.feed.loaded && (
|
||||||
<div className="cards-feed-container">
|
<div className="cards-feed-container">
|
||||||
{
|
{
|
||||||
this.props.items.map((item, index) => (
|
this.props.items.map((item) => (
|
||||||
<DefaultCard
|
<DefaultCard
|
||||||
feedId={this.props.feed.id}
|
feedId={this.props.feed.id}
|
||||||
index={index}
|
|
||||||
key={item.id}
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
source={this.props.sourceMap[item.source]}
|
source={this.props.sourceMap[item.source]}
|
||||||
|
@ -10,7 +10,7 @@ type FeedProps = FeedReduxProps & {
|
|||||||
markRead: (item: RSSItem) => void
|
markRead: (item: RSSItem) => void
|
||||||
contextMenu: (item: RSSItem, e) => void
|
contextMenu: (item: RSSItem, e) => void
|
||||||
loadMore: (feed: RSSFeed) => void
|
loadMore: (feed: RSSFeed) => void
|
||||||
showItem: (fid: FeedIdType, index: number) => void
|
showItem: (fid: FeedIdType, item: RSSItem) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Feed extends React.Component<FeedProps> { }
|
export class Feed extends React.Component<FeedProps> { }
|
@ -6,6 +6,7 @@ import { ProgressIndicator } from "@fluentui/react"
|
|||||||
|
|
||||||
type NavProps = {
|
type NavProps = {
|
||||||
state: AppState,
|
state: AppState,
|
||||||
|
itemShown: boolean,
|
||||||
fetch: () => void,
|
fetch: () => void,
|
||||||
menu: () => void,
|
menu: () => void,
|
||||||
logs: () => void,
|
logs: () => void,
|
||||||
@ -51,6 +52,7 @@ class Nav extends React.Component<NavProps, NavState> {
|
|||||||
canFetch = () => this.props.state.sourceInit && this.props.state.feedInit && !this.props.state.fetchingItems
|
canFetch = () => this.props.state.sourceInit && this.props.state.feedInit && !this.props.state.fetchingItems
|
||||||
fetching = () => !this.canFetch() ? " fetching" : ""
|
fetching = () => !this.canFetch() ? " fetching" : ""
|
||||||
menuOn = () => this.props.state.menu ? " menu-on" : ""
|
menuOn = () => this.props.state.menu ? " menu-on" : ""
|
||||||
|
itemOn = () => this.props.itemShown ? " item-on" : ""
|
||||||
hideButtons = () => this.props.state.settings.display ? "hide-btns" : ""
|
hideButtons = () => this.props.state.settings.display ? "hide-btns" : ""
|
||||||
|
|
||||||
fetch = () => {
|
fetch = () => {
|
||||||
@ -65,7 +67,7 @@ class Nav extends React.Component<NavProps, NavState> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<nav className={this.hideButtons() + this.menuOn()}>
|
<nav className={this.hideButtons() + this.menuOn() + this.itemOn()}>
|
||||||
<div className="btn-group">
|
<div className="btn-group">
|
||||||
<a className="btn hide-wide" title="菜单" onClick={this.props.menu}><Icon iconName="GlobalNavButton" /></a>
|
<a className="btn hide-wide" title="菜单" onClick={this.props.menu}><Icon iconName="GlobalNavButton" /></a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,12 +5,13 @@ import { RSSItem } from "../scripts/models/item"
|
|||||||
import Article from "./article"
|
import Article from "./article"
|
||||||
import { dismissItem } from "../scripts/models/page"
|
import { dismissItem } from "../scripts/models/page"
|
||||||
import { AnimationClassNames } from "@fluentui/react"
|
import { AnimationClassNames } from "@fluentui/react"
|
||||||
|
import ArticleContainer from "../containers/article-container"
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
menuOn: boolean
|
menuOn: boolean
|
||||||
settingsOn: boolean
|
settingsOn: boolean
|
||||||
feeds: FeedIdType[]
|
feeds: FeedIdType[]
|
||||||
item: RSSItem
|
itemId: number
|
||||||
dismissItem: () => void
|
dismissItem: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,10 +24,10 @@ class Page extends React.Component<PageProps> {
|
|||||||
<FeedContainer feedId={fid} key={fid} />
|
<FeedContainer feedId={fid} key={fid} />
|
||||||
))}
|
))}
|
||||||
</div>}
|
</div>}
|
||||||
{this.props.item && (
|
{this.props.itemId >= 0 && (
|
||||||
<div className="article-container" onClick={this.props.dismissItem}>
|
<div className="article-container" onClick={this.props.dismissItem}>
|
||||||
<div className={"article-wrapper " + AnimationClassNames.slideUpIn20} onClick={e => e.stopPropagation()}>
|
<div className={"article-wrapper " + AnimationClassNames.slideUpIn20} onClick={e => e.stopPropagation()}>
|
||||||
<Article item={this.props.item} dismiss={dismissItem} />
|
<ArticleContainer itemId={this.props.itemId} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
34
src/containers/article-container.tsx
Normal file
34
src/containers/article-container.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { connect } from "react-redux"
|
||||||
|
import { createSelector } from "reselect"
|
||||||
|
import { RootState } from "../scripts/reducer"
|
||||||
|
import { RSSItem, markUnread, markRead } from "../scripts/models/item"
|
||||||
|
import { AppDispatch } from "../scripts/utils"
|
||||||
|
import { dismissItem } from "../scripts/models/page"
|
||||||
|
import Article from "../components/article"
|
||||||
|
|
||||||
|
type ArticleContainerProps = {
|
||||||
|
itemId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const getItem = (state: RootState, props: ArticleContainerProps) => state.items[props.itemId]
|
||||||
|
const getSource = (state: RootState, props: ArticleContainerProps) => state.sources[state.items[props.itemId].source]
|
||||||
|
|
||||||
|
const makeMapStateToProps = () => {
|
||||||
|
return createSelector(
|
||||||
|
[getItem, getSource],
|
||||||
|
(item, source) => ({
|
||||||
|
item: item,
|
||||||
|
source: source
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch: AppDispatch) => {
|
||||||
|
return {
|
||||||
|
dismiss: () => dispatch(dismissItem()),
|
||||||
|
toggleHasRead: (item: RSSItem) => dispatch(item.hasRead ? markUnread(item) : markRead(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArticleContainer = connect(makeMapStateToProps, mapDispatchToProps)(Article)
|
||||||
|
export default ArticleContainer
|
@ -30,7 +30,7 @@ const mapDispatchToProps = dispatch => {
|
|||||||
markRead: (item: RSSItem) => dispatch(markRead(item)),
|
markRead: (item: RSSItem) => dispatch(markRead(item)),
|
||||||
contextMenu: (item: RSSItem, e) => dispatch(openItemMenu(item, e)),
|
contextMenu: (item: RSSItem, e) => dispatch(openItemMenu(item, e)),
|
||||||
loadMore: (feed: RSSFeed) => dispatch(loadMore(feed)),
|
loadMore: (feed: RSSFeed) => dispatch(loadMore(feed)),
|
||||||
showItem: (fid: FeedIdType, index: number) => dispatch(showItem(fid, index))
|
showItem: (fid: FeedIdType, item: RSSItem) => dispatch(showItem(fid, item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,10 +6,15 @@ import { toggleMenu, toggleLogMenu, toggleSettings } from "../scripts/models/app
|
|||||||
import Nav from "../components/nav"
|
import Nav from "../components/nav"
|
||||||
|
|
||||||
const getState = (state: RootState) => state.app
|
const getState = (state: RootState) => state.app
|
||||||
|
const getItemShown = (state: RootState) => state.page.itemId >= 0
|
||||||
|
|
||||||
const mapStateToProps = createSelector(getState, (state) => ({
|
const mapStateToProps = createSelector(
|
||||||
state: state
|
[getState, getItemShown],
|
||||||
}))
|
(state, itemShown) => ({
|
||||||
|
state: state,
|
||||||
|
itemShown: itemShown
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
fetch: () => dispatch(fetchItems()),
|
fetch: () => dispatch(fetchItems()),
|
||||||
|
@ -8,18 +8,14 @@ import { dismissItem } from "../scripts/models/page"
|
|||||||
const getPage = (state: RootState) => state.page
|
const getPage = (state: RootState) => state.page
|
||||||
const getSettings = (state: RootState) => state.app.settings.display
|
const getSettings = (state: RootState) => state.app.settings.display
|
||||||
const getMenu = (state: RootState) => state.app.menu
|
const getMenu = (state: RootState) => state.app.menu
|
||||||
const getItems = (state: RootState) => state.items
|
|
||||||
const getFeeds = (state: RootState) => state.feeds
|
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
[getPage, getSettings, getMenu, getItems, getFeeds],
|
[getPage, getSettings, getMenu],
|
||||||
(page, settingsOn, menuOn, items, feeds) => ({
|
(page, settingsOn, menuOn) => ({
|
||||||
feeds: [page.feedId],
|
feeds: [page.feedId],
|
||||||
settingsOn: settingsOn,
|
settingsOn: settingsOn,
|
||||||
menuOn: menuOn,
|
menuOn: menuOn,
|
||||||
item: page.itemIndex >= 0 // && page.itemIndex < feeds[page.feedId].iids.length
|
itemId: page.itemId
|
||||||
? items[feeds[page.feedId].iids[page.itemIndex]]
|
|
||||||
: null
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src *; style-src 'self' 'unsafe-inline'; font-src 'self' https://static2.sharepointonline.com; connect-src *; frame-src *">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src *; style-src 'self' 'unsafe-inline'; font-src 'self' https://static2.sharepointonline.com; connect-src *">
|
||||||
<title>Hello World!</title>
|
<title>Hello World!</title>
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
</head>
|
</head>
|
||||||
|
@ -193,7 +193,7 @@ export function appReducer(
|
|||||||
...state,
|
...state,
|
||||||
logMenu: {
|
logMenu: {
|
||||||
...state.logMenu,
|
...state.logMenu,
|
||||||
notify: true,
|
notify: !state.logMenu.display,
|
||||||
logs: [...state.logMenu.logs, new AppLog(
|
logs: [...state.logMenu.logs, new AppLog(
|
||||||
AppLogType.Failure,
|
AppLogType.Failure,
|
||||||
`无法加载订阅源“${action.errSource.name}”`,
|
`无法加载订阅源“${action.errSource.name}”`,
|
||||||
|
@ -199,18 +199,12 @@ export function itemReducer(
|
|||||||
}
|
}
|
||||||
default: return state
|
default: return state
|
||||||
}
|
}
|
||||||
|
case MARK_UNREAD:
|
||||||
case MARK_READ: return {
|
case MARK_READ: return {
|
||||||
...state,
|
...state,
|
||||||
[action.item.id] : {
|
[action.item.id] : {
|
||||||
...action.item,
|
...action.item,
|
||||||
hasRead: true
|
hasRead: action.type === MARK_READ
|
||||||
}
|
|
||||||
}
|
|
||||||
case MARK_UNREAD: return {
|
|
||||||
...state,
|
|
||||||
[action.item.id] : {
|
|
||||||
...action.item,
|
|
||||||
hasRead: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case LOAD_MORE:
|
case LOAD_MORE:
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ALL, SOURCE, FeedIdType } from "./feed"
|
import { ALL, SOURCE, FeedIdType } from "./feed"
|
||||||
import { getWindowBreakpoint } from "../utils"
|
import { getWindowBreakpoint } from "../utils"
|
||||||
|
import { RSSItem, ItemActionTypes, MARK_READ, MARK_UNREAD } from "./item"
|
||||||
|
|
||||||
export const SELECT_PAGE = "SELECT_PAGE"
|
export const SELECT_PAGE = "SELECT_PAGE"
|
||||||
export const SHOW_ITEM = "SHOW_ITEM"
|
export const SHOW_ITEM = "SHOW_ITEM"
|
||||||
@ -22,7 +23,7 @@ interface SelectPageAction {
|
|||||||
interface ShowItemAction {
|
interface ShowItemAction {
|
||||||
type: typeof SHOW_ITEM
|
type: typeof SHOW_ITEM
|
||||||
feedId: FeedIdType
|
feedId: FeedIdType
|
||||||
itemIndex: number
|
item: RSSItem
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DismissItemAction { type: typeof DISMISS_ITEM }
|
interface DismissItemAction { type: typeof DISMISS_ITEM }
|
||||||
@ -50,11 +51,11 @@ export function selectSources(sids: number[], menuKey: string, title: string): P
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showItem(feedId: FeedIdType, itemIndex: number): PageActionTypes {
|
export function showItem(feedId: FeedIdType, item: RSSItem): PageActionTypes {
|
||||||
return {
|
return {
|
||||||
type: SHOW_ITEM,
|
type: SHOW_ITEM,
|
||||||
feedId: feedId,
|
feedId: feedId,
|
||||||
itemIndex: itemIndex
|
item: item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ export const dismissItem = (): PageActionTypes => ({ type: DISMISS_ITEM })
|
|||||||
|
|
||||||
export class PageState {
|
export class PageState {
|
||||||
feedId = ALL as FeedIdType
|
feedId = ALL as FeedIdType
|
||||||
itemIndex = -1
|
itemId = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pageReducer(
|
export function pageReducer(
|
||||||
@ -84,11 +85,11 @@ export function pageReducer(
|
|||||||
}
|
}
|
||||||
case SHOW_ITEM: return {
|
case SHOW_ITEM: return {
|
||||||
...state,
|
...state,
|
||||||
itemIndex: action.itemIndex
|
itemId: action.item.id
|
||||||
}
|
}
|
||||||
case DISMISS_ITEM: return {
|
case DISMISS_ITEM: return {
|
||||||
...state,
|
...state,
|
||||||
itemIndex: -1
|
itemId: -1
|
||||||
}
|
}
|
||||||
default: return state
|
default: return state
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user