diff --git a/dist/article/article.css b/dist/article/article.css index 06faa55..c333c78 100644 --- a/dist/article/article.css +++ b/dist/article/article.css @@ -1,7 +1,11 @@ html, body { 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 { @@ -22,8 +26,10 @@ a:hover, a:active { article { line-height: 1.6; } -article img { +article > * { max-width: 100%; +} +article img { height: auto; } article figure { diff --git a/dist/article/article.html b/dist/article/article.html index 8314b32..086eb46 100644 --- a/dist/article/article.html +++ b/dist/article/article.html @@ -3,7 +3,7 @@ + content="default-src 'self'; img-src *; style-src 'self' 'unsafe-inline'; frame-src *; media-src *"> Hello World! diff --git a/dist/article/article.js b/dist/article/article.js index 86cd60f..1810a39 100644 --- a/dist/article/article.js +++ b/dist/article/article.js @@ -2,9 +2,10 @@ function get(name) { if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(location.search)) return decodeURIComponent(name[1]); } +document.documentElement.style.fontSize = get("s") + "px" let main = document.getElementById("main") main.innerHTML = decodeURIComponent(window.atob(get("h"))) document.addEventListener("click", event => { event.preventDefault() - if (event.target.href) ipcRenderer.sendToHost("request-navigation", event.target.href) + if (event.target.href) post("request-navigation", event.target.href) }) \ No newline at end of file diff --git a/dist/article/preload.js b/dist/article/preload.js index 82734e1..c15cd6d 100644 --- a/dist/article/preload.js +++ b/dist/article/preload.js @@ -1 +1 @@ -global.ipcRenderer = require("electron").ipcRenderer \ No newline at end of file +global.post = require("electron").ipcRenderer.sendToHost \ No newline at end of file diff --git a/dist/icons/fabric-icons-5-f95ba260.woff b/dist/icons/fabric-icons-5-f95ba260.woff new file mode 100644 index 0000000..f403adf Binary files /dev/null and b/dist/icons/fabric-icons-5-f95ba260.woff differ diff --git a/dist/styles.css b/dist/styles.css index 6d69d31..d976e70 100644 --- a/dist/styles.css +++ b/dist/styles.css @@ -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 { 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; } .btn-group .btn:hover { @@ -161,7 +161,6 @@ nav.menu-on .btn-group .btn.system { } .article-container { z-index: 6; - background-color: #fff6; } .menu-container .menu { position: absolute; @@ -273,6 +272,9 @@ img.favicon { nav.menu-on .btn-group .btn.system { color: #000; } + nav.item-on .btn-group .btn.system { + color: #fff; + } .menu-container { width: 280px; } @@ -313,9 +315,21 @@ img.favicon { } .article webview { width: 100%; - height: calc(100vh - 50px); + height: calc(100vh - 86px); 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 { display: inline-flex; diff --git a/src/components/article.tsx b/src/components/article.tsx index f27a8e5..daf2dca 100644 --- a/src/components/article.tsx +++ b/src/components/article.tsx @@ -2,47 +2,122 @@ import * as React from "react" import { renderToString } from "react-dom/server" import { RSSItem } from "../scripts/models/item" import { openExternal } from "../scripts/utils" +import { Stack, CommandBarButton, IContextualMenuProps } from "@fluentui/react" +import { RSSSource } 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 = { item: RSSItem + source: RSSSource dismiss: () => void + toggleHasRead: (item: RSSItem) => void } -class Article extends React.Component { - webview: HTMLWebViewElement +type ArticleState = { + fontSize: number +} +class Article extends React.Component { + webview: HTMLWebViewElement + constructor(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 => { if (event.channel === "request-navigation") { openExternal(event.args[0]) } } + popUpHandler = event => { + openExternal(event.url) + } componentDidMount = () => { this.webview = document.getElementById("article") this.webview.addEventListener("ipc-message", this.ipcHandler) + this.webview.addEventListener("new-window", this.popUpHandler) this.webview.addEventListener("will-navigate", this.props.dismiss) } componentWillUnmount = () => { this.webview.removeEventListener("ipc-message", this.ipcHandler) + this.webview.removeEventListener("new-window", this.popUpHandler) this.webview.removeEventListener("will-navigate", this.props.dismiss) } + openInBrowser = () => { + openExternal(this.props.item.link) + } + articleView = () => "article/article.html?h=" + window.btoa(encodeURIComponent(renderToString(<>

{this.props.item.title}

- ))) + ))) + "&s=" + this.state.fontSize render = () => (
+ + + + + {this.props.source.iconurl && } + {this.props.source.name} + + this.props.toggleHasRead(this.props.item)} /> + + + + + + + + + preload="article/preload.js" + partition="sandbox" />
) } diff --git a/src/components/cards/card.tsx b/src/components/cards/card.tsx index 23dac05..61ddedb 100644 --- a/src/components/cards/card.tsx +++ b/src/components/cards/card.tsx @@ -6,12 +6,11 @@ import { FeedIdType } from "../../scripts/models/feed" export interface CardProps { feedId: FeedIdType - index: number item: RSSItem source: RSSSource markRead: (item: RSSItem) => void contextMenu: (item: RSSItem, e) => void - showItem: (fid: FeedIdType, index: number) => void + showItem: (fid: FeedIdType, item: RSSItem) => void } export class Card extends React.Component { @@ -24,7 +23,7 @@ export class Card extends React.Component { e.preventDefault() e.stopPropagation() 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) => { diff --git a/src/components/feeds/cards-feed.tsx b/src/components/feeds/cards-feed.tsx index f5f4cf1..143f877 100644 --- a/src/components/feeds/cards-feed.tsx +++ b/src/components/feeds/cards-feed.tsx @@ -32,10 +32,9 @@ class CardsFeed extends Feed { return this.props.feed.loaded && (
{ - this.props.items.map((item, index) => ( + this.props.items.map((item) => ( void contextMenu: (item: RSSItem, e) => void loadMore: (feed: RSSFeed) => void - showItem: (fid: FeedIdType, index: number) => void + showItem: (fid: FeedIdType, item: RSSItem) => void } export class Feed extends React.Component { } \ No newline at end of file diff --git a/src/components/nav.tsx b/src/components/nav.tsx index 2d18731..92223e5 100644 --- a/src/components/nav.tsx +++ b/src/components/nav.tsx @@ -6,6 +6,7 @@ import { ProgressIndicator } from "@fluentui/react" type NavProps = { state: AppState, + itemShown: boolean, fetch: () => void, menu: () => void, logs: () => void, @@ -51,6 +52,7 @@ class Nav extends React.Component { canFetch = () => this.props.state.sourceInit && this.props.state.feedInit && !this.props.state.fetchingItems fetching = () => !this.canFetch() ? " fetching" : "" menuOn = () => this.props.state.menu ? " menu-on" : "" + itemOn = () => this.props.itemShown ? " item-on" : "" hideButtons = () => this.props.state.settings.display ? "hide-btns" : "" fetch = () => { @@ -65,7 +67,7 @@ class Nav extends React.Component { render() { return ( -
} - {this.props.item && ( + {this.props.itemId >= 0 && (
e.stopPropagation()}> -
+
)} diff --git a/src/containers/article-container.tsx b/src/containers/article-container.tsx new file mode 100644 index 0000000..6f41f66 --- /dev/null +++ b/src/containers/article-container.tsx @@ -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 \ No newline at end of file diff --git a/src/containers/feed-container.tsx b/src/containers/feed-container.tsx index 5209cad..fc7a858 100644 --- a/src/containers/feed-container.tsx +++ b/src/containers/feed-container.tsx @@ -30,7 +30,7 @@ const mapDispatchToProps = dispatch => { markRead: (item: RSSItem) => dispatch(markRead(item)), contextMenu: (item: RSSItem, e) => dispatch(openItemMenu(item, e)), loadMore: (feed: RSSFeed) => dispatch(loadMore(feed)), - showItem: (fid: FeedIdType, index: number) => dispatch(showItem(fid, index)) + showItem: (fid: FeedIdType, item: RSSItem) => dispatch(showItem(fid, item)) } } diff --git a/src/containers/nav-container.tsx b/src/containers/nav-container.tsx index 7adc9e4..c2b7a2f 100644 --- a/src/containers/nav-container.tsx +++ b/src/containers/nav-container.tsx @@ -6,10 +6,15 @@ import { toggleMenu, toggleLogMenu, toggleSettings } from "../scripts/models/app import Nav from "../components/nav" const getState = (state: RootState) => state.app +const getItemShown = (state: RootState) => state.page.itemId >= 0 -const mapStateToProps = createSelector(getState, (state) => ({ - state: state -})) +const mapStateToProps = createSelector( + [getState, getItemShown], + (state, itemShown) => ({ + state: state, + itemShown: itemShown + } +)) const mapDispatchToProps = (dispatch) => ({ fetch: () => dispatch(fetchItems()), diff --git a/src/containers/page-container.tsx b/src/containers/page-container.tsx index 3368aa3..f770ca1 100644 --- a/src/containers/page-container.tsx +++ b/src/containers/page-container.tsx @@ -8,18 +8,14 @@ import { dismissItem } from "../scripts/models/page" const getPage = (state: RootState) => state.page const getSettings = (state: RootState) => state.app.settings.display const getMenu = (state: RootState) => state.app.menu -const getItems = (state: RootState) => state.items -const getFeeds = (state: RootState) => state.feeds const mapStateToProps = createSelector( - [getPage, getSettings, getMenu, getItems, getFeeds], - (page, settingsOn, menuOn, items, feeds) => ({ + [getPage, getSettings, getMenu], + (page, settingsOn, menuOn) => ({ feeds: [page.feedId], settingsOn: settingsOn, menuOn: menuOn, - item: page.itemIndex >= 0 // && page.itemIndex < feeds[page.feedId].iids.length - ? items[feeds[page.feedId].iids[page.itemIndex]] - : null + itemId: page.itemId }) ) diff --git a/src/index.html b/src/index.html index 0753f9e..0b72c16 100644 --- a/src/index.html +++ b/src/index.html @@ -2,7 +2,7 @@ - + Hello World! diff --git a/src/scripts/models/app.ts b/src/scripts/models/app.ts index bfc7490..e071b9b 100644 --- a/src/scripts/models/app.ts +++ b/src/scripts/models/app.ts @@ -193,7 +193,7 @@ export function appReducer( ...state, logMenu: { ...state.logMenu, - notify: true, + notify: !state.logMenu.display, logs: [...state.logMenu.logs, new AppLog( AppLogType.Failure, `无法加载订阅源“${action.errSource.name}”`, diff --git a/src/scripts/models/item.ts b/src/scripts/models/item.ts index 702760a..c3090da 100644 --- a/src/scripts/models/item.ts +++ b/src/scripts/models/item.ts @@ -199,18 +199,12 @@ export function itemReducer( } default: return state } + case MARK_UNREAD: case MARK_READ: return { ...state, [action.item.id] : { ...action.item, - hasRead: true - } - } - case MARK_UNREAD: return { - ...state, - [action.item.id] : { - ...action.item, - hasRead: false + hasRead: action.type === MARK_READ } } case LOAD_MORE: diff --git a/src/scripts/models/page.ts b/src/scripts/models/page.ts index 5a7fddc..e6d2f27 100644 --- a/src/scripts/models/page.ts +++ b/src/scripts/models/page.ts @@ -1,5 +1,6 @@ import { ALL, SOURCE, FeedIdType } from "./feed" import { getWindowBreakpoint } from "../utils" +import { RSSItem, ItemActionTypes, MARK_READ, MARK_UNREAD } from "./item" export const SELECT_PAGE = "SELECT_PAGE" export const SHOW_ITEM = "SHOW_ITEM" @@ -22,7 +23,7 @@ interface SelectPageAction { interface ShowItemAction { type: typeof SHOW_ITEM feedId: FeedIdType - itemIndex: number + item: RSSItem } 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 { type: SHOW_ITEM, feedId: feedId, - itemIndex: itemIndex + item: item } } @@ -62,7 +63,7 @@ export const dismissItem = (): PageActionTypes => ({ type: DISMISS_ITEM }) export class PageState { feedId = ALL as FeedIdType - itemIndex = -1 + itemId = -1 } export function pageReducer( @@ -84,11 +85,11 @@ export function pageReducer( } case SHOW_ITEM: return { ...state, - itemIndex: action.itemIndex + itemId: action.item.id } case DISMISS_ITEM: return { ...state, - itemIndex: -1 + itemId: -1 } default: return state }