diff --git a/eslint-problems b/eslint-problems index b0598afe68..535ca222ce 100644 --- a/eslint-problems +++ b/eslint-problems @@ -134,23 +134,6 @@ ./source/views/menus/types.ts 35:29 warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any -./source/views/news/news-container.tsx - 28:2 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types - 34:14 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types - 59:2 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types - -./source/views/news/news-list.tsx - 22:19 warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any - 29:16 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types - 33:20 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types - 39:15 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types - 47:17 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types - 49:2 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types - -./source/views/news/news-row.tsx - 14:13 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types - 22:2 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types - ./source/views/settings/components/logo.tsx 26:2 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types 32:2 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types @@ -307,5 +290,5 @@ ./source/views/student-orgs/util.ts 3:8 warning Missing return type on function @typescript-eslint/explicit-module-boundary-types -✖ 192 problems (0 errors, 192 warnings) +✖ 181 problems (0 errors, 181 warnings) diff --git a/images/news-sources/index.ts b/images/news-sources/index.ts index 93f5561f91..4c9df5f034 100644 --- a/images/news-sources/index.ts +++ b/images/news-sources/index.ts @@ -1 +1,5 @@ export {ksto} from '../streaming' + +export * as mess from './mess.png' +export * as oleville from './oleville.png' +export * as stolaf from './stolaf.png' diff --git a/source/navigation/routes.tsx b/source/navigation/routes.tsx index 8654c69ba9..bd128ec43a 100644 --- a/source/navigation/routes.tsx +++ b/source/navigation/routes.tsx @@ -29,7 +29,7 @@ import { MenuItemDetailView, DetailNavigationOptions, } from '@frogpond/food-menu/food-item-detail' -// import NewsView from '../views/news' +import * as news from '../views/news' import * as settings from '../views/settings/' // import SISView from '../views/sis' import * as streaming from '../views/streaming' @@ -214,10 +214,16 @@ export function RootStack(): JSX.Element { options={orgs.DetailNavigationOptions} /> + + + {/* - ( - () + +const StOlafNewsView = () => ( + +) +const MessNewsView = () => ( + +) +const OlevilleNewsView = () => ( + +) + +const NewsView = (): JSX.Element => { + return ( + + - ), - navigationOptions: { - tabBarLabel: 'St. Olaf', - tabBarIcon: TabBarIcon('school'), - }, - }, - - OlevilleNewsView: { - screen: ({navigation}) => ( - - ), - navigationOptions: { - tabBarLabel: 'Oleville', - tabBarIcon: TabBarIcon('happy'), - }, - }, - - MessNewsView: { - screen: ({navigation}) => ( - - ), - navigationOptions: { - tabBarLabel: 'The Mess', - tabBarIcon: TabBarIcon('paper'), - }, - }, -}) -NewsView.navigationOptions = { - title: 'News', + + ) } -export default NewsView +export {NewsView as View} + +export const NavigationOptions: NativeStackNavigationOptions = { + title: 'News', +} diff --git a/source/views/news/news-container.tsx b/source/views/news/news-container.tsx deleted file mode 100644 index 6f08562b86..0000000000 --- a/source/views/news/news-container.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import * as React from 'react' -import type {StoryType} from './types' -import {LoadingView} from '@frogpond/notice' -import type {TopLevelViewPropsType} from '../types' -import {NewsList} from './news-list' -import {fetch} from '@frogpond/fetch' -import {API} from '@frogpond/api' - -type Props = TopLevelViewPropsType & { - source: string | {url: string; type: 'rss' | 'wp-json'} - thumbnail: false | number - title: string -} - -type State = { - entries: StoryType[] - initialLoadComplete: boolean - refreshing: boolean -} - -export default class NewsContainer extends React.PureComponent { - state = { - entries: [], - initialLoadComplete: false, - refreshing: false, - } - - componentDidMount() { - this.fetchData().then(() => { - this.setState(() => ({initialLoadComplete: true})) - }) - } - - fetchData = async (reload?: boolean) => { - let url - if (typeof this.props.source === 'string') { - url = API(`/news/named/${this.props.source}`) - } else if (this.props.source.type === 'rss') { - url = API('/news/rss', {url: this.props.source.url}) - } else if (this.props.source.type === 'wp-json') { - url = API('/news/wpjson', {url: this.props.source.url}) - } else { - throw new Error('invalid news source type!') - } - - let entries: Array = await fetch(url, { - delay: reload ? 500 : 0, - }).json() - - this.setState(() => ({entries})) - } - - refresh = async (): Promise => { - this.setState(() => ({refreshing: true})) - await this.fetchData(true) - this.setState(() => ({refreshing: false})) - } - - render() { - if (!this.state.initialLoadComplete) { - return - } - - return ( - - ) - } -} diff --git a/source/views/news/news-list.tsx b/source/views/news/news-list.tsx index 588f2b2231..cd0f395bb5 100644 --- a/source/views/news/news-list.tsx +++ b/source/views/news/news-list.tsx @@ -1,12 +1,18 @@ import * as React from 'react' -import {StyleSheet, FlatList} from 'react-native' -import * as c from '@frogpond/colors' +import {FlatList, StyleSheet} from 'react-native' import type {StoryType} from './types' +import {API} from '@frogpond/api' +import * as c from '@frogpond/colors' +import {useFetch} from 'react-async' import {ListSeparator} from '@frogpond/lists' -import {NoticeView} from '@frogpond/notice' -import type {TopLevelViewPropsType} from '../types' -import {NewsRow} from './news-row' +import {LoadingView, NoticeView} from '@frogpond/notice' import {openUrl} from '@frogpond/open-url' +import {NewsRow} from './news-row' + +type Props = { + source: string | {url: string; type: 'rss' | 'wp-json'} + thumbnail: false | number +} const styles = StyleSheet.create({ listContainer: { @@ -17,54 +23,72 @@ const styles = StyleSheet.create({ }, }) -type Props = TopLevelViewPropsType & { - name: string - onRefresh: () => any - entries: StoryType[] - loading: boolean - thumbnail: false | number -} - -export class NewsList extends React.PureComponent { - onPressNews = (url: string) => { - return openUrl(url) +const useNews = (source: Props['source']) => { + let url + if (typeof source === 'string') { + url = API(`/news/named/${source}`) + } else if (source.type === 'rss') { + url = API('/news/rss', {url: source.url}) + } else if (source.type === 'wp-json') { + url = API('/news/wpjson', {url: source.url}) + } else { + throw new Error('invalid news source type!') } - renderSeparator = () => ( - - ) - - renderItem = ({item}: {item: StoryType}) => ( - - ) + return useFetch(url, { + headers: {accept: 'application/json'}, + }) +} - keyExtractor = (item: StoryType) => item.title +export const NewsList = (props: Props): JSX.Element => { + let { + data = [], + error, + reload, + isPending, + isInitial, + isLoading, + } = useNews(props.source) - render() { - // remove all entries with blank excerpts - // remove all entries with a
entry.excerpt.trim() !== '') - .filter((entry) => !entry.content.includes(' entry.excerpt.trim() !== '') + .filter((entry) => !entry.content.includes('} - contentContainerStyle={styles.contentContainer} - data={entries} - keyExtractor={this.keyExtractor} - onRefresh={this.props.onRefresh} - refreshing={this.props.loading} - renderItem={this.renderItem} - style={styles.listContainer} + ) } + + return ( + ( + + )} + ListEmptyComponent={ + isLoading ? : + } + contentContainerStyle={styles.contentContainer} + data={entries} + keyExtractor={(item: StoryType) => item.title} + onRefresh={reload} + refreshing={isPending && !isInitial} + renderItem={({item}: {item: StoryType}) => ( + openUrl(url)} + story={item} + thumbnail={props.thumbnail} + /> + )} + style={styles.listContainer} + /> + ) } diff --git a/source/views/news/news-row.tsx b/source/views/news/news-row.tsx index 94904428cc..a734c6843b 100644 --- a/source/views/news/news-row.tsx +++ b/source/views/news/news-row.tsx @@ -10,42 +10,41 @@ type Props = { thumbnail: false | number } -export class NewsRow extends React.PureComponent { - _onPress = () => { - if (!this.props.story.link) { +export const NewsRow = (props: Props): JSX.Element => { + let _onPress = () => { + if (!props.story.link) { Alert.alert('There is nowhere to go for this story') return } - this.props.onPress(this.props.story.link) + props.onPress(props.story.link) } - render() { - let {story} = this.props - let thumb = - this.props.thumbnail !== false - ? story.featuredImage - ? {uri: story.featuredImage} - : this.props.thumbnail - : null + let {story} = props - return ( - - - {thumb !== null ? ( - - ) : null} - - {story.title} - {story.excerpt} - - - - ) - } + let thumb = + props.thumbnail !== false + ? story.featuredImage + ? {uri: story.featuredImage} + : props.thumbnail + : null + + return ( + + + {thumb !== null ? ( + + ) : null} + + {story.title} + {story.excerpt} + + + + ) } const styles = StyleSheet.create({ diff --git a/tsc-counts b/tsc-counts index e492f91aac..3ec226576a 100644 --- a/tsc-counts +++ b/tsc-counts @@ -1,13 +1,13 @@ 18 TS2740 16 TS2345 13 TS2322 -8 TS7031 7 TS2769 -7 TS2339 6 TS2307 +5 TS7031 5 TS7016 -4 TS2571 +4 TS2339 3 TS2739 +3 TS2571 2 TS7006 2 TS2783 2 TS2613 diff --git a/tsc-errors b/tsc-errors index 07e294ee5e..b296d140d9 100644 --- a/tsc-errors +++ b/tsc-errors @@ -86,13 +86,6 @@ source/views/menus/lib/process-menu-shorthands.ts(31,3): error TS2740: Type '{}' source/views/menus/lib/process-menu-shorthands.ts(35,3): error TS2783: 'special' is specified more than once, so this usage will be overwritten. source/views/menus/menu-github.tsx(105,4): error TS2322: Type '{ foodItems: MenuItemContainerType; meals: ProcessedMealType[]; menuCorIcons: MasterCorIconMapType; name: string; navigation: NavigationScreenProp<...>; now: Moment; }' is not assignable to type 'IntrinsicAttributes & ReactProps'. Property 'navigation' does not exist on type 'IntrinsicAttributes & ReactProps'. -source/views/news/index.tsx(9,13): error TS7031: Binding element 'navigation' implicitly has an 'any' type. -source/views/news/index.tsx(13,27): error TS2339: Property 'stolaf' does not exist on type 'typeof import("./images/news-sources/index")'. -source/views/news/index.tsx(24,13): error TS7031: Binding element 'navigation' implicitly has an 'any' type. -source/views/news/index.tsx(28,27): error TS2339: Property 'oleville' does not exist on type 'typeof import("./images/news-sources/index")'. -source/views/news/index.tsx(39,13): error TS7031: Binding element 'navigation' implicitly has an 'any' type. -source/views/news/index.tsx(43,27): error TS2339: Property 'mess' does not exist on type 'typeof import("./images/news-sources/index")'. -source/views/news/index.tsx(53,1): error TS2571: Object is of type 'unknown'. source/views/settings/components/logo.tsx(2,24): error TS7016: Could not find a declaration file for module '@hawkrives/react-native-alternate-icons'. './node_modules/@hawkrives/react-native-alternate-icons/index.js' implicitly has an 'any' type. Try `npm i --save-dev @types/hawkrives__react-native-alternate-icons` if it exists or add a new declaration (.d.ts) file containing `declare module '@hawkrives/react-native-alternate-icons';` source/views/settings/components/logo.tsx(27,29): error TS7006: Parameter 'name' implicitly has an 'any' type.