mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-03-25 07:50:09 +01:00
302 lines
8.9 KiB
TypeScript
302 lines
8.9 KiB
TypeScript
import { ALL, SOURCE, loadMore, FeedFilter, FilterType, initFeeds, FeedActionTypes, INIT_FEED } from "./feed"
|
|
import { getWindowBreakpoint, AppThunk, ActionStatus } from "../utils"
|
|
import { RSSItem, markRead } from "./item"
|
|
import { SourceActionTypes, DELETE_SOURCE } from "./source"
|
|
import { toggleMenu } from "./app"
|
|
import { ViewType, ViewConfigs } from "../../schema-types"
|
|
|
|
export const SELECT_PAGE = "SELECT_PAGE"
|
|
export const SWITCH_VIEW = "SWITCH_VIEW"
|
|
export const SET_VIEW_CONFIGS = "SET_VIEW_CONFIGS"
|
|
export const SHOW_ITEM = "SHOW_ITEM"
|
|
export const SHOW_OFFSET_ITEM = "SHOW_OFFSET_ITEM"
|
|
export const DISMISS_ITEM = "DISMISS_ITEM"
|
|
export const APPLY_FILTER = "APPLY_FILTER"
|
|
export const TOGGLE_SEARCH = "TOGGLE_SEARCH"
|
|
|
|
export enum PageType {
|
|
AllArticles, Sources, Page
|
|
}
|
|
|
|
interface SelectPageAction {
|
|
type: typeof SELECT_PAGE
|
|
pageType: PageType
|
|
init: boolean
|
|
keepMenu: boolean
|
|
filter: FeedFilter
|
|
sids?: number[]
|
|
menuKey?: string
|
|
title?: string
|
|
}
|
|
|
|
interface SwitchViewAction {
|
|
type: typeof SWITCH_VIEW
|
|
viewType: ViewType
|
|
}
|
|
|
|
interface SetViewConfigsAction {
|
|
type: typeof SET_VIEW_CONFIGS
|
|
configs: ViewConfigs
|
|
}
|
|
|
|
interface ShowItemAction {
|
|
type: typeof SHOW_ITEM
|
|
feedId: string
|
|
item: RSSItem
|
|
}
|
|
|
|
interface ApplyFilterAction {
|
|
type: typeof APPLY_FILTER
|
|
filter: FeedFilter
|
|
}
|
|
|
|
interface DismissItemAction { type: typeof DISMISS_ITEM }
|
|
interface ToggleSearchAction { type: typeof TOGGLE_SEARCH }
|
|
|
|
export type PageActionTypes = SelectPageAction | SwitchViewAction | ShowItemAction
|
|
| DismissItemAction | ApplyFilterAction | ToggleSearchAction | SetViewConfigsAction
|
|
|
|
export function selectAllArticles(init = false): AppThunk {
|
|
return (dispatch, getState) => {
|
|
dispatch({
|
|
type: SELECT_PAGE,
|
|
keepMenu: getWindowBreakpoint(),
|
|
filter: getState().page.filter,
|
|
pageType: PageType.AllArticles,
|
|
init: init
|
|
} as PageActionTypes)
|
|
}
|
|
}
|
|
|
|
export function selectSources(sids: number[], menuKey: string, title: string): AppThunk {
|
|
return (dispatch, getState) => {
|
|
if (getState().app.menuKey !== menuKey) {
|
|
dispatch({
|
|
type: SELECT_PAGE,
|
|
pageType: PageType.Sources,
|
|
keepMenu: getWindowBreakpoint(),
|
|
filter: getState().page.filter,
|
|
sids: sids,
|
|
menuKey: menuKey,
|
|
title: title,
|
|
init: true
|
|
} as PageActionTypes)
|
|
}
|
|
}
|
|
}
|
|
|
|
export function switchView(viewType: ViewType): PageActionTypes {
|
|
return {
|
|
type: SWITCH_VIEW,
|
|
viewType: viewType
|
|
}
|
|
}
|
|
|
|
export function setViewConfigs(configs: ViewConfigs): AppThunk {
|
|
return (dispatch, getState) => {
|
|
window.settings.setViewConfigs(getState().page.viewType, configs)
|
|
dispatch({
|
|
type: "SET_VIEW_CONFIGS",
|
|
configs: configs
|
|
})
|
|
}
|
|
}
|
|
|
|
export function showItem(feedId: string, item: RSSItem): AppThunk {
|
|
return (dispatch, getState) => {
|
|
const state = getState()
|
|
if (state.items.hasOwnProperty(item._id) && state.sources.hasOwnProperty(item.source)) {
|
|
dispatch({
|
|
type: SHOW_ITEM,
|
|
feedId: feedId,
|
|
item: item
|
|
})
|
|
}
|
|
}
|
|
}
|
|
export function showItemFromId(iid: string): AppThunk {
|
|
return (dispatch, getState) => {
|
|
const state = getState()
|
|
const item = state.items[iid]
|
|
if (!item.hasRead) dispatch(markRead(item))
|
|
if (item) dispatch(showItem(null, item))
|
|
}
|
|
}
|
|
|
|
export const dismissItem = (): PageActionTypes => ({ type: DISMISS_ITEM })
|
|
|
|
export const toggleSearch = (): AppThunk => {
|
|
return (dispatch, getState) => {
|
|
let state = getState()
|
|
dispatch(({ type: TOGGLE_SEARCH }))
|
|
if (!getWindowBreakpoint() && state.app.menu) {
|
|
dispatch(toggleMenu())
|
|
}
|
|
if (state.page.searchOn) {
|
|
dispatch(applyFilter({
|
|
...state.page.filter,
|
|
search: ""
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
export function showOffsetItem(offset: number): AppThunk {
|
|
return (dispatch, getState) => {
|
|
let state = getState()
|
|
if (!state.page.itemFromFeed) return
|
|
let [itemId, feedId] = [state.page.itemId, state.page.feedId]
|
|
let feed = state.feeds[feedId]
|
|
let iids = feed.iids
|
|
let itemIndex = iids.indexOf(itemId)
|
|
let newIndex = itemIndex + offset
|
|
if (itemIndex < 0) {
|
|
let item = state.items[itemId]
|
|
let prevs = feed.iids
|
|
.map((id, index) => [state.items[id], index] as [RSSItem, number])
|
|
.filter(([i, _]) => i.date > item.date)
|
|
if (prevs.length > 0) {
|
|
let prev = prevs[0]
|
|
for (let j = 1; j < prevs.length; j += 1) {
|
|
if (prevs[j][0].date < prev[0].date) prev = prevs[j]
|
|
}
|
|
newIndex = prev[1] + offset + (offset < 0 ? 1 : 0)
|
|
} else {
|
|
newIndex = offset - 1
|
|
}
|
|
}
|
|
if (newIndex >= 0) {
|
|
if (newIndex < iids.length) {
|
|
let item = state.items[iids[newIndex]]
|
|
dispatch(markRead(item))
|
|
dispatch(showItem(feedId, item))
|
|
return
|
|
} else if (!feed.allLoaded){
|
|
dispatch(loadMore(feed)).then(() => {
|
|
dispatch(showOffsetItem(offset))
|
|
}).catch(() =>
|
|
dispatch(dismissItem())
|
|
)
|
|
return
|
|
}
|
|
}
|
|
dispatch(dismissItem())
|
|
}
|
|
}
|
|
|
|
const applyFilterDone = (filter: FeedFilter): PageActionTypes => ({
|
|
type: APPLY_FILTER,
|
|
filter: filter
|
|
})
|
|
|
|
function applyFilter(filter: FeedFilter): AppThunk {
|
|
return (dispatch, getState) => {
|
|
const oldFilterType = getState().page.filter.type
|
|
if (filter.type !== oldFilterType) window.settings.setFilterType(filter.type)
|
|
dispatch(applyFilterDone(filter))
|
|
dispatch(initFeeds(true))
|
|
}
|
|
}
|
|
|
|
export function switchFilter(filter: FilterType): AppThunk {
|
|
return (dispatch, getState) => {
|
|
let oldFilter = getState().page.filter
|
|
let oldType = oldFilter.type
|
|
let newType = filter | (oldType & FilterType.Toggles)
|
|
if (oldType != newType) {
|
|
dispatch(applyFilter({
|
|
...oldFilter,
|
|
type: newType
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
export function toggleFilter(filter: FilterType): AppThunk {
|
|
return (dispatch, getState) => {
|
|
let nextFilter = { ...getState().page.filter }
|
|
nextFilter.type ^= filter
|
|
dispatch(applyFilter(nextFilter))
|
|
}
|
|
}
|
|
|
|
export function performSearch(query: string): AppThunk {
|
|
return (dispatch, getState) => {
|
|
let state = getState()
|
|
if (state.page.searchOn) {
|
|
dispatch(applyFilter({
|
|
...state.page.filter,
|
|
search: query
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
export class PageState {
|
|
viewType = window.settings.getDefaultView()
|
|
viewConfigs = window.settings.getViewConfigs(window.settings.getDefaultView())
|
|
filter = new FeedFilter()
|
|
feedId = ALL
|
|
itemId = null as string
|
|
itemFromFeed = true
|
|
searchOn = false
|
|
}
|
|
|
|
export function pageReducer(
|
|
state = new PageState(),
|
|
action: PageActionTypes | SourceActionTypes | FeedActionTypes
|
|
): PageState {
|
|
switch (action.type) {
|
|
case SELECT_PAGE:
|
|
switch (action.pageType) {
|
|
case PageType.AllArticles: return {
|
|
...state,
|
|
feedId: ALL,
|
|
itemId: null
|
|
}
|
|
case PageType.Sources: return {
|
|
...state,
|
|
feedId: SOURCE,
|
|
itemId: null
|
|
}
|
|
default: return state
|
|
}
|
|
case SWITCH_VIEW: return {
|
|
...state,
|
|
viewType: action.viewType,
|
|
viewConfigs: window.settings.getViewConfigs(action.viewType),
|
|
itemId: null
|
|
}
|
|
case SET_VIEW_CONFIGS: return {
|
|
...state,
|
|
viewConfigs: action.configs
|
|
}
|
|
case APPLY_FILTER: return {
|
|
...state,
|
|
filter: action.filter
|
|
}
|
|
case SHOW_ITEM: return {
|
|
...state,
|
|
itemId: action.item._id,
|
|
itemFromFeed: Boolean(action.feedId)
|
|
}
|
|
case INIT_FEED: switch (action.status) {
|
|
case ActionStatus.Success: return {
|
|
...state,
|
|
itemId: (action.feed._id === state.feedId && action.items.filter(i => i._id === state.itemId).length === 0)
|
|
? null : state.itemId
|
|
}
|
|
default: return state
|
|
}
|
|
case DELETE_SOURCE:
|
|
case DISMISS_ITEM: return {
|
|
...state,
|
|
itemId: null
|
|
}
|
|
case TOGGLE_SEARCH: return {
|
|
...state,
|
|
searchOn: !state.searchOn
|
|
}
|
|
default: return state
|
|
}
|
|
} |