add sources through UI

This commit is contained in:
Bruce Liu 2020-05-31 18:24:43 +08:00
parent b308df349c
commit 324aaee5f2
6 changed files with 107 additions and 66 deletions

View File

@ -5,7 +5,8 @@ import { SourceState, RSSSource } from "../../scripts/models/source"
import { urlTest } from "../../scripts/utils"
type SourcesTabProps = SourcesTabReduxProps & {
sources: SourceState
sources: SourceState,
addSource: (url: string) => void
}
type SourcesTabState = {
@ -79,7 +80,10 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
onChange={this.handleInputChange} />
</Stack.Item>
<Stack.Item>
<PrimaryButton disabled={!urlTest(this.state.newUrl)} text="添加" />
<PrimaryButton
disabled={!urlTest(this.state.newUrl)}
onClick={() => this.props.addSource(this.state.newUrl)}
text="添加" />
</Stack.Item>
</Stack>

View File

@ -2,6 +2,7 @@ import { connect } from "react-redux"
import { createSelector } from "reselect"
import { RootState } from "../../scripts/reducer"
import SourcesTab from "../../components/settings/sources"
import { addSource } from "../../scripts/models/source"
const getSources = (state: RootState) => state.sources
@ -14,8 +15,8 @@ const mapStateToProps = createSelector(
const mapDispatchToProps = dispatch => {
return {
}
addSource: (url: string) => dispatch(addSource(url))
}
}
const connector = connect(mapStateToProps, mapDispatchToProps)

View File

@ -1,4 +1,4 @@
import { RSSSource, INIT_SOURCES, SourceActionTypes } from "./source"
import { RSSSource, INIT_SOURCES, SourceActionTypes, ADD_SOURCE } from "./source"
import { RSSItem, ItemActionTypes, FETCH_ITEMS, fetchItems } from "./item"
import { ActionStatus, AppThunk } from "../utils"
import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds } from "./feed"
@ -117,11 +117,9 @@ export function exitSettings(): AppThunk {
if (getState().app.settings.changed) {
dispatch(saveSettings())
dispatch(selectAllArticles(true))
dispatch(fetchItems())
.then(() =>{
dispatch(initFeeds(true)).then(() =>
dispatch(toggleSettings())
dispatch(initFeeds(true))
})
)
} else {
dispatch(toggleSettings())
}
@ -143,6 +141,26 @@ export function appReducer(
}
default: return state
}
case ADD_SOURCE:
switch (action.status) {
case ActionStatus.Request: return {
...state,
fetchingItems: true,
settings: {
...state.settings,
changed: true,
saving: true
}
}
default: return {
...state,
fetchingItems: false,
settings: {
...state.settings,
saving: false
}
}
}
case INIT_FEEDS:
switch (action.status) {
case ActionStatus.Request: return {

View File

@ -96,6 +96,25 @@ export function fetchItemsFailure(source: RSSSource, err): ItemActionTypes {
}
}
export function insertItems(items: RSSItem[]): Promise<RSSItem[]> {
return new Promise<RSSItem[]>((resolve, reject) => {
db.idb.count({}, (err, count) => {
if (err) {
reject(err)
}
items.sort((a, b) => a.date.getTime() - b.date.getTime())
for (let i of items) i.id = count++
db.idb.insert(items, (err) => {
if (err) {
reject(err)
} else {
resolve(items)
}
})
})
})
}
export function fetchItems(): AppThunk<Promise<void>> {
return (dispatch, getState) => {
let p = new Array<Promise<RSSItem[]>>()
@ -105,30 +124,22 @@ export function fetchItems(): AppThunk<Promise<void>> {
}
dispatch(fetchItemsRequest())
return Promise.allSettled(p).then(results => new Promise<void>((resolve, reject) => {
db.idb.count({}, (err, count) => {
if (err) {
console.log(err)
reject(err)
let items = new Array<RSSItem>()
results.map((r, i) => {
if (r.status === "fulfilled") items.push(...r.value)
else {
console.log(r.reason)
dispatch(fetchItemsFailure(getState().sources[i], r.reason))
}
let items = new Array<RSSItem>()
results.map((r, i) => {
if (r.status === "fulfilled") items.push(...r.value)
else {
console.log(r.reason)
dispatch(fetchItemsFailure(getState().sources[i], r.reason))
}
})
items.sort((a, b) => a.date.getTime() - b.date.getTime())
for (let i of items) i.id = count++
db.idb.insert(items, (err) => {
if (err) {
console.log(err)
reject(err)
} else {
dispatch(fetchItemsSuccess(items.reverse()))
resolve()
}
})
})
insertItems(items)
.then(() => {
dispatch(fetchItemsSuccess(items.reverse()))
resolve()
})
.catch(err => {
console.log(err)
reject(err)
})
}))
}

View File

@ -66,7 +66,7 @@ export function selectSources(sids: number[], menuKey: string, title: string) {
export class PageState {
feedId = ALL
sourceGroups = new Array<SourceGroup>()
sourceGroups = SourceGroup.load()
}
@ -75,14 +75,6 @@ export function pageReducer(
action: PageActionTypes | SourceActionTypes
): PageState {
switch(action.type) {
case INIT_SOURCES:
switch (action.status) {
case ActionStatus.Success: return {
...state,
sourceGroups: [new SourceGroup(action.sources, "中文")]
}
default: return state
}
case ADD_SOURCE:
switch (action.status) {
case ActionStatus.Success: return {

View File

@ -1,7 +1,8 @@
import Parser = require("rss-parser")
import * as db from "../db"
import { rssParser, faviconPromise, ActionStatus, AppThunk } from "../utils"
import { RSSItem } from "./item"
import { RSSItem, fetchItemsSuccess, insertItems } from "./item"
import { SourceGroup } from "./page"
export class RSSSource {
sid: number
@ -25,11 +26,15 @@ export class RSSSource {
if (this.iconurl === null) {
let f = domain + "/favicon.ico"
let result = await fetch(f)
if (result.status == 200) this.iconurl = f
if (result.status == 200 && result.headers.has("Content-Type")
&& result.headers.get("Content-Type") === "image/x-icon") {
this.iconurl = f
}
}
return feed
}
private static checkItem(source:RSSSource, item: Parser.Item, db: Nedb<RSSItem>): Promise<RSSItem> {
private static checkItem(source: RSSSource, item: Parser.Item, db: Nedb<RSSItem>): Promise<RSSItem> {
return new Promise<RSSItem>((resolve, reject) => {
let i = new RSSItem(item, source)
db.findOne({
@ -49,30 +54,32 @@ export class RSSSource {
})
}
static fetchItems(source:RSSSource, parser: Parser, db: Nedb<RSSItem>): Promise<RSSItem[]> {
static checkItems(source: RSSSource, items: Parser.Item[], db: Nedb<RSSItem>): Promise<RSSItem[]> {
return new Promise<RSSItem[]>((resolve, reject) => {
parser.parseURL(source.url)
.then(feed => {
let p = new Array<Promise<RSSItem>>()
for (let item of feed.items) {
p.push(this.checkItem(source, item, db))
}
Promise.all(p).then(values => {
resolve(values.filter(v => v != null))
}).catch(e => { reject(e) })
})
.catch(e => { reject(e) })
let p = new Array<Promise<RSSItem>>()
for (let item of items) {
p.push(this.checkItem(source, item, db))
}
Promise.all(p).then(values => {
resolve(values.filter(v => v != null))
}).catch(e => { reject(e) })
})
}
static async fetchItems(source: RSSSource, parser: Parser, db: Nedb<RSSItem>) {
let feed = await parser.parseURL(source.url)
return await this.checkItems(source, feed.items, db)
}
}
export type SourceState = {
[sid: number]: RSSSource
}
export const INIT_SOURCES = 'INIT_SOURCES'
export const ADD_SOURCE = 'ADD_SOURCE'
export const UPDATE_SOURCE = 'UPDATE_SOURCE'
export const INIT_SOURCES = "INIT_SOURCES"
export const ADD_SOURCE = "ADD_SOURCE"
export const UPDATE_SOURCE = "UPDATE_SOURCE"
export const DELETE_SOURCE = "DELETE_SOURCE"
interface InitSourcesAction {
type: typeof INIT_SOURCES
@ -162,11 +169,12 @@ export function addSourceFailure(err): SourceActionTypes {
export function addSource(url: string): AppThunk<Promise<void>> {
return (dispatch, getState) => {
if (getState().app.sourceInit) {
let app = getState().app
if (app.sourceInit && !app.fetchingItems) {
dispatch(addSourceRequest())
let source = new RSSSource(url)
return source.fetchMetaData(rssParser)
.then(() => {
.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) => {
@ -177,10 +185,17 @@ export function addSource(url: string): AppThunk<Promise<void>> {
reject(err)
} else {
dispatch(addSourceSuccess(source))
/* dispatch(fetchItems()).then(() => {
dispatch(initFeeds())
}) */
resolve()
RSSSource.checkItems(source, feed.items, db.idb)
.then(items => insertItems(items))
.then(items => {
dispatch(fetchItemsSuccess(items))
SourceGroup.save(getState().page.sourceGroups)
resolve()
})
.catch(err => {
console.log(err)
reject(err)
})
}
})
})
@ -190,7 +205,7 @@ export function addSource(url: string): AppThunk<Promise<void>> {
dispatch(addSourceFailure(e))
})
}
return new Promise((_, reject) => { reject("Need to init sources before adding.") })
return new Promise((_, reject) => { reject("Sources not initialized or fetching items.") })
}
}