This commit is contained in:
刘浩远 2020-06-14 13:04:59 +08:00
parent d649bde776
commit 7385592ba7
17 changed files with 251 additions and 66 deletions

25
dist/styles.css vendored
View File

@ -337,7 +337,6 @@ img.favicon {
.main {
height: calc(100% - 32px);
position: relative;
overflow-y: scroll;
}
.main::before {
@ -352,6 +351,19 @@ img.favicon {
background: linear-gradient(var(--neutralLighterAlt), #faf9f800);
z-index: 1;
}
.article-search {
z-index: 4;
position: absolute;
top:0;
left: 36px;
width: 100%;
max-width: calc(100% - 484px);
margin: 4px 16px;
border: none;
-webkit-app-region: none;
height: 28px;
box-shadow: 0 1.6px 3.6px 0 rgba(0,0,0,.132), 0 0.3px 0.9px 0 rgba(0,0,0,.108);
}
@media (min-width: 1441px) {
#root > nav.menu-on {
@ -386,6 +398,10 @@ img.favicon {
.main.menu-on, .list-main.menu-on {
margin-left: 280px;
}
.menu-on .article-search {
left: 280px;
max-width: calc(100% - 728px);
}
nav.hide-btns .btn-group .btn, nav.menu-on .btn-group .btn.hide-wide, .menu .btn-group .btn.hide-wide {
display: none;
@ -460,7 +476,7 @@ img.favicon {
.side-article-wrapper .article > .ms-Stack {
border-top: 1px solid var(--neutralQuaternaryAlt);
}
.list-feed-container::before, .side-article-wrapper::before {
.list-feed-container:first-child::before, .side-article-wrapper::before {
content: "";
display: block;
width: 100%;
@ -478,6 +494,11 @@ img.favicon {
overflow: hidden;
background: var(--white);
}
.list-main .article-search {
left: 0;
max-width: 330px;
margin: 4px 10px;
}
.list-feed-container {
width: 350px;
background-color: var(--neutralLighterAlt);

View File

@ -7,7 +7,7 @@
"build": "webpack --config ./webpack.config.js",
"electron": "electron ./dist/electron.js",
"start": "npm run build && npm run electron",
"package-win": "electron-builder --win --x64",
"package-win": "electron-builder -w --x64 && electron-builder -w --ia32 && electron-builder -w --arm64",
"package-mac": "sudo electron-builder --mac"
},
"keywords": [],
@ -19,13 +19,10 @@
"copyright": "Copyright © 2020 Haoyuan Liu",
"files": "./dist/**/*",
"directories": {
"output": "./bin/"
"output": "./bin/${platform}/${arch}/"
},
"win": {
"target": [
"nsis",
"appx"
],
"target": [ "nsis", "appx" ],
"certificateFile": "./bin/key.pfx"
},
"appx": {
@ -41,7 +38,7 @@
"setBuildNumber": true
},
"mac": {
"target": ["dmg"]
"target": [ "dmg" ]
}
},
"devDependencies": {
@ -55,7 +52,7 @@
"@types/reselect": "^2.2.0",
"@yang991178/electron-proxy-agent": "^1.2.1",
"@yang991178/rss-parser": "^3.8.1",
"electron": "^8.3.0",
"electron": "^9.0.4",
"electron-builder": "^22.7.0",
"electron-react-devtools": "^0.5.3",
"electron-store": "^5.2.0",

View File

@ -24,7 +24,7 @@ class DefaultCard extends Card {
) : null}
<CardInfo source={this.props.source} item={this.props.item} />
<h3 className="title">{this.props.item.title}</h3>
<p className={"snippet"+(this.props.item.thumb?"":" show")}>{this.props.item.snippet}</p>
<p className={"snippet"+(this.props.item.thumb?"":" show")}>{this.props.item.snippet.slice(0, 325)}</p>
</div>
)
}

View File

@ -7,7 +7,7 @@ import { ContextMenuType } from "../scripts/models/app"
import { RSSItem } from "../scripts/models/item"
import { ContextReduxProps } from "../containers/context-menu-container"
import { ViewType } from "../scripts/models/page"
import { FeedFilter } from "../scripts/models/feed"
import { FilterType } from "../scripts/models/feed"
export type ContextMenuProps = ContextReduxProps & {
type: ContextMenuType
@ -17,7 +17,7 @@ export type ContextMenuProps = ContextReduxProps & {
feedId?: string
text?: string
viewType?: ViewType
filter?: FeedFilter
filter?: FilterType
sids?: number[]
showItem: (feedId: string, item: RSSItem) => void
markRead: (item: RSSItem) => void
@ -25,8 +25,8 @@ export type ContextMenuProps = ContextReduxProps & {
toggleStarred: (item: RSSItem) => void
toggleHidden: (item: RSSItem) => void
switchView: (viewType: ViewType) => void
switchFilter: (filter: FeedFilter) => void
toggleFilter: (filter: FeedFilter) => void
switchFilter: (filter: FilterType) => void
toggleFilter: (filter: FilterType) => void
markAllRead: (sids: number[]) => void
settings: () => void
close: () => void
@ -146,34 +146,41 @@ export class ContextMenu extends React.Component<ContextMenuProps> {
text: intl.get("allArticles"),
iconProps: { iconName: "ClearFilter" },
canCheck: true,
checked: (this.props.filter & ~FeedFilter.ShowHidden) == FeedFilter.Default,
onClick: () => this.props.switchFilter(FeedFilter.Default)
checked: (this.props.filter & ~FilterType.Toggles) == FilterType.Default,
onClick: () => this.props.switchFilter(FilterType.Default)
},
{
key: "unreadOnly",
text: intl.get("context.unreadOnly"),
iconProps: { iconName: "RadioBtnOn", style: { fontSize: 14, textAlign: "center" } },
canCheck: true,
checked: (this.props.filter & ~FeedFilter.ShowHidden) == FeedFilter.UnreadOnly,
onClick: () => this.props.switchFilter(FeedFilter.UnreadOnly)
checked: (this.props.filter & ~FilterType.Toggles) == FilterType.UnreadOnly,
onClick: () => this.props.switchFilter(FilterType.UnreadOnly)
},
{
key: "starredOnly",
text: intl.get("context.starredOnly"),
iconProps: { iconName: "FavoriteStarFill" },
canCheck: true,
checked: (this.props.filter & ~FeedFilter.ShowHidden) == FeedFilter.StarredOnly,
onClick: () => this.props.switchFilter(FeedFilter.StarredOnly)
checked: (this.props.filter & ~FilterType.Toggles) == FilterType.StarredOnly,
onClick: () => this.props.switchFilter(FilterType.StarredOnly)
}
]
}
},
{
key: "fullSearch",
text: intl.get("context.fullSearch"),
canCheck: true,
checked: Boolean(this.props.filter & FilterType.FullSearch),
onClick: () => this.props.toggleFilter(FilterType.FullSearch)
},
{
key: "showHidden",
text: intl.get("context.showHidden"),
canCheck: true,
checked: Boolean(this.props.filter & FeedFilter.ShowHidden),
onClick: () => this.props.toggleFilter(FeedFilter.ShowHidden)
checked: Boolean(this.props.filter & FilterType.ShowHidden),
onClick: () => this.props.toggleFilter(FilterType.ShowHidden)
}
]
case ContextMenuType.Group: return [

View File

@ -13,12 +13,14 @@ export type MenuProps = {
selected: string,
sources: SourceState,
groups: SourceGroup[],
searchOn: boolean,
toggleMenu: () => void,
allArticles: () => void,
selectSourceGroup: (group: SourceGroup, menuKey: string) => void,
selectSource: (source: RSSSource) => void,
groupContextMenu: (sids: number[], event: React.MouseEvent) => void,
updateGroupExpansion: (event: React.MouseEvent<HTMLElement>, key: string, selected: string) => void,
toggleSearch: () => void,
}
export class Menu extends React.Component<MenuProps> {
@ -29,8 +31,10 @@ export class Menu extends React.Component<MenuProps> {
links: [
{
name: intl.get("search"),
ariaLabel: this.props.searchOn ? "✓" : "0",
key: "search",
icon: "Search",
onClick: this.props.toggleSearch,
url: null
},
{
@ -100,7 +104,6 @@ export class Menu extends React.Component<MenuProps> {
{link.ariaLabel !== "0" && <div className="unread-count">{link.ariaLabel}</div>}
</Stack>
)
return ;
};
render() {

View File

@ -3,6 +3,7 @@ import { FeedContainer } from "../containers/feed-container"
import { AnimationClassNames, Icon } from "@fluentui/react"
import ArticleContainer from "../containers/article-container"
import { ViewType } from "../scripts/models/page"
import ArticleSearch from "./utils/article-search"
type PageProps = {
menuOn: boolean
@ -29,6 +30,7 @@ class Page extends React.Component<PageProps> {
<>
{this.props.settingsOn ? null :
<div className={"main" + (this.props.menuOn ? " menu-on" : "")}>
<ArticleSearch />
{this.props.feeds.map(fid => (
<FeedContainer viewType={this.props.viewType} feedId={fid} key={fid} />
))}
@ -48,6 +50,7 @@ class Page extends React.Component<PageProps> {
<>
{this.props.settingsOn ? null :
<div className={"list-main" + (this.props.menuOn ? " menu-on" : "")}>
<ArticleSearch />
<div className="list-feed-container">
{this.props.feeds.map(fid => (
<FeedContainer viewType={this.props.viewType} feedId={fid} key={fid} />

View File

@ -0,0 +1,63 @@
import * as React from "react"
import intl = require("react-intl-universal")
import { connect } from "react-redux"
import { RootState } from "../../scripts/reducer"
import { SearchBox, IRefObject, ISearchBox } from "@fluentui/react"
import { AppDispatch } from "../../scripts/utils"
import { performSearch } from "../../scripts/models/page"
class Debounced {
public use = (func: (...args: any[]) => any, delay: number): ((...args: any[]) => void) => {
let timer: NodeJS.Timeout
return (...args: any[]) => {
clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
}
}
type SearchProps = {
searchOn: boolean
initQuery: string
dispatch: AppDispatch
}
class ArticleSearch extends React.Component<SearchProps> {
debouncedSearch: (query: string) => void
inputRef: React.RefObject<ISearchBox>
constructor(props: SearchProps) {
super(props)
this.debouncedSearch = new Debounced().use((query: string) => props.dispatch(performSearch(query)), 750)
this.inputRef = React.createRef<ISearchBox>()
}
onSearchChange = (_, newValue: string) => {
this.debouncedSearch(newValue)
}
componentDidUpdate(prevProps: SearchProps) {
if (this.props.searchOn && !prevProps.searchOn) {
this.inputRef.current.focus()
}
}
render() {
return this.props.searchOn && (
<SearchBox
componentRef={this.inputRef}
className="article-search"
placeholder={intl.get("search")}
defaultValue={this.props.initQuery}
onChange={this.onSearchChange} />
)
}
}
const getSearchProps = (state: RootState) => ({
searchOn: state.page.searchOn,
initQuery: state.page.filter.search
})
export default connect(getSearchProps)(ArticleSearch)

View File

@ -6,7 +6,7 @@ import { ContextMenu } from "../components/context-menu"
import { RSSItem, markRead, markUnread, toggleStarred, toggleHidden, markAllRead } from "../scripts/models/item"
import { showItem, switchView, ViewType, switchFilter, toggleFilter } from "../scripts/models/page"
import { setDefaultView } from "../scripts/settings"
import { FeedFilter } from "../scripts/models/feed"
import { FilterType } from "../scripts/models/feed"
const getContext = (state: RootState) => state.app.contextMenu
const getViewType = (state: RootState) => state.page.viewType
@ -31,7 +31,7 @@ const mapStateToProps = createSelector(
type: context.type,
event: context.event,
viewType: viewType,
filter: filter
filter: filter.type
}
case ContextMenuType.Group: return {
type: context.type,
@ -60,8 +60,8 @@ const mapDispatchToProps = dispatch => {
setDefaultView(viewType)
dispatch(switchView(viewType))
},
switchFilter: (filter: FeedFilter) => dispatch(switchFilter(filter)),
toggleFilter: (filter: FeedFilter) => dispatch(toggleFilter(filter)),
switchFilter: (filter: FilterType) => dispatch(switchFilter(filter)),
toggleFilter: (filter: FilterType) => dispatch(toggleFilter(filter)),
markAllRead: (sids: number[]) => dispatch(markAllRead(sids)),
settings: () => dispatch(toggleSettings()),
close: () => dispatch(closeContextMenu())

View File

@ -4,22 +4,24 @@ import { RootState } from "../scripts/reducer"
import { Menu } from "../components/menu"
import { toggleMenu, openGroupMenu } from "../scripts/models/app"
import { SourceGroup, toggleGroupExpansion } from "../scripts/models/group"
import { selectAllArticles, selectSources } from "../scripts/models/page"
import { selectAllArticles, selectSources, toggleSearch } from "../scripts/models/page"
import { initFeeds } from "../scripts/models/feed"
import { RSSSource } from "../scripts/models/source"
const getApp = (state: RootState) => state.app
const getSources = (state: RootState) => state.sources
const getGroups = (state: RootState) => state.groups
const getSearchOn = (state: RootState) => state.page.searchOn
const mapStateToProps = createSelector(
[getApp, getSources, getGroups],
(app, sources, groups) => ({
[getApp, getSources, getGroups, getSearchOn],
(app, sources, groups, searchOn) => ({
status: app.sourceInit,
display: app.menu,
selected: app.menuKey,
sources: sources,
groups: groups
groups: groups,
searchOn: searchOn,
})
)
@ -45,7 +47,8 @@ const mapDispatchToProps = dispatch => ({
let [type, index] = key.split("-")
if (type === "g") dispatch(toggleGroupExpansion(parseInt(index)))
}
}
},
toggleSearch: () => dispatch(toggleSearch()),
})
const MenuContainer = connect(mapStateToProps, mapDispatchToProps)(Menu)

View File

@ -28,14 +28,15 @@ const mapDispatchToProps = (dispatch: AppDispatch) => {
},
deleteSource: (source: RSSSource) => dispatch(deleteSource(source)),
importOPML: () => {
let path = remote.dialog.showOpenDialogSync(
remote.dialog.showOpenDialog(
remote.getCurrentWindow(),
{
filters: [{ name: intl.get("sources.opmlFile"), extensions: ["xml", "opml"] }],
properties: ["openFile"]
}
)
if (path && path.length > 0) dispatch(importOPML(path[0]))
).then(result => {
if (!result.canceled && result.filePaths.length > 0) dispatch(importOPML(result.filePaths[0]))
})
},
exportOPML: () => {
remote.dialog.showSaveDialog(

View File

@ -26,7 +26,8 @@ function createWindow() {
show: false,
webPreferences: {
nodeIntegration: true,
webviewTag: true
webviewTag: true,
enableRemoteModule: true
}
})
mainWindowState.manage(mainWindow)

View File

@ -35,6 +35,7 @@
"subscriptions": "Subscriptions"
},
"article": {
"untitled": "(Untitled)",
"hide": "Hide article",
"unhide": "Unhide article",
"markRead": "Mark as read",
@ -56,6 +57,7 @@
"filter": "Filtering",
"unreadOnly": "Unread only",
"starredOnly": "Starred only",
"fullSearch": "Search in full text",
"showHidden": "Show hidden articles",
"manageSources": "Manage sources"
},
@ -72,6 +74,8 @@
"feedback": "Feedback"
},
"sources": {
"untitled": "Source",
"errorAdd": "An error has occured when adding the source.",
"errorImport": "Error importing {count, plural, =1 {# source} other {# sources}}.",
"opmlFile": "OPML File",
"name": "Source name",

View File

@ -35,6 +35,7 @@
"subscriptions": "订阅源"
},
"article": {
"untitled": "(无标题)",
"hide": "隐藏文章",
"unhide": "取消隐藏",
"markRead": "标为已读",
@ -56,6 +57,7 @@
"filter": "筛选",
"unreadOnly": "仅未读文章",
"starredOnly": "仅星标文章",
"fullSearch": "在正文中搜索",
"showHidden": "显示隐藏文章",
"manageSources": "管理订阅源"
},
@ -72,6 +74,8 @@
"feedback": "反馈"
},
"sources": {
"untitled": "订阅源",
"errorAdd": "添加订阅源时出错",
"errorImport": "导入{count}项订阅源时出错",
"opmlFile": "OPML文件",
"name": "订阅源名称",

View File

@ -4,34 +4,65 @@ import { ItemActionTypes, FETCH_ITEMS, RSSItem, MARK_READ, MARK_UNREAD, TOGGLE_S
import { ActionStatus, AppThunk } from "../utils"
import { PageActionTypes, SELECT_PAGE, PageType, APPLY_FILTER } from "./page"
export enum FeedFilter {
export enum FilterType {
None,
ShowRead = 1 << 0,
ShowNotStarred = 1 << 1,
ShowHidden = 1 << 2,
FullSearch = 1 << 3,
Default = ShowRead | ShowNotStarred,
UnreadOnly = ShowNotStarred,
StarredOnly = ShowRead
StarredOnly = ShowRead,
Toggles = ShowHidden | FullSearch
}
export namespace FeedFilter {
export function toQueryObject(filter: FeedFilter) {
export class FeedFilter {
type: FilterType
search: string
constructor(type=FilterType.Default, search="") {
this.type = type
this.search = search
}
static toQueryObject(filter: FeedFilter) {
let type = filter.type
let query = {
hasRead: false,
starred: true,
hidden: { $exists: 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 !== "") {
let regex = RegExp(filter.search)
if (type & FilterType.FullSearch) {
query.$or = [
{ title: { $regex: regex } },
{ snippet: { $regex: regex } }
]
} else {
query.title = { $regex: regex }
}
}
if (filter & FeedFilter.ShowRead) delete query.hasRead
if (filter & FeedFilter.ShowNotStarred) delete query.starred
if (filter & FeedFilter.ShowHidden) delete query.hidden
return query
}
export function testItem(filter: FeedFilter, item: RSSItem) {
static testItem(filter: FeedFilter, item: RSSItem) {
let type = filter.type
let flag = true
if (!(filter & FeedFilter.ShowRead)) flag = flag && !item.hasRead
if (!(filter & FeedFilter.ShowNotStarred)) flag = flag && item.starred
if (!(filter & FeedFilter.ShowHidden)) flag = flag && !item.hidden
if (!(type & FilterType.ShowRead)) flag = flag && !item.hasRead
if (!(type & FilterType.ShowNotStarred)) flag = flag && item.starred
if (!(type & FilterType.ShowHidden)) flag = flag && !item.hidden
if (filter.search !== "") {
let regex = RegExp(filter.search)
if (type & FilterType.FullSearch) {
flag = flag && (regex.test(item.title) || regex.test(item.snippet))
} else {
flag = flag && regex.test(item.title)
}
}
return Boolean(flag)
}
}
@ -50,13 +81,13 @@ export class RSSFeed {
iids: string[]
filter: FeedFilter
constructor (id: string = null, sids=[], filter=FeedFilter.Default) {
constructor (id: string = null, sids=[], filter=null) {
this._id = id
this.sids = sids
this.iids = []
this.loaded = false
this.allLoaded = false
this.filter = filter
this.filter = filter === null ? new FeedFilter() : filter
}
static loadFeed(feed: RSSFeed, init = false): Promise<RSSItem[]> {

View File

@ -1,4 +1,5 @@
import * as db from "../db"
import intl = require("react-intl-universal")
import { rssParser, domParser, htmlDecode, ActionStatus, AppThunk } from "../utils"
import { RSSSource } from "./source"
import { FeedActionTypes, INIT_FEED, LOAD_MORE, FeedFilter } from "./feed"
@ -15,14 +16,13 @@ export class RSSItem {
content: string
snippet: string
creator?: string
categories?: string[]
hasRead: boolean
starred?: true
hidden?: true
constructor (item: Parser.Item, source: RSSSource) {
this.source = source.sid
this.title = item.title || ""
this.title = item.title || intl.get("article.untitled")
this.link = item.link || ""
this.fetchedDate = new Date()
this.date = item.isoDate ? new Date(item.isoDate) : this.fetchedDate
@ -44,7 +44,6 @@ export class RSSItem {
this.snippet = htmlDecode(item.contentSnippet || "")
}
this.creator = item.creator
this.categories = item.categories
this.hasRead = false
}
}

View File

@ -1,8 +1,9 @@
import { ALL, SOURCE, loadMore, FeedFilter, initFeeds } from "./feed"
import { ALL, SOURCE, loadMore, FeedFilter, FilterType, initFeeds } from "./feed"
import { getWindowBreakpoint, AppThunk } from "../utils"
import { getDefaultView } from "../settings"
import { RSSItem, markRead } from "./item"
import { SourceActionTypes, DELETE_SOURCE } from "./source"
import { toggleMenu } from "./app"
export const SELECT_PAGE = "SELECT_PAGE"
export const SWITCH_VIEW = "SWITCH_VIEW"
@ -10,6 +11,7 @@ export const SHOW_ITEM = "SHOW_ITEM"
export const SHOW_OFFSET_ITEM = "SHOW_OFFSET_ITEM"
export const DISMISS_ITEM = "DISMISS_ITEM"
export const APPLY_FILTER = "APPLY_FILTER"
export const TOGGLE_SEARCH = "TOGGLE_SEARCH"
export enum PageType {
AllArticles, Sources, Page
@ -47,8 +49,10 @@ interface ApplyFilterAction {
}
interface DismissItemAction { type: typeof DISMISS_ITEM }
interface ToggleSearchAction { type: typeof TOGGLE_SEARCH }
export type PageActionTypes = SelectPageAction | SwitchViewAction | ShowItemAction | DismissItemAction | ApplyFilterAction
export type PageActionTypes = SelectPageAction | SwitchViewAction | ShowItemAction
| DismissItemAction | ApplyFilterAction | ToggleSearchAction
export function selectAllArticles(init = false): AppThunk {
return (dispatch, getState) => {
@ -96,6 +100,22 @@ export function showItem(feedId: string, item: RSSItem): PageActionTypes {
export const dismissItem = (): PageActionTypes => ({ type: DISMISS_ITEM })
export const toggleSearch = (): AppThunk => {
return (dispatch, getState) => {
let state = getState()
dispatch(({ type: TOGGLE_SEARCH }))
if (!getWindowBreakpoint()) {
dispatch(toggleMenu())
}
if (state.page.searchOn) {
dispatch(applyFilter({
...state.page.filter,
search: ""
}))
}
}
}
export function showOffsetItem(offset: number): AppThunk {
return (dispatch, getState) => {
let state = getState()
@ -150,28 +170,46 @@ function applyFilter(filter: FeedFilter): AppThunk {
}
}
export function switchFilter(filter: FeedFilter): AppThunk {
export function switchFilter(filter: FilterType): AppThunk {
return (dispatch, getState) => {
let oldFilter = getState().page.filter
let newFilter = filter | (oldFilter & FeedFilter.ShowHidden)
if (newFilter != oldFilter) {
dispatch(applyFilter(newFilter))
let oldType = oldFilter.type
let newType = filter | (oldType & FilterType.Toggles)
if (oldType != newType) {
dispatch(applyFilter({
...oldFilter,
type: newType
}))
}
}
}
export function toggleFilter(filter: FeedFilter): AppThunk {
export function toggleFilter(filter: FilterType): AppThunk {
return (dispatch, getState) => {
let oldFilter = getState().page.filter
dispatch(applyFilter(oldFilter ^ filter))
let nextFilter = { ...getState().page.filter }
nextFilter.type ^= filter
dispatch(applyFilter(nextFilter))
}
}
export function performSearch(query: string): AppThunk {
return (dispatch, getState) => {
let state = getState()
if (state.page.searchOn) {
dispatch(applyFilter({
...state.page.filter,
search: query
}))
}
}
}
export class PageState {
viewType = getDefaultView()
filter = FeedFilter.Default
filter = new FeedFilter()
feedId = ALL
itemId = null as string
searchOn = false
}
export function pageReducer(
@ -209,6 +247,10 @@ export function pageReducer(
...state,
itemId: null
}
case TOGGLE_SEARCH: return {
...state,
searchOn: !state.searchOn
}
default: return state
}
}

View File

@ -1,9 +1,11 @@
import Parser = require("@yang991178/rss-parser")
import intl = require("react-intl-universal")
import * as db from "../db"
import { rssParser, faviconPromise, ActionStatus, AppThunk } from "../utils"
import { RSSItem, insertItems, ItemActionTypes, FETCH_ITEMS, MARK_READ, MARK_UNREAD, MARK_ALL_READ } from "./item"
import { SourceGroup } from "./group"
import { saveSettings } from "./app"
import { remote } from "electron"
export enum SourceOpenTarget {
Local, Webpage, External
@ -14,7 +16,6 @@ export class RSSSource {
url: string
iconurl: string
name: string
description: string
openTarget: SourceOpenTarget
unreadCount: number
@ -26,8 +27,10 @@ export class RSSSource {
async fetchMetaData(parser: Parser) {
let feed = await parser.parseURL(this.url)
if (!this.name && feed.title) this.name = feed.title.trim()
this.description = feed.description
if (!this.name) {
if (feed.title) this.name = feed.title.trim()
this.name = this.name || intl.get("sources.untitled")
}
let domain = this.url.split("/").slice(0, 3).join("/")
let f: string = null
try {
@ -232,6 +235,9 @@ export function addSource(url: string, name: string = null, batch = false): AppT
.catch(e => {
console.log(e)
dispatch(addSourceFailure(e, batch))
if (!batch) {
remote.dialog.showErrorBox(intl.get("sources.errorAdd"), String(e))
}
return new Promise((_, reject) => { reject(e) })
})
}