lazily load feeds

This commit is contained in:
刘浩远 2020-07-01 11:38:25 +08:00
parent 59c5d663f1
commit c64a4593a6
14 changed files with 401 additions and 133 deletions

5
dist/styles.css vendored
View File

@ -635,6 +635,11 @@ body.darwin .list-main .article-search {
overflow: hidden scroll; overflow: hidden scroll;
margin-top: var(--navHeight); margin-top: var(--navHeight);
} }
.cards-feed-container .ms-List-page {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
}
.cards-feed-container > div.load-more-wrapper, .flex-fix { .cards-feed-container > div.load-more-wrapper, .flex-fix {
text-align: center; text-align: center;
} }

View File

@ -1,6 +1,8 @@
import { ipcRenderer } from "electron" import { ipcRenderer } from "electron"
const utilsBridge = { const utilsBridge = {
platform: process.platform,
getVersion: (): string => { getVersion: (): string => {
return ipcRenderer.sendSync("get-version") return ipcRenderer.sendSync("get-version")
}, },

View File

@ -129,7 +129,7 @@ class Article extends React.Component<ArticleProps, ArticleState> {
}) })
this.webview = webview this.webview = webview
webview.focus() webview.focus()
let card = document.querySelector(`#refocus>div[data-iid="${this.props.item._id}"]`) as HTMLElement let card = document.querySelector(`#refocus div[data-iid="${this.props.item._id}"]`) as HTMLElement
// @ts-ignore // @ts-ignore
if (card) card.scrollIntoViewIfNeeded() if (card) card.scrollIntoViewIfNeeded()
} }
@ -142,7 +142,7 @@ class Article extends React.Component<ArticleProps, ArticleState> {
} }
componentWillUnmount = () => { componentWillUnmount = () => {
let refocus = document.querySelector(`#refocus>div[data-iid="${this.props.item._id}"]`) as HTMLElement let refocus = document.querySelector(`#refocus div[data-iid="${this.props.item._id}"]`) as HTMLElement
if (refocus) refocus.focus() if (refocus) refocus.focus()
} }

View File

@ -2,7 +2,8 @@ import * as React from "react"
import { RSSSource, SourceOpenTarget } from "../../scripts/models/source" import { RSSSource, SourceOpenTarget } from "../../scripts/models/source"
import { RSSItem } from "../../scripts/models/item" import { RSSItem } from "../../scripts/models/item"
export interface CardProps { export namespace Card {
export type Props = {
feedId: string feedId: string
item: RSSItem item: RSSItem
source: RSSSource source: RSSSource
@ -12,42 +13,41 @@ export interface CardProps {
showItem: (fid: string, item: RSSItem) => void showItem: (fid: string, item: RSSItem) => void
} }
export class Card extends React.Component<CardProps> { export const openInBrowser = (props: Props) => {
openInBrowser = () => { props.markRead(props.item)
this.props.markRead(this.props.item) window.utils.openExternal(props.item.link)
window.utils.openExternal(this.props.item.link)
} }
onClick = (e: React.MouseEvent) => { export const onClick = (props: Props, e: React.MouseEvent) => {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
switch (this.props.source.openTarget) { switch (props.source.openTarget) {
case SourceOpenTarget.Local: case SourceOpenTarget.Local:
case SourceOpenTarget.Webpage: { case SourceOpenTarget.Webpage: {
this.props.markRead(this.props.item) props.markRead(props.item)
this.props.showItem(this.props.feedId, this.props.item) props.showItem(props.feedId, props.item)
break break
} }
case SourceOpenTarget.External: { case SourceOpenTarget.External: {
this.openInBrowser() openInBrowser(props)
break break
} }
} }
} }
onMouseUp = (e: React.MouseEvent) => { export const onMouseUp = (props: Props, e: React.MouseEvent) => {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
switch (e.button) { switch (e.button) {
case 1: case 1:
this.openInBrowser() openInBrowser(props)
break break
case 2: case 2:
this.props.contextMenu(this.props.feedId, this.props.item, e) props.contextMenu(props.feedId, props.item, e)
} }
} }
onKeyDown = (e: React.KeyboardEvent) => { export const onKeyDown = (props: Props, e: React.KeyboardEvent) => {
this.props.shortcuts(this.props.item, e.key) props.shortcuts(props.item, e.key)
} }
} }

View File

@ -1,38 +1,33 @@
import * as React from "react" import * as React from "react"
import { Card } from "./card" import { Card } from "./card"
import { AnimationClassNames } from "@fluentui/react"
import CardInfo from "./info" import CardInfo from "./info"
class DefaultCard extends Card { const className = (props: Card.Props) => {
className = () => { let cn = ["card"]
let cn = ["card", AnimationClassNames.slideUpIn10] if (props.item.snippet && props.item.thumb) cn.push("transform")
if (this.props.item.snippet && this.props.item.thumb) cn.push("transform") if (props.item.hidden) cn.push("hidden")
if (this.props.item.hidden) cn.push("hidden")
return cn.join(" ") return cn.join(" ")
} }
render() { const DefaultCard: React.FunctionComponent<Card.Props> = (props) => (
return (
<div <div
className={this.className()} className={className(props)}
onClick={this.onClick} onClick={e => Card.onClick(props, e)}
onMouseUp={this.onMouseUp} onMouseUp={e => Card.onMouseUp(props, e)}
onKeyDown={this.onKeyDown} onKeyDown={e => Card.onKeyDown(props, e)}
data-iid={this.props.item._id} data-iid={props.item._id}
data-is-focusable> data-is-focusable>
{this.props.item.thumb ? ( {props.item.thumb ? (
<img className="bg" src={this.props.item.thumb} /> <img className="bg" src={props.item.thumb} />
) : null} ) : null}
<div className="bg"></div> <div className="bg"></div>
{this.props.item.thumb ? ( {props.item.thumb ? (
<img className="head" src={this.props.item.thumb} /> <img className="head" src={props.item.thumb} />
) : null} ) : null}
<CardInfo source={this.props.source} item={this.props.item} /> <CardInfo source={props.source} item={props.item} />
<h3 className="title">{this.props.item.title}</h3> <h3 className="title">{props.item.title}</h3>
<p className={"snippet"+(this.props.item.thumb?"":" show")}>{this.props.item.snippet.slice(0, 325)}</p> <p className={"snippet" + (props.item.thumb ? "" : " show")}>{props.item.snippet.slice(0, 325)}</p>
</div> </div>
) )
}
}
export default DefaultCard export default DefaultCard

View File

@ -8,7 +8,7 @@ type CardInfoProps = {
item: RSSItem item: RSSItem
} }
const CardInfo = (props: CardInfoProps) => ( const CardInfo: React.FunctionComponent<CardInfoProps> = (props) => (
<p className="info"> <p className="info">
{props.source.iconurl ? <img src={props.source.iconurl} /> : null} {props.source.iconurl ? <img src={props.source.iconurl} /> : null}
<span className="name">{props.source.name}</span> <span className="name">{props.source.name}</span>

View File

@ -1,34 +1,29 @@
import * as React from "react" import * as React from "react"
import { Card } from "./card" import { Card } from "./card"
import { AnimationClassNames } from "@fluentui/react"
import CardInfo from "./info" import CardInfo from "./info"
class ListCard extends Card { const className = (props: Card.Props) => {
className = () => { let cn = ["list-card"]
let cn = ["list-card", AnimationClassNames.slideUpIn10] if (props.item.hidden) cn.push("hidden")
if (this.props.item.hidden) cn.push("hidden")
return cn.join(" ") return cn.join(" ")
} }
render() { const ListCard: React.FunctionComponent<Card.Props> = (props) => (
return (
<div <div
className={this.className()} className={className(props)}
onClick={this.onClick} onClick={e => Card.onClick(props, e)}
onMouseUp={this.onMouseUp} onMouseUp={e => Card.onMouseUp(props, e)}
onKeyDown={this.onKeyDown} onKeyDown={e => Card.onKeyDown(props, e)}
data-iid={this.props.item._id} data-iid={props.item._id}
data-is-focusable> data-is-focusable>
{this.props.item.thumb ? ( {props.item.thumb ? (
<div className="head"><img src={this.props.item.thumb} /></div> <div className="head"><img src={props.item.thumb} /></div>
) : null} ) : null}
<div className="data"> <div className="data">
<CardInfo source={this.props.source} item={this.props.item} /> <CardInfo source={props.source} item={props.item} />
<h3 className="title">{this.props.item.title}</h3> <h3 className="title">{props.item.title}</h3>
</div> </div>
</div> </div>
) )
}
}
export default ListCard export default ListCard

View File

@ -3,37 +3,45 @@ import intl from "react-intl-universal"
import { FeedProps } from "./feed" import { FeedProps } from "./feed"
import DefaultCard from "../cards/default-card" import DefaultCard from "../cards/default-card"
import { PrimaryButton, FocusZone } from 'office-ui-fabric-react'; import { PrimaryButton, FocusZone } from 'office-ui-fabric-react';
import { RSSItem } from "../../scripts/models/item";
import { List, AnimationClassNames } from "@fluentui/react";
class CardsFeed extends React.Component<FeedProps> { class CardsFeed extends React.Component<FeedProps> {
state = { width: window.innerWidth - 12 } observer: ResizeObserver
state = { width: window.innerWidth, height: window.innerHeight }
updateWidth = () => { updateWindowSize = (entries: ResizeObserverEntry[]) => {
this.setState({ width: window.innerWidth - 12 }); if (entries) {
this.setState({ width: entries[0].contentRect.width - 40, height: window.innerHeight })
}
}; };
componentDidMount() { componentDidMount() {
window.addEventListener('resize', this.updateWidth); this.setState({ width: document.querySelector(".main").clientWidth - 40 })
this.observer = new ResizeObserver(this.updateWindowSize)
this.observer.observe(document.querySelector(".main"))
} }
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener('resize', this.updateWidth); this.observer.disconnect()
} }
flexFix = () => { getItemCountForPage = () => {
let elemPerRow = Math.floor(this.state.width / 280) let elemPerRow = Math.floor(this.state.width / 280)
//let elemLastRow = this.props.items.length % elemPerRow let rows = Math.ceil(this.state.height / 304)
let fixes = new Array<JSX.Element>() return elemPerRow * rows
for (let i = 0; i < elemPerRow; i += 1) {
fixes.push(<div className="flex-fix" key={"f-"+i}></div>)
} }
return fixes getPageHeight = () => {
return this.state.height + (304 - this.state.height % 304)
} }
render() { flexFixItems = () => {
return this.props.feed.loaded && ( let elemPerRow = Math.floor(this.state.width / 280)
<FocusZone as="div" id="refocus" className="cards-feed-container"> let elemLastRow = this.props.items.length % elemPerRow
{ let items = [ ...this.props.items ]
this.props.items.map((item) => ( for (let i = 0; i < (elemPerRow - elemLastRow); i += 1) items.push(null)
return items
}
onRenderItem = (item: RSSItem, index: number) => item ? (
<DefaultCard <DefaultCard
feedId={this.props.feed._id} feedId={this.props.feed._id}
key={item._id} key={item._id}
@ -43,9 +51,18 @@ class CardsFeed extends React.Component<FeedProps> {
markRead={this.props.markRead} markRead={this.props.markRead}
contextMenu={this.props.contextMenu} contextMenu={this.props.contextMenu}
showItem={this.props.showItem} /> showItem={this.props.showItem} />
)) ) : (<div className="flex-fix" key={"f-"+index}></div>)
}
{ this.flexFix() } render() {
return this.props.feed.loaded && (
<FocusZone as="div" id="refocus" className="cards-feed-container" data-is-scrollable>
<List
className={AnimationClassNames.slideUpIn10}
items={this.flexFixItems()}
onRenderCell={this.onRenderItem}
getItemCountForPage={this.getItemCountForPage}
getPageHeight={this.getPageHeight}
usePageCache />
{ {
(this.props.feed.loaded && !this.props.feed.allLoaded) (this.props.feed.loaded && !this.props.feed.allLoaded)
? <div className="load-more-wrapper"><PrimaryButton ? <div className="load-more-wrapper"><PrimaryButton

View File

@ -1,15 +1,13 @@
import * as React from "react" import * as React from "react"
import intl from "react-intl-universal" import intl from "react-intl-universal"
import { FeedProps } from "./feed" import { FeedProps } from "./feed"
import { DefaultButton, FocusZone, FocusZoneDirection } from 'office-ui-fabric-react'; import { DefaultButton, FocusZone, FocusZoneDirection, List } from 'office-ui-fabric-react';
import ListCard from "../cards/list-card"; import ListCard from "../cards/list-card";
import { RSSItem } from "../../scripts/models/item";
import { AnimationClassNames } from "@fluentui/react";
class ListFeed extends React.Component<FeedProps> { class ListFeed extends React.Component<FeedProps> {
render() { onRenderItem = (item: RSSItem) => (
return this.props.feed.loaded && (
<FocusZone as="div" id="refocus" direction={FocusZoneDirection.vertical} className="list-feed">
{
this.props.items.map((item) => (
<ListCard <ListCard
feedId={this.props.feed._id} feedId={this.props.feed._id}
key={item._id} key={item._id}
@ -19,8 +17,16 @@ class ListFeed extends React.Component<FeedProps> {
markRead={this.props.markRead} markRead={this.props.markRead}
contextMenu={this.props.contextMenu} contextMenu={this.props.contextMenu}
showItem={this.props.showItem} /> showItem={this.props.showItem} />
)) )
}
render() {
return this.props.feed.loaded && (
<FocusZone as="div" id="refocus" direction={FocusZoneDirection.vertical} className="list-feed" data-is-scrollable>
<List
className={AnimationClassNames.slideUpIn10}
items={this.props.items}
onRenderCell={this.onRenderItem}
usePageCache />
{ {
(this.props.feed.loaded && !this.props.feed.allLoaded) (this.props.feed.loaded && !this.props.feed.allLoaded)
? <div className="load-more-wrapper"><DefaultButton ? <div className="load-more-wrapper"><DefaultButton

View File

@ -120,7 +120,7 @@ export class Menu extends React.Component<MenuProps> {
<div className="btn-group"> <div className="btn-group">
<a className="btn hide-wide" title={intl.get("menu.close")} onClick={this.props.toggleMenu}><Icon iconName="Back" /></a> <a className="btn hide-wide" title={intl.get("menu.close")} onClick={this.props.toggleMenu}><Icon iconName="Back" /></a>
<a className="btn inline-block-wide" title={intl.get("menu.close")} onClick={this.props.toggleMenu}> <a className="btn inline-block-wide" title={intl.get("menu.close")} onClick={this.props.toggleMenu}>
<Icon iconName={process.platform === "darwin" ? "SidePanel" : "GlobalNavButton"} /> <Icon iconName={window.utils.platform === "darwin" ? "SidePanel" : "GlobalNavButton"} />
</a> </a>
</div> </div>
<div className="nav-wrapper"> <div className="nav-wrapper">

View File

@ -111,7 +111,7 @@ class Nav extends React.Component<NavProps, NavState> {
<a className="btn hide-wide" <a className="btn hide-wide"
title={intl.get("nav.menu")} title={intl.get("nav.menu")}
onClick={this.props.menu}> onClick={this.props.menu}>
<Icon iconName={process.platform === "darwin" ? "SidePanel" : "GlobalNavButton"} /> <Icon iconName={window.utils.platform === "darwin" ? "SidePanel" : "GlobalNavButton"} />
</a> </a>
</div> </div>
<span className="title">{this.props.state.title}</span> <span className="title">{this.props.state.title}</span>

242
src/components/utils/ResizeObserver.d.ts vendored Normal file
View File

@ -0,0 +1,242 @@
/**
* The **ResizeObserver** interface reports changes to the dimensions of an
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element)'s content
* or border box, or the bounding box of an
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement).
*
* > **Note**: The content box is the box in which content can be placed,
* > meaning the border box minus the padding and border width. The border box
* > encompasses the content, padding, and border. See
* > [The box model](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/The_box_model)
* > for further explanation.
*
* `ResizeObserver` avoids infinite callback loops and cyclic dependencies that
* are often created when resizing via a callback function. It does this by only
* processing elements deeper in the DOM in subsequent frames. Implementations
* should, if they follow the specification, invoke resize events before paint
* and after layout.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
*/
declare class ResizeObserver {
/**
* The **ResizeObserver** constructor creates a new `ResizeObserver` object,
* which can be used to report changes to the content or border box of an
* `Element` or the bounding box of an `SVGElement`.
*
* @example
* var ResizeObserver = new ResizeObserver(callback)
*
* @param callback
* The function called whenever an observed resize occurs. The function is
* called with two parameters:
* * **entries**
* An array of
* [ResizeObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry)
* objects that can be used to access the new dimensions of the element
* after each change.
* * **observer**
* A reference to the `ResizeObserver` itself, so it will definitely be
* accessible from inside the callback, should you need it. This could be
* used for example to automatically unobserve the observer when a certain
* condition is reached, but you can omit it if you don't need it.
*
* The callback will generally follow a pattern along the lines of:
* ```js
* function(entries, observer) {
* for (let entry of entries) {
* // Do something to each entry
* // and possibly something to the observer itself
* }
* }
* ```
*
* The following snippet is taken from the
* [resize-observer-text.html](https://mdn.github.io/dom-examples/resize-observer/resize-observer-text.html)
* ([see source](https://github.com/mdn/dom-examples/blob/master/resize-observer/resize-observer-text.html))
* example:
* @example
* const resizeObserver = new ResizeObserver(entries => {
* for (let entry of entries) {
* if(entry.contentBoxSize) {
* h1Elem.style.fontSize = Math.max(1.5, entry.contentBoxSize.inlineSize/200) + 'rem';
* pElem.style.fontSize = Math.max(1, entry.contentBoxSize.inlineSize/600) + 'rem';
* } else {
* h1Elem.style.fontSize = Math.max(1.5, entry.contentRect.width/200) + 'rem';
* pElem.style.fontSize = Math.max(1, entry.contentRect.width/600) + 'rem';
* }
* }
* });
*
* resizeObserver.observe(divElem);
*/
constructor(callback: ResizeObserverCallback);
/**
* The **disconnect()** method of the
* [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
* interface unobserves all observed
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement)
* targets.
*/
disconnect: () => void;
/**
* The `observe()` method of the
* [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
* interface starts observing the specified
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement).
*
* @example
* resizeObserver.observe(target, options);
*
* @param target
* A reference to an
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement)
* to be observed.
*
* @param options
* An options object allowing you to set options for the observation.
* Currently this only has one possible option that can be set.
*/
observe: (target: Element, options?: ResizeObserverObserveOptions) => void;
/**
* The **unobserve()** method of the
* [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
* interface ends the observing of a specified
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement).
*/
unobserve: (target: Element) => void;
}
interface ResizeObserverObserveOptions {
/**
* Sets which box model the observer will observe changes to. Possible values
* are `content-box` (the default), and `border-box`.
*
* @default "content-box"
*/
box?: "content-box" | "border-box";
}
/**
* The function called whenever an observed resize occurs. The function is
* called with two parameters:
*
* @param entries
* An array of
* [ResizeObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry)
* objects that can be used to access the new dimensions of the element after
* each change.
*
* @param observer
* A reference to the `ResizeObserver` itself, so it will definitely be
* accessible from inside the callback, should you need it. This could be used
* for example to automatically unobserve the observer when a certain condition
* is reached, but you can omit it if you don't need it.
*
* The callback will generally follow a pattern along the lines of:
* @example
* function(entries, observer) {
* for (let entry of entries) {
* // Do something to each entry
* // and possibly something to the observer itself
* }
* }
*
* @example
* const resizeObserver = new ResizeObserver(entries => {
* for (let entry of entries) {
* if(entry.contentBoxSize) {
* h1Elem.style.fontSize = Math.max(1.5, entry.contentBoxSize.inlineSize/200) + 'rem';
* pElem.style.fontSize = Math.max(1, entry.contentBoxSize.inlineSize/600) + 'rem';
* } else {
* h1Elem.style.fontSize = Math.max(1.5, entry.contentRect.width/200) + 'rem';
* pElem.style.fontSize = Math.max(1, entry.contentRect.width/600) + 'rem';
* }
* }
* });
*
* resizeObserver.observe(divElem);
*/
type ResizeObserverCallback = (
entries: ResizeObserverEntry[],
observer: ResizeObserver,
) => void;
/**
* The **ResizeObserverEntry** interface represents the object passed to the
* [ResizeObserver()](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver)
* constructor's callback function, which allows you to access the new
* dimensions of the
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement)
* being observed.
*/
interface ResizeObserverEntry {
/**
* An object containing the new border box size of the observed element when
* the callback is run.
*/
readonly borderBoxSize: ResizeObserverEntryBoxSize;
/**
* An object containing the new content box size of the observed element when
* the callback is run.
*/
readonly contentBoxSize: ResizeObserverEntryBoxSize;
/**
* A [DOMRectReadOnly](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly)
* object containing the new size of the observed element when the callback is
* run. Note that this is better supported than the above two properties, but
* it is left over from an earlier implementation of the Resize Observer API,
* is still included in the spec for web compat reasons, and may be deprecated
* in future versions.
*/
// node_modules/typescript/lib/lib.dom.d.ts
readonly contentRect: DOMRectReadOnly;
/**
* A reference to the
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement)
* being observed.
*/
readonly target: Element;
}
/**
* The **borderBoxSize** read-only property of the
* [ResizeObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry)
* interface returns an object containing the new border box size of the
* observed element when the callback is run.
*/
interface ResizeObserverEntryBoxSize {
/**
* The length of the observed element's border box in the block dimension. For
* boxes with a horizontal
* [writing-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode),
* this is the vertical dimension, or height; if the writing-mode is vertical,
* this is the horizontal dimension, or width.
*/
blockSize: number;
/**
* The length of the observed element's border box in the inline dimension.
* For boxes with a horizontal
* [writing-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode),
* this is the horizontal dimension, or width; if the writing-mode is
* vertical, this is the vertical dimension, or height.
*/
inlineSize: number;
}
interface Window {
ResizeObserver: typeof ResizeObserver;
}

View File

@ -1,5 +1,5 @@
import intl from "react-intl-universal" import intl from "react-intl-universal"
import { RSSSource, INIT_SOURCES, SourceActionTypes, ADD_SOURCE, UPDATE_SOURCE, DELETE_SOURCE, initSources } from "./source" import { INIT_SOURCES, SourceActionTypes, ADD_SOURCE, UPDATE_SOURCE, DELETE_SOURCE, initSources } from "./source"
import { RSSItem, ItemActionTypes, FETCH_ITEMS, fetchItems } from "./item" import { RSSItem, ItemActionTypes, FETCH_ITEMS, fetchItems } from "./item"
import { ActionStatus, AppThunk, getWindowBreakpoint } from "../utils" import { ActionStatus, AppThunk, getWindowBreakpoint } from "../utils"
import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds } from "./feed" import { INIT_FEEDS, FeedActionTypes, ALL, initFeeds } from "./feed"
@ -199,7 +199,7 @@ export function initIntl(): AppThunk<Promise<void>> {
export function initApp(): AppThunk { export function initApp(): AppThunk {
return (dispatch) => { return (dispatch) => {
document.body.classList.add(process.platform) document.body.classList.add(window.utils.platform)
dispatch(initIntl()).then(() => dispatch(initIntl()).then(() =>
dispatch(initSources()) dispatch(initSources())
).then(() => ).then(() =>

View File

@ -39,7 +39,13 @@ export class RSSItem {
item.snippet = htmlDecode(parsed.contentSnippet || "") item.snippet = htmlDecode(parsed.contentSnippet || "")
} }
if (parsed.thumb) item.thumb = parsed.thumb if (parsed.thumb) item.thumb = parsed.thumb
else if (parsed.image) item.thumb = parsed.image else if (parsed.image) {
if (parsed.image.$ && parsed.image.$.url) {
item.thumb = parsed.image.$.url
} else if (typeof parsed.image === "string") {
item.thumb = parsed.image
}
}
else if (parsed.mediaContent) { else if (parsed.mediaContent) {
let images = parsed.mediaContent.filter(c => c.$ && c.$.medium === "image" && c.$.url) let images = parsed.mediaContent.filter(c => c.$ && c.$.medium === "image" && c.$.url)
if (images.length > 0) item.thumb = images[0].$.url if (images.length > 0) item.thumb = images[0].$.url