source grouping settings

This commit is contained in:
Bruce Liu 2020-06-02 15:51:51 +08:00
parent 600f21b8aa
commit 22f528d3e0
11 changed files with 559 additions and 36 deletions

BIN
dist/icons/fabric-icons-16-9cf93f3b.woff vendored Normal file

Binary file not shown.

View File

@ -4,8 +4,8 @@ import { AnimationClassNames } from "@fluentui/react/lib/Styling"
import { SettingsReduxProps } from "../containers/settings-container"
import AboutTab from "./settings/about"
import { Pivot, PivotItem, Spinner } from "@fluentui/react"
import { SourcesTabContainer } from "../containers/settings/sources-container"
import GroupsTab from "./settings/groups"
import SourcesTabContainer from "../containers/settings/sources-container"
import GroupsTabContainer from "../containers/settings/groups-container"
import ProxyTab from "./settings/proxy"
type SettingsProps = SettingsReduxProps & {
@ -36,7 +36,7 @@ class Settings extends React.Component<SettingsProps> {
<SourcesTabContainer />
</PivotItem>
<PivotItem headerText="分组与排序" itemIcon="GroupList">
<GroupsTab />
<GroupsTabContainer />
</PivotItem>
<PivotItem headerText="代理" itemIcon="Globe">
<ProxyTab />

View File

@ -1,9 +1,279 @@
import * as React from "react"
import { SourceGroup } from "../../scripts/models/page"
import { SourceState, RSSSource } from "../../scripts/models/source"
import { IColumn, Selection, SelectionMode, DetailsList, Label, Stack,
TextField, PrimaryButton, DefaultButton, Dropdown, IDropdownOption, CommandBarButton } from "@fluentui/react"
import DangerButton from "../utils/danger-button"
type GroupsTabProps = {
sources: SourceState,
groups: SourceGroup[],
createGroup: (name: string) => void,
updateGroup: (group: SourceGroup) => void,
addToGroup: (groupIndex: number, sid: number) => void,
deleteGroup: (groupIndex: number) => void,
removeFromGroup: (groupIndex: number, sids: number[]) => void
}
type GroupsTabState = {
[formName: string]: any,
selectedGroup: SourceGroup,
selectedSources: RSSSource[],
dropdownIndex: number,
manageGroup: boolean
}
class GroupsTab extends React.Component<GroupsTabProps, GroupsTabState> {
groupSelection: Selection
sourcesSelection: Selection
constructor(props) {
super(props)
this.state = {
editGroupName: "",
newGroupName: "",
selectedGroup: null,
selectedSources: null,
dropdownIndex: null,
manageGroup: false
}
this.groupSelection = new Selection({
getKey: g => (g as SourceGroup).index,
onSelectionChanged: () => {
let g = this.groupSelection.getSelectedCount()
? this.groupSelection.getSelection()[0] as SourceGroup : null
this.setState({
selectedGroup: g,
editGroupName: g && g.isMultiple ? g.name : ""
})
}
})
this.sourcesSelection = new Selection({
getKey: s => (s as RSSSource).sid,
onSelectionChanged: () => {
let sources = this.sourcesSelection.getSelectedCount()
? this.sourcesSelection.getSelection() as RSSSource[] : null
this.setState({
selectedSources: sources
})
}
})
}
groupColumns: IColumn[] = [
{
key: "type",
name: "类型",
minWidth: 40,
maxWidth: 40,
data: "string",
onRender: (g: SourceGroup) => <>
{g.isMultiple ? "分组" : "订阅源"}
</>
},
{
key: "capacity",
name: "容量",
minWidth: 40,
maxWidth: 40,
data: "string",
onRender: (g: SourceGroup) => <>
{g.isMultiple ? g.sids.length : ""}
</>
},
{
key: "name",
name: "名称",
minWidth: 200,
data: "string",
isRowHeader: true,
onRender: (g: SourceGroup) => <>
{g.isMultiple ? g.name : this.props.sources[g.sids[0]].name}
</>
}
]
sourceColumns: IColumn[] = [
{
key: "favicon",
name: "图标",
fieldName: "name",
isIconOnly: true,
iconName: "ImagePixel",
minWidth: 16,
maxWidth: 16,
onRender: (s: RSSSource) => s.iconurl && (
<img src={s.iconurl} className="favicon" />
)
},
{
key: "name",
name: "名称",
fieldName: "name",
minWidth: 200,
data: 'string',
isRowHeader: true
},
{
key: "url",
name: "URL",
fieldName: "url",
minWidth: 280,
data: 'string'
}
]
manageGroup = (g: SourceGroup) => {
if (g.isMultiple) {
this.setState({
selectedGroup: g,
editGroupName: g && g.isMultiple ? g.name : "",
manageGroup: true
})
}
}
dropdownOptions = () => this.props.groups.filter(g => g.isMultiple).map(g => ({
key: g.index,
text: g.name
}))
handleInputChange = (event) => {
const name: string = event.target.name
this.setState({[name]: event.target.value.trim()})
}
createGroup = () => {
this.props.createGroup(this.state.newGroupName)
}
addToGroup = () => {
this.props.addToGroup(this.state.dropdownIndex, this.state.selectedGroup.sids[0])
}
removeFromGroup = () => {
this.props.removeFromGroup(this.state.selectedGroup.index, this.state.selectedSources.map(s => s.sid))
this.setState({ selectedSources: null })
}
deleteGroup = () => {
this.props.deleteGroup(this.state.selectedGroup.index)
this.groupSelection.setIndexSelected(this.state.selectedGroup.index, false, false)
this.setState({ selectedGroup: null })
}
updateGroupName = () => {
let group = this.state.selectedGroup
group = { ...group, name: this.state.editGroupName }
this.props.updateGroup(group)
}
dropdownChange = (_, item: IDropdownOption) => {
this.setState({ dropdownIndex: item ? Number(item.key) : null })
}
class GroupsTab extends React.Component {
render = () => (
<div className="tab-body">
<p>Groups</p>
{this.state.manageGroup
?<>
<Stack horizontal horizontalAlign="space-between" style={{height: 44}}>
<CommandBarButton
text="退出分组"
iconProps={{iconName: "BackToWindow"}}
onClick={() => this.setState({manageGroup: false})} />
{this.state.selectedSources != null && <CommandBarButton
text="从分组删除订阅源"
onClick={this.removeFromGroup}
iconProps={{iconName: "RemoveFromShoppingList", style: {color: "#d13438"}}} />}
</Stack>
<DetailsList
compact={true}
items={this.state.selectedGroup.sids.map(sid => this.props.sources[sid])}
columns={this.sourceColumns}
setKey="multiple"
selection={this.sourcesSelection}
selectionMode={SelectionMode.multiple} />
</>
:<>
<Label></Label>
<Stack horizontal>
<Stack.Item grow>
<TextField
onGetErrorMessage={v => v.trim().length == 0 ? "名称不得为空" : ""}
validateOnLoad={false}
placeholder="输入名称"
value={this.state.newGroupName}
name="newGroupName"
onChange={this.handleInputChange} />
</Stack.Item>
<Stack.Item>
<PrimaryButton
disabled={this.state.newGroupName.length == 0}
onClick={this.createGroup}
text="新建" />
</Stack.Item>
</Stack>
<DetailsList
compact={true}
items={Object.values(this.props.groups)}
columns={this.groupColumns}
setKey="selected"
onItemInvoked={this.manageGroup}
selection={this.groupSelection}
selectionMode={SelectionMode.single} />
{this.state.selectedGroup && (
this.state.selectedGroup.isMultiple
?<>
<Label></Label>
<Stack horizontal>
<Stack.Item grow>
<TextField
onGetErrorMessage={v => v.trim().length == 0 ? "名称不得为空" : ""}
validateOnLoad={false}
placeholder="分组名称"
value={this.state.editGroupName}
name="editGroupName"
onChange={this.handleInputChange} />
</Stack.Item>
<Stack.Item>
<DefaultButton
disabled={this.state.editGroupName.length == 0}
onClick={this.updateGroupName}
text="修改名称" />
</Stack.Item>
<Stack.Item>
<DangerButton
key={this.state.selectedGroup.index}
onClick={this.deleteGroup}
text={`删除分组`} />
</Stack.Item>
</Stack>
</>
:<>
<Label></Label>
<Stack horizontal>
<Stack.Item grow>
<Dropdown
placeholder="选择分组"
selectedKey={this.state.dropdownIndex}
options={this.dropdownOptions()}
onChange={this.dropdownChange} />
</Stack.Item>
<Stack.Item>
<DefaultButton
disabled={this.state.dropdownIndex === null}
onClick={this.addToGroup}
text="添加至分组" />
</Stack.Item>
</Stack>
</>
)}
</>}
</div>
)
}

View File

@ -1,16 +1,16 @@
import * as React from "react"
import { Label, DefaultButton, TextField, Stack, PrimaryButton, DetailsList,
IColumn, SelectionMode, Selection } from "@fluentui/react"
import { SourcesTabReduxProps } from "../../containers/settings/sources-container"
import { SourceState, RSSSource } from "../../scripts/models/source"
import { urlTest } from "../../scripts/utils"
import DangerButton from "../utils/danger-button"
type SourcesTabProps = SourcesTabReduxProps & {
type SourcesTabProps = {
sources: SourceState,
addSource: (url: string) => void,
updateSourceName: (source: RSSSource, name: string) => void,
deleteSource: (source: RSSSource) => void
deleteSource: (source: RSSSource) => void,
importOPML: () => void
}
type SourcesTabState = {
@ -81,7 +81,7 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
<Label>OPML文件</Label>
<Stack horizontal>
<Stack.Item>
<PrimaryButton text="导入文件" />
<PrimaryButton onClick={this.props.importOPML} text="导入文件" />
</Stack.Item>
<Stack.Item>
<DefaultButton text="导出文件" />
@ -115,7 +115,7 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
selection={this.selection}
selectionMode={SelectionMode.single} />
{this.state.selectedSource && (<>
{this.state.selectedSource && <>
<Label></Label>
<Stack horizontal>
<Stack.Item grow>
@ -140,7 +140,7 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
text={`删除订阅源`} />
</Stack.Item>
</Stack>
</>)}
</>}
</div>
)
}

View File

@ -0,0 +1,29 @@
import { remote } from "electron"
import { connect } from "react-redux"
import { createSelector } from "reselect"
import { RootState } from "../../scripts/reducer"
import GroupsTab from "../../components/settings/groups"
import { createSourceGroup, SourceGroup, updateSourceGroup, addSourceToGroup, deleteSourceGroup, removeSourceFromGroup } from "../../scripts/models/page"
const getSources = (state: RootState) => state.sources
const getGroups = (state: RootState) => state.page.sourceGroups
const mapStateToProps = createSelector(
[getSources, getGroups],
(sources, groups) => ({
sources: sources,
groups: groups.map((g, i) => ({ ...g, index: i })),
key: groups.length
})
)
const mapDispatchToProps = dispatch => ({
createGroup: (name: string) => dispatch(createSourceGroup(name)),
updateGroup: (group: SourceGroup) => dispatch(updateSourceGroup(group)),
addToGroup: (groupIndex: number, sid: number) => dispatch(addSourceToGroup(groupIndex, sid)),
deleteGroup: (groupIndex: number) => dispatch(deleteSourceGroup(groupIndex)),
removeFromGroup: (groupIndex: number, sids: number[]) => dispatch(removeSourceFromGroup(groupIndex, sids))
})
const GroupsTabContainer = connect(mapStateToProps, mapDispatchToProps)(GroupsTab)
export default GroupsTabContainer

View File

@ -1,8 +1,10 @@
import { remote } from "electron"
import { connect } from "react-redux"
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"
const getSources = (state: RootState) => state.sources
@ -19,10 +21,19 @@ const mapDispatchToProps = dispatch => {
updateSourceName: (source: RSSSource, name: string) => {
dispatch(updateSource({ ...source, name: name } as RSSSource))
},
deleteSource: (source: RSSSource) => dispatch(deleteSource(source))
deleteSource: (source: RSSSource) => dispatch(deleteSource(source)),
importOPML: () => {
let path = remote.dialog.showOpenDialogSync(
remote.getCurrentWindow(),
{
filters: [{ name: "OPML文件", extensions: ["xml", "opml"] }],
properties: ["openFile"]
}
)
if (path.length > 0) dispatch(importOPML(path[0]))
}
}
}
const connector = connect(mapStateToProps, mapDispatchToProps)
export type SourcesTabReduxProps = typeof connector
export const SourcesTabContainer = connector(SourcesTab)
const SourcesTabContainer = connect(mapStateToProps, mapDispatchToProps)(SourcesTab)
export default SourcesTabContainer

View File

@ -10,13 +10,14 @@ import { initSources, addSource } from "./scripts/models/source"
import { fetchItems } from "./scripts/models/item"
import Root from "./components/root"
import { initFeeds } from "./scripts/models/feed"
import { AppDispatch } from "./scripts/utils"
loadTheme({ defaultFontStyle: { fontFamily: '"Source Han Sans", sans-serif' } })
initializeIcons("icons/")
const store = createStore(
rootReducer,
applyMiddleware<ThunkDispatch<RootState, undefined, AnyAction>, RootState>(thunkMiddleware)
applyMiddleware<AppDispatch, RootState>(thunkMiddleware)
)
store.dispatch(initSources()).then(() => store.dispatch(initFeeds())).then(() => store.dispatch(fetchItems()))

View File

@ -2,7 +2,7 @@ import { RSSSource, INIT_SOURCES, SourceActionTypes, ADD_SOURCE, UPDATE_SOURCE,
import { RSSItem, ItemActionTypes, FETCH_ITEMS, fetchItems } from "./item"
import { ActionStatus, AppThunk } from "../utils"
import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds } from "./feed"
import { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles } from "./page"
import { PageActionTypes, SELECT_PAGE, PageType, selectAllArticles, SourceGroupActionTypes, UPDATE_SOURCE_GROUP, ADD_SOURCE_TO_GROUP, DELETE_SOURCE_GROUP, REMOVE_SOURCE_FROM_GROUP } from "./page"
export enum ContextMenuType {
Hidden, Item
@ -130,7 +130,7 @@ export function exitSettings(): AppThunk {
export function appReducer(
state = new AppState(),
action: SourceActionTypes | ItemActionTypes | ContextMenuActionTypes | SettingsActionTypes
| MenuActionTypes | LogMenuActionType | FeedActionTypes | PageActionTypes
| MenuActionTypes | LogMenuActionType | FeedActionTypes | PageActionTypes | SourceGroupActionTypes
): AppState {
switch (action.type) {
case INIT_SOURCES:
@ -162,7 +162,11 @@ export function appReducer(
}
}
case UPDATE_SOURCE:
case DELETE_SOURCE: return {
case DELETE_SOURCE:
case UPDATE_SOURCE_GROUP:
case ADD_SOURCE_TO_GROUP:
case REMOVE_SOURCE_FROM_GROUP:
case DELETE_SOURCE_GROUP: return {
...state,
settings: {
...state.settings,

View File

@ -1,6 +1,8 @@
import { RSSSource, SourceActionTypes, INIT_SOURCES, ADD_SOURCE, DELETE_SOURCE } from "./source"
import { RSSSource, SourceActionTypes, INIT_SOURCES, ADD_SOURCE, DELETE_SOURCE, addSource } from "./source"
import { ALL, SOURCE } from "./feed"
import { ActionStatus } from "../utils"
import { ActionStatus, AppThunk, domParser, AppDispatch } from "../utils"
import fs = require("fs")
import { saveSettings } from "./app"
const GROUPS_STORE_KEY = "sourceGroups"
@ -8,15 +10,17 @@ export class SourceGroup {
isMultiple: boolean
sids: number[]
name?: string
index?: number // available only from groups tab container
constructor(sources: RSSSource[], name: string = "订阅源组") {
if (sources.length == 1) {
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 = sources.map(s => s.sid)
this.sids = sids
}
static save(groups: SourceGroup[]) {
@ -24,7 +28,8 @@ export class SourceGroup {
}
static load(): SourceGroup[] {
return <SourceGroup[]>JSON.parse(localStorage.getItem(GROUPS_STORE_KEY))
let stored = localStorage.getItem(GROUPS_STORE_KEY)
return stored ? <SourceGroup[]>JSON.parse(stored) : []
}
}
@ -64,15 +69,175 @@ 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)
}
}
async function outlineToSource(dispatch: AppDispatch, outline: Element): Promise<number> {
let url = outline.getAttribute("xmlUrl").trim()
let name = outline.getAttribute("text") || outline.getAttribute("name")
if (url) {
let sid = await dispatch(addSource(url, name))
return sid || 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, failures: number
let doc = domParser.parseFromString(data, "text/xml")
for (let el of doc.body.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))
let sid = await outlineToSource(dispatch, el)
if (sid === null) failures += 1
else {
successes += 1
dispatch(addSourceToGroup(gid, sid))
}
}
}
}
})
}
}
export class PageState {
feedId = ALL
sourceGroups = SourceGroup.load()
}
export function pageReducer(
state = new PageState(),
action: PageActionTypes | SourceActionTypes
action: PageActionTypes | SourceActionTypes | SourceGroupActionTypes
): PageState {
switch(action.type) {
case ADD_SOURCE:
@ -81,7 +246,7 @@ export function pageReducer(
...state,
sourceGroups: [
...state.sourceGroups,
new SourceGroup([action.source])
new SourceGroup([action.source.sid])
]
}
default: return state
@ -105,7 +270,47 @@ export function pageReducer(
...state,
feedId: SOURCE
}
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 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
}
}

View File

@ -14,14 +14,15 @@ export class RSSSource {
description: string
useProxy: boolean
constructor(url: string, useProxy=false) {
constructor(url: string, name: string = null) {
this.url = url
this.useProxy = useProxy
this.name = name
this.useProxy = false
}
async fetchMetaData(parser: Parser) {
let feed = await parser.parseURL(this.url)
this.name = feed.title.trim()
if (!this.name) this.name = feed.title.trim()
this.description = feed.description
let domain = this.url.split("/").slice(0, 3).join("/")
let f = await faviconPromise(domain)
@ -170,17 +171,17 @@ export function addSourceFailure(err): SourceActionTypes {
}
}
export function addSource(url: string): AppThunk<Promise<void>> {
export function addSource(url: string, name: string = null): AppThunk<Promise<void|number>> {
return (dispatch, getState) => {
let app = getState().app
if (app.sourceInit && !app.fetchingItems) {
dispatch(addSourceRequest())
let source = new RSSSource(url)
let source = new RSSSource(url, name)
return source.fetchMetaData(rssParser)
.then(feed => {
let sids = Object.values(getState().sources).map(s => s.sid)
source.sid = Math.max(...sids, -1) + 1
return new Promise<void>((resolve, reject) => {
return new Promise<number>((resolve, reject) => {
db.sdb.insert(source, (err) => {
if (err) {
reject(err)
@ -191,7 +192,7 @@ export function addSource(url: string): AppThunk<Promise<void>> {
.then(items => {
//dispatch(fetchItemsSuccess(items))
SourceGroup.save(getState().page.sourceGroups)
resolve()
resolve(source.sid)
})
}
})

View File

@ -1,5 +1,5 @@
import { shell } from "electron"
import { ThunkAction } from "redux-thunk"
import { ThunkAction, ThunkDispatch } from "redux-thunk"
import { AnyAction } from "redux"
import { RootState } from "./reducer"
@ -14,6 +14,8 @@ export type AppThunk<ReturnType = void> = ThunkAction<
AnyAction
>
export type AppDispatch = ThunkDispatch<RootState, undefined, AnyAction>
import Parser = require("rss-parser")
const customFields = {
item: ["thumb", "image", ["content:encoded", "fullContent"]] as Parser.CustomFieldItem[]