mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-02-01 01:47:07 +01:00
split source groups into model
This commit is contained in:
parent
454cf48a45
commit
143a295024
8
dist/styles.css
vendored
8
dist/styles.css
vendored
@ -1,6 +1,6 @@
|
||||
html, body {
|
||||
background-color: #faf9f8;
|
||||
font-family: "Source Han Sans SC Regular", "Microsoft YaHei", sans-serif;
|
||||
font-family: "Segoe UI Regular", "Source Han Sans SC Regular", "Microsoft YaHei", sans-serif;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
@ -236,6 +236,10 @@ img.favicon {
|
||||
overflow-y: auto;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.settings-hint {
|
||||
font-size: 12px;
|
||||
color: #605e5c;
|
||||
}
|
||||
|
||||
.main {
|
||||
height: calc(100% - 32px);
|
||||
@ -409,7 +413,7 @@ img.favicon {
|
||||
.card h3.title {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
font-weight: 700;
|
||||
font-weight: 600;
|
||||
margin: 10px 12px;
|
||||
position: relative;
|
||||
-webkit-line-clamp: 3;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as React from "react"
|
||||
import { Icon } from "@fluentui/react/lib/Icon"
|
||||
import { Nav, INavLink, INavLinkGroup } from "office-ui-fabric-react/lib/Nav"
|
||||
import { SourceGroup } from "../scripts/models/page"
|
||||
import { SourceGroup } from "../scripts/models/group"
|
||||
import { SourceState, RSSSource } from "../scripts/models/source"
|
||||
import { ALL } from "../scripts/models/feed"
|
||||
import { AnimationClassNames } from "@fluentui/react"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as React from "react"
|
||||
import { SourceGroup } from "../../scripts/models/page"
|
||||
import { SourceGroup } from "../../scripts/models/group"
|
||||
import { SourceState, RSSSource } from "../../scripts/models/source"
|
||||
import { IColumn, Selection, SelectionMode, DetailsList, Label, Stack,
|
||||
TextField, PrimaryButton, DefaultButton, Dropdown, IDropdownOption, CommandBarButton, MarqueeSelection, IDragDropEvents, IDragDropContext } from "@fluentui/react"
|
||||
@ -261,7 +261,7 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
iconProps={{iconName: "RemoveFromShoppingList", style: {color: "#d13438"}}} />}
|
||||
</Stack>
|
||||
|
||||
<MarqueeSelection selection={this.sourcesSelection}>
|
||||
<MarqueeSelection selection={this.sourcesSelection} isDraggingConstrainedToRoot={true}>
|
||||
<DetailsList
|
||||
compact={true}
|
||||
items={this.state.selectedGroup.sids.map(sid => this.props.sources[sid])}
|
||||
@ -307,8 +307,8 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
selection={this.groupSelection}
|
||||
selectionMode={SelectionMode.single} />
|
||||
|
||||
{this.state.selectedGroup && (
|
||||
this.state.selectedGroup.isMultiple
|
||||
{this.state.selectedGroup
|
||||
? ( this.state.selectedGroup.isMultiple
|
||||
?<>
|
||||
<Label>选中分组</Label>
|
||||
<Stack horizontal>
|
||||
@ -353,7 +353,9 @@ class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
)
|
||||
: <p className="settings-hint">双击分组以修改订阅源,可通过拖拽排序</p>
|
||||
}
|
||||
</> : null}
|
||||
</div>
|
||||
)
|
||||
|
@ -3,14 +3,15 @@ import { createSelector } from "reselect"
|
||||
import { RootState } from "../scripts/reducer"
|
||||
import { Menu } from "../components/menu"
|
||||
import { toggleMenu } from "../scripts/models/app"
|
||||
import { selectAllArticles, selectSources, SourceGroup } from "../scripts/models/page"
|
||||
import { SourceGroup } from "../scripts/models/group"
|
||||
import { selectAllArticles, selectSources } from "../scripts/models/page"
|
||||
import { initFeeds } from "../scripts/models/feed"
|
||||
import { RSSSource } from "../scripts/models/source"
|
||||
|
||||
const getStatus = (state: RootState) => state.app.menu && state.app.sourceInit
|
||||
const getKey = (state: RootState) => state.app.menuKey
|
||||
const getSources = (state: RootState) => state.sources
|
||||
const getGroups = (state: RootState) => state.page.sourceGroups
|
||||
const getGroups = (state: RootState) => state.groups
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
[getStatus, getKey, getSources, getGroups],
|
||||
|
@ -4,10 +4,10 @@ import { createSelector } from "reselect"
|
||||
import { RootState } from "../../scripts/reducer"
|
||||
import GroupsTab from "../../components/settings/groups"
|
||||
import { createSourceGroup, SourceGroup, updateSourceGroup, addSourceToGroup,
|
||||
deleteSourceGroup, removeSourceFromGroup, reorderSourceGroups } from "../../scripts/models/page"
|
||||
deleteSourceGroup, removeSourceFromGroup, reorderSourceGroups } from "../../scripts/models/group"
|
||||
|
||||
const getSources = (state: RootState) => state.sources
|
||||
const getGroups = (state: RootState) => state.page.sourceGroups
|
||||
const getGroups = (state: RootState) => state.groups
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
[getSources, getGroups],
|
||||
|
@ -4,7 +4,7 @@ import { createSelector } from "reselect"
|
||||
import { RootState } from "../../scripts/reducer"
|
||||
import SourcesTab from "../../components/settings/sources"
|
||||
import { addSource, RSSSource, updateSource, deleteSource } from "../../scripts/models/source"
|
||||
import { importOPML } from "../../scripts/models/page"
|
||||
import { importOPML } from "../../scripts/models/group"
|
||||
|
||||
const getSources = (state: RootState) => state.sources
|
||||
|
||||
|
@ -2,7 +2,8 @@ import { RSSSource, INIT_SOURCES, SourceActionTypes, ADD_SOURCE, UPDATE_SOURCE,
|
||||
import { RSSItem, ItemActionTypes, FETCH_ITEMS } from "./item"
|
||||
import { ActionStatus, AppThunk, getWindowBreakpoint } from "../utils"
|
||||
import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds } from "./feed"
|
||||
import { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles, SourceGroupActionTypes, UPDATE_SOURCE_GROUP, ADD_SOURCE_TO_GROUP, DELETE_SOURCE_GROUP, REMOVE_SOURCE_FROM_GROUP } from "./page"
|
||||
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"
|
||||
|
||||
export enum ContextMenuType {
|
||||
Hidden, Item
|
||||
|
271
src/scripts/models/group.ts
Normal file
271
src/scripts/models/group.ts
Normal file
@ -0,0 +1,271 @@
|
||||
import fs = require("fs")
|
||||
import { SourceActionTypes, ADD_SOURCE, DELETE_SOURCE, addSource } from "./source"
|
||||
|
||||
import { ActionStatus, AppThunk, domParser, AppDispatch, getWindowBreakpoint } from "../utils"
|
||||
import { saveSettings } from "./app"
|
||||
|
||||
const GROUPS_STORE_KEY = "sourceGroups"
|
||||
|
||||
export class SourceGroup {
|
||||
isMultiple: boolean
|
||||
sids: number[]
|
||||
name?: string
|
||||
index?: number // available only from groups tab container
|
||||
|
||||
constructor(sids: number[], name: string = null) {
|
||||
name = (name && name.trim()) || "订阅源组"
|
||||
if (sids.length == 1) {
|
||||
this.isMultiple = false
|
||||
} else {
|
||||
this.isMultiple = true
|
||||
this.name = name
|
||||
}
|
||||
this.sids = sids
|
||||
}
|
||||
|
||||
static save(groups: SourceGroup[]) {
|
||||
localStorage.setItem(GROUPS_STORE_KEY, JSON.stringify(groups))
|
||||
}
|
||||
|
||||
static load(): SourceGroup[] {
|
||||
let stored = localStorage.getItem(GROUPS_STORE_KEY)
|
||||
return stored ? <SourceGroup[]>JSON.parse(stored) : []
|
||||
}
|
||||
}
|
||||
|
||||
export const CREATE_SOURCE_GROUP = "CREATE_SOURCE_GROUP"
|
||||
export const ADD_SOURCE_TO_GROUP = "ADD_SOURCE_TO_GROUP"
|
||||
export const REMOVE_SOURCE_FROM_GROUP = "REMOVE_SOURCE_FROM_GROUP"
|
||||
export const UPDATE_SOURCE_GROUP = "UPDATE_SOURCE_GROUP"
|
||||
export const REORDER_SOURCE_GROUPS = "REORDER_SOURCE_GROUPS"
|
||||
export const DELETE_SOURCE_GROUP = "DELETE_SOURCE_GROUP"
|
||||
|
||||
interface CreateSourceGroupAction {
|
||||
type: typeof CREATE_SOURCE_GROUP,
|
||||
group: SourceGroup
|
||||
}
|
||||
|
||||
interface AddSourceToGroupAction {
|
||||
type: typeof ADD_SOURCE_TO_GROUP,
|
||||
groupIndex: number,
|
||||
sid: number
|
||||
}
|
||||
|
||||
interface RemoveSourceFromGroupAction {
|
||||
type: typeof REMOVE_SOURCE_FROM_GROUP,
|
||||
groupIndex: number,
|
||||
sids: number[]
|
||||
}
|
||||
|
||||
interface UpdateSourceGroupAction {
|
||||
type: typeof UPDATE_SOURCE_GROUP,
|
||||
groupIndex: number,
|
||||
group: SourceGroup
|
||||
}
|
||||
|
||||
interface ReorderSourceGroupsAction {
|
||||
type: typeof REORDER_SOURCE_GROUPS,
|
||||
groups: SourceGroup[]
|
||||
}
|
||||
|
||||
interface DeleteSourceGroupAction {
|
||||
type: typeof DELETE_SOURCE_GROUP,
|
||||
groupIndex: number
|
||||
}
|
||||
|
||||
export type SourceGroupActionTypes = CreateSourceGroupAction | AddSourceToGroupAction
|
||||
| RemoveSourceFromGroupAction | UpdateSourceGroupAction | ReorderSourceGroupsAction
|
||||
| DeleteSourceGroupAction
|
||||
|
||||
export function createSourceGroupDone(group: SourceGroup): SourceGroupActionTypes {
|
||||
return {
|
||||
type: CREATE_SOURCE_GROUP,
|
||||
group: group
|
||||
}
|
||||
}
|
||||
|
||||
export function createSourceGroup(name: string): AppThunk<number> {
|
||||
return (dispatch, getState) => {
|
||||
let group = new SourceGroup([], name)
|
||||
dispatch(createSourceGroupDone(group))
|
||||
let groups = getState().groups
|
||||
SourceGroup.save(groups)
|
||||
return groups.length - 1
|
||||
}
|
||||
}
|
||||
|
||||
function addSourceToGroupDone(groupIndex: number, sid: number): SourceGroupActionTypes {
|
||||
return {
|
||||
type: ADD_SOURCE_TO_GROUP,
|
||||
groupIndex: groupIndex,
|
||||
sid: sid
|
||||
}
|
||||
}
|
||||
|
||||
export function addSourceToGroup(groupIndex: number, sid: number): AppThunk {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(addSourceToGroupDone(groupIndex, sid))
|
||||
SourceGroup.save(getState().groups)
|
||||
}
|
||||
}
|
||||
|
||||
function removeSourceFromGroupDone(groupIndex: number, sids: number[]): SourceGroupActionTypes {
|
||||
return {
|
||||
type: REMOVE_SOURCE_FROM_GROUP,
|
||||
groupIndex: groupIndex,
|
||||
sids: sids
|
||||
}
|
||||
}
|
||||
|
||||
export function removeSourceFromGroup(groupIndex: number, sids: number[]): AppThunk {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(removeSourceFromGroupDone(groupIndex, sids))
|
||||
SourceGroup.save(getState().groups)
|
||||
}
|
||||
}
|
||||
|
||||
function deleteSourceGroupDone(groupIndex: number): SourceGroupActionTypes {
|
||||
return {
|
||||
type: DELETE_SOURCE_GROUP,
|
||||
groupIndex: groupIndex
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteSourceGroup(groupIndex: number): AppThunk {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(deleteSourceGroupDone(groupIndex))
|
||||
SourceGroup.save(getState().groups)
|
||||
}
|
||||
}
|
||||
|
||||
function updateSourceGroupDone(group: SourceGroup): SourceGroupActionTypes {
|
||||
return {
|
||||
type: UPDATE_SOURCE_GROUP,
|
||||
groupIndex: group.index,
|
||||
group: group
|
||||
}
|
||||
}
|
||||
|
||||
export function updateSourceGroup(group: SourceGroup): AppThunk {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(updateSourceGroupDone(group))
|
||||
SourceGroup.save(getState().groups)
|
||||
}
|
||||
}
|
||||
|
||||
function reorderSourceGroupsDone(groups: SourceGroup[]): SourceGroupActionTypes {
|
||||
return {
|
||||
type: REORDER_SOURCE_GROUPS,
|
||||
groups: groups
|
||||
}
|
||||
}
|
||||
|
||||
export function reorderSourceGroups(groups: SourceGroup[]): AppThunk {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(reorderSourceGroupsDone(groups))
|
||||
SourceGroup.save(getState().groups)
|
||||
}
|
||||
}
|
||||
|
||||
async function outlineToSource(dispatch: AppDispatch, outline: Element): Promise<number> {
|
||||
let url = outline.getAttribute("xmlUrl")
|
||||
let name = outline.getAttribute("text") || outline.getAttribute("name")
|
||||
if (url) {
|
||||
try {
|
||||
return await dispatch(addSource(url.trim(), name, true))
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function importOPML(path: string): AppThunk {
|
||||
return async (dispatch) => {
|
||||
fs.readFile(path, "utf-8", async (err, data) => {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
} else {
|
||||
dispatch(saveSettings())
|
||||
let successes: number = 0, failures: number = 0
|
||||
let doc = domParser.parseFromString(data, "text/xml").getElementsByTagName("body")
|
||||
if (doc.length == 0) {
|
||||
dispatch(saveSettings())
|
||||
return
|
||||
}
|
||||
for (let el of doc[0].children) {
|
||||
if (el.getAttribute("type") === "rss") {
|
||||
let sid = await outlineToSource(dispatch, el)
|
||||
if (sid === null) failures += 1
|
||||
else successes += 1
|
||||
} else if (el.hasAttribute("text") || el.hasAttribute("title")) {
|
||||
let groupName = el.getAttribute("text") || el.getAttribute("title")
|
||||
let gid = dispatch(createSourceGroup(groupName))
|
||||
for (let child of el.children) {
|
||||
let sid = await outlineToSource(dispatch, child)
|
||||
if (sid === null) {
|
||||
failures += 1
|
||||
} else {
|
||||
successes += 1
|
||||
dispatch(addSourceToGroup(gid, sid))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(failures, successes)
|
||||
dispatch(saveSettings())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export type GroupState = SourceGroup[]
|
||||
|
||||
export function groupReducer(
|
||||
state = SourceGroup.load(),
|
||||
action: SourceActionTypes | SourceGroupActionTypes
|
||||
): GroupState {
|
||||
switch(action.type) {
|
||||
case ADD_SOURCE:
|
||||
switch (action.status) {
|
||||
case ActionStatus.Success: return [
|
||||
...state,
|
||||
new SourceGroup([action.source.sid])
|
||||
]
|
||||
default: return state
|
||||
}
|
||||
case DELETE_SOURCE: return [
|
||||
...state.map(group => ({
|
||||
...group,
|
||||
sids: group.sids.filter(sid => sid != action.source.sid)
|
||||
})).filter(g => g.isMultiple || g.sids.length == 1)
|
||||
]
|
||||
case CREATE_SOURCE_GROUP: return [ ...state, action.group ]
|
||||
case ADD_SOURCE_TO_GROUP: return state.map((g, i) => i == action.groupIndex ? ({
|
||||
...g,
|
||||
sids: [ ...g.sids, action.sid ]
|
||||
}) : g).filter(g => g.isMultiple || !g.sids.includes(action.sid))
|
||||
case REMOVE_SOURCE_FROM_GROUP: return [
|
||||
...state.slice(0, action.groupIndex),
|
||||
{
|
||||
...state[action.groupIndex],
|
||||
sids: state[action.groupIndex].sids.filter(sid => !action.sids.includes(sid))
|
||||
},
|
||||
...action.sids.map(sid => new SourceGroup([sid])),
|
||||
...state.slice(action.groupIndex + 1)
|
||||
]
|
||||
case UPDATE_SOURCE_GROUP: return [
|
||||
...state.slice(0, action.groupIndex),
|
||||
action.group,
|
||||
...state.slice(action.groupIndex + 1)
|
||||
]
|
||||
case REORDER_SOURCE_GROUPS: return action.groups
|
||||
case DELETE_SOURCE_GROUP: return [
|
||||
...state.slice(0, action.groupIndex),
|
||||
...state[action.groupIndex].sids.map(sid => new SourceGroup([sid])),
|
||||
...state.slice(action.groupIndex + 1)
|
||||
]
|
||||
default: return state
|
||||
}
|
||||
}
|
@ -1,37 +1,5 @@
|
||||
import fs = require("fs")
|
||||
import { SourceActionTypes, ADD_SOURCE, DELETE_SOURCE, addSource } from "./source"
|
||||
import { ALL, SOURCE } from "./feed"
|
||||
import { ActionStatus, AppThunk, domParser, AppDispatch, getWindowBreakpoint } from "../utils"
|
||||
import { saveSettings } from "./app"
|
||||
|
||||
const GROUPS_STORE_KEY = "sourceGroups"
|
||||
|
||||
export class SourceGroup {
|
||||
isMultiple: boolean
|
||||
sids: number[]
|
||||
name?: string
|
||||
index?: number // available only from groups tab container
|
||||
|
||||
constructor(sids: number[], name: string = null) {
|
||||
name = (name && name.trim()) || "订阅源组"
|
||||
if (sids.length == 1) {
|
||||
this.isMultiple = false
|
||||
} else {
|
||||
this.isMultiple = true
|
||||
this.name = name
|
||||
}
|
||||
this.sids = sids
|
||||
}
|
||||
|
||||
static save(groups: SourceGroup[]) {
|
||||
localStorage.setItem(GROUPS_STORE_KEY, JSON.stringify(groups))
|
||||
}
|
||||
|
||||
static load(): SourceGroup[] {
|
||||
let stored = localStorage.getItem(GROUPS_STORE_KEY)
|
||||
return stored ? <SourceGroup[]>JSON.parse(stored) : []
|
||||
}
|
||||
}
|
||||
import { ALL, SOURCE, FeedIdType } from "./feed"
|
||||
import { getWindowBreakpoint } from "../utils"
|
||||
|
||||
export const SELECT_PAGE = "SELECT_PAGE"
|
||||
|
||||
@ -72,223 +40,15 @@ export function selectSources(sids: number[], menuKey: string, title: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export const CREATE_SOURCE_GROUP = "CREATE_SOURCE_GROUP"
|
||||
export const ADD_SOURCE_TO_GROUP = "ADD_SOURCE_TO_GROUP"
|
||||
export const REMOVE_SOURCE_FROM_GROUP = "REMOVE_SOURCE_FROM_GROUP"
|
||||
export const UPDATE_SOURCE_GROUP = "UPDATE_SOURCE_GROUP"
|
||||
export const REORDER_SOURCE_GROUPS = "REORDER_SOURCE_GROUPS"
|
||||
export const DELETE_SOURCE_GROUP = "DELETE_SOURCE_GROUP"
|
||||
|
||||
interface CreateSourceGroupAction {
|
||||
type: typeof CREATE_SOURCE_GROUP,
|
||||
group: SourceGroup
|
||||
}
|
||||
|
||||
interface AddSourceToGroupAction {
|
||||
type: typeof ADD_SOURCE_TO_GROUP,
|
||||
groupIndex: number,
|
||||
sid: number
|
||||
}
|
||||
|
||||
interface RemoveSourceFromGroupAction {
|
||||
type: typeof REMOVE_SOURCE_FROM_GROUP,
|
||||
groupIndex: number,
|
||||
sids: number[]
|
||||
}
|
||||
|
||||
interface UpdateSourceGroupAction {
|
||||
type: typeof UPDATE_SOURCE_GROUP,
|
||||
groupIndex: number,
|
||||
group: SourceGroup
|
||||
}
|
||||
|
||||
interface ReorderSourceGroupsAction {
|
||||
type: typeof REORDER_SOURCE_GROUPS,
|
||||
groups: SourceGroup[]
|
||||
}
|
||||
|
||||
interface DeleteSourceGroupAction {
|
||||
type: typeof DELETE_SOURCE_GROUP,
|
||||
groupIndex: number
|
||||
}
|
||||
|
||||
export type SourceGroupActionTypes = CreateSourceGroupAction | AddSourceToGroupAction
|
||||
| RemoveSourceFromGroupAction | UpdateSourceGroupAction | ReorderSourceGroupsAction
|
||||
| DeleteSourceGroupAction
|
||||
|
||||
export function createSourceGroupDone(group: SourceGroup): SourceGroupActionTypes {
|
||||
return {
|
||||
type: CREATE_SOURCE_GROUP,
|
||||
group: group
|
||||
}
|
||||
}
|
||||
|
||||
export function createSourceGroup(name: string): AppThunk<number> {
|
||||
return (dispatch, getState) => {
|
||||
let group = new SourceGroup([], name)
|
||||
dispatch(createSourceGroupDone(group))
|
||||
let groups = getState().page.sourceGroups
|
||||
SourceGroup.save(groups)
|
||||
return groups.length - 1
|
||||
}
|
||||
}
|
||||
|
||||
function addSourceToGroupDone(groupIndex: number, sid: number): SourceGroupActionTypes {
|
||||
return {
|
||||
type: ADD_SOURCE_TO_GROUP,
|
||||
groupIndex: groupIndex,
|
||||
sid: sid
|
||||
}
|
||||
}
|
||||
|
||||
export function addSourceToGroup(groupIndex: number, sid: number): AppThunk {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(addSourceToGroupDone(groupIndex, sid))
|
||||
SourceGroup.save(getState().page.sourceGroups)
|
||||
}
|
||||
}
|
||||
|
||||
function removeSourceFromGroupDone(groupIndex: number, sids: number[]): SourceGroupActionTypes {
|
||||
return {
|
||||
type: REMOVE_SOURCE_FROM_GROUP,
|
||||
groupIndex: groupIndex,
|
||||
sids: sids
|
||||
}
|
||||
}
|
||||
|
||||
export function removeSourceFromGroup(groupIndex: number, sids: number[]): AppThunk {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(removeSourceFromGroupDone(groupIndex, sids))
|
||||
SourceGroup.save(getState().page.sourceGroups)
|
||||
}
|
||||
}
|
||||
|
||||
function deleteSourceGroupDone(groupIndex: number): SourceGroupActionTypes {
|
||||
return {
|
||||
type: DELETE_SOURCE_GROUP,
|
||||
groupIndex: groupIndex
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteSourceGroup(groupIndex: number): AppThunk {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(deleteSourceGroupDone(groupIndex))
|
||||
SourceGroup.save(getState().page.sourceGroups)
|
||||
}
|
||||
}
|
||||
|
||||
function updateSourceGroupDone(group: SourceGroup): SourceGroupActionTypes {
|
||||
return {
|
||||
type: UPDATE_SOURCE_GROUP,
|
||||
groupIndex: group.index,
|
||||
group: group
|
||||
}
|
||||
}
|
||||
|
||||
export function updateSourceGroup(group: SourceGroup): AppThunk {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(updateSourceGroupDone(group))
|
||||
SourceGroup.save(getState().page.sourceGroups)
|
||||
}
|
||||
}
|
||||
|
||||
function reorderSourceGroupsDone(groups: SourceGroup[]): SourceGroupActionTypes {
|
||||
return {
|
||||
type: REORDER_SOURCE_GROUPS,
|
||||
groups: groups
|
||||
}
|
||||
}
|
||||
|
||||
export function reorderSourceGroups(groups: SourceGroup[]): AppThunk {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(reorderSourceGroupsDone(groups))
|
||||
SourceGroup.save(getState().page.sourceGroups)
|
||||
}
|
||||
}
|
||||
|
||||
async function outlineToSource(dispatch: AppDispatch, outline: Element): Promise<number> {
|
||||
let url = outline.getAttribute("xmlUrl")
|
||||
let name = outline.getAttribute("text") || outline.getAttribute("name")
|
||||
if (url) {
|
||||
try {
|
||||
return await dispatch(addSource(url.trim(), name, true))
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function importOPML(path: string): AppThunk {
|
||||
return async (dispatch) => {
|
||||
fs.readFile(path, "utf-8", async (err, data) => {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
} else {
|
||||
dispatch(saveSettings())
|
||||
let successes: number = 0, failures: number = 0
|
||||
let doc = domParser.parseFromString(data, "text/xml").getElementsByTagName("body")
|
||||
if (doc.length == 0) {
|
||||
dispatch(saveSettings())
|
||||
return
|
||||
}
|
||||
for (let el of doc[0].children) {
|
||||
if (el.getAttribute("type") === "rss") {
|
||||
let sid = await outlineToSource(dispatch, el)
|
||||
if (sid === null) failures += 1
|
||||
else successes += 1
|
||||
} else if (el.hasAttribute("text") || el.hasAttribute("title")) {
|
||||
let groupName = el.getAttribute("text") || el.getAttribute("title")
|
||||
let gid = dispatch(createSourceGroup(groupName))
|
||||
for (let child of el.children) {
|
||||
let sid = await outlineToSource(dispatch, child)
|
||||
if (sid === null) {
|
||||
failures += 1
|
||||
} else {
|
||||
successes += 1
|
||||
dispatch(addSourceToGroup(gid, sid))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(failures, successes)
|
||||
dispatch(saveSettings())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class PageState {
|
||||
feedId = ALL
|
||||
sourceGroups = SourceGroup.load()
|
||||
}
|
||||
|
||||
export function pageReducer(
|
||||
state = new PageState(),
|
||||
action: PageActionTypes | SourceActionTypes | SourceGroupActionTypes
|
||||
action: PageActionTypes
|
||||
): PageState {
|
||||
switch(action.type) {
|
||||
case ADD_SOURCE:
|
||||
switch (action.status) {
|
||||
case ActionStatus.Success: return {
|
||||
...state,
|
||||
sourceGroups: [
|
||||
...state.sourceGroups,
|
||||
new SourceGroup([action.source.sid])
|
||||
]
|
||||
}
|
||||
default: return state
|
||||
}
|
||||
case DELETE_SOURCE: return {
|
||||
...state,
|
||||
sourceGroups: [
|
||||
...state.sourceGroups.map(group => ({
|
||||
...group,
|
||||
sids: group.sids.filter(sid => sid != action.source.sid)
|
||||
})).filter(g => g.isMultiple || g.sids.length == 1)
|
||||
]
|
||||
}
|
||||
switch (action.type) {
|
||||
case SELECT_PAGE:
|
||||
switch (action.pageType) {
|
||||
case PageType.AllArticles: return {
|
||||
@ -301,49 +61,6 @@ export function pageReducer(
|
||||
}
|
||||
default: return state
|
||||
}
|
||||
case CREATE_SOURCE_GROUP: return {
|
||||
...state,
|
||||
sourceGroups: [ ...state.sourceGroups, action.group ]
|
||||
}
|
||||
case ADD_SOURCE_TO_GROUP: return {
|
||||
...state,
|
||||
sourceGroups: state.sourceGroups.map((g, i) => i == action.groupIndex ? ({
|
||||
...g,
|
||||
sids: [ ...g.sids, action.sid ]
|
||||
}) : g).filter(g => g.isMultiple || !g.sids.includes(action.sid) )
|
||||
}
|
||||
case REMOVE_SOURCE_FROM_GROUP: return {
|
||||
...state,
|
||||
sourceGroups: [
|
||||
...state.sourceGroups.slice(0, action.groupIndex),
|
||||
{
|
||||
...state.sourceGroups[action.groupIndex],
|
||||
sids: state.sourceGroups[action.groupIndex].sids.filter(sid => !action.sids.includes(sid))
|
||||
},
|
||||
...action.sids.map(sid => new SourceGroup([sid])),
|
||||
...state.sourceGroups.slice(action.groupIndex + 1)
|
||||
]
|
||||
}
|
||||
case UPDATE_SOURCE_GROUP: return {
|
||||
...state,
|
||||
sourceGroups: [
|
||||
...state.sourceGroups.slice(0, action.groupIndex),
|
||||
action.group,
|
||||
...state.sourceGroups.slice(action.groupIndex + 1)
|
||||
]
|
||||
}
|
||||
case REORDER_SOURCE_GROUPS: return {
|
||||
...state,
|
||||
sourceGroups: action.groups
|
||||
}
|
||||
case DELETE_SOURCE_GROUP: return {
|
||||
...state,
|
||||
sourceGroups: [
|
||||
...state.sourceGroups.slice(0, action.groupIndex),
|
||||
...state.sourceGroups[action.groupIndex].sids.map(sid => new SourceGroup([sid])),
|
||||
...state.sourceGroups.slice(action.groupIndex + 1)
|
||||
]
|
||||
}
|
||||
default: return state
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
import Parser = require("@yang991178/rss-parser")
|
||||
import * as db from "../db"
|
||||
import { rssParser, faviconPromise, ActionStatus, AppThunk } from "../utils"
|
||||
import { RSSItem, fetchItemsSuccess, insertItems } from "./item"
|
||||
import { SourceGroup } from "./page"
|
||||
import { initFeeds } from "./feed"
|
||||
import { RSSItem, insertItems } from "./item"
|
||||
import { SourceGroup } from "./group"
|
||||
import { saveSettings } from "./app"
|
||||
|
||||
export class RSSSource {
|
||||
@ -199,7 +198,7 @@ export function addSource(url: string, name: string = null, batch = false): AppT
|
||||
.then(items => insertItems(items))
|
||||
.then(items => {
|
||||
//dispatch(fetchItemsSuccess(items))
|
||||
SourceGroup.save(getState().page.sourceGroups)
|
||||
SourceGroup.save(getState().groups)
|
||||
resolve(source.sid)
|
||||
})
|
||||
}
|
||||
@ -254,7 +253,7 @@ export function deleteSource(source: RSSSource): AppThunk {
|
||||
dispatch(saveSettings())
|
||||
} else {
|
||||
dispatch(deleteSourceDone(source))
|
||||
SourceGroup.save(getState().page.sourceGroups)
|
||||
SourceGroup.save(getState().groups)
|
||||
dispatch(saveSettings())
|
||||
}
|
||||
})
|
||||
|
@ -4,12 +4,14 @@ import { sourceReducer } from "./models/source"
|
||||
import { itemReducer } from "./models/item"
|
||||
import { feedReducer } from "./models/feed"
|
||||
import { appReducer } from "./models/app"
|
||||
import { groupReducer } from "./models/group"
|
||||
import { pageReducer } from "./models/page"
|
||||
|
||||
export const rootReducer = combineReducers({
|
||||
sources: sourceReducer,
|
||||
items: itemReducer,
|
||||
feeds: feedReducer,
|
||||
groups: groupReducer,
|
||||
page: pageReducer,
|
||||
app: appReducer
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user