mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-04-11 17:11:20 +02:00
list view
This commit is contained in:
parent
165597a454
commit
53a280cd72
4
dist/article/article.css
vendored
4
dist/article/article.css
vendored
@ -17,6 +17,10 @@ a:hover, a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#main {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#main > p.title {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
|
BIN
dist/icons/fabric-icons-15-3807251b.woff
vendored
Normal file
BIN
dist/icons/fabric-icons-15-3807251b.woff
vendored
Normal file
Binary file not shown.
121
dist/styles.css
vendored
121
dist/styles.css
vendored
@ -59,6 +59,10 @@ i.ms-Nav-chevron {
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
#root > nav .btn, #root > nav span {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
nav .progress {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@ -113,7 +117,7 @@ nav .progress {
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.btn-group .btn.system {
|
||||
#root > nav .btn-group .btn.system {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
@ -301,11 +305,11 @@ img.favicon {
|
||||
height: 120%;
|
||||
box-shadow: inset 5px 0 20px #0004;
|
||||
}
|
||||
.main.menu-on {
|
||||
.main.menu-on, .list-main.menu-on {
|
||||
padding-left: 280px;
|
||||
}
|
||||
|
||||
nav.menu-on .btn-group .btn.hide-wide, .menu .btn-group .btn.hide-wide {
|
||||
nav.hide-btns .btn-group .btn, nav.menu-on .btn-group .btn.hide-wide, .menu .btn-group .btn.hide-wide {
|
||||
display: none;
|
||||
}
|
||||
.btn-group .btn.inline-block-wide {
|
||||
@ -335,9 +339,12 @@ img.favicon {
|
||||
.article-container .btn-group.next {
|
||||
right: calc(50% - 486px);
|
||||
}
|
||||
.article {
|
||||
height: 100%;
|
||||
}
|
||||
.article webview {
|
||||
width: 100%;
|
||||
height: calc(100vh - 86px);
|
||||
height: calc(100% - 36px);
|
||||
border: none;
|
||||
}
|
||||
.article i.ms-Icon {
|
||||
@ -358,6 +365,63 @@ img.favicon {
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
}
|
||||
.side-article-wrapper {
|
||||
flex-grow: 1;
|
||||
padding-top: 32px;
|
||||
height: calc(100% - 32px);
|
||||
background: #fff;
|
||||
}
|
||||
.side-article-wrapper .article {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
.side-article-wrapper .article .actions {
|
||||
border-bottom: none;
|
||||
}
|
||||
.side-article-wrapper .article > .ms-Stack {
|
||||
border-top: 1px solid #e1dfdd;
|
||||
}
|
||||
.list-feed-container::before, .side-article-wrapper::before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #e1dfdd;
|
||||
position: absolute;
|
||||
top: 31px;
|
||||
}
|
||||
|
||||
.list-main {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
top: -32px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
.list-feed-container {
|
||||
width: 350px;
|
||||
background-color: #faf9f8;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.list-feed-container::after {
|
||||
content: "";
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: -10%;
|
||||
right: 0;
|
||||
width: 120%;
|
||||
height: 120%;
|
||||
box-shadow: inset 5px 0 20px #0004;
|
||||
}
|
||||
.list-feed {
|
||||
margin-top: 32px;
|
||||
height: calc(100% - 32px);
|
||||
overflow: hidden scroll;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cards-feed-container {
|
||||
display: inline-flex;
|
||||
@ -496,4 +560,51 @@ img.favicon {
|
||||
}
|
||||
.card p.snippet.show {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.list-card {
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: #161514;
|
||||
user-select: none;
|
||||
transition: box-shadow linear .08s;
|
||||
border-bottom: 1px solid #e1dfdd;
|
||||
transform: scale(1);
|
||||
cursor: pointer;
|
||||
box-shadow: #0000 0px 5px 15px;
|
||||
}
|
||||
.list-card:hover {
|
||||
box-shadow: #0004 0px 5px 15px;
|
||||
}
|
||||
.list-card:active {
|
||||
box-shadow: #0000 0px 5px 15px, inset #0004 0px 0px 15px;
|
||||
}
|
||||
.list-card div.head {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 8px 0 8px 10px;
|
||||
}
|
||||
.list-card div.head img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
object-fit: cover;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
.list-card .data {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.list-card .info {
|
||||
margin: 8px 10px;
|
||||
}
|
||||
.list-card h3.title {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
font-weight: 600;
|
||||
margin: 8px 10px;
|
||||
position: relative;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
@ -36,6 +36,7 @@
|
||||
"redux-devtools": "^3.5.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"simplebar-react": "^2.2.0",
|
||||
"ts-loader": "^7.0.4",
|
||||
"typescript": "^3.9.2",
|
||||
"webpack": "^4.43.0",
|
||||
|
@ -152,7 +152,7 @@ class Article extends React.Component<ArticleProps, ArticleState> {
|
||||
<Stack horizontal horizontalAlign="end" style={{width: 112}}>
|
||||
<CommandBarButton
|
||||
title="关闭"
|
||||
iconProps={{iconName: "Cancel"}}
|
||||
iconProps={{iconName: "BackToWindow"}}
|
||||
onClick={this.props.dismiss} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
28
src/components/cards/list-card.tsx
Normal file
28
src/components/cards/list-card.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import * as React from "react"
|
||||
import { Card } from "./card"
|
||||
import Time from "../utils/time"
|
||||
import { AnimationClassNames } from "@fluentui/react"
|
||||
|
||||
class ListCard extends Card {
|
||||
render() {
|
||||
return (
|
||||
<div className={"list-card "+AnimationClassNames.slideUpIn10+(this.props.item.snippet&&this.props.item.thumb?" transform":"")}
|
||||
onClick={this.onClick} onMouseUp={this.onMouseUp} >
|
||||
{this.props.item.thumb ? (
|
||||
<div className="head"><img src={this.props.item.thumb} /></div>
|
||||
) : null}
|
||||
<div className="data">
|
||||
<p className="info">
|
||||
{this.props.source.iconurl ? <img src={this.props.source.iconurl} /> : null}
|
||||
<span className="name">{this.props.source.name}</span>
|
||||
<Time date={this.props.item.date} />
|
||||
{this.props.item.hasRead ? null : <span className="read-indicator"></span>}
|
||||
</p>
|
||||
<h3 className="title">{this.props.item.title}</h3>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ListCard
|
@ -6,17 +6,20 @@ 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 & {
|
||||
type: ContextMenuType
|
||||
event?: MouseEvent
|
||||
event?: MouseEvent | string
|
||||
position?: [number, number]
|
||||
item?: RSSItem
|
||||
feedId?: FeedIdType
|
||||
text?: string
|
||||
viewType: ViewType
|
||||
showItem: (feedId: FeedIdType, item: RSSItem) => void
|
||||
markRead: (item: RSSItem) => void
|
||||
markUnread: (item: RSSItem) => void
|
||||
switchView: (viewType: ViewType) => void
|
||||
close: () => void
|
||||
}
|
||||
|
||||
@ -88,6 +91,24 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
|
||||
onClick: () => { googleSearch(this.props.text) }
|
||||
}
|
||||
]
|
||||
case ContextMenuType.View: return [
|
||||
{
|
||||
key: "cardView",
|
||||
text: "卡片视图",
|
||||
iconProps: { iconName: "GridViewMedium" },
|
||||
canCheck: true,
|
||||
checked: this.props.viewType === ViewType.Cards,
|
||||
onClick: () => this.props.switchView(ViewType.Cards)
|
||||
},
|
||||
{
|
||||
key: "listView",
|
||||
text: "列表视图",
|
||||
iconProps: { iconName: "BacklogList" },
|
||||
canCheck: true,
|
||||
checked: this.props.viewType === ViewType.List,
|
||||
onClick: () => this.props.switchView(ViewType.List)
|
||||
}
|
||||
]
|
||||
default: return []
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import * as React from "react"
|
||||
import { Feed } from "./feed"
|
||||
import { Feed, FeedProps } from "./feed"
|
||||
import DefaultCard from "../cards/default-card"
|
||||
import { PrimaryButton } from 'office-ui-fabric-react';
|
||||
|
||||
class CardsFeed extends Feed {
|
||||
class CardsFeed extends React.Component<FeedProps> {
|
||||
state = { width: window.innerWidth - 12 }
|
||||
|
||||
updateWidth = () => {
|
||||
|
@ -2,9 +2,13 @@ import * as React from "react"
|
||||
import { RSSItem } from "../../scripts/models/item"
|
||||
import { FeedReduxProps } from "../../containers/feed-container"
|
||||
import { RSSFeed, FeedIdType } from "../../scripts/models/feed"
|
||||
import { ViewType } from "../../scripts/models/page"
|
||||
import CardsFeed from "./cards-feed"
|
||||
import ListFeed from "./list-feed"
|
||||
|
||||
type FeedProps = FeedReduxProps & {
|
||||
export type FeedProps = FeedReduxProps & {
|
||||
feed: RSSFeed
|
||||
viewType: ViewType
|
||||
items: RSSItem[]
|
||||
sourceMap: Object
|
||||
markRead: (item: RSSItem) => void
|
||||
@ -13,4 +17,15 @@ type FeedProps = FeedReduxProps & {
|
||||
showItem: (fid: FeedIdType, item: RSSItem) => void
|
||||
}
|
||||
|
||||
export class Feed extends React.Component<FeedProps> { }
|
||||
export class Feed extends React.Component<FeedProps> {
|
||||
render() {
|
||||
switch (this.props.viewType) {
|
||||
case (ViewType.Cards): return (
|
||||
<CardsFeed {...this.props} />
|
||||
)
|
||||
case (ViewType.List): return (
|
||||
<ListFeed {...this.props} />
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
35
src/components/feeds/list-feed.tsx
Normal file
35
src/components/feeds/list-feed.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import * as React from "react"
|
||||
import { FeedProps } from "./feed"
|
||||
import { PrimaryButton } from 'office-ui-fabric-react';
|
||||
import ListCard from "../cards/list-card";
|
||||
|
||||
class ListFeed extends React.Component<FeedProps> {
|
||||
render() {
|
||||
return this.props.feed.loaded && (
|
||||
<div className="list-feed">
|
||||
{
|
||||
this.props.items.map((item) => (
|
||||
<ListCard
|
||||
feedId={this.props.feed.id}
|
||||
key={item.id}
|
||||
item={item}
|
||||
source={this.props.sourceMap[item.source]}
|
||||
markRead={this.props.markRead}
|
||||
contextMenu={this.props.contextMenu}
|
||||
showItem={this.props.showItem} />
|
||||
))
|
||||
}
|
||||
{
|
||||
(this.props.feed.loaded && !this.props.feed.allLoaded)
|
||||
? <div className="load-more-wrapper"><PrimaryButton
|
||||
text="加载更多"
|
||||
disabled={this.props.feed.loading}
|
||||
onClick={() => this.props.loadMore(this.props.feed)} /></div>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ListFeed
|
@ -10,6 +10,7 @@ type NavProps = {
|
||||
fetch: () => void,
|
||||
menu: () => void,
|
||||
logs: () => void,
|
||||
views: () => void,
|
||||
settings: () => void
|
||||
}
|
||||
|
||||
@ -59,6 +60,12 @@ class Nav extends React.Component<NavProps, NavState> {
|
||||
if (this.canFetch()) this.props.fetch()
|
||||
}
|
||||
|
||||
views = () => {
|
||||
if (this.props.state.contextMenu.event !== "#view-toggle") {
|
||||
this.props.views()
|
||||
}
|
||||
}
|
||||
|
||||
getProgress = () => {
|
||||
return this.props.state.fetchingTotal > 0
|
||||
? this.props.state.fetchingProgress / this.props.state.fetchingTotal
|
||||
@ -78,7 +85,9 @@ class Nav extends React.Component<NavProps, NavState> {
|
||||
<a className="btn" id="log-toggle" title="消息" onClick={this.props.logs}>
|
||||
{this.props.state.logMenu.notify ? <Icon iconName="RingerSolid" /> : <Icon iconName="Ringer" />}
|
||||
</a>
|
||||
<a className="btn" title="视图"><Icon iconName="View" /></a>
|
||||
<a className="btn" id="view-toggle" title="视图" onClick={this.props.views}
|
||||
onMouseDown={e => {if (this.props.state.contextMenu.event === "#view-toggle") e.stopPropagation()}}>
|
||||
<Icon iconName="View" /></a>
|
||||
<a className="btn" title="选项" onClick={this.props.settings}><Icon iconName="Settings" /></a>
|
||||
<span className="seperator"></span>
|
||||
<a className="btn system" title="最小化" onClick={this.minimize} style={{fontSize: 12}}><Icon iconName="Remove" /></a>
|
||||
|
@ -3,12 +3,14 @@ import { FeedIdType } from "../scripts/models/feed"
|
||||
import { FeedContainer } from "../containers/feed-container"
|
||||
import { AnimationClassNames, Icon } from "@fluentui/react"
|
||||
import ArticleContainer from "../containers/article-container"
|
||||
import { ViewType } from "../scripts/models/page"
|
||||
|
||||
type PageProps = {
|
||||
menuOn: boolean
|
||||
settingsOn: boolean
|
||||
feeds: FeedIdType[]
|
||||
itemId: number
|
||||
viewType: ViewType
|
||||
dismissItem: () => void
|
||||
offsetItem: (offset: number) => void
|
||||
}
|
||||
@ -23,12 +25,13 @@ class Page extends React.Component<PageProps> {
|
||||
this.props.offsetItem(1)
|
||||
}
|
||||
|
||||
render = () => (
|
||||
render = () => this.props.viewType == ViewType.Cards
|
||||
? (
|
||||
<>
|
||||
{this.props.settingsOn ? null :
|
||||
<div className={"main" + (this.props.menuOn ? " menu-on" : "")}>
|
||||
{this.props.feeds.map(fid => (
|
||||
<FeedContainer feedId={fid} key={fid} />
|
||||
<FeedContainer viewType={this.props.viewType} feedId={fid} key={fid} />
|
||||
))}
|
||||
</div>}
|
||||
{this.props.itemId >= 0 && (
|
||||
@ -42,6 +45,23 @@ class Page extends React.Component<PageProps> {
|
||||
)}
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
{this.props.settingsOn ? null :
|
||||
<div className={"list-main" + (this.props.menuOn ? " menu-on" : "")}>
|
||||
<div className="list-feed-container">
|
||||
{this.props.feeds.map(fid => (
|
||||
<FeedContainer viewType={this.props.viewType} feedId={fid} key={fid} />
|
||||
))}
|
||||
</div>
|
||||
{this.props.itemId >= 0 && (
|
||||
<div className="side-article-wrapper">
|
||||
<ArticleContainer itemId={this.props.itemId} />
|
||||
</div>
|
||||
)}
|
||||
</div>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page
|
@ -272,6 +272,7 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
selectionMode={SelectionMode.multiple} />
|
||||
</MarqueeSelection>
|
||||
|
||||
<span className="settings-hint">拖拽订阅源以排序</span>
|
||||
</>}
|
||||
{(!this.state.manageGroup || !this.state.selectedGroup)
|
||||
?<>
|
||||
|
@ -4,14 +4,16 @@ 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 { showItem } from "../scripts/models/page"
|
||||
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
|
||||
const getViewType = (state: RootState) => state.page.viewType
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
[getContext],
|
||||
(context) => {
|
||||
[getContext, getViewType],
|
||||
(context, viewType) => {
|
||||
switch (context.type) {
|
||||
case ContextMenuType.Item: return {
|
||||
type: context.type,
|
||||
@ -24,6 +26,11 @@ const mapStateToProps = createSelector(
|
||||
position: context.position,
|
||||
text: context.target as string
|
||||
}
|
||||
case ContextMenuType.View: return {
|
||||
type: context.type,
|
||||
event: context.event,
|
||||
viewType: viewType
|
||||
}
|
||||
default: return { type: ContextMenuType.Hidden }
|
||||
}
|
||||
}
|
||||
@ -34,6 +41,10 @@ const mapDispatchToProps = dispatch => {
|
||||
showItem: (feedId: FeedIdType, item: RSSItem) => dispatch(showItem(feedId, item)),
|
||||
markRead: (item: RSSItem) => dispatch(markRead(item)),
|
||||
markUnread: (item: RSSItem) => dispatch(markUnread(item)),
|
||||
switchView: (viewType: ViewType) => {
|
||||
setDefaultView(viewType)
|
||||
dispatch(switchView(viewType))
|
||||
},
|
||||
close: () => dispatch(closeContextMenu())
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,30 @@
|
||||
import { connect } from "react-redux"
|
||||
import { createSelector } from "reselect"
|
||||
import { RootState } from "../scripts/reducer"
|
||||
import CardsFeed from "../components/feeds/cards-feed"
|
||||
import { markRead, RSSItem } from "../scripts/models/item"
|
||||
import { openItemMenu } from "../scripts/models/app"
|
||||
import { FeedIdType, loadMore, RSSFeed } from "../scripts/models/feed"
|
||||
import { showItem } from "../scripts/models/page"
|
||||
import { showItem, ViewType } from "../scripts/models/page"
|
||||
import { Feed } from "../components/feeds/feed"
|
||||
|
||||
interface FeedContainerProps {
|
||||
feedId: FeedIdType
|
||||
viewType: ViewType
|
||||
}
|
||||
|
||||
const getSources = (state: RootState) => state.sources
|
||||
const getItems = (state: RootState) => state.items
|
||||
const getFeed = (state: RootState, props: FeedContainerProps) => state.feeds[props.feedId]
|
||||
const getView = (_, props: FeedContainerProps) => props.viewType
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
return createSelector(
|
||||
[getSources, getItems, getFeed],
|
||||
(sources, items, feed) => ({
|
||||
[getSources, getItems, getFeed, getView],
|
||||
(sources, items, feed, viewType) => ({
|
||||
feed: feed,
|
||||
items: feed.iids.map(iid => items[iid]),
|
||||
sourceMap: sources
|
||||
sourceMap: sources,
|
||||
viewType: viewType
|
||||
})
|
||||
)
|
||||
}
|
||||
@ -36,4 +39,4 @@ const mapDispatchToProps = dispatch => {
|
||||
|
||||
const connector = connect(makeMapStateToProps, mapDispatchToProps)
|
||||
export type FeedReduxProps = typeof connector
|
||||
export const FeedContainer = connector(CardsFeed)
|
||||
export const FeedContainer = connector(Feed)
|
@ -2,11 +2,12 @@ import { connect } from "react-redux"
|
||||
import { createSelector } from "reselect"
|
||||
import { RootState } from "../scripts/reducer"
|
||||
import { fetchItems } from "../scripts/models/item"
|
||||
import { toggleMenu, toggleLogMenu, toggleSettings } from "../scripts/models/app"
|
||||
import { toggleMenu, toggleLogMenu, toggleSettings, openViewMenu } from "../scripts/models/app"
|
||||
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
|
||||
const getItemShown = (state: RootState) => (state.page.itemId >= 0) && state.page.viewType !== ViewType.List
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
[getState, getItemShown],
|
||||
@ -20,6 +21,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
fetch: () => dispatch(fetchItems()),
|
||||
menu: () => dispatch(toggleMenu()),
|
||||
logs: () => dispatch(toggleLogMenu()),
|
||||
views: () => dispatch(openViewMenu()),
|
||||
settings: () => dispatch(toggleSettings())
|
||||
})
|
||||
|
||||
|
@ -15,7 +15,8 @@ const mapStateToProps = createSelector(
|
||||
feeds: [page.feedId],
|
||||
settingsOn: settingsOn,
|
||||
menuOn: menuOn,
|
||||
itemId: page.itemId
|
||||
itemId: page.itemId,
|
||||
viewType: page.viewType
|
||||
})
|
||||
)
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { SourceGroupActionTypes, UPDATE_SOURCE_GROUP, ADD_SOURCE_TO_GROUP, DELET
|
||||
import { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles } from "./page"
|
||||
|
||||
export enum ContextMenuType {
|
||||
Hidden, Item, Text
|
||||
Hidden, Item, Text, View
|
||||
}
|
||||
|
||||
export enum AppLogType {
|
||||
@ -64,6 +64,7 @@ export class AppState {
|
||||
export const CLOSE_CONTEXT_MENU = "CLOSE_CONTEXT_MENU"
|
||||
export const OPEN_ITEM_MENU = "OPEN_ITEM_MENU"
|
||||
export const OPEN_TEXT_MENU = "OPEN_TEXT_MENU"
|
||||
export const OPEN_VIEW_MENU = "OPEN_VIEW_MENU"
|
||||
|
||||
interface CloseContextMenuAction {
|
||||
type: typeof CLOSE_CONTEXT_MENU
|
||||
@ -82,7 +83,11 @@ interface OpenTextMenuAction {
|
||||
item: string
|
||||
}
|
||||
|
||||
export type ContextMenuActionTypes = CloseContextMenuAction | OpenItemMenuAction | OpenTextMenuAction
|
||||
interface OpenViewMenuAction {
|
||||
type: typeof OPEN_VIEW_MENU
|
||||
}
|
||||
|
||||
export type ContextMenuActionTypes = CloseContextMenuAction | OpenItemMenuAction | OpenTextMenuAction | OpenViewMenuAction
|
||||
|
||||
export const TOGGLE_LOGS = "TOGGLE_LOGS"
|
||||
export interface LogMenuActionType { type: typeof TOGGLE_LOGS }
|
||||
@ -121,6 +126,8 @@ export function openTextMenu(text: string, position: [number, number]): ContextM
|
||||
}
|
||||
}
|
||||
|
||||
export const openViewMenu = (): ContextMenuActionTypes => ({ type: OPEN_VIEW_MENU })
|
||||
|
||||
export const toggleMenu = () => ({ type: TOGGLE_MENU })
|
||||
export const toggleLogMenu = () => ({ type: TOGGLE_LOGS })
|
||||
export const toggleSettings = () => ({ type: TOGGLE_SETTINGS })
|
||||
@ -274,6 +281,13 @@ export function appReducer(
|
||||
target: action.item
|
||||
}
|
||||
}
|
||||
case OPEN_VIEW_MENU: return {
|
||||
...state,
|
||||
contextMenu: {
|
||||
type: ContextMenuType.View,
|
||||
event: "#view-toggle"
|
||||
}
|
||||
}
|
||||
case TOGGLE_MENU: return {
|
||||
...state,
|
||||
menu: !state.menu
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { ALL, SOURCE, FeedIdType, loadMore } from "./feed"
|
||||
import { getWindowBreakpoint, AppThunk } from "../utils"
|
||||
import { RSSItem, ItemActionTypes, MARK_READ, MARK_UNREAD, markRead } from "./item"
|
||||
import { getWindowBreakpoint, AppThunk, getDefaultView } from "../utils"
|
||||
import { RSSItem, markRead } from "./item"
|
||||
import { SourceActionTypes, DELETE_SOURCE } from "./source"
|
||||
|
||||
export const SELECT_PAGE = "SELECT_PAGE"
|
||||
export const SWITCH_VIEW = "SWITCH_VIEW"
|
||||
export const SHOW_ITEM = "SHOW_ITEM"
|
||||
export const SHOW_OFFSET_ITEM = "SHOW_OFFSET_ITEM"
|
||||
export const DISMISS_ITEM = "DISMISS_ITEM"
|
||||
@ -11,6 +13,10 @@ export enum PageType {
|
||||
AllArticles, Sources, Page
|
||||
}
|
||||
|
||||
export enum ViewType {
|
||||
Cards, List, Customized
|
||||
}
|
||||
|
||||
interface SelectPageAction {
|
||||
type: typeof SELECT_PAGE
|
||||
pageType: PageType
|
||||
@ -21,6 +27,11 @@ interface SelectPageAction {
|
||||
title?: string
|
||||
}
|
||||
|
||||
interface SwitchViewAction {
|
||||
type: typeof SWITCH_VIEW
|
||||
viewType: ViewType
|
||||
}
|
||||
|
||||
interface ShowItemAction {
|
||||
type: typeof SHOW_ITEM
|
||||
feedId: FeedIdType
|
||||
@ -29,7 +40,7 @@ interface ShowItemAction {
|
||||
|
||||
interface DismissItemAction { type: typeof DISMISS_ITEM }
|
||||
|
||||
export type PageActionTypes = SelectPageAction | ShowItemAction | DismissItemAction
|
||||
export type PageActionTypes = SelectPageAction | SwitchViewAction | ShowItemAction | DismissItemAction
|
||||
|
||||
export function selectAllArticles(init = false): PageActionTypes {
|
||||
return {
|
||||
@ -52,6 +63,13 @@ export function selectSources(sids: number[], menuKey: string, title: string): P
|
||||
}
|
||||
}
|
||||
|
||||
export function switchView(viewType: ViewType): PageActionTypes {
|
||||
return {
|
||||
type: SWITCH_VIEW,
|
||||
viewType: viewType
|
||||
}
|
||||
}
|
||||
|
||||
export function showItem(feedId: FeedIdType, item: RSSItem): PageActionTypes {
|
||||
return {
|
||||
type: SHOW_ITEM,
|
||||
@ -90,13 +108,14 @@ export function showOffsetItem(offset: number): AppThunk {
|
||||
}
|
||||
|
||||
export class PageState {
|
||||
viewType = getDefaultView()
|
||||
feedId = ALL as FeedIdType
|
||||
itemId = -1
|
||||
}
|
||||
|
||||
export function pageReducer(
|
||||
state = new PageState(),
|
||||
action: PageActionTypes
|
||||
action: PageActionTypes | SourceActionTypes
|
||||
): PageState {
|
||||
switch (action.type) {
|
||||
case SELECT_PAGE:
|
||||
@ -111,10 +130,16 @@ export function pageReducer(
|
||||
}
|
||||
default: return state
|
||||
}
|
||||
case SWITCH_VIEW: return {
|
||||
...state,
|
||||
viewType: action.viewType,
|
||||
itemId: action.viewType === ViewType.List ? state.itemId : -1
|
||||
}
|
||||
case SHOW_ITEM: return {
|
||||
...state,
|
||||
itemId: action.item.id
|
||||
}
|
||||
case DELETE_SOURCE:
|
||||
case DISMISS_ITEM: return {
|
||||
...state,
|
||||
itemId: -1
|
||||
|
@ -48,6 +48,7 @@ export function setProxy(address = null) {
|
||||
}
|
||||
|
||||
import ElectronProxyAgent = require("@yang991178/electron-proxy-agent")
|
||||
import { ViewType } from "./models/page"
|
||||
let agent = new ElectronProxyAgent(remote.getCurrentWebContents().session)
|
||||
export const rssParser = new Parser({
|
||||
customFields: customFields,
|
||||
@ -92,3 +93,12 @@ export const cutText = (s: string, length: number) => {
|
||||
}
|
||||
|
||||
export const googleSearch = (text: string) => openExternal("https://www.google.com/search?q=" + encodeURIComponent(text))
|
||||
|
||||
const VIEW_STORE_KEY = "view"
|
||||
export const getDefaultView = () => {
|
||||
let view = localStorage.getItem(VIEW_STORE_KEY)
|
||||
return view ? parseInt(view) as ViewType : ViewType.Cards
|
||||
}
|
||||
export const setDefaultView = (viewType: ViewType) => {
|
||||
localStorage.setItem(VIEW_STORE_KEY, String(viewType))
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user