mirror of
https://github.com/yang991178/fluent-reader.git
synced 2025-02-07 23:38:41 +01:00
use lovefield for items
This commit is contained in:
parent
8251bb25ac
commit
06757d0fcd
@ -15,16 +15,14 @@ const mapDispatchToProps = (dispatch: AppDispatch) => ({
|
|||||||
window.settings.setFetchInterval(interval)
|
window.settings.setFetchInterval(interval)
|
||||||
dispatch(setupAutoFetch())
|
dispatch(setupAutoFetch())
|
||||||
},
|
},
|
||||||
deleteArticles: (days: number) => new Promise((resolve) => {
|
deleteArticles: async (days: number) => {
|
||||||
dispatch(saveSettings())
|
dispatch(saveSettings())
|
||||||
let date = new Date()
|
let date = new Date()
|
||||||
date.setTime(date.getTime() - days * 86400000)
|
date.setTime(date.getTime() - days * 86400000)
|
||||||
db.idb.remove({ date: { $lt: date } }, { multi: true }, () => {
|
await db.itemsDB.delete().from(db.items).where(db.items.date.lt(date)).exec()
|
||||||
dispatch(updateUnreadCounts()).then(() => dispatch(saveSettings()))
|
await dispatch(updateUnreadCounts())
|
||||||
db.idb.prependOnceListener("compaction.done", resolve)
|
dispatch(saveSettings())
|
||||||
db.idb.persistence.compactDatafile()
|
},
|
||||||
})
|
|
||||||
}),
|
|
||||||
importAll: async () => {
|
importAll: async () => {
|
||||||
dispatch(saveSettings())
|
dispatch(saveSettings())
|
||||||
let cancelled = await importAll()
|
let cancelled = await importAll()
|
||||||
|
@ -37,17 +37,6 @@ idbSchema.createTable("items").
|
|||||||
addNullable(["thumb", "creator", "serviceRef"]).
|
addNullable(["thumb", "creator", "serviceRef"]).
|
||||||
addIndex("idxDate", ["date"], false, lf.Order.DESC)
|
addIndex("idxDate", ["date"], false, lf.Order.DESC)
|
||||||
|
|
||||||
export const idb = new Datastore<RSSItem>({
|
|
||||||
filename: "items",
|
|
||||||
autoload: true,
|
|
||||||
onload: (err) => {
|
|
||||||
if (err) window.console.log(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
idb.ensureIndex({ fieldName: "source" })
|
|
||||||
//idb.removeIndex("id")
|
|
||||||
//idb.update({}, {$unset: {id: true}}, {multi: true})
|
|
||||||
//idb.remove({}, { multi: true })
|
|
||||||
export let sourcesDB: lf.Database
|
export let sourcesDB: lf.Database
|
||||||
export let sources: lf.schema.Table
|
export let sources: lf.schema.Table
|
||||||
export let itemsDB: lf.Database
|
export let itemsDB: lf.Database
|
||||||
@ -66,6 +55,13 @@ export async function init() {
|
|||||||
if (err) window.console.log(err)
|
if (err) window.console.log(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const idb = new Datastore<RSSItem>({
|
||||||
|
filename: "items",
|
||||||
|
autoload: true,
|
||||||
|
onload: (err) => {
|
||||||
|
if (err) window.console.log(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
const sourceDocs = await new Promise<RSSSource[]>(resolve => {
|
const sourceDocs = await new Promise<RSSSource[]>(resolve => {
|
||||||
sdb.find({}, (_, docs) => {
|
sdb.find({}, (_, docs) => {
|
||||||
resolve(docs)
|
resolve(docs)
|
||||||
|
@ -301,7 +301,6 @@ export function initApp(): AppThunk {
|
|||||||
dispatch(fixBrokenGroups())
|
dispatch(fixBrokenGroups())
|
||||||
await dispatch(fetchItems())
|
await dispatch(fetchItems())
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
db.idb.persistence.compactDatafile()
|
|
||||||
dispatch(updateFavicon())
|
dispatch(updateFavicon())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import * as db from "../db"
|
import * as db from "../db"
|
||||||
|
import lf from "lovefield"
|
||||||
import { SourceActionTypes, INIT_SOURCES, ADD_SOURCE, DELETE_SOURCE } from "./source"
|
import { SourceActionTypes, INIT_SOURCES, ADD_SOURCE, DELETE_SOURCE } from "./source"
|
||||||
import { ItemActionTypes, FETCH_ITEMS, RSSItem, MARK_READ, MARK_UNREAD, TOGGLE_STARRED, TOGGLE_HIDDEN, applyItemReduction } from "./item"
|
import { ItemActionTypes, FETCH_ITEMS, RSSItem, MARK_READ, MARK_UNREAD, TOGGLE_STARRED, TOGGLE_HIDDEN, applyItemReduction } from "./item"
|
||||||
import { ActionStatus, AppThunk, mergeSortedArrays } from "../utils"
|
import { ActionStatus, AppThunk, mergeSortedArrays } from "../utils"
|
||||||
@ -30,29 +31,25 @@ export class FeedFilter {
|
|||||||
this.search = search
|
this.search = search
|
||||||
}
|
}
|
||||||
|
|
||||||
static toQueryObject(filter: FeedFilter) {
|
static toPredicates(filter: FeedFilter) {
|
||||||
let type = filter.type
|
let type = filter.type
|
||||||
let query = {
|
const predicates = new Array<lf.Predicate>()
|
||||||
hasRead: false,
|
if (!(type & FilterType.ShowRead)) predicates.push(db.items.hasRead.eq(false))
|
||||||
starred: true,
|
if (!(type & FilterType.ShowNotStarred)) predicates.push(db.items.starred.eq(true))
|
||||||
hidden: { $exists: false }
|
if (!(type & FilterType.ShowHidden)) predicates.push(db.items.hidden.eq(false))
|
||||||
} as any
|
|
||||||
if (type & FilterType.ShowRead) delete query.hasRead
|
|
||||||
if (type & FilterType.ShowNotStarred) delete query.starred
|
|
||||||
if (type & FilterType.ShowHidden) delete query.hidden
|
|
||||||
if (filter.search !== "") {
|
if (filter.search !== "") {
|
||||||
const flags = (type & FilterType.CaseInsensitive) ? "i" : ""
|
const flags = (type & FilterType.CaseInsensitive) ? "i" : ""
|
||||||
const regex = RegExp(filter.search, flags)
|
const regex = RegExp(filter.search, flags)
|
||||||
if (type & FilterType.FullSearch) {
|
if (type & FilterType.FullSearch) {
|
||||||
query.$or = [
|
predicates.push(lf.op.or(
|
||||||
{ title: { $regex: regex } },
|
db.items.title.match(regex),
|
||||||
{ snippet: { $regex: regex } }
|
db.items.snippet.match(regex)
|
||||||
]
|
))
|
||||||
} else {
|
} else {
|
||||||
query.title = { $regex: regex }
|
predicates.push(db.items.title.match(regex))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return query
|
return predicates
|
||||||
}
|
}
|
||||||
|
|
||||||
static testItem(filter: FeedFilter, item: RSSItem) {
|
static testItem(filter: FeedFilter, item: RSSItem) {
|
||||||
@ -99,24 +96,15 @@ export class RSSFeed {
|
|||||||
this.filter = filter === null ? new FeedFilter() : filter
|
this.filter = filter === null ? new FeedFilter() : filter
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadFeed(feed: RSSFeed, init = false): Promise<RSSItem[]> {
|
static async loadFeed(feed: RSSFeed, init = false): Promise<RSSItem[]> {
|
||||||
return new Promise<RSSItem[]>((resolve, reject) => {
|
const predicates = FeedFilter.toPredicates(feed.filter)
|
||||||
let query = {
|
predicates.push(db.items.source.in(feed.sids))
|
||||||
source: { $in: feed.sids },
|
return (await db.itemsDB.select().from(db.items).where(
|
||||||
...FeedFilter.toQueryObject(feed.filter)
|
lf.op.and.apply(null, predicates)
|
||||||
}
|
).orderBy(db.items.date, lf.Order.DESC)
|
||||||
db.idb.find(query)
|
.skip(init ? 0 : feed.iids.length)
|
||||||
.sort({ date: -1 })
|
.limit(LOAD_QUANTITY)
|
||||||
.skip(init ? 0 : feed.iids.length)
|
.exec()) as RSSItem[]
|
||||||
.limit(LOAD_QUANTITY)
|
|
||||||
.exec((err, docs) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err)
|
|
||||||
} else {
|
|
||||||
resolve(docs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import * as db from "../db"
|
import * as db from "../db"
|
||||||
|
import lf from "lovefield"
|
||||||
import intl from "react-intl-universal"
|
import intl from "react-intl-universal"
|
||||||
import { domParser, htmlDecode, ActionStatus, AppThunk, platformCtrl } from "../utils"
|
import { domParser, htmlDecode, ActionStatus, AppThunk, platformCtrl } from "../utils"
|
||||||
import { RSSSource, updateSource } from "./source"
|
import { RSSSource, updateSource, updateUnreadCounts } from "./source"
|
||||||
import { FeedActionTypes, INIT_FEED, LOAD_MORE, FilterType, initFeeds } from "./feed"
|
import { FeedActionTypes, INIT_FEED, LOAD_MORE, FilterType, initFeeds } from "./feed"
|
||||||
import Parser from "@yang991178/rss-parser"
|
import Parser from "@yang991178/rss-parser"
|
||||||
import { pushNotification, setupAutoFetch } from "./app"
|
import { pushNotification, setupAutoFetch } from "./app"
|
||||||
@ -19,9 +20,9 @@ export class RSSItem {
|
|||||||
snippet: string
|
snippet: string
|
||||||
creator?: string
|
creator?: string
|
||||||
hasRead: boolean
|
hasRead: boolean
|
||||||
starred?: boolean
|
starred: boolean
|
||||||
hidden?: boolean
|
hidden: boolean
|
||||||
notify?: boolean
|
notify: boolean
|
||||||
serviceRef?: string | number
|
serviceRef?: string | number
|
||||||
|
|
||||||
constructor (item: Parser.Item, source: RSSSource) {
|
constructor (item: Parser.Item, source: RSSSource) {
|
||||||
@ -36,6 +37,9 @@ export class RSSItem {
|
|||||||
this.date = item.isoDate ? new Date(item.isoDate) : this.fetchedDate
|
this.date = item.isoDate ? new Date(item.isoDate) : this.fetchedDate
|
||||||
this.creator = item.creator
|
this.creator = item.creator
|
||||||
this.hasRead = false
|
this.hasRead = false
|
||||||
|
this.starred = false
|
||||||
|
this.hidden = false
|
||||||
|
this.notify = false
|
||||||
}
|
}
|
||||||
|
|
||||||
static parseContent(item: RSSItem, parsed: Parser.Item) {
|
static parseContent(item: RSSItem, parsed: Parser.Item) {
|
||||||
@ -103,7 +107,6 @@ interface MarkReadAction {
|
|||||||
interface MarkAllReadAction {
|
interface MarkAllReadAction {
|
||||||
type: typeof MARK_ALL_READ,
|
type: typeof MARK_ALL_READ,
|
||||||
sids: number[]
|
sids: number[]
|
||||||
counts?: number[]
|
|
||||||
time?: number
|
time?: number
|
||||||
before?: boolean
|
before?: boolean
|
||||||
}
|
}
|
||||||
@ -159,17 +162,10 @@ export function fetchItemsIntermediate(): ItemActionTypes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insertItems(items: RSSItem[]): Promise<RSSItem[]> {
|
export async function insertItems(items: RSSItem[]): Promise<RSSItem[]> {
|
||||||
return new Promise<RSSItem[]>((resolve, reject) => {
|
items.sort((a, b) => a.date.getTime() - b.date.getTime())
|
||||||
items.sort((a, b) => a.date.getTime() - b.date.getTime())
|
const rows = items.map(item => db.items.createRow(item))
|
||||||
db.idb.insert(items, (err, inserted) => {
|
return (await db.itemsDB.insert().into(db.items).values(rows).exec()) as RSSItem[]
|
||||||
if (err) {
|
|
||||||
reject(err)
|
|
||||||
} else {
|
|
||||||
resolve(inserted)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchItems(background = false, sids: number[] = null): AppThunk<Promise<void>> {
|
export function fetchItems(background = false, sids: number[] = null): AppThunk<Promise<void>> {
|
||||||
@ -244,7 +240,7 @@ const markUnreadDone = (item: RSSItem): ItemActionTypes => ({
|
|||||||
export function markRead(item: RSSItem): AppThunk {
|
export function markRead(item: RSSItem): AppThunk {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
if (!item.hasRead) {
|
if (!item.hasRead) {
|
||||||
db.idb.update({ _id: item._id }, { $set: { hasRead: true } })
|
db.itemsDB.update(db.items).where(db.items._id.eq(item._id)).set(db.items.hasRead, true).exec()
|
||||||
dispatch(markReadDone(item))
|
dispatch(markReadDone(item))
|
||||||
if (item.serviceRef) {
|
if (item.serviceRef) {
|
||||||
dispatch(dispatch(getServiceHooks()).markRead?.(item))
|
dispatch(dispatch(getServiceHooks()).markRead?.(item))
|
||||||
@ -262,52 +258,39 @@ export function markAllRead(sids: number[] = null, date: Date = null, before = t
|
|||||||
}
|
}
|
||||||
const action = dispatch(getServiceHooks()).markAllRead?.(sids, date, before)
|
const action = dispatch(getServiceHooks()).markAllRead?.(sids, date, before)
|
||||||
if (action) await dispatch(action)
|
if (action) await dispatch(action)
|
||||||
let query = {
|
const predicates: lf.Predicate[] = [
|
||||||
source: { $in: sids },
|
db.items.source.in(sids),
|
||||||
hasRead: false,
|
db.items.hasRead.eq(false)
|
||||||
} as any
|
]
|
||||||
if (date) {
|
if (date) {
|
||||||
query.date = before ? { $lte: date } : { $gte: date }
|
predicates.push(before ? db.items.date.lte(date) : db.items.date.gte(date))
|
||||||
}
|
}
|
||||||
const callback = (items: RSSItem[] = null) => {
|
const query = lf.op.and.apply(null, predicates)
|
||||||
if (items) {
|
await db.itemsDB.update(db.items).set(db.items.hasRead, true).where(query).exec()
|
||||||
const counts = new Map<number, number>()
|
if (date) {
|
||||||
for (let sid of sids) {
|
dispatch({
|
||||||
counts.set(sid, 0)
|
type: MARK_ALL_READ,
|
||||||
}
|
sids: sids,
|
||||||
for (let item of items) {
|
time: date.getTime(),
|
||||||
counts.set(item.source, counts.get(item.source) + 1)
|
before: before
|
||||||
}
|
})
|
||||||
dispatch({
|
dispatch(updateUnreadCounts())
|
||||||
type: MARK_ALL_READ,
|
} else {
|
||||||
sids: sids,
|
dispatch({
|
||||||
counts: sids.map(i => counts.get(i)),
|
type: MARK_ALL_READ,
|
||||||
time: date.getTime(),
|
sids: sids
|
||||||
before: before
|
})
|
||||||
})
|
}
|
||||||
} else {
|
if (!(state.page.filter.type & FilterType.ShowRead)) {
|
||||||
dispatch({
|
dispatch(initFeeds(true))
|
||||||
type: MARK_ALL_READ,
|
|
||||||
sids: sids
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (!(state.page.filter.type & FilterType.ShowRead)) {
|
|
||||||
dispatch(initFeeds(true))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
db.idb.update(query, { $set: { hasRead: true } }, { multi: true, returnUpdatedDocs: Boolean(date) },
|
|
||||||
(err, _, affectedDocuments) => {
|
|
||||||
if (err) console.log(err)
|
|
||||||
if (date) callback(affectedDocuments as unknown as RSSItem[])
|
|
||||||
})
|
|
||||||
if (!date) callback()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markUnread(item: RSSItem): AppThunk {
|
export function markUnread(item: RSSItem): AppThunk {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
if (item.hasRead) {
|
if (item.hasRead) {
|
||||||
db.idb.update({ _id: item._id }, { $set: { hasRead: false } })
|
db.itemsDB.update(db.items).where(db.items._id.eq(item._id)).set(db.items.hasRead, false).exec()
|
||||||
dispatch(markUnreadDone(item))
|
dispatch(markUnreadDone(item))
|
||||||
if (item.serviceRef) {
|
if (item.serviceRef) {
|
||||||
dispatch(dispatch(getServiceHooks()).markUnread?.(item))
|
dispatch(dispatch(getServiceHooks()).markUnread?.(item))
|
||||||
@ -324,9 +307,9 @@ const toggleStarredDone = (item: RSSItem): ItemActionTypes => ({
|
|||||||
export function toggleStarred(item: RSSItem): AppThunk {
|
export function toggleStarred(item: RSSItem): AppThunk {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
if (item.starred === true) {
|
if (item.starred === true) {
|
||||||
db.idb.update({ _id: item._id }, { $unset: { starred: true } })
|
db.itemsDB.update(db.items).where(db.items._id.eq(item._id)).set(db.items.starred, false).exec()
|
||||||
} else {
|
} else {
|
||||||
db.idb.update({ _id: item._id }, { $set: { starred: true } })
|
db.itemsDB.update(db.items).where(db.items._id.eq(item._id)).set(db.items.starred, true).exec()
|
||||||
}
|
}
|
||||||
dispatch(toggleStarredDone(item))
|
dispatch(toggleStarredDone(item))
|
||||||
if (item.serviceRef) {
|
if (item.serviceRef) {
|
||||||
@ -345,9 +328,9 @@ const toggleHiddenDone = (item: RSSItem): ItemActionTypes => ({
|
|||||||
export function toggleHidden(item: RSSItem): AppThunk {
|
export function toggleHidden(item: RSSItem): AppThunk {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
if (item.hidden === true) {
|
if (item.hidden === true) {
|
||||||
db.idb.update({ _id: item._id }, { $unset: { hidden: true } })
|
db.itemsDB.update(db.items).where(db.items._id.eq(item._id)).set(db.items.hidden, true).exec()
|
||||||
} else {
|
} else {
|
||||||
db.idb.update({ _id: item._id }, { $set: { hidden: true } })
|
db.itemsDB.update(db.items).where(db.items._id.eq(item._id)).set(db.items.hidden, false).exec()
|
||||||
}
|
}
|
||||||
dispatch(toggleHiddenDone(item))
|
dispatch(toggleHiddenDone(item))
|
||||||
}
|
}
|
||||||
@ -384,13 +367,11 @@ export function applyItemReduction(item: RSSItem, type: string) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case TOGGLE_STARRED: {
|
case TOGGLE_STARRED: {
|
||||||
if (item.starred === true) delete nextItem.starred
|
nextItem.starred = !item.starred
|
||||||
else nextItem.starred = true
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case TOGGLE_HIDDEN: {
|
case TOGGLE_HIDDEN: {
|
||||||
if (item.hidden === true) delete nextItem.hidden
|
item.hidden = !item.hidden
|
||||||
else nextItem.hidden = true
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -461,11 +442,7 @@ export function itemReducer(
|
|||||||
if (item.hasOwnProperty("serviceRef")) {
|
if (item.hasOwnProperty("serviceRef")) {
|
||||||
const nextItem = { ...item }
|
const nextItem = { ...item }
|
||||||
nextItem.hasRead = !unreadSet.has(nextItem.serviceRef as number)
|
nextItem.hasRead = !unreadSet.has(nextItem.serviceRef as number)
|
||||||
if (starredSet.has(item.serviceRef as number)) {
|
nextItem.starred = starredSet.has(item.serviceRef as number)
|
||||||
nextItem.starred = true
|
|
||||||
} else {
|
|
||||||
delete nextItem.starred
|
|
||||||
}
|
|
||||||
nextState[id] = nextItem
|
nextState[id] = nextItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,32 +31,16 @@ type ActionTransformType = {
|
|||||||
}
|
}
|
||||||
const actionTransform: ActionTransformType = {
|
const actionTransform: ActionTransformType = {
|
||||||
[ItemAction.Read]: (i, f) => {
|
[ItemAction.Read]: (i, f) => {
|
||||||
if (f) {
|
i.hasRead = f
|
||||||
i.hasRead = true
|
|
||||||
} else {
|
|
||||||
i.hasRead = false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[ItemAction.Star]: (i, f) => {
|
[ItemAction.Star]: (i, f) => {
|
||||||
if (f) {
|
i.starred = f
|
||||||
i.starred = true
|
|
||||||
} else if (i.starred) {
|
|
||||||
delete i.starred
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[ItemAction.Hide]: (i, f) => {
|
[ItemAction.Hide]: (i, f) => {
|
||||||
if (f) {
|
i.hidden = f
|
||||||
i.hidden = true
|
|
||||||
} else if (i.hidden) {
|
|
||||||
delete i.hidden
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[ItemAction.Notify]: (i, f) => {
|
[ItemAction.Notify]: (i, f) => {
|
||||||
if (f) {
|
i.notify = f
|
||||||
i.notify = true
|
|
||||||
} else if (i.notify) {
|
|
||||||
delete i.notify
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import * as db from "../db"
|
import * as db from "../db"
|
||||||
|
import lf from "lovefield"
|
||||||
import { SyncService, ServiceConfigs } from "../../schema-types"
|
import { SyncService, ServiceConfigs } from "../../schema-types"
|
||||||
import { AppThunk, ActionStatus } from "../utils"
|
import { AppThunk, ActionStatus } from "../utils"
|
||||||
import { RSSItem, insertItems, fetchItemsSuccess } from "./item"
|
import { RSSItem, insertItems, fetchItemsSuccess } from "./item"
|
||||||
@ -104,12 +105,8 @@ function updateSources(hook: ServiceHooks["updateSources"]): AppThunk<Promise<vo
|
|||||||
doc.serviceRef = s.serviceRef
|
doc.serviceRef = s.serviceRef
|
||||||
doc.unreadCount = 0
|
doc.unreadCount = 0
|
||||||
await dispatch(updateSource(doc))
|
await dispatch(updateSource(doc))
|
||||||
await new Promise((resolve, reject) => {
|
await db.itemsDB.delete().from(db.items).where(db.items.source.eq(doc.sid)).exec()
|
||||||
db.idb.remove({ source: doc.sid }, { multi: true }, (err) => {
|
return doc
|
||||||
if (err) reject(err)
|
|
||||||
else resolve(doc)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
return docs[0]
|
return docs[0]
|
||||||
}
|
}
|
||||||
@ -141,38 +138,29 @@ function syncItems(hook: ServiceHooks["syncItems"]): AppThunk<Promise<void>> {
|
|||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const state = getState()
|
const state = getState()
|
||||||
const [unreadRefs, starredRefs] = await dispatch(hook())
|
const [unreadRefs, starredRefs] = await dispatch(hook())
|
||||||
const promises = new Array<Promise<number>>()
|
const promises = [
|
||||||
promises.push(new Promise((resolve) => {
|
db.itemsDB.update(db.items).set(db.items.hasRead, false).where(lf.op.and(
|
||||||
db.idb.update({
|
db.items.serviceRef.in(unreadRefs),
|
||||||
serviceRef: { $exists: true, $in: unreadRefs },
|
db.items.hasRead.eq(true)
|
||||||
hasRead: true
|
)).exec(),
|
||||||
}, { $set: { hasRead: false } }, { multi: true }, (_, num) => resolve(num))
|
db.itemsDB.update(db.items).set(db.items.hasRead, true).where(lf.op.and(
|
||||||
}))
|
lf.op.not(db.items.serviceRef.in(unreadRefs)),
|
||||||
promises.push(new Promise((resolve) => {
|
db.items.hasRead.eq(false)
|
||||||
db.idb.update({
|
)).exec(),
|
||||||
serviceRef: { $exists: true, $nin: unreadRefs },
|
db.itemsDB.update(db.items).set(db.items.starred, true).where(lf.op.and(
|
||||||
hasRead: false
|
db.items.serviceRef.in(starredRefs),
|
||||||
}, { $set: { hasRead: true } }, { multi: true }, (_, num) => resolve(num))
|
db.items.starred.eq(false)
|
||||||
}))
|
)).exec(),
|
||||||
promises.push(new Promise((resolve) => {
|
db.itemsDB.update(db.items).set(db.items.starred, false).where(lf.op.and(
|
||||||
db.idb.update({
|
lf.op.not(db.items.serviceRef.in(starredRefs)),
|
||||||
serviceRef: { $exists: true, $in: starredRefs },
|
db.items.hasRead.eq(true)
|
||||||
starred: { $exists: false }
|
)).exec(),
|
||||||
}, { $set: { starred: true } }, { multi: true }, (_, num) => resolve(num))
|
]
|
||||||
}))
|
await Promise.all(promises)
|
||||||
promises.push(new Promise((resolve) => {
|
await dispatch(updateUnreadCounts())
|
||||||
db.idb.update({
|
dispatch(syncLocalItems(unreadRefs, starredRefs))
|
||||||
serviceRef: { $exists: true, $nin: starredRefs },
|
if (!(state.page.filter.type & FilterType.ShowRead) || !(state.page.filter.type & FilterType.ShowNotStarred)) {
|
||||||
starred: true
|
dispatch(initFeeds(true))
|
||||||
}, { $unset: { starred: true } }, { multi: true }, (_, num) => resolve(num))
|
|
||||||
}))
|
|
||||||
const affected = (await Promise.all(promises)).reduce((a, b) => a + b, 0)
|
|
||||||
if (affected > 0) {
|
|
||||||
dispatch(syncLocalItems(unreadRefs, starredRefs))
|
|
||||||
if (!(state.page.filter.type & FilterType.ShowRead) || !(state.page.filter.type & FilterType.ShowNotStarred)) {
|
|
||||||
dispatch(initFeeds(true))
|
|
||||||
}
|
|
||||||
await dispatch(updateUnreadCounts())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import intl from "react-intl-universal"
|
import intl from "react-intl-universal"
|
||||||
import * as db from "../../db"
|
import * as db from "../../db"
|
||||||
|
import lf from "lovefield"
|
||||||
import { ServiceHooks } from "../service"
|
import { ServiceHooks } from "../service"
|
||||||
import { ServiceConfigs, SyncService } from "../../../schema-types"
|
import { ServiceConfigs, SyncService } from "../../../schema-types"
|
||||||
import { createSourceGroup } from "../group"
|
import { createSourceGroup } from "../group"
|
||||||
@ -145,9 +146,11 @@ export const feedbinServiceHooks: ServiceHooks = {
|
|||||||
snippet: dom.documentElement.textContent.trim(),
|
snippet: dom.documentElement.textContent.trim(),
|
||||||
creator: i.author,
|
creator: i.author,
|
||||||
hasRead: !unread.has(i.id),
|
hasRead: !unread.has(i.id),
|
||||||
|
starred: starred.has(i.id),
|
||||||
|
hidden: false,
|
||||||
|
notify: false,
|
||||||
serviceRef: i.id,
|
serviceRef: i.id,
|
||||||
} as RSSItem
|
} as RSSItem
|
||||||
if (starred.has(i.id)) item.starred = true
|
|
||||||
if (i.images && i.images.original_url) {
|
if (i.images && i.images.original_url) {
|
||||||
item.thumb = i.images.original_url
|
item.thumb = i.images.original_url
|
||||||
} else {
|
} else {
|
||||||
@ -171,26 +174,21 @@ export const feedbinServiceHooks: ServiceHooks = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
markAllRead: (sids, date, before) => (_, getState) => new Promise(resolve => {
|
markAllRead: (sids, date, before) => async (_, getState) => {
|
||||||
const state = getState()
|
const state = getState()
|
||||||
const configs = state.service as FeedbinConfigs
|
const configs = state.service as FeedbinConfigs
|
||||||
const query: any = {
|
const predicates: lf.Predicate[] = [
|
||||||
source: { $in: sids },
|
db.items.source.in(sids),
|
||||||
hasRead: false,
|
db.items.hasRead.eq(false),
|
||||||
serviceRef: { $exists: true }
|
db.items.serviceRef.isNotNull()
|
||||||
}
|
]
|
||||||
if (date) {
|
if (date) {
|
||||||
query.date = before ? { $lte: date } : { $gte: date }
|
predicates.push(before ? db.items.date.lte(date) : db.items.date.gte(date))
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
const rows = await db.itemsDB.select(db.items.serviceRef).where(lf.op.and.apply(null, predicates)).exec()
|
||||||
db.idb.find(query, { serviceRef: 1 }, (err, docs) => {
|
const refs = rows.map(row => row["serviceRef"]) as number[]
|
||||||
resolve()
|
markItems(configs, "unread", "DELETE", refs)
|
||||||
if (!err) {
|
},
|
||||||
const refs = docs.map(i => i.serviceRef as number)
|
|
||||||
markItems(configs, "unread", "DELETE", refs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
|
|
||||||
markRead: (item: RSSItem) => async (_, getState) => {
|
markRead: (item: RSSItem) => async (_, getState) => {
|
||||||
await markItems(getState().service as FeedbinConfigs, "unread", "DELETE", [item.serviceRef as number])
|
await markItems(getState().service as FeedbinConfigs, "unread", "DELETE", [item.serviceRef as number])
|
||||||
|
@ -122,9 +122,11 @@ export const feverServiceHooks: ServiceHooks = {
|
|||||||
snippet: htmlDecode(i.html).trim(),
|
snippet: htmlDecode(i.html).trim(),
|
||||||
creator: i.author,
|
creator: i.author,
|
||||||
hasRead: Boolean(i.is_read),
|
hasRead: Boolean(i.is_read),
|
||||||
|
starred: Boolean(i.is_saved),
|
||||||
|
hidden: false,
|
||||||
|
notify: false,
|
||||||
serviceRef: typeof i.id === "string" ? parseInt(i.id) : i.id,
|
serviceRef: typeof i.id === "string" ? parseInt(i.id) : i.id,
|
||||||
} as RSSItem
|
} as RSSItem
|
||||||
if (i.is_saved) item.starred = true
|
|
||||||
// Try to get the thumbnail of the item
|
// Try to get the thumbnail of the item
|
||||||
let dom = domParser.parseFromString(item.content, "text/html")
|
let dom = domParser.parseFromString(item.content, "text/html")
|
||||||
let baseEl = dom.createElement('base')
|
let baseEl = dom.createElement('base')
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Parser from "@yang991178/rss-parser"
|
import Parser from "@yang991178/rss-parser"
|
||||||
import intl from "react-intl-universal"
|
import intl from "react-intl-universal"
|
||||||
import * as db from "../db"
|
import * as db from "../db"
|
||||||
|
import lf from "lovefield"
|
||||||
import { fetchFavicon, ActionStatus, AppThunk, parseRSS } from "../utils"
|
import { fetchFavicon, ActionStatus, AppThunk, parseRSS } from "../utils"
|
||||||
import { RSSItem, insertItems, ItemActionTypes, FETCH_ITEMS, MARK_READ, MARK_UNREAD, MARK_ALL_READ } from "./item"
|
import { RSSItem, insertItems, ItemActionTypes, FETCH_ITEMS, MARK_READ, MARK_UNREAD, MARK_ALL_READ } from "./item"
|
||||||
import { saveSettings } from "./app"
|
import { saveSettings } from "./app"
|
||||||
@ -39,26 +40,20 @@ export class RSSSource {
|
|||||||
return feed
|
return feed
|
||||||
}
|
}
|
||||||
|
|
||||||
private static checkItem(source: RSSSource, item: Parser.Item): Promise<RSSItem> {
|
private static async checkItem(source: RSSSource, item: Parser.Item): Promise<RSSItem> {
|
||||||
return new Promise<RSSItem>((resolve, reject) => {
|
let i = new RSSItem(item, source)
|
||||||
let i = new RSSItem(item, source)
|
const items = (await db.itemsDB.select().from(db.items).where(lf.op.and(
|
||||||
db.idb.findOne({
|
db.items.source.eq(i.source),
|
||||||
source: i.source,
|
db.items.title.eq(i.title),
|
||||||
title: i.title,
|
db.items.date.eq(i.date)
|
||||||
date: i.date
|
)).limit(1).exec()) as RSSItem[]
|
||||||
},
|
if (items.length === 0) {
|
||||||
(err, doc) => {
|
RSSItem.parseContent(i, item)
|
||||||
if (err) {
|
if (source.rules) SourceRule.applyAll(source.rules, i)
|
||||||
reject(err)
|
return i
|
||||||
} else if (doc === null) {
|
} else {
|
||||||
RSSItem.parseContent(i, item)
|
return null
|
||||||
if (source.rules) SourceRule.applyAll(source.rules, i)
|
}
|
||||||
resolve(i)
|
|
||||||
} else {
|
|
||||||
resolve(null)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static checkItems(source: RSSSource, items: Parser.Item[]): Promise<RSSItem[]> {
|
static checkItems(source: RSSSource, items: Parser.Item[]): Promise<RSSItem[]> {
|
||||||
@ -138,17 +133,14 @@ export function initSourcesFailure(err): SourceActionTypes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unreadCount(source: RSSSource): Promise<RSSSource> {
|
async function unreadCount(source: RSSSource): Promise<RSSSource> {
|
||||||
return new Promise<RSSSource>((resolve, reject) => {
|
source.unreadCount = (await db.itemsDB.select(
|
||||||
db.idb.count({ source: source.sid, hasRead: false }, (err, n) => {
|
lf.fn.count(db.items._id)
|
||||||
if (err) {
|
).from(db.items).where(lf.op.and(
|
||||||
reject(err)
|
db.items.source.eq(source.sid),
|
||||||
} else {
|
db.items.hasRead.eq(false)
|
||||||
source.unreadCount = n
|
)).exec())[0]["COUNT(_id)"]
|
||||||
resolve(source)
|
return source
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateUnreadCounts(): AppThunk<Promise<void>> {
|
export function updateUnreadCounts(): AppThunk<Promise<void>> {
|
||||||
@ -268,30 +260,18 @@ export function deleteSourceDone(source: RSSSource): SourceActionTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function deleteSource(source: RSSSource, batch = false): AppThunk<Promise<void>> {
|
export function deleteSource(source: RSSSource, batch = false): AppThunk<Promise<void>> {
|
||||||
return (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
return new Promise((resolve) => {
|
if (!batch) dispatch(saveSettings())
|
||||||
|
try {
|
||||||
|
await db.itemsDB.delete().from(db.items).where(db.items.source.eq(source.sid)).exec()
|
||||||
|
await db.sourcesDB.delete().from(db.sources).where(db.sources.sid.eq(source.sid)).exec()
|
||||||
|
dispatch(deleteSourceDone(source))
|
||||||
|
window.settings.saveGroups(getState().groups)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
} finally {
|
||||||
if (!batch) dispatch(saveSettings())
|
if (!batch) dispatch(saveSettings())
|
||||||
db.idb.remove({ source: source.sid }, { multi: true }, (err) => {
|
}
|
||||||
if (err) {
|
|
||||||
console.log(err)
|
|
||||||
if (!batch) dispatch(saveSettings())
|
|
||||||
resolve()
|
|
||||||
} else {
|
|
||||||
db.sourcesDB.delete().from(db.sources).where(
|
|
||||||
db.sources.sid.eq(source.sid)
|
|
||||||
).exec().then(() => {
|
|
||||||
dispatch(deleteSourceDone(source))
|
|
||||||
window.settings.saveGroups(getState().groups)
|
|
||||||
if (!batch) dispatch(saveSettings())
|
|
||||||
resolve()
|
|
||||||
}).catch(err => {
|
|
||||||
console.log(err)
|
|
||||||
if (!batch) dispatch(saveSettings())
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,9 +378,7 @@ export function sourceReducer(
|
|||||||
action.sids.map((sid, i) => {
|
action.sids.map((sid, i) => {
|
||||||
nextState[sid] = {
|
nextState[sid] = {
|
||||||
...state[sid],
|
...state[sid],
|
||||||
unreadCount: action.counts
|
unreadCount: action.time ? state[sid].unreadCount : 0
|
||||||
? state[sid].unreadCount - action.counts[i]
|
|
||||||
: 0
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return nextState
|
return nextState
|
||||||
|
Loading…
x
Reference in New Issue
Block a user