use _id & csp update
This commit is contained in:
parent
0128728889
commit
2ff5e13219
6
dist/article/article.html
vendored
6
dist/article/article.html
vendored
@ -3,12 +3,12 @@
|
|||||||
<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 *; media-src *">
|
content="default-src 'none'; script-src-elem 'sha256-34JRfFnY5YiFDB1MABAIxq6OfvlgM/Ba2el4MdA0WoM='; img-src http://* https://*; style-src 'self' 'unsafe-inline'; frame-src http://* https://*; media-src http://* https://*">
|
||||||
<title>Hello World!</title>
|
<title>Article</title>
|
||||||
<link rel="stylesheet" href="article.css" />
|
<link rel="stylesheet" href="article.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="main"></div>
|
<div id="main"></div>
|
||||||
<script src="article.js"></script>
|
<script integrity="sha256-34JRfFnY5YiFDB1MABAIxq6OfvlgM/Ba2el4MdA0WoM=" src="article.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
14
dist/article/article.js
vendored
14
dist/article/article.js
vendored
@ -3,8 +3,20 @@ function get(name) {
|
|||||||
return decodeURIComponent(name[1]);
|
return decodeURIComponent(name[1]);
|
||||||
}
|
}
|
||||||
document.documentElement.style.fontSize = get("s") + "px"
|
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 i of dom.querySelectorAll("img")) {
|
||||||
|
i.src = i.src
|
||||||
|
}
|
||||||
|
for (let s of dom.querySelectorAll("script")) {
|
||||||
|
s.parentNode.removeChild(s)
|
||||||
|
}
|
||||||
let main = document.getElementById("main")
|
let main = document.getElementById("main")
|
||||||
main.innerHTML = decodeURIComponent(window.atob(get("h")))
|
main.innerHTML = dom.body.innerHTML
|
||||||
document.addEventListener("click", event => {
|
document.addEventListener("click", event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (event.target.href) post("request-navigation", event.target.href)
|
if (event.target.href) post("request-navigation", event.target.href)
|
||||||
|
1
dist/styles.css
vendored
1
dist/styles.css
vendored
@ -510,6 +510,7 @@ img.favicon {
|
|||||||
transition: box-shadow linear .08s;
|
transition: box-shadow linear .08s;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
animation-fill-mode: none;
|
||||||
}
|
}
|
||||||
.card:hover {
|
.card:hover {
|
||||||
box-shadow: #0006 0px 5px 40px;
|
box-shadow: #0006 0px 5px 40px;
|
||||||
|
@ -13,6 +13,8 @@ type ArticleProps = {
|
|||||||
source: RSSSource
|
source: RSSSource
|
||||||
dismiss: () => void
|
dismiss: () => void
|
||||||
toggleHasRead: (item: RSSItem) => void
|
toggleHasRead: (item: RSSItem) => void
|
||||||
|
toggleStarred: (item: RSSItem) => void
|
||||||
|
toggleHidden: (item: RSSItem) => void
|
||||||
textMenu: (text: string, position: [number, number]) => void
|
textMenu: (text: string, position: [number, number]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +53,22 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
moreMenuProps = (): IContextualMenuProps => ({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
key: "openInBrowser",
|
||||||
|
text: "在浏览器中打开",
|
||||||
|
iconProps: {iconName: "NavigateExternalInline"},
|
||||||
|
onClick: this.openInBrowser
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "toggleHidden",
|
||||||
|
text: this.props.item.hidden ? "取消隐藏" : "隐藏文章",
|
||||||
|
onClick: () => { this.props.toggleHidden(this.props.item) }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
ipcHandler = event => {
|
ipcHandler = event => {
|
||||||
switch (event.channel) {
|
switch (event.channel) {
|
||||||
case "request-navigation": {
|
case "request-navigation": {
|
||||||
@ -84,7 +102,7 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentDidUpdate = (prevProps: ArticleProps) => {
|
componentDidUpdate = (prevProps: ArticleProps) => {
|
||||||
if (prevProps.item.id != this.props.item.id) {
|
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})
|
||||||
}
|
}
|
||||||
this.componentDidMount()
|
this.componentDidMount()
|
||||||
@ -112,7 +130,7 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
<p className="title">{this.props.item.title}</p>
|
<p className="title">{this.props.item.title}</p>
|
||||||
<p className="date">{this.props.item.date.toLocaleString("zh-cn", {hour12: false})}</p>
|
<p className="date">{this.props.item.date.toLocaleString("zh-cn", {hour12: false})}</p>
|
||||||
<article dangerouslySetInnerHTML={{__html: this.props.item.content}}></article>
|
<article dangerouslySetInnerHTML={{__html: this.props.item.content}}></article>
|
||||||
</>))) + "&s=" + this.state.fontSize
|
</>))) + `&s=${this.state.fontSize}&u=${this.props.item.link}`
|
||||||
|
|
||||||
render = () => (
|
render = () => (
|
||||||
<div className="article">
|
<div className="article">
|
||||||
@ -128,11 +146,13 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
<CommandBarButton
|
<CommandBarButton
|
||||||
title={this.props.item.hasRead ? "标为未读" : "标为已读"}
|
title={this.props.item.hasRead ? "标为未读" : "标为已读"}
|
||||||
iconProps={this.props.item.hasRead
|
iconProps={this.props.item.hasRead
|
||||||
? {iconName: "RadioBtnOn", style: {fontSize: 14, textAlign: "center"}}
|
? {iconName: "StatusCircleRing"}
|
||||||
: {iconName: "StatusCircleRing"}}
|
: {iconName: "RadioBtnOn", style: {fontSize: 14, textAlign: "center"}}}
|
||||||
onClick={() => this.props.toggleHasRead(this.props.item)} />
|
onClick={() => this.props.toggleHasRead(this.props.item)} />
|
||||||
<CommandBarButton
|
<CommandBarButton
|
||||||
iconProps={{iconName: "FavoriteStar"}} />
|
title={this.props.item.starred ? "取消星标" : "标为星标"}
|
||||||
|
iconProps={{iconName: this.props.item.starred ? "FavoriteStarFill" : "FavoriteStar"}}
|
||||||
|
onClick={() => this.props.toggleStarred(this.props.item)} />
|
||||||
<CommandBarButton
|
<CommandBarButton
|
||||||
title="字体大小"
|
title="字体大小"
|
||||||
disabled={this.state.loadWebpage}
|
disabled={this.state.loadWebpage}
|
||||||
@ -145,9 +165,10 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
iconProps={{iconName: "Globe"}}
|
iconProps={{iconName: "Globe"}}
|
||||||
onClick={this.toggleWebpage} />
|
onClick={this.toggleWebpage} />
|
||||||
<CommandBarButton
|
<CommandBarButton
|
||||||
title="在浏览器中打开"
|
title="更多"
|
||||||
iconProps={{iconName: "NavigateExternalInline", style: {marginTop: -4}}}
|
iconProps={{iconName: "More"}}
|
||||||
onClick={this.openInBrowser} />
|
menuIconProps={{style: {display: "none"}}}
|
||||||
|
menuProps={this.moreMenuProps()} />
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack horizontal horizontalAlign="end" style={{width: 112}}>
|
<Stack horizontal horizontalAlign="end" style={{width: 112}}>
|
||||||
<CommandBarButton
|
<CommandBarButton
|
||||||
@ -158,7 +179,7 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
|||||||
</Stack>
|
</Stack>
|
||||||
<webview
|
<webview
|
||||||
id="article"
|
id="article"
|
||||||
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()}
|
||||||
preload={this.state.loadWebpage ? null : "article/preload.js"}
|
preload={this.state.loadWebpage ? null : "article/preload.js"}
|
||||||
partition="sandbox" />
|
partition="sandbox" />
|
||||||
|
@ -2,15 +2,14 @@ import * as React from "react"
|
|||||||
import { openExternal } from "../../scripts/utils"
|
import { openExternal } from "../../scripts/utils"
|
||||||
import { RSSSource, SourceOpenTarget } from "../../scripts/models/source"
|
import { RSSSource, SourceOpenTarget } from "../../scripts/models/source"
|
||||||
import { RSSItem } from "../../scripts/models/item"
|
import { RSSItem } from "../../scripts/models/item"
|
||||||
import { FeedIdType } from "../../scripts/models/feed"
|
|
||||||
|
|
||||||
export interface CardProps {
|
export interface CardProps {
|
||||||
feedId: FeedIdType
|
feedId: string
|
||||||
item: RSSItem
|
item: RSSItem
|
||||||
source: RSSSource
|
source: RSSSource
|
||||||
markRead: (item: RSSItem) => void
|
markRead: (item: RSSItem) => void
|
||||||
contextMenu: (feedId: FeedIdType, item: RSSItem, e) => void
|
contextMenu: (feedId: string, item: RSSItem, e) => void
|
||||||
showItem: (fid: FeedIdType, item: RSSItem) => void
|
showItem: (fid: string, item: RSSItem) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Card extends React.Component<CardProps> {
|
export class Card extends React.Component<CardProps> {
|
||||||
|
@ -5,7 +5,6 @@ import { ContextualMenu, IContextualMenuItem, ContextualMenuItemType, Directiona
|
|||||||
import { ContextMenuType } from "../scripts/models/app"
|
import { ContextMenuType } from "../scripts/models/app"
|
||||||
import { RSSItem } from "../scripts/models/item"
|
import { RSSItem } from "../scripts/models/item"
|
||||||
import { ContextReduxProps } from "../containers/context-menu-container"
|
import { ContextReduxProps } from "../containers/context-menu-container"
|
||||||
import { FeedIdType } from "../scripts/models/feed"
|
|
||||||
import { ViewType } from "../scripts/models/page"
|
import { ViewType } from "../scripts/models/page"
|
||||||
|
|
||||||
export type ContextMenuProps = ContextReduxProps & {
|
export type ContextMenuProps = ContextReduxProps & {
|
||||||
@ -13,12 +12,14 @@ export type ContextMenuProps = ContextReduxProps & {
|
|||||||
event?: MouseEvent | string
|
event?: MouseEvent | string
|
||||||
position?: [number, number]
|
position?: [number, number]
|
||||||
item?: RSSItem
|
item?: RSSItem
|
||||||
feedId?: FeedIdType
|
feedId?: string
|
||||||
text?: string
|
text?: string
|
||||||
viewType: ViewType
|
viewType: ViewType
|
||||||
showItem: (feedId: FeedIdType, item: RSSItem) => void
|
showItem: (feedId: string, item: RSSItem) => void
|
||||||
markRead: (item: RSSItem) => void
|
markRead: (item: RSSItem) => void
|
||||||
markUnread: (item: RSSItem) => void
|
markUnread: (item: RSSItem) => void
|
||||||
|
toggleStarred: (item: RSSItem) => void
|
||||||
|
toggleHidden: (item: RSSItem) => void
|
||||||
switchView: (viewType: ViewType) => void
|
switchView: (viewType: ViewType) => void
|
||||||
close: () => void
|
close: () => void
|
||||||
}
|
}
|
||||||
@ -59,8 +60,15 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
|||||||
onClick: () => { this.props.markRead(this.props.item) }
|
onClick: () => { this.props.markRead(this.props.item) }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "markBelowAsRead",
|
key: "toggleStarred",
|
||||||
text: "将以下标为已读"
|
text: this.props.item.starred ? "取消星标" : "标为星标",
|
||||||
|
iconProps: { iconName: this.props.item.starred ? "FavoriteStar" : "FavoriteStarFill" },
|
||||||
|
onClick: () => { this.props.toggleStarred(this.props.item) }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "toggleHidden",
|
||||||
|
text: this.props.item.hidden ? "取消隐藏" : "隐藏文章",
|
||||||
|
onClick: () => { this.props.toggleHidden(this.props.item) }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "divider_1",
|
key: "divider_1",
|
||||||
|
@ -34,8 +34,8 @@ class CardsFeed extends React.Component<FeedProps> {
|
|||||||
{
|
{
|
||||||
this.props.items.map((item) => (
|
this.props.items.map((item) => (
|
||||||
<DefaultCard
|
<DefaultCard
|
||||||
feedId={this.props.feed.id}
|
feedId={this.props.feed._id}
|
||||||
key={item.id}
|
key={item._id}
|
||||||
item={item}
|
item={item}
|
||||||
source={this.props.sourceMap[item.source]}
|
source={this.props.sourceMap[item.source]}
|
||||||
markRead={this.props.markRead}
|
markRead={this.props.markRead}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { RSSItem } from "../../scripts/models/item"
|
import { RSSItem } from "../../scripts/models/item"
|
||||||
import { FeedReduxProps } from "../../containers/feed-container"
|
import { FeedReduxProps } from "../../containers/feed-container"
|
||||||
import { RSSFeed, FeedIdType } from "../../scripts/models/feed"
|
import { RSSFeed } from "../../scripts/models/feed"
|
||||||
import { ViewType } from "../../scripts/models/page"
|
import { ViewType } from "../../scripts/models/page"
|
||||||
import CardsFeed from "./cards-feed"
|
import CardsFeed from "./cards-feed"
|
||||||
import ListFeed from "./list-feed"
|
import ListFeed from "./list-feed"
|
||||||
@ -12,9 +12,9 @@ export type FeedProps = FeedReduxProps & {
|
|||||||
items: RSSItem[]
|
items: RSSItem[]
|
||||||
sourceMap: Object
|
sourceMap: Object
|
||||||
markRead: (item: RSSItem) => void
|
markRead: (item: RSSItem) => void
|
||||||
contextMenu: (feedId: FeedIdType, item: RSSItem, e) => void
|
contextMenu: (feedId: string, item: RSSItem, e) => void
|
||||||
loadMore: (feed: RSSFeed) => void
|
loadMore: (feed: RSSFeed) => void
|
||||||
showItem: (fid: FeedIdType, item: RSSItem) => void
|
showItem: (fid: string, item: RSSItem) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Feed extends React.Component<FeedProps> {
|
export class Feed extends React.Component<FeedProps> {
|
||||||
|
@ -10,8 +10,8 @@ class ListFeed extends React.Component<FeedProps> {
|
|||||||
{
|
{
|
||||||
this.props.items.map((item) => (
|
this.props.items.map((item) => (
|
||||||
<ListCard
|
<ListCard
|
||||||
feedId={this.props.feed.id}
|
feedId={this.props.feed._id}
|
||||||
key={item.id}
|
key={item._id}
|
||||||
item={item}
|
item={item}
|
||||||
source={this.props.sourceMap[item.source]}
|
source={this.props.sourceMap[item.source]}
|
||||||
markRead={this.props.markRead}
|
markRead={this.props.markRead}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { FeedIdType } from "../scripts/models/feed"
|
|
||||||
import { FeedContainer } from "../containers/feed-container"
|
import { FeedContainer } from "../containers/feed-container"
|
||||||
import { AnimationClassNames, Icon } from "@fluentui/react"
|
import { AnimationClassNames, Icon } from "@fluentui/react"
|
||||||
import ArticleContainer from "../containers/article-container"
|
import ArticleContainer from "../containers/article-container"
|
||||||
@ -8,8 +7,8 @@ import { ViewType } from "../scripts/models/page"
|
|||||||
type PageProps = {
|
type PageProps = {
|
||||||
menuOn: boolean
|
menuOn: boolean
|
||||||
settingsOn: boolean
|
settingsOn: boolean
|
||||||
feeds: FeedIdType[]
|
feeds: string[]
|
||||||
itemId: number
|
itemId: string
|
||||||
viewType: ViewType
|
viewType: ViewType
|
||||||
dismissItem: () => void
|
dismissItem: () => void
|
||||||
offsetItem: (offset: number) => void
|
offsetItem: (offset: number) => void
|
||||||
@ -34,7 +33,7 @@ class Page extends React.Component<PageProps> {
|
|||||||
<FeedContainer viewType={this.props.viewType} feedId={fid} key={fid} />
|
<FeedContainer viewType={this.props.viewType} feedId={fid} key={fid} />
|
||||||
))}
|
))}
|
||||||
</div>}
|
</div>}
|
||||||
{this.props.itemId >= 0 && (
|
{this.props.itemId && (
|
||||||
<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()}>
|
||||||
<ArticleContainer itemId={this.props.itemId} />
|
<ArticleContainer itemId={this.props.itemId} />
|
||||||
@ -54,7 +53,7 @@ class Page extends React.Component<PageProps> {
|
|||||||
<FeedContainer viewType={this.props.viewType} feedId={fid} key={fid} />
|
<FeedContainer viewType={this.props.viewType} feedId={fid} key={fid} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{this.props.itemId >= 0 && (
|
{this.props.itemId && (
|
||||||
<div className="side-article-wrapper">
|
<div className="side-article-wrapper">
|
||||||
<ArticleContainer itemId={this.props.itemId} />
|
<ArticleContainer itemId={this.props.itemId} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import { createSelector } from "reselect"
|
import { createSelector } from "reselect"
|
||||||
import { RootState } from "../scripts/reducer"
|
import { RootState } from "../scripts/reducer"
|
||||||
import { RSSItem, markUnread, markRead } from "../scripts/models/item"
|
import { RSSItem, markUnread, markRead, toggleStarred, toggleHidden } from "../scripts/models/item"
|
||||||
import { AppDispatch } from "../scripts/utils"
|
import { AppDispatch } from "../scripts/utils"
|
||||||
import { dismissItem } from "../scripts/models/page"
|
import { dismissItem } from "../scripts/models/page"
|
||||||
import Article from "../components/article"
|
import Article from "../components/article"
|
||||||
import { openTextMenu } from "../scripts/models/app"
|
import { openTextMenu } from "../scripts/models/app"
|
||||||
|
|
||||||
type ArticleContainerProps = {
|
type ArticleContainerProps = {
|
||||||
itemId: number
|
itemId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const getItem = (state: RootState, props: ArticleContainerProps) => state.items[props.itemId]
|
const getItem = (state: RootState, props: ArticleContainerProps) => state.items[props.itemId]
|
||||||
@ -28,6 +28,8 @@ const mapDispatchToProps = (dispatch: AppDispatch) => {
|
|||||||
return {
|
return {
|
||||||
dismiss: () => dispatch(dismissItem()),
|
dismiss: () => dispatch(dismissItem()),
|
||||||
toggleHasRead: (item: RSSItem) => dispatch(item.hasRead ? markUnread(item) : markRead(item)),
|
toggleHasRead: (item: RSSItem) => dispatch(item.hasRead ? markUnread(item) : markRead(item)),
|
||||||
|
toggleStarred: (item: RSSItem) => dispatch(toggleStarred(item)),
|
||||||
|
toggleHidden: (item: RSSItem) => dispatch(toggleHidden(item)),
|
||||||
textMenu: (text: string, position: [number, number]) => dispatch(openTextMenu(text, position))
|
textMenu: (text: string, position: [number, number]) => dispatch(openTextMenu(text, position))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,8 @@ import { createSelector } from "reselect"
|
|||||||
import { RootState } from "../scripts/reducer"
|
import { RootState } from "../scripts/reducer"
|
||||||
import { ContextMenuType, closeContextMenu } from "../scripts/models/app"
|
import { ContextMenuType, closeContextMenu } from "../scripts/models/app"
|
||||||
import { ContextMenu } from "../components/context-menu"
|
import { ContextMenu } from "../components/context-menu"
|
||||||
import { RSSItem, markRead, markUnread } from "../scripts/models/item"
|
import { RSSItem, markRead, markUnread, toggleStarred, toggleHidden } from "../scripts/models/item"
|
||||||
import { showItem, switchView, ViewType } from "../scripts/models/page"
|
import { showItem, switchView, ViewType } from "../scripts/models/page"
|
||||||
import { FeedIdType } from "../scripts/models/feed"
|
|
||||||
import { setDefaultView } from "../scripts/utils"
|
import { setDefaultView } from "../scripts/utils"
|
||||||
|
|
||||||
const getContext = (state: RootState) => state.app.contextMenu
|
const getContext = (state: RootState) => state.app.contextMenu
|
||||||
@ -38,9 +37,11 @@ const mapStateToProps = createSelector(
|
|||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
showItem: (feedId: FeedIdType, item: RSSItem) => dispatch(showItem(feedId, item)),
|
showItem: (feedId: string, item: RSSItem) => dispatch(showItem(feedId, item)),
|
||||||
markRead: (item: RSSItem) => dispatch(markRead(item)),
|
markRead: (item: RSSItem) => dispatch(markRead(item)),
|
||||||
markUnread: (item: RSSItem) => dispatch(markUnread(item)),
|
markUnread: (item: RSSItem) => dispatch(markUnread(item)),
|
||||||
|
toggleStarred: (item: RSSItem) => dispatch(toggleStarred(item)),
|
||||||
|
toggleHidden: (item: RSSItem) => dispatch(toggleHidden(item)),
|
||||||
switchView: (viewType: ViewType) => {
|
switchView: (viewType: ViewType) => {
|
||||||
setDefaultView(viewType)
|
setDefaultView(viewType)
|
||||||
dispatch(switchView(viewType))
|
dispatch(switchView(viewType))
|
||||||
|
@ -3,12 +3,12 @@ import { createSelector } from "reselect"
|
|||||||
import { RootState } from "../scripts/reducer"
|
import { RootState } from "../scripts/reducer"
|
||||||
import { markRead, RSSItem } from "../scripts/models/item"
|
import { markRead, RSSItem } from "../scripts/models/item"
|
||||||
import { openItemMenu } from "../scripts/models/app"
|
import { openItemMenu } from "../scripts/models/app"
|
||||||
import { FeedIdType, loadMore, RSSFeed } from "../scripts/models/feed"
|
import { loadMore, RSSFeed } from "../scripts/models/feed"
|
||||||
import { showItem, ViewType } from "../scripts/models/page"
|
import { showItem, ViewType } from "../scripts/models/page"
|
||||||
import { Feed } from "../components/feeds/feed"
|
import { Feed } from "../components/feeds/feed"
|
||||||
|
|
||||||
interface FeedContainerProps {
|
interface FeedContainerProps {
|
||||||
feedId: FeedIdType
|
feedId: string
|
||||||
viewType: ViewType
|
viewType: ViewType
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,9 +31,9 @@ const makeMapStateToProps = () => {
|
|||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
markRead: (item: RSSItem) => dispatch(markRead(item)),
|
markRead: (item: RSSItem) => dispatch(markRead(item)),
|
||||||
contextMenu: (feedId: FeedIdType, item: RSSItem, e) => dispatch(openItemMenu(item, feedId, e)),
|
contextMenu: (feedId: string, item: RSSItem, e) => dispatch(openItemMenu(item, feedId, e)),
|
||||||
loadMore: (feed: RSSFeed) => dispatch(loadMore(feed)),
|
loadMore: (feed: RSSFeed) => dispatch(loadMore(feed)),
|
||||||
showItem: (fid: FeedIdType, item: RSSItem) => dispatch(showItem(fid, item))
|
showItem: (fid: string, item: RSSItem) => dispatch(showItem(fid, item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { ViewType } from "../scripts/models/page"
|
|||||||
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) && state.page.viewType !== ViewType.List
|
const getItemShown = (state: RootState) => state.page.itemId && state.page.viewType !== ViewType.List
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
[getState, getItemShown],
|
[getState, getItemShown],
|
||||||
|
@ -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 *">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self'; img-src *; style-src 'self' 'unsafe-inline'; font-src 'self' https://static2.sharepointonline.com; connect-src https://* http://*">
|
||||||
<title>Fluent Reader</title>
|
<title>Fluent Reader</title>
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
</head>
|
</head>
|
||||||
|
@ -20,5 +20,6 @@ export const idb = new Datastore<RSSItem>({
|
|||||||
if (err) window.console.log(err)
|
if (err) window.console.log(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
idb.ensureIndex({ fieldName: "id", unique: true })
|
idb.removeIndex("id")
|
||||||
|
idb.update({}, {$unset: {id: true}}, {multi: true})
|
||||||
//idb.remove({}, { multi: true })
|
//idb.remove({}, { multi: true })
|
@ -1,7 +1,7 @@
|
|||||||
import { RSSSource, INIT_SOURCES, SourceActionTypes, ADD_SOURCE, UPDATE_SOURCE, DELETE_SOURCE } from "./source"
|
import { RSSSource, INIT_SOURCES, SourceActionTypes, ADD_SOURCE, UPDATE_SOURCE, DELETE_SOURCE } from "./source"
|
||||||
import { RSSItem, ItemActionTypes, FETCH_ITEMS } from "./item"
|
import { RSSItem, ItemActionTypes, FETCH_ITEMS } from "./item"
|
||||||
import { ActionStatus, AppThunk, getWindowBreakpoint } from "../utils"
|
import { ActionStatus, AppThunk, getWindowBreakpoint } from "../utils"
|
||||||
import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds, FeedIdType } from "./feed"
|
import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds } from "./feed"
|
||||||
import { SourceGroupActionTypes, UPDATE_SOURCE_GROUP, ADD_SOURCE_TO_GROUP, DELETE_SOURCE_GROUP, REMOVE_SOURCE_FROM_GROUP } from "./group"
|
import { SourceGroupActionTypes, UPDATE_SOURCE_GROUP, ADD_SOURCE_TO_GROUP, DELETE_SOURCE_GROUP, REMOVE_SOURCE_FROM_GROUP } from "./group"
|
||||||
import { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles } from "./page"
|
import { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles } from "./page"
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ export class AppState {
|
|||||||
type: ContextMenuType,
|
type: ContextMenuType,
|
||||||
event?: MouseEvent | string,
|
event?: MouseEvent | string,
|
||||||
position?: [number, number],
|
position?: [number, number],
|
||||||
target?: [RSSItem, FeedIdType] | RSSSource | string
|
target?: [RSSItem, string] | RSSSource | string
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -74,7 +74,7 @@ interface OpenItemMenuAction {
|
|||||||
type: typeof OPEN_ITEM_MENU
|
type: typeof OPEN_ITEM_MENU
|
||||||
event: MouseEvent
|
event: MouseEvent
|
||||||
item: RSSItem
|
item: RSSItem
|
||||||
feedId: FeedIdType
|
feedId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OpenTextMenuAction {
|
interface OpenTextMenuAction {
|
||||||
@ -109,7 +109,7 @@ export function closeContextMenu(): ContextMenuActionTypes {
|
|||||||
return { type: CLOSE_CONTEXT_MENU }
|
return { type: CLOSE_CONTEXT_MENU }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openItemMenu(item: RSSItem, feedId: FeedIdType, event: React.MouseEvent): ContextMenuActionTypes {
|
export function openItemMenu(item: RSSItem, feedId: string, event: React.MouseEvent): ContextMenuActionTypes {
|
||||||
return {
|
return {
|
||||||
type: OPEN_ITEM_MENU,
|
type: OPEN_ITEM_MENU,
|
||||||
event: event.nativeEvent,
|
event: event.nativeEvent,
|
||||||
|
@ -6,20 +6,19 @@ import { PageActionTypes, SELECT_PAGE, PageType } from "./page"
|
|||||||
|
|
||||||
export const ALL = "ALL"
|
export const ALL = "ALL"
|
||||||
export const SOURCE = "SOURCE"
|
export const SOURCE = "SOURCE"
|
||||||
export type FeedIdType = number | string
|
|
||||||
|
|
||||||
const LOAD_QUANTITY = 50
|
const LOAD_QUANTITY = 50
|
||||||
|
|
||||||
export class RSSFeed {
|
export class RSSFeed {
|
||||||
id: FeedIdType
|
_id: string
|
||||||
loaded: boolean
|
loaded: boolean
|
||||||
loading: boolean
|
loading: boolean
|
||||||
allLoaded: boolean
|
allLoaded: boolean
|
||||||
sids: number[]
|
sids: number[]
|
||||||
iids: number[]
|
iids: string[]
|
||||||
|
|
||||||
constructor (id: FeedIdType, sids=[]) {
|
constructor (id: string = null, sids=[]) {
|
||||||
this.id = id
|
this._id = id
|
||||||
this.sids = sids
|
this.sids = sids
|
||||||
this.iids = []
|
this.iids = []
|
||||||
this.loaded = false
|
this.loaded = false
|
||||||
@ -44,7 +43,7 @@ export class RSSFeed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type FeedState = {
|
export type FeedState = {
|
||||||
[id in FeedIdType]: RSSFeed
|
[_id: string]: RSSFeed
|
||||||
}
|
}
|
||||||
|
|
||||||
export const INIT_FEEDS = 'INIT_FEEDS'
|
export const INIT_FEEDS = 'INIT_FEEDS'
|
||||||
@ -200,7 +199,7 @@ export function feedReducer(
|
|||||||
let nextState = { ...state }
|
let nextState = { ...state }
|
||||||
for (let k of Object.keys(state)) {
|
for (let k of Object.keys(state)) {
|
||||||
if (state[k].loaded) {
|
if (state[k].loaded) {
|
||||||
let iids = action.items.filter(i => state[k].sids.includes(i.source)).map(i => i.id)
|
let iids = action.items.filter(i => state[k].sids.includes(i.source)).map(i => i._id)
|
||||||
if (iids.length > 0) {
|
if (iids.length > 0) {
|
||||||
nextState[k] = {
|
nextState[k] = {
|
||||||
...nextState[k],
|
...nextState[k],
|
||||||
@ -217,11 +216,11 @@ export function feedReducer(
|
|||||||
switch (action.status) {
|
switch (action.status) {
|
||||||
case ActionStatus.Success: return {
|
case ActionStatus.Success: return {
|
||||||
...state,
|
...state,
|
||||||
[action.feed.id]: {
|
[action.feed._id]: {
|
||||||
...action.feed,
|
...action.feed,
|
||||||
loaded: true,
|
loaded: true,
|
||||||
allLoaded: action.items.length < LOAD_QUANTITY,
|
allLoaded: action.items.length < LOAD_QUANTITY,
|
||||||
iids: action.items.map(i => i.id)
|
iids: action.items.map(i => i._id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default: return state
|
default: return state
|
||||||
@ -230,23 +229,23 @@ export function feedReducer(
|
|||||||
switch (action.status) {
|
switch (action.status) {
|
||||||
case ActionStatus.Request: return {
|
case ActionStatus.Request: return {
|
||||||
...state,
|
...state,
|
||||||
[action.feed.id] : {
|
[action.feed._id] : {
|
||||||
...action.feed,
|
...action.feed,
|
||||||
loading: true
|
loading: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ActionStatus.Success: return {
|
case ActionStatus.Success: return {
|
||||||
...state,
|
...state,
|
||||||
[action.feed.id] : {
|
[action.feed._id] : {
|
||||||
...action.feed,
|
...action.feed,
|
||||||
loading: false,
|
loading: false,
|
||||||
allLoaded: action.items.length < LOAD_QUANTITY,
|
allLoaded: action.items.length < LOAD_QUANTITY,
|
||||||
iids: [...action.feed.iids, ...action.items.map(i => i.id)]
|
iids: [...action.feed.iids, ...action.items.map(i => i._id)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ActionStatus.Failure: return {
|
case ActionStatus.Failure: return {
|
||||||
...state,
|
...state,
|
||||||
[action.feed.id] : {
|
[action.feed._id] : {
|
||||||
...action.feed,
|
...action.feed,
|
||||||
loading: false
|
loading: false
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import fs = require("fs")
|
import fs = require("fs")
|
||||||
import { SourceActionTypes, ADD_SOURCE, DELETE_SOURCE, addSource } from "./source"
|
import { SourceActionTypes, ADD_SOURCE, DELETE_SOURCE, addSource } from "./source"
|
||||||
|
|
||||||
import { ActionStatus, AppThunk, domParser, AppDispatch, getWindowBreakpoint } from "../utils"
|
import { ActionStatus, AppThunk, domParser, AppDispatch } from "../utils"
|
||||||
import { saveSettings } from "./app"
|
import { saveSettings } from "./app"
|
||||||
|
|
||||||
const GROUPS_STORE_KEY = "sourceGroups"
|
const GROUPS_STORE_KEY = "sourceGroups"
|
||||||
|
@ -5,7 +5,7 @@ import { FeedActionTypes, INIT_FEED, LOAD_MORE } from "./feed"
|
|||||||
import Parser = require("@yang991178/rss-parser")
|
import Parser = require("@yang991178/rss-parser")
|
||||||
|
|
||||||
export class RSSItem {
|
export class RSSItem {
|
||||||
id: number
|
_id: string
|
||||||
source: number
|
source: number
|
||||||
title: string
|
title: string
|
||||||
link: string
|
link: string
|
||||||
@ -14,20 +14,25 @@ export class RSSItem {
|
|||||||
thumb?: string
|
thumb?: string
|
||||||
content: string
|
content: string
|
||||||
snippet: string
|
snippet: string
|
||||||
creator: string
|
creator?: string
|
||||||
categories: string[]
|
categories?: string[]
|
||||||
hasRead: boolean
|
hasRead: boolean
|
||||||
|
starred?: true
|
||||||
|
hidden?: true
|
||||||
|
|
||||||
constructor (item: Parser.Item, source: RSSSource) {
|
constructor (item: Parser.Item, source: RSSSource) {
|
||||||
this.source = source.sid
|
this.source = source.sid
|
||||||
this.title = item.title
|
this.title = item.title || ""
|
||||||
this.link = item.link
|
this.link = item.link || ""
|
||||||
this.date = new Date(item.isoDate)
|
|
||||||
this.fetchedDate = new Date()
|
this.fetchedDate = new Date()
|
||||||
|
this.date = item.isoDate ? new Date(item.isoDate) : this.fetchedDate
|
||||||
if (item.thumb) this.thumb = item.thumb
|
if (item.thumb) this.thumb = item.thumb
|
||||||
else if (item.image) this.thumb = item.image
|
else if (item.image) this.thumb = item.image
|
||||||
else {
|
else {
|
||||||
let dom = domParser.parseFromString(item.content, "text/html")
|
let dom = domParser.parseFromString(item.content, "text/html")
|
||||||
|
let baseEl = dom.createElement('base')
|
||||||
|
baseEl.setAttribute('href', this.link.split("/").slice(0, 3).join("/"))
|
||||||
|
dom.head.append(baseEl)
|
||||||
let img = dom.querySelector("img")
|
let img = dom.querySelector("img")
|
||||||
if (img && img.src) this.thumb = img.src
|
if (img && img.src) this.thumb = img.src
|
||||||
}
|
}
|
||||||
@ -35,8 +40,8 @@ export class RSSItem {
|
|||||||
this.content = item.fullContent
|
this.content = item.fullContent
|
||||||
this.snippet = htmlDecode(item.fullContent)
|
this.snippet = htmlDecode(item.fullContent)
|
||||||
} else {
|
} else {
|
||||||
this.content = item.content
|
this.content = item.content || ""
|
||||||
this.snippet = htmlDecode(item.contentSnippet)
|
this.snippet = htmlDecode(item.contentSnippet || "")
|
||||||
}
|
}
|
||||||
this.creator = item.creator
|
this.creator = item.creator
|
||||||
this.categories = item.categories
|
this.categories = item.categories
|
||||||
@ -45,12 +50,14 @@ export class RSSItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ItemState = {
|
export type ItemState = {
|
||||||
[id: number]: RSSItem
|
[_id: string]: RSSItem
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FETCH_ITEMS = 'FETCH_ITEMS'
|
export const FETCH_ITEMS = 'FETCH_ITEMS'
|
||||||
export const MARK_READ = "MARK_READ"
|
export const MARK_READ = "MARK_READ"
|
||||||
export const MARK_UNREAD = "MARK_UNREAD"
|
export const MARK_UNREAD = "MARK_UNREAD"
|
||||||
|
export const TOGGLE_STARRED = "TOGGLE_STARRED"
|
||||||
|
export const TOGGLE_HIDDEN = "TOGGLE_HIDDEN"
|
||||||
|
|
||||||
interface FetchItemsAction {
|
interface FetchItemsAction {
|
||||||
type: typeof FETCH_ITEMS
|
type: typeof FETCH_ITEMS
|
||||||
@ -71,7 +78,17 @@ interface MarkUnreadAction {
|
|||||||
item: RSSItem
|
item: RSSItem
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ItemActionTypes = FetchItemsAction | MarkReadAction | MarkUnreadAction
|
interface ToggleStarredAction {
|
||||||
|
type: typeof TOGGLE_STARRED
|
||||||
|
item: RSSItem
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToggleHiddenAction {
|
||||||
|
type: typeof TOGGLE_HIDDEN
|
||||||
|
item: RSSItem
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ItemActionTypes = FetchItemsAction | MarkReadAction | MarkUnreadAction | ToggleStarredAction | ToggleHiddenAction
|
||||||
|
|
||||||
export function fetchItemsRequest(fetchCount = 0): ItemActionTypes {
|
export function fetchItemsRequest(fetchCount = 0): ItemActionTypes {
|
||||||
return {
|
return {
|
||||||
@ -107,22 +124,15 @@ export function fetchItemsIntermediate(): ItemActionTypes {
|
|||||||
|
|
||||||
export function insertItems(items: RSSItem[]): Promise<RSSItem[]> {
|
export function insertItems(items: RSSItem[]): Promise<RSSItem[]> {
|
||||||
return new Promise<RSSItem[]>((resolve, reject) => {
|
return new Promise<RSSItem[]>((resolve, reject) => {
|
||||||
db.idb.find({}).projection({ id: 1 }).sort({ id: -1 }).limit(1).exec((err, docs) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
let count = (docs.length == 0) ? 0 : (docs[0].id + 1)
|
|
||||||
items.sort((a, b) => a.date.getTime() - b.date.getTime())
|
items.sort((a, b) => a.date.getTime() - b.date.getTime())
|
||||||
for (let i of items) i.id = count++
|
db.idb.insert(items, (err, inserted) => {
|
||||||
db.idb.insert(items, (err) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err)
|
reject(err)
|
||||||
} else {
|
} else {
|
||||||
resolve(items)
|
resolve(inserted)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchItems(): AppThunk<Promise<void>> {
|
export function fetchItems(): AppThunk<Promise<void>> {
|
||||||
@ -145,8 +155,8 @@ export function fetchItems(): AppThunk<Promise<void>> {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
insertItems(items)
|
insertItems(items)
|
||||||
.then(() => {
|
.then(inserted => {
|
||||||
dispatch(fetchItemsSuccess(items.reverse()))
|
dispatch(fetchItemsSuccess(inserted.reverse()))
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
@ -159,30 +169,62 @@ export function fetchItems(): AppThunk<Promise<void>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const markReadDone = (item: RSSItem): ItemActionTypes => ({
|
const markReadDone = (item: RSSItem): ItemActionTypes => ({
|
||||||
type: MARK_READ,
|
type: MARK_READ,
|
||||||
item: item
|
item: item
|
||||||
})
|
})
|
||||||
|
|
||||||
export const markUnreadDone = (item: RSSItem): ItemActionTypes => ({
|
const markUnreadDone = (item: RSSItem): ItemActionTypes => ({
|
||||||
type: MARK_UNREAD,
|
type: MARK_UNREAD,
|
||||||
item: item
|
item: item
|
||||||
})
|
})
|
||||||
|
|
||||||
export function markRead(item: RSSItem): AppThunk {
|
export function markRead(item: RSSItem): AppThunk {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
db.idb.update({ id: item.id }, { $set: { hasRead: true } })
|
db.idb.update({ _id: item._id }, { $set: { hasRead: true } })
|
||||||
dispatch(markReadDone(item))
|
dispatch(markReadDone(item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markUnread(item: RSSItem): AppThunk {
|
export function markUnread(item: RSSItem): AppThunk {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
db.idb.update({ id: item.id }, { $set: { hasRead: false } })
|
db.idb.update({ _id: item._id }, { $set: { hasRead: false } })
|
||||||
dispatch(markUnreadDone(item))
|
dispatch(markUnreadDone(item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toggleStarredDone = (item: RSSItem): ItemActionTypes => ({
|
||||||
|
type: TOGGLE_STARRED,
|
||||||
|
item: item
|
||||||
|
})
|
||||||
|
|
||||||
|
export function toggleStarred(item: RSSItem): AppThunk {
|
||||||
|
return (dispatch) => {
|
||||||
|
if (item.starred === true) {
|
||||||
|
db.idb.update({ _id: item._id }, { $unset: { starred: true } })
|
||||||
|
} else {
|
||||||
|
db.idb.update({ _id: item._id }, { $set: { starred: true } })
|
||||||
|
}
|
||||||
|
dispatch(toggleStarredDone(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleHiddenDone = (item: RSSItem): ItemActionTypes => ({
|
||||||
|
type: TOGGLE_HIDDEN,
|
||||||
|
item: item
|
||||||
|
})
|
||||||
|
|
||||||
|
export function toggleHidden(item: RSSItem): AppThunk {
|
||||||
|
return (dispatch) => {
|
||||||
|
if (item.hidden === true) {
|
||||||
|
db.idb.update({ _id: item._id }, { $unset: { hidden: true } })
|
||||||
|
} else {
|
||||||
|
db.idb.update({ _id: item._id }, { $set: { hidden: true } })
|
||||||
|
}
|
||||||
|
dispatch(toggleHiddenDone(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function itemReducer(
|
export function itemReducer(
|
||||||
state: ItemState = {},
|
state: ItemState = {},
|
||||||
action: ItemActionTypes | FeedActionTypes
|
action: ItemActionTypes | FeedActionTypes
|
||||||
@ -193,7 +235,7 @@ export function itemReducer(
|
|||||||
case ActionStatus.Success: {
|
case ActionStatus.Success: {
|
||||||
let newMap = {}
|
let newMap = {}
|
||||||
for (let i of action.items) {
|
for (let i of action.items) {
|
||||||
newMap[i.id] = i
|
newMap[i._id] = i
|
||||||
}
|
}
|
||||||
return {...newMap, ...state}
|
return {...newMap, ...state}
|
||||||
}
|
}
|
||||||
@ -202,18 +244,36 @@ export function itemReducer(
|
|||||||
case MARK_UNREAD:
|
case MARK_UNREAD:
|
||||||
case MARK_READ: return {
|
case MARK_READ: return {
|
||||||
...state,
|
...state,
|
||||||
[action.item.id] : {
|
[action.item._id] : {
|
||||||
...action.item,
|
...action.item,
|
||||||
hasRead: action.type === MARK_READ
|
hasRead: action.type === MARK_READ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case TOGGLE_STARRED: {
|
||||||
|
let newItem = { ...action.item }
|
||||||
|
if (newItem.starred === true) delete newItem.starred
|
||||||
|
else newItem.starred = true
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[newItem._id]: newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case TOGGLE_HIDDEN: {
|
||||||
|
let newItem = { ...action.item }
|
||||||
|
if (newItem.hidden === true) delete newItem.hidden
|
||||||
|
else newItem.hidden = true
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[newItem._id]: newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
case LOAD_MORE:
|
case LOAD_MORE:
|
||||||
case INIT_FEED: {
|
case INIT_FEED: {
|
||||||
switch (action.status) {
|
switch (action.status) {
|
||||||
case ActionStatus.Success: {
|
case ActionStatus.Success: {
|
||||||
let nextState = { ...state }
|
let nextState = { ...state }
|
||||||
for (let i of action.items) {
|
for (let i of action.items) {
|
||||||
nextState[i.id] = i
|
nextState[i._id] = i
|
||||||
}
|
}
|
||||||
return nextState
|
return nextState
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ALL, SOURCE, FeedIdType, loadMore } from "./feed"
|
import { ALL, SOURCE, loadMore } from "./feed"
|
||||||
import { getWindowBreakpoint, AppThunk, getDefaultView } from "../utils"
|
import { getWindowBreakpoint, AppThunk, getDefaultView } from "../utils"
|
||||||
import { RSSItem, markRead } from "./item"
|
import { RSSItem, markRead } from "./item"
|
||||||
import { SourceActionTypes, DELETE_SOURCE } from "./source"
|
import { SourceActionTypes, DELETE_SOURCE } from "./source"
|
||||||
@ -34,7 +34,7 @@ interface SwitchViewAction {
|
|||||||
|
|
||||||
interface ShowItemAction {
|
interface ShowItemAction {
|
||||||
type: typeof SHOW_ITEM
|
type: typeof SHOW_ITEM
|
||||||
feedId: FeedIdType
|
feedId: string
|
||||||
item: RSSItem
|
item: RSSItem
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ export function switchView(viewType: ViewType): PageActionTypes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showItem(feedId: FeedIdType, item: RSSItem): PageActionTypes {
|
export function showItem(feedId: string, item: RSSItem): PageActionTypes {
|
||||||
return {
|
return {
|
||||||
type: SHOW_ITEM,
|
type: SHOW_ITEM,
|
||||||
feedId: feedId,
|
feedId: feedId,
|
||||||
@ -109,8 +109,8 @@ export function showOffsetItem(offset: number): AppThunk {
|
|||||||
|
|
||||||
export class PageState {
|
export class PageState {
|
||||||
viewType = getDefaultView()
|
viewType = getDefaultView()
|
||||||
feedId = ALL as FeedIdType
|
feedId = ALL
|
||||||
itemId = -1
|
itemId = null as string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pageReducer(
|
export function pageReducer(
|
||||||
@ -133,16 +133,16 @@ export function pageReducer(
|
|||||||
case SWITCH_VIEW: return {
|
case SWITCH_VIEW: return {
|
||||||
...state,
|
...state,
|
||||||
viewType: action.viewType,
|
viewType: action.viewType,
|
||||||
itemId: action.viewType === ViewType.List ? state.itemId : -1
|
itemId: action.viewType === ViewType.List ? state.itemId : null
|
||||||
}
|
}
|
||||||
case SHOW_ITEM: return {
|
case SHOW_ITEM: return {
|
||||||
...state,
|
...state,
|
||||||
itemId: action.item.id
|
itemId: action.item._id
|
||||||
}
|
}
|
||||||
case DELETE_SOURCE:
|
case DELETE_SOURCE:
|
||||||
case DISMISS_ITEM: return {
|
case DISMISS_ITEM: return {
|
||||||
...state,
|
...state,
|
||||||
itemId: -1
|
itemId: null
|
||||||
}
|
}
|
||||||
default: return state
|
default: return state
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { shell, remote } from "electron"
|
|||||||
import { ThunkAction, ThunkDispatch } from "redux-thunk"
|
import { ThunkAction, ThunkDispatch } from "redux-thunk"
|
||||||
import { AnyAction } from "redux"
|
import { AnyAction } from "redux"
|
||||||
import { RootState } from "./reducer"
|
import { RootState } from "./reducer"
|
||||||
|
import URL = require("url")
|
||||||
|
|
||||||
export enum ActionStatus {
|
export enum ActionStatus {
|
||||||
Request, Success, Failure, Intermediate
|
Request, Success, Failure, Intermediate
|
||||||
@ -49,6 +50,7 @@ export function setProxy(address = null) {
|
|||||||
|
|
||||||
import ElectronProxyAgent = require("@yang991178/electron-proxy-agent")
|
import ElectronProxyAgent = require("@yang991178/electron-proxy-agent")
|
||||||
import { ViewType } from "./models/page"
|
import { ViewType } from "./models/page"
|
||||||
|
import { RSSSource } from "./models/source"
|
||||||
let agent = new ElectronProxyAgent(remote.getCurrentWebContents().session)
|
let agent = new ElectronProxyAgent(remote.getCurrentWebContents().session)
|
||||||
export const rssParser = new Parser({
|
export const rssParser = new Parser({
|
||||||
customFields: customFields,
|
customFields: customFields,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user