mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-04-24 23:18:46 +02:00
add sources through UI
This commit is contained in:
parent
b308df349c
commit
324aaee5f2
@ -5,7 +5,8 @@ import { SourceState, RSSSource } from "../../scripts/models/source"
|
|||||||
import { urlTest } from "../../scripts/utils"
|
import { urlTest } from "../../scripts/utils"
|
||||||
|
|
||||||
type SourcesTabProps = SourcesTabReduxProps & {
|
type SourcesTabProps = SourcesTabReduxProps & {
|
||||||
sources: SourceState
|
sources: SourceState,
|
||||||
|
addSource: (url: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
type SourcesTabState = {
|
type SourcesTabState = {
|
||||||
@ -79,7 +80,10 @@ class SourcesTab extends React.Component<SourcesTabProps, SourcesTabState> {
|
|||||||
onChange={this.handleInputChange} />
|
onChange={this.handleInputChange} />
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
<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.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import { connect } from "react-redux"
|
|||||||
import { createSelector } from "reselect"
|
import { createSelector } from "reselect"
|
||||||
import { RootState } from "../../scripts/reducer"
|
import { RootState } from "../../scripts/reducer"
|
||||||
import SourcesTab from "../../components/settings/sources"
|
import SourcesTab from "../../components/settings/sources"
|
||||||
|
import { addSource } from "../../scripts/models/source"
|
||||||
|
|
||||||
const getSources = (state: RootState) => state.sources
|
const getSources = (state: RootState) => state.sources
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ const mapStateToProps = createSelector(
|
|||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
|
addSource: (url: string) => dispatch(addSource(url))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 { RSSItem, ItemActionTypes, FETCH_ITEMS, fetchItems } from "./item"
|
||||||
import { ActionStatus, AppThunk } from "../utils"
|
import { ActionStatus, AppThunk } from "../utils"
|
||||||
import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds } from "./feed"
|
import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds } from "./feed"
|
||||||
@ -117,11 +117,9 @@ export function exitSettings(): AppThunk {
|
|||||||
if (getState().app.settings.changed) {
|
if (getState().app.settings.changed) {
|
||||||
dispatch(saveSettings())
|
dispatch(saveSettings())
|
||||||
dispatch(selectAllArticles(true))
|
dispatch(selectAllArticles(true))
|
||||||
dispatch(fetchItems())
|
dispatch(initFeeds(true)).then(() =>
|
||||||
.then(() =>{
|
|
||||||
dispatch(toggleSettings())
|
dispatch(toggleSettings())
|
||||||
dispatch(initFeeds(true))
|
)
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
dispatch(toggleSettings())
|
dispatch(toggleSettings())
|
||||||
}
|
}
|
||||||
@ -143,6 +141,26 @@ export function appReducer(
|
|||||||
}
|
}
|
||||||
default: return state
|
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:
|
case INIT_FEEDS:
|
||||||
switch (action.status) {
|
switch (action.status) {
|
||||||
case ActionStatus.Request: return {
|
case ActionStatus.Request: return {
|
||||||
|
@ -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>> {
|
export function fetchItems(): AppThunk<Promise<void>> {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
let p = new Array<Promise<RSSItem[]>>()
|
let p = new Array<Promise<RSSItem[]>>()
|
||||||
@ -105,11 +124,6 @@ export function fetchItems(): AppThunk<Promise<void>> {
|
|||||||
}
|
}
|
||||||
dispatch(fetchItemsRequest())
|
dispatch(fetchItemsRequest())
|
||||||
return Promise.allSettled(p).then(results => new Promise<void>((resolve, reject) => {
|
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>()
|
let items = new Array<RSSItem>()
|
||||||
results.map((r, i) => {
|
results.map((r, i) => {
|
||||||
if (r.status === "fulfilled") items.push(...r.value)
|
if (r.status === "fulfilled") items.push(...r.value)
|
||||||
@ -118,17 +132,14 @@ export function fetchItems(): AppThunk<Promise<void>> {
|
|||||||
dispatch(fetchItemsFailure(getState().sources[i], r.reason))
|
dispatch(fetchItemsFailure(getState().sources[i], r.reason))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
items.sort((a, b) => a.date.getTime() - b.date.getTime())
|
insertItems(items)
|
||||||
for (let i of items) i.id = count++
|
.then(() => {
|
||||||
db.idb.insert(items, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.log(err)
|
|
||||||
reject(err)
|
|
||||||
} else {
|
|
||||||
dispatch(fetchItemsSuccess(items.reverse()))
|
dispatch(fetchItemsSuccess(items.reverse()))
|
||||||
resolve()
|
resolve()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
reject(err)
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ export function selectSources(sids: number[], menuKey: string, title: string) {
|
|||||||
|
|
||||||
export class PageState {
|
export class PageState {
|
||||||
feedId = ALL
|
feedId = ALL
|
||||||
sourceGroups = new Array<SourceGroup>()
|
sourceGroups = SourceGroup.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -75,14 +75,6 @@ export function pageReducer(
|
|||||||
action: PageActionTypes | SourceActionTypes
|
action: PageActionTypes | SourceActionTypes
|
||||||
): PageState {
|
): PageState {
|
||||||
switch(action.type) {
|
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:
|
case ADD_SOURCE:
|
||||||
switch (action.status) {
|
switch (action.status) {
|
||||||
case ActionStatus.Success: return {
|
case ActionStatus.Success: return {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import Parser = require("rss-parser")
|
import Parser = require("rss-parser")
|
||||||
import * as db from "../db"
|
import * as db from "../db"
|
||||||
import { rssParser, faviconPromise, ActionStatus, AppThunk } from "../utils"
|
import { rssParser, faviconPromise, ActionStatus, AppThunk } from "../utils"
|
||||||
import { RSSItem } from "./item"
|
import { RSSItem, fetchItemsSuccess, insertItems } from "./item"
|
||||||
|
import { SourceGroup } from "./page"
|
||||||
|
|
||||||
export class RSSSource {
|
export class RSSSource {
|
||||||
sid: number
|
sid: number
|
||||||
@ -25,11 +26,15 @@ export class RSSSource {
|
|||||||
if (this.iconurl === null) {
|
if (this.iconurl === null) {
|
||||||
let f = domain + "/favicon.ico"
|
let f = domain + "/favicon.ico"
|
||||||
let result = await fetch(f)
|
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) => {
|
return new Promise<RSSItem>((resolve, reject) => {
|
||||||
let i = new RSSItem(item, source)
|
let i = new RSSItem(item, source)
|
||||||
db.findOne({
|
db.findOne({
|
||||||
@ -49,20 +54,21 @@ 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) => {
|
return new Promise<RSSItem[]>((resolve, reject) => {
|
||||||
parser.parseURL(source.url)
|
|
||||||
.then(feed => {
|
|
||||||
let p = new Array<Promise<RSSItem>>()
|
let p = new Array<Promise<RSSItem>>()
|
||||||
for (let item of feed.items) {
|
for (let item of items) {
|
||||||
p.push(this.checkItem(source, item, db))
|
p.push(this.checkItem(source, item, db))
|
||||||
}
|
}
|
||||||
Promise.all(p).then(values => {
|
Promise.all(p).then(values => {
|
||||||
resolve(values.filter(v => v != null))
|
resolve(values.filter(v => v != null))
|
||||||
}).catch(e => { reject(e) })
|
}).catch(e => { reject(e) })
|
||||||
})
|
})
|
||||||
.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,9 +76,10 @@ export type SourceState = {
|
|||||||
[sid: number]: RSSSource
|
[sid: number]: RSSSource
|
||||||
}
|
}
|
||||||
|
|
||||||
export const INIT_SOURCES = 'INIT_SOURCES'
|
export const INIT_SOURCES = "INIT_SOURCES"
|
||||||
export const ADD_SOURCE = 'ADD_SOURCE'
|
export const ADD_SOURCE = "ADD_SOURCE"
|
||||||
export const UPDATE_SOURCE = 'UPDATE_SOURCE'
|
export const UPDATE_SOURCE = "UPDATE_SOURCE"
|
||||||
|
export const DELETE_SOURCE = "DELETE_SOURCE"
|
||||||
|
|
||||||
interface InitSourcesAction {
|
interface InitSourcesAction {
|
||||||
type: typeof INIT_SOURCES
|
type: typeof INIT_SOURCES
|
||||||
@ -162,11 +169,12 @@ export function addSourceFailure(err): SourceActionTypes {
|
|||||||
|
|
||||||
export function addSource(url: string): AppThunk<Promise<void>> {
|
export function addSource(url: string): AppThunk<Promise<void>> {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
if (getState().app.sourceInit) {
|
let app = getState().app
|
||||||
|
if (app.sourceInit && !app.fetchingItems) {
|
||||||
dispatch(addSourceRequest())
|
dispatch(addSourceRequest())
|
||||||
let source = new RSSSource(url)
|
let source = new RSSSource(url)
|
||||||
return source.fetchMetaData(rssParser)
|
return source.fetchMetaData(rssParser)
|
||||||
.then(() => {
|
.then(feed => {
|
||||||
let sids = Object.values(getState().sources).map(s=>s.sid)
|
let sids = Object.values(getState().sources).map(s=>s.sid)
|
||||||
source.sid = Math.max(...sids, -1) + 1
|
source.sid = Math.max(...sids, -1) + 1
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
@ -177,10 +185,17 @@ export function addSource(url: string): AppThunk<Promise<void>> {
|
|||||||
reject(err)
|
reject(err)
|
||||||
} else {
|
} else {
|
||||||
dispatch(addSourceSuccess(source))
|
dispatch(addSourceSuccess(source))
|
||||||
/* dispatch(fetchItems()).then(() => {
|
RSSSource.checkItems(source, feed.items, db.idb)
|
||||||
dispatch(initFeeds())
|
.then(items => insertItems(items))
|
||||||
}) */
|
.then(items => {
|
||||||
|
dispatch(fetchItemsSuccess(items))
|
||||||
|
SourceGroup.save(getState().page.sourceGroups)
|
||||||
resolve()
|
resolve()
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -190,7 +205,7 @@ export function addSource(url: string): AppThunk<Promise<void>> {
|
|||||||
dispatch(addSourceFailure(e))
|
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.") })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user