diff --git a/dist/article/article.html b/dist/article/article.html index 086eb46..20ccc2d 100644 --- a/dist/article/article.html +++ b/dist/article/article.html @@ -3,12 +3,12 @@ - Hello World! + 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://*"> + Article
- + \ No newline at end of file diff --git a/dist/article/article.js b/dist/article/article.js index 1295c7c..03213c1 100644 --- a/dist/article/article.js +++ b/dist/article/article.js @@ -3,8 +3,20 @@ function get(name) { 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 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") -main.innerHTML = decodeURIComponent(window.atob(get("h"))) +main.innerHTML = dom.body.innerHTML document.addEventListener("click", event => { event.preventDefault() if (event.target.href) post("request-navigation", event.target.href) diff --git a/dist/styles.css b/dist/styles.css index 27bd1b7..27dfcc1 100644 --- a/dist/styles.css +++ b/dist/styles.css @@ -510,6 +510,7 @@ img.favicon { transition: box-shadow linear .08s; transform: scale(1); cursor: pointer; + animation-fill-mode: none; } .card:hover { box-shadow: #0006 0px 5px 40px; diff --git a/src/components/article.tsx b/src/components/article.tsx index a9fc52e..4b0d5ad 100644 --- a/src/components/article.tsx +++ b/src/components/article.tsx @@ -13,6 +13,8 @@ type ArticleProps = { source: RSSSource dismiss: () => void toggleHasRead: (item: RSSItem) => void + toggleStarred: (item: RSSItem) => void + toggleHidden: (item: RSSItem) => void textMenu: (text: string, position: [number, number]) => void } @@ -51,6 +53,22 @@ class Article extends React.Component { })) }) + 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 => { switch (event.channel) { case "request-navigation": { @@ -84,7 +102,7 @@ class Article extends React.Component { } } 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.componentDidMount() @@ -112,7 +130,7 @@ class Article extends React.Component {

{this.props.item.title}

{this.props.item.date.toLocaleString("zh-cn", {hour12: false})}

- ))) + "&s=" + this.state.fontSize + ))) + `&s=${this.state.fontSize}&u=${this.props.item.link}` render = () => (
@@ -128,11 +146,13 @@ class Article extends React.Component { this.props.toggleHasRead(this.props.item)} /> + title={this.props.item.starred ? "取消星标" : "标为星标"} + iconProps={{iconName: this.props.item.starred ? "FavoriteStarFill" : "FavoriteStar"}} + onClick={() => this.props.toggleStarred(this.props.item)} /> { iconProps={{iconName: "Globe"}} onClick={this.toggleWebpage} /> + title="更多" + iconProps={{iconName: "More"}} + menuIconProps={{style: {display: "none"}}} + menuProps={this.moreMenuProps()} /> { diff --git a/src/components/cards/card.tsx b/src/components/cards/card.tsx index 3bd5085..780df09 100644 --- a/src/components/cards/card.tsx +++ b/src/components/cards/card.tsx @@ -2,15 +2,14 @@ import * as React from "react" import { openExternal } from "../../scripts/utils" import { RSSSource, SourceOpenTarget } from "../../scripts/models/source" import { RSSItem } from "../../scripts/models/item" -import { FeedIdType } from "../../scripts/models/feed" export interface CardProps { - feedId: FeedIdType + feedId: string item: RSSItem source: RSSSource markRead: (item: RSSItem) => void - contextMenu: (feedId: FeedIdType, item: RSSItem, e) => void - showItem: (fid: FeedIdType, item: RSSItem) => void + contextMenu: (feedId: string, item: RSSItem, e) => void + showItem: (fid: string, item: RSSItem) => void } export class Card extends React.Component { diff --git a/src/components/context-menu.tsx b/src/components/context-menu.tsx index bd9031d..ceab24a 100644 --- a/src/components/context-menu.tsx +++ b/src/components/context-menu.tsx @@ -5,7 +5,6 @@ import { ContextualMenu, IContextualMenuItem, ContextualMenuItemType, Directiona import { ContextMenuType } from "../scripts/models/app" import { RSSItem } from "../scripts/models/item" import { ContextReduxProps } from "../containers/context-menu-container" -import { FeedIdType } from "../scripts/models/feed" import { ViewType } from "../scripts/models/page" export type ContextMenuProps = ContextReduxProps & { @@ -13,12 +12,14 @@ export type ContextMenuProps = ContextReduxProps & { event?: MouseEvent | string position?: [number, number] item?: RSSItem - feedId?: FeedIdType + feedId?: string text?: string viewType: ViewType - showItem: (feedId: FeedIdType, item: RSSItem) => void + showItem: (feedId: string, item: RSSItem) => void markRead: (item: RSSItem) => void markUnread: (item: RSSItem) => void + toggleStarred: (item: RSSItem) => void + toggleHidden: (item: RSSItem) => void switchView: (viewType: ViewType) => void close: () => void } @@ -59,8 +60,15 @@ export class ContextMenu extends React.Component { onClick: () => { this.props.markRead(this.props.item) } }, { - key: "markBelowAsRead", - text: "将以下标为已读" + key: "toggleStarred", + 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", diff --git a/src/components/feeds/cards-feed.tsx b/src/components/feeds/cards-feed.tsx index 854ba11..916025a 100644 --- a/src/components/feeds/cards-feed.tsx +++ b/src/components/feeds/cards-feed.tsx @@ -34,8 +34,8 @@ class CardsFeed extends React.Component { { this.props.items.map((item) => ( void - contextMenu: (feedId: FeedIdType, item: RSSItem, e) => void + contextMenu: (feedId: string, item: RSSItem, e) => void loadMore: (feed: RSSFeed) => void - showItem: (fid: FeedIdType, item: RSSItem) => void + showItem: (fid: string, item: RSSItem) => void } export class Feed extends React.Component { diff --git a/src/components/feeds/list-feed.tsx b/src/components/feeds/list-feed.tsx index 5455607..d737305 100644 --- a/src/components/feeds/list-feed.tsx +++ b/src/components/feeds/list-feed.tsx @@ -10,8 +10,8 @@ class ListFeed extends React.Component { { this.props.items.map((item) => ( void offsetItem: (offset: number) => void @@ -34,7 +33,7 @@ class Page extends React.Component { ))}
} - {this.props.itemId >= 0 && ( + {this.props.itemId && (
e.stopPropagation()}> @@ -54,7 +53,7 @@ class Page extends React.Component { ))}
- {this.props.itemId >= 0 && ( + {this.props.itemId && (
diff --git a/src/containers/article-container.tsx b/src/containers/article-container.tsx index df7d9f5..b4a088f 100644 --- a/src/containers/article-container.tsx +++ b/src/containers/article-container.tsx @@ -1,14 +1,14 @@ import { connect } from "react-redux" import { createSelector } from "reselect" 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 { dismissItem } from "../scripts/models/page" import Article from "../components/article" import { openTextMenu } from "../scripts/models/app" type ArticleContainerProps = { - itemId: number + itemId: string } const getItem = (state: RootState, props: ArticleContainerProps) => state.items[props.itemId] @@ -28,6 +28,8 @@ const mapDispatchToProps = (dispatch: AppDispatch) => { return { dismiss: () => dispatch(dismissItem()), 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)) } } diff --git a/src/containers/context-menu-container.tsx b/src/containers/context-menu-container.tsx index 206fbdc..37c7fae 100644 --- a/src/containers/context-menu-container.tsx +++ b/src/containers/context-menu-container.tsx @@ -3,9 +3,8 @@ import { createSelector } from "reselect" import { RootState } from "../scripts/reducer" import { ContextMenuType, closeContextMenu } from "../scripts/models/app" 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 { FeedIdType } from "../scripts/models/feed" import { setDefaultView } from "../scripts/utils" const getContext = (state: RootState) => state.app.contextMenu @@ -38,9 +37,11 @@ const mapStateToProps = createSelector( const mapDispatchToProps = dispatch => { 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)), markUnread: (item: RSSItem) => dispatch(markUnread(item)), + toggleStarred: (item: RSSItem) => dispatch(toggleStarred(item)), + toggleHidden: (item: RSSItem) => dispatch(toggleHidden(item)), switchView: (viewType: ViewType) => { setDefaultView(viewType) dispatch(switchView(viewType)) diff --git a/src/containers/feed-container.tsx b/src/containers/feed-container.tsx index 755801f..13e21bb 100644 --- a/src/containers/feed-container.tsx +++ b/src/containers/feed-container.tsx @@ -3,12 +3,12 @@ import { createSelector } from "reselect" import { RootState } from "../scripts/reducer" import { markRead, RSSItem } from "../scripts/models/item" 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 { Feed } from "../components/feeds/feed" interface FeedContainerProps { - feedId: FeedIdType + feedId: string viewType: ViewType } @@ -31,9 +31,9 @@ const makeMapStateToProps = () => { const mapDispatchToProps = dispatch => { return { 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)), - showItem: (fid: FeedIdType, item: RSSItem) => dispatch(showItem(fid, item)) + showItem: (fid: string, item: RSSItem) => dispatch(showItem(fid, item)) } } diff --git a/src/containers/nav-container.tsx b/src/containers/nav-container.tsx index 2515526..10953a4 100644 --- a/src/containers/nav-container.tsx +++ b/src/containers/nav-container.tsx @@ -7,7 +7,7 @@ import { ViewType } from "../scripts/models/page" import Nav from "../components/nav" 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( [getState, getItemShown], diff --git a/src/index.html b/src/index.html index 63237d4..22aadf8 100644 --- a/src/index.html +++ b/src/index.html @@ -2,7 +2,7 @@ - + Fluent Reader diff --git a/src/scripts/db.ts b/src/scripts/db.ts index 1452649..2ee4385 100644 --- a/src/scripts/db.ts +++ b/src/scripts/db.ts @@ -20,5 +20,6 @@ export const idb = new Datastore({ 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 }) \ No newline at end of file diff --git a/src/scripts/models/app.ts b/src/scripts/models/app.ts index 6d56200..8ebf162 100644 --- a/src/scripts/models/app.ts +++ b/src/scripts/models/app.ts @@ -1,7 +1,7 @@ import { RSSSource, INIT_SOURCES, SourceActionTypes, ADD_SOURCE, UPDATE_SOURCE, DELETE_SOURCE } from "./source" import { RSSItem, ItemActionTypes, FETCH_ITEMS } from "./item" 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 { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles } from "./page" @@ -51,7 +51,7 @@ export class AppState { type: ContextMenuType, event?: MouseEvent | string, position?: [number, number], - target?: [RSSItem, FeedIdType] | RSSSource | string + target?: [RSSItem, string] | RSSSource | string } constructor() { @@ -74,7 +74,7 @@ interface OpenItemMenuAction { type: typeof OPEN_ITEM_MENU event: MouseEvent item: RSSItem - feedId: FeedIdType + feedId: string } interface OpenTextMenuAction { @@ -109,7 +109,7 @@ export function closeContextMenu(): ContextMenuActionTypes { 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 { type: OPEN_ITEM_MENU, event: event.nativeEvent, diff --git a/src/scripts/models/feed.ts b/src/scripts/models/feed.ts index 7a30b2c..fa6b06d 100644 --- a/src/scripts/models/feed.ts +++ b/src/scripts/models/feed.ts @@ -6,20 +6,19 @@ import { PageActionTypes, SELECT_PAGE, PageType } from "./page" export const ALL = "ALL" export const SOURCE = "SOURCE" -export type FeedIdType = number | string const LOAD_QUANTITY = 50 export class RSSFeed { - id: FeedIdType + _id: string loaded: boolean loading: boolean allLoaded: boolean sids: number[] - iids: number[] + iids: string[] - constructor (id: FeedIdType, sids=[]) { - this.id = id + constructor (id: string = null, sids=[]) { + this._id = id this.sids = sids this.iids = [] this.loaded = false @@ -44,7 +43,7 @@ export class RSSFeed { } export type FeedState = { - [id in FeedIdType]: RSSFeed + [_id: string]: RSSFeed } export const INIT_FEEDS = 'INIT_FEEDS' @@ -200,7 +199,7 @@ export function feedReducer( let nextState = { ...state } for (let k of Object.keys(state)) { 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) { nextState[k] = { ...nextState[k], @@ -217,11 +216,11 @@ export function feedReducer( switch (action.status) { case ActionStatus.Success: return { ...state, - [action.feed.id]: { + [action.feed._id]: { ...action.feed, loaded: true, allLoaded: action.items.length < LOAD_QUANTITY, - iids: action.items.map(i => i.id) + iids: action.items.map(i => i._id) } } default: return state @@ -230,23 +229,23 @@ export function feedReducer( switch (action.status) { case ActionStatus.Request: return { ...state, - [action.feed.id] : { + [action.feed._id] : { ...action.feed, loading: true } } case ActionStatus.Success: return { ...state, - [action.feed.id] : { + [action.feed._id] : { ...action.feed, loading: false, 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 { ...state, - [action.feed.id] : { + [action.feed._id] : { ...action.feed, loading: false } diff --git a/src/scripts/models/group.ts b/src/scripts/models/group.ts index ced4acf..662e049 100644 --- a/src/scripts/models/group.ts +++ b/src/scripts/models/group.ts @@ -1,7 +1,7 @@ import fs = require("fs") 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" const GROUPS_STORE_KEY = "sourceGroups" diff --git a/src/scripts/models/item.ts b/src/scripts/models/item.ts index c3090da..6c09de0 100644 --- a/src/scripts/models/item.ts +++ b/src/scripts/models/item.ts @@ -5,7 +5,7 @@ import { FeedActionTypes, INIT_FEED, LOAD_MORE } from "./feed" import Parser = require("@yang991178/rss-parser") export class RSSItem { - id: number + _id: string source: number title: string link: string @@ -14,20 +14,25 @@ export class RSSItem { thumb?: string content: string snippet: string - creator: string - categories: string[] + creator?: string + categories?: string[] hasRead: boolean + starred?: true + hidden?: true constructor (item: Parser.Item, source: RSSSource) { this.source = source.sid - this.title = item.title - this.link = item.link - this.date = new Date(item.isoDate) + this.title = item.title || "" + this.link = item.link || "" this.fetchedDate = new Date() + this.date = item.isoDate ? new Date(item.isoDate) : this.fetchedDate if (item.thumb) this.thumb = item.thumb else if (item.image) this.thumb = item.image else { 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") if (img && img.src) this.thumb = img.src } @@ -35,8 +40,8 @@ export class RSSItem { this.content = item.fullContent this.snippet = htmlDecode(item.fullContent) } else { - this.content = item.content - this.snippet = htmlDecode(item.contentSnippet) + this.content = item.content || "" + this.snippet = htmlDecode(item.contentSnippet || "") } this.creator = item.creator this.categories = item.categories @@ -45,12 +50,14 @@ export class RSSItem { } export type ItemState = { - [id: number]: RSSItem + [_id: string]: RSSItem } export const FETCH_ITEMS = 'FETCH_ITEMS' export const MARK_READ = "MARK_READ" export const MARK_UNREAD = "MARK_UNREAD" +export const TOGGLE_STARRED = "TOGGLE_STARRED" +export const TOGGLE_HIDDEN = "TOGGLE_HIDDEN" interface FetchItemsAction { type: typeof FETCH_ITEMS @@ -71,7 +78,17 @@ interface MarkUnreadAction { 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 { return { @@ -107,20 +124,13 @@ export function fetchItemsIntermediate(): ItemActionTypes { export function insertItems(items: RSSItem[]): Promise { return new Promise((resolve, reject) => { - db.idb.find({}).projection({ id: 1 }).sort({ id: -1 }).limit(1).exec((err, docs) => { + items.sort((a, b) => a.date.getTime() - b.date.getTime()) + db.idb.insert(items, (err, inserted) => { if (err) { reject(err) + } else { + resolve(inserted) } - let count = (docs.length == 0) ? 0 : (docs[0].id + 1) - items.sort((a, b) => a.date.getTime() - b.date.getTime()) - for (let i of items) i.id = count++ - db.idb.insert(items, (err) => { - if (err) { - reject(err) - } else { - resolve(items) - } - }) }) }) } @@ -145,8 +155,8 @@ export function fetchItems(): AppThunk> { } }) insertItems(items) - .then(() => { - dispatch(fetchItemsSuccess(items.reverse())) + .then(inserted => { + dispatch(fetchItemsSuccess(inserted.reverse())) resolve() }) .catch(err => { @@ -159,30 +169,62 @@ export function fetchItems(): AppThunk> { } } -export const markReadDone = (item: RSSItem): ItemActionTypes => ({ +const markReadDone = (item: RSSItem): ItemActionTypes => ({ type: MARK_READ, item: item }) -export const markUnreadDone = (item: RSSItem): ItemActionTypes => ({ +const markUnreadDone = (item: RSSItem): ItemActionTypes => ({ type: MARK_UNREAD, item: item }) export function markRead(item: RSSItem): AppThunk { return (dispatch) => { - db.idb.update({ id: item.id }, { $set: { hasRead: true } }) + db.idb.update({ _id: item._id }, { $set: { hasRead: true } }) dispatch(markReadDone(item)) } } export function markUnread(item: RSSItem): AppThunk { return (dispatch) => { - db.idb.update({ id: item.id }, { $set: { hasRead: false } }) + db.idb.update({ _id: item._id }, { $set: { hasRead: false } }) 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( state: ItemState = {}, action: ItemActionTypes | FeedActionTypes @@ -193,7 +235,7 @@ export function itemReducer( case ActionStatus.Success: { let newMap = {} for (let i of action.items) { - newMap[i.id] = i + newMap[i._id] = i } return {...newMap, ...state} } @@ -202,18 +244,36 @@ export function itemReducer( case MARK_UNREAD: case MARK_READ: return { ...state, - [action.item.id] : { + [action.item._id] : { ...action.item, 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 INIT_FEED: { switch (action.status) { case ActionStatus.Success: { let nextState = { ...state } for (let i of action.items) { - nextState[i.id] = i + nextState[i._id] = i } return nextState } diff --git a/src/scripts/models/page.ts b/src/scripts/models/page.ts index c01cbc8..52055b3 100644 --- a/src/scripts/models/page.ts +++ b/src/scripts/models/page.ts @@ -1,4 +1,4 @@ -import { ALL, SOURCE, FeedIdType, loadMore } from "./feed" +import { ALL, SOURCE, loadMore } from "./feed" import { getWindowBreakpoint, AppThunk, getDefaultView } from "../utils" import { RSSItem, markRead } from "./item" import { SourceActionTypes, DELETE_SOURCE } from "./source" @@ -34,7 +34,7 @@ interface SwitchViewAction { interface ShowItemAction { type: typeof SHOW_ITEM - feedId: FeedIdType + feedId: string 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 { type: SHOW_ITEM, feedId: feedId, @@ -109,8 +109,8 @@ export function showOffsetItem(offset: number): AppThunk { export class PageState { viewType = getDefaultView() - feedId = ALL as FeedIdType - itemId = -1 + feedId = ALL + itemId = null as string } export function pageReducer( @@ -133,16 +133,16 @@ export function pageReducer( case SWITCH_VIEW: return { ...state, viewType: action.viewType, - itemId: action.viewType === ViewType.List ? state.itemId : -1 + itemId: action.viewType === ViewType.List ? state.itemId : null } case SHOW_ITEM: return { ...state, - itemId: action.item.id + itemId: action.item._id } case DELETE_SOURCE: case DISMISS_ITEM: return { ...state, - itemId: -1 + itemId: null } default: return state } diff --git a/src/scripts/utils.ts b/src/scripts/utils.ts index 152d71e..d43d1e1 100644 --- a/src/scripts/utils.ts +++ b/src/scripts/utils.ts @@ -2,6 +2,7 @@ import { shell, remote } from "electron" import { ThunkAction, ThunkDispatch } from "redux-thunk" import { AnyAction } from "redux" import { RootState } from "./reducer" +import URL = require("url") export enum ActionStatus { Request, Success, Failure, Intermediate @@ -49,6 +50,7 @@ export function setProxy(address = null) { import ElectronProxyAgent = require("@yang991178/electron-proxy-agent") import { ViewType } from "./models/page" +import { RSSSource } from "./models/source" let agent = new ElectronProxyAgent(remote.getCurrentWebContents().session) export const rssParser = new Parser({ customFields: customFields,