diff --git a/dialpods/App.tsx b/dialpods/App.tsx index 125fe1b..1df26c6 100644 --- a/dialpods/App.tsx +++ b/dialpods/App.tsx @@ -5,6 +5,7 @@ * @format */ +import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; import React from 'react'; import type {PropsWithChildren} from 'react'; import { @@ -25,6 +26,14 @@ import { ReloadInstructions, } from 'react-native/Libraries/NewAppScreen'; +import McIcon from 'react-native-vector-icons/MaterialCommunityIcons'; +import Fa6 from 'react-native-vector-icons/FontAwesome6'; +import {NavigationContainer} from '@react-navigation/native'; + +// pages +import SearchPage from './pages/Search.tsx'; +// ; @@ -63,38 +72,51 @@ function App(): React.JSX.Element { }; return ( - - - -
- -
- Edit App.tsx to change this - screen and then come back to see your edits. -
-
- -
-
- -
-
- Read the docs to discover what to do next: -
- -
- - + + + ); } +// function App(): React.JSX.Element { +// const isDarkMode = useColorScheme() === 'dark'; + +// const backgroundStyle = { +// backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, +// }; + +// return ( +// +// +// +//
+// +//
+// Edit App.tsx to change this +// screen and then come back to see your edits. +//
+//
+// +//
+//
+// +//
+//
+// Read the docs to discover what to do next: +//
+// +//
+// +// +// ); +// } const styles = StyleSheet.create({ sectionContainer: { @@ -116,3 +138,51 @@ const styles = StyleSheet.create({ }); export default App; + +function TabNav() { + const Footer = createBottomTabNavigator(); + return ( + + { + if (focused) return ; + else return ; + }, + }} + /> + { + if (focused) return ; + else return ; + }, + }} + /> + { + if (focused) return ; + else return ; + }, + }} + /> + + ); +} + +function Home() { + return Hi; +} +function Search() { + return Search; +} +function Library() { + return Library; +} diff --git a/dialpods/logic/constants.ts b/dialpods/logic/constants.ts new file mode 100644 index 0000000..e69de29 diff --git a/dialpods/logic/store.ts b/dialpods/logic/store.ts new file mode 100644 index 0000000..3152be4 --- /dev/null +++ b/dialpods/logic/store.ts @@ -0,0 +1,49 @@ +import {create} from 'zustand'; +import {createJSONStorage, persist} from 'zustand/middleware'; +import {useShallow} from 'zustand/shallow'; +import type {Podcasts} from './types/types'; + +interface UIState { + subs: Podcasts; +} +type ProcessState = {subs: Podcasts}; +type WsMessage = + | {kind: 'error'; data: string} + | { + kind: 'state'; + data: ProcessState; + }; +// | { +// kind: 'post'; +// data: Post['data']; +// stream_name: string; +// post_id: string; +// } +// | { +// kind: 'image'; +// data: { +// image: string; +// uri: string; +// }; +// } +// | { +// kind: 'profile'; +// data: CuratorProfileInfo; +// }; +const storeInner = create()( + // persist( + (set, get) => ({ + subs: {}, + }), + // { + // name: 'dial_pods_store', + // storage: createJSONStorage(() => sessionStorage), + // }, + // ), +); + +const useUIStore = any>( + selector: T, +): ReturnType => storeInner(useShallow(selector)); + +export default useUIStore; diff --git a/dialpods/logic/types/types.ts b/dialpods/logic/types/types.ts new file mode 100644 index 0000000..954eb98 --- /dev/null +++ b/dialpods/logic/types/types.ts @@ -0,0 +1,62 @@ +export type AsyncRes = Promise>; +export type Result = {ok: T} | {error: string}; + +export type Podcasts = Record; +export type Ack = null; +export type Podcast = SearchResult; + +export type SearchResult = {image: string; name: string; url: string}; + +export type YoutubeChannel = { + channel: { + author: string; + authorUrl: string; + channelId: string; + published: string; + title: string; + }; + entries: YoutubeItem[]; +}; +export type YoutubeItem = { + id: string; + videoId: string; + title: string; + published: string; + updated: string; + link: string; + thumbnail: string; + description: string; + views: number; + rating: { + count: number; + average: number; + }; +}; +export type RSSFeed = { + podcast: { + title: string; + description: string; + link: string; + copyright: string; + language: string; + lastBuildDate?: string; + author: string; + type: string; + image: string; + // categories: Array<{ text: string; subcategories: string[] }>; + // podcastGuid: string; + }; + episodes: RSSFeedItem[]; +}; +// TODO deal with CData encoding +export type RSSFeedItem = { + title: string; + link: string; + guid: {value: string; isPermaLink: boolean}; + pubDate: string; + // author: string; + description: string; + // content: string; + enclosure: {url: string; length: number; type: string}; + // podcastInfo: { season: string; episode: string }; +}; diff --git a/dialpods/logic/utils.ts b/dialpods/logic/utils.ts new file mode 100644 index 0000000..476f847 --- /dev/null +++ b/dialpods/logic/utils.ts @@ -0,0 +1,336 @@ +import {Result, RSSFeed, SearchResult, YoutubeChannel} from './types/types'; +import DOMparser from 'react-native-html-parser'; + +export async function parseHTMLRes(htmls: string) { + const parser = new DOMparser.DOMParser(); + const html = parser.parseFromString(htmls, 'text/html'); + return html; +} +export function parseYoutubeChannel( + head: HTMLHeadElement, +): Result { + const linkEl = head.querySelector('link[type="application/rss+xml"]'); + if (!linkEl) return {error: `not found`}; + const url = linkEl!.getAttribute('href'); + if (!url) return {error: `not found`}; + const og = head.querySelectorAll('meta'); + let image = ''; + let name = ''; + for (const meta of og) { + if (image && name) break; + const n = meta.getAttribute('property'); + if (n === 'og:image') image = meta.getAttribute('content')!; + if (n === 'og:title') name = meta.getAttribute('content')!; + } + if (image && name && url) return {ok: {image, name, url}}; + else return {error: 'not found'}; +} +export async function parseXMLRes(s: string) { + const parser = new DOMparser.DOMParser(); + const xml = parser.parseFromString(s, 'text/xml'); + return xml; +} + +export function parseRSSFeed(url: string, doc: Document): Result { + const chan = doc.getElementsByTagName('channel')[0]; + const title = chan.getElementsByTagName('title')[0]; + const image = chan.getElementsByTagName('image')[0]; + const imageURL = image.getElementsByTagName('url')[0]; + try { + return { + ok: { + name: title.textContent!, + image: imageURL.textContent!, + url, + }, + }; + } catch (_) { + return {error: 'not found'}; + } +} +const namespaces: any = { + itunes: 'http://www.itunes.com/dtds/podcast-1.0.dtd', + podcast: 'https://podcastindex.org/namespace/1.0', + content: 'http://purl.org/rss/1.0/modules/content/', + dc: 'http://purl.org/dc/elements/1.1/', + atom: 'http://www.w3.org/2005/Atom', + podaccess: 'https://access.acast.com/schema/1.0', + media: 'http://search.yahoo.com/mrss/', +}; +export function parseRSSFull(url: string, doc: Document): Result { + try { + // Helper function to get namespaced elements + const getNS = (element: Element, namespace: string, tagName: string) => + element.getElementsByTagNameNS(namespaces[namespace], tagName)[0]; + + // Parse channel (podcast) information + const channel = doc.getElementsByTagName('channel')[0]; + const podcastInfo = { + title: channel.getElementsByTagName('title')[0].textContent || '', + description: + channel.getElementsByTagName('description')[0].textContent || '', + link: channel.getElementsByTagName('link')[0].textContent || '', + copyright: channel.getElementsByTagName('copyright')[0].textContent || '', + language: channel.getElementsByTagName('language')[0].textContent || '', + // + // + // + // lastBuildDate: + // channel.getElementsByTagName('lastBuildDate')[0], + // ), + + // iTunes specific fields + author: getNS(channel, 'itunes', 'author').textContent || '', + type: getNS(channel, 'itunes', 'type').textContent || '', + // owner: { + // name: + // getNS(channel, 'itunes', 'owner')?.getElementsByTagNameNS( + // namespaces.itunes, + // 'name', + // )[0], + // ), + // email: + // getNS(channel, 'itunes', 'owner')?.getElementsByTagNameNS( + // namespaces.itunes, + // 'email', + // )[0], + // ), + // }, + image: + channel.getElementsByTagName('image')[0]?.getElementsByTagName('url')[0] + ?.textContent || '', + + // image: { + // url: channel + // .getElementsByTagName('image')[0] + // ?.getElementsByTagName('url')[0]?.textContent, + // itunesUrl: getNS(channel, 'itunes', 'image')?.getAttribute('href'), + // }, + + // Categories + categories: Array.from( + channel.getElementsByTagNameNS(namespaces.itunes, 'category'), + ).map(category => ({ + text: category.getAttribute('text') || '', + subcategories: Array.from( + category.getElementsByTagNameNS(namespaces.itunes, 'category'), + ).map(sub => sub.getAttribute('text') || ''), + })), + + // Podcast namespace specific fields + // podcastGuid: getNS(channel, 'podcast', 'guid')), + // hosts: Array.from( + // channel.getElementsByTagNameNS(namespaces.podcast, 'person'), + // ) + // .filter((person) => person.getAttribute('role') === 'Host') + // .map((person) => ({ + // name: person.textContent, + // role: person.getAttribute('role'), + // img: person.getAttribute('img'), + // href: person.getAttribute('href'), + // })), + }; + + // Parse episodes + const episodes = Array.from(doc.getElementsByTagName('item')).map(item => { + // Get episode images + const itunesImage = getNS(item, 'itunes', 'image'); + + return { + title: item.getElementsByTagName('title')[0].textContent || '', + link: item.getElementsByTagName('link')[0].textContent || '', + guid: { + value: item.getElementsByTagName('guid')[0].textContent || '', + isPermaLink: + item + .getElementsByTagName('guid')[0] + ?.getAttribute('isPermaLink') === 'true', + }, + pubDate: item.getElementsByTagName('pubDate')[0].textContent || '', + + // author: item.getElementsByTagName('author')[0], + + // Episode content + description: + item.getElementsByTagName('description')[0].textContent || '', + + // content: + // getNS(item, 'content', 'encoded'), + // )?.trim(), + + // Media information + enclosure: { + url: item.getElementsByTagName('enclosure')[0]?.getAttribute('url')!, + length: parseInt( + item.getElementsByTagName('enclosure')[0]?.getAttribute('length')!, + ), + type: item + .getElementsByTagName('enclosure')[0] + ?.getAttribute('type')!, + }, + + // iTunes specific episode fields + // itunesInfo: { + // episode: getNS(item, 'itunes', 'episode')), + // season: getNS(item, 'itunes', 'season')), + // duration: getNS(item, 'itunes', 'duration')), + // explicit: getNS(item, 'itunes', 'explicit')), + // episodeType: + // getNS(item, 'itunes', 'episodeType'), + // ), + // image: itunesImage + // ? itunesImage.getAttribute('href') + // : null, + // }, + + // Podcast namespace fields + // podcastInfo: { + // season: getNS(item, 'podcast', 'season')), + // episode: getNS(item, 'podcast', 'episode')), + // hosts: Array.from( + // item.getElementsByTagNameNS( + // namespaces.podcast, + // 'person', + // ), + // ) + // .filter( + // (person) => person.getAttribute('role') === 'Host', + // ) + // .map((person) => ({ + // name: person.textContent, + // role: person.getAttribute('role'), + // img: person.getAttribute('img'), + // href: person.getAttribute('href'), + // })), + // }, + }; + }); + + return { + ok: { + podcast: podcastInfo, + episodes, + }, + }; + } catch (e) { + return {error: 'error parsing feed'}; + } +} + +export function parseYTFeed(doc: Document): Result { + // Create parser and parse XML string + // Define namespaces used in the document + const namespaces = { + media: 'http://search.yahoo.com/mrss/', + yt: 'http://www.youtube.com/xml/schemas/2015', + atom: 'http://www.w3.org/2005/Atom', + }; + + // Helper function to safely get text content + const getTextContent = (element: Element) => element.textContent!; + + // Parse each entry + const entries = Array.from(doc.getElementsByTagName('entry')).map(entry => { + // Get media:group element + const mediaGroup = entry.getElementsByTagNameNS( + namespaces.media, + 'group', + )[0]; + + // Get statistics and rating from media:community + const mediaCommunity = mediaGroup.getElementsByTagNameNS( + namespaces.media, + 'community', + )[0]; + const statistics = mediaCommunity.getElementsByTagNameNS( + namespaces.media, + 'statistics', + )[0]; + const starRating = mediaCommunity.getElementsByTagNameNS( + namespaces.media, + 'starRating', + )[0]; + + return { + id: getTextContent(entry.getElementsByTagName('id')[0]), + videoId: getTextContent( + entry.getElementsByTagNameNS(namespaces.yt, 'videoId')[0], + ), + title: getTextContent(entry.getElementsByTagName('title')[0]), + published: getTextContent(entry.getElementsByTagName('published')[0]), + updated: getTextContent(entry.getElementsByTagName('updated')[0]), + link: entry.getElementsByTagName('link')[0]?.getAttribute('href')!, + + // Media group information + thumbnail: mediaGroup + .getElementsByTagNameNS(namespaces.media, 'thumbnail')[0] + ?.getAttribute('url')!, + description: getTextContent( + mediaGroup.getElementsByTagNameNS(namespaces.media, 'description')[0], + ), + + // Statistics + views: parseInt(statistics?.getAttribute('views') || '0'), + rating: { + count: parseInt(starRating?.getAttribute('count') || '0'), + average: parseFloat(starRating?.getAttribute('average') || '0'), + }, + }; + }); + + // Get channel information + const channelInfo = { + title: getTextContent(doc.getElementsByTagName('title')[0]), + channelId: getTextContent( + doc.getElementsByTagNameNS(namespaces.yt, 'channelId')[0], + ), + published: getTextContent(doc.getElementsByTagName('published')[0]), + author: getTextContent(doc.getElementsByTagName('name')[0]), + authorUrl: getTextContent(doc.getElementsByTagName('uri')[0]), + }; + + return { + ok: { + channel: channelInfo, + entries: entries, + }, + }; +} + +export function abbreviate(s: string, length: number): string { + if (s.length <= length) return s; + else return s.substring(0, length) + '...'; +} + +export function date_diff(date: Date) { + const now = new Date().getTime(); + const s = now - date.getTime(); + if (s < 60) { + return 'now'; + } else if (s < 3600) { + return `${Math.ceil(s / 60)}minutes ago`; + } else if (s < 86400) { + return `${Math.ceil(s / 60 / 60)}hours ago`; + } else if (s < 32140800) { + return date.toLocaleString('default', { + month: 'long', + day: 'numeric', + }); + } else { + return date.toLocaleString('default', { + month: 'long', + day: 'numeric', + year: 'numeric', + }); + } +} +export function durationString(secs: number): string { + const hours = Math.floor(secs / 3600); + const minutes = Math.floor((secs % 3600) / 60); + const seconds = Math.floor(secs % 60); + return hours > 0 + ? `${hours}:${minutes.toString().padStart(2, '0')}:${seconds + .toString() + .padStart(2, '0')}` + : `${minutes}:${seconds.toString().padStart(2, '0')}`; +} diff --git a/dialpods/package-lock.json b/dialpods/package-lock.json index 163f255..205f890 100644 --- a/dialpods/package-lock.json +++ b/dialpods/package-lock.json @@ -8,8 +8,18 @@ "name": "dialpods", "version": "0.0.1", "dependencies": { + "@react-navigation/bottom-tabs": "^7.1.3", + "@react-navigation/native": "^7.0.13", + "@react-navigation/native-stack": "^7.1.14", "react": "18.3.1", - "react-native": "0.76.4" + "react-native": "0.76.4", + "react-native-fast-image": "^8.6.3", + "react-native-html-parser": "^0.1.0", + "react-native-safe-area-context": "^5.0.0", + "react-native-screens": "^4.3.0", + "react-native-url-polyfill": "^2.0.0", + "react-native-vector-icons": "^10.2.0", + "zustand": "^5.0.2" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -23,6 +33,7 @@ "@react-native/metro-config": "0.76.4", "@react-native/typescript-config": "0.76.4", "@types/react": "^18.2.6", + "@types/react-native-vector-icons": "^6.4.18", "@types/react-test-renderer": "^18.0.0", "babel-jest": "^29.6.3", "eslint": "^8.19.0", @@ -3411,6 +3422,111 @@ } } }, + "node_modules/@react-navigation/bottom-tabs": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.1.3.tgz", + "integrity": "sha512-cK5zE7OpZAgZLpFBnoH9AhZbSZfH9Qavdi3kIRd2vpQDtCfnnG5bQ2eM2u/IKHDLdI50Mhsf+srqYJgG2VcmVQ==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.2.4", + "color": "^4.2.3" + }, + "peerDependencies": { + "@react-navigation/native": "^7.0.13", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, + "node_modules/@react-navigation/core": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.3.0.tgz", + "integrity": "sha512-mfUPRdFCuHkaC+uU5iczqevn0PCTKzf6ApxFwgG9E8DfAVbAT7/piZEzFye2inaIRkipBwyNW40h+mEvYqE1og==", + "license": "MIT", + "dependencies": { + "@react-navigation/routers": "^7.1.1", + "escape-string-regexp": "^4.0.0", + "nanoid": "3.3.7", + "query-string": "^7.1.3", + "react-is": "^18.2.0", + "use-latest-callback": "^0.2.1", + "use-sync-external-store": "^1.2.2" + }, + "peerDependencies": { + "react": ">= 18.2.0" + } + }, + "node_modules/@react-navigation/core/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/@react-navigation/elements": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.2.4.tgz", + "integrity": "sha512-/H6Gu/Hn2E/pBQTkZEMbP5SDi7C2q96PrHGvsDJiFtxFgOJusA3+ygUguqTeTP402s/5KvJm47g0UloCMiECwA==", + "license": "MIT", + "dependencies": { + "color": "^4.2.3" + }, + "peerDependencies": { + "@react-native-masked-view/masked-view": ">= 0.2.0", + "@react-navigation/native": "^7.0.13", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0" + }, + "peerDependenciesMeta": { + "@react-native-masked-view/masked-view": { + "optional": true + } + } + }, + "node_modules/@react-navigation/native": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.0.13.tgz", + "integrity": "sha512-HLoMyp453qIDGjG72cJ2xLeGHHpP4PQve5gQvSn3o/6r2+DAmDuIcd/jXTMJGCHd2LeR9LfuqIvpiIlihg1iBg==", + "license": "MIT", + "dependencies": { + "@react-navigation/core": "^7.3.0", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "3.3.7", + "use-latest-callback": "^0.2.1" + }, + "peerDependencies": { + "react": ">= 18.2.0", + "react-native": "*" + } + }, + "node_modules/@react-navigation/native-stack": { + "version": "7.1.14", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.1.14.tgz", + "integrity": "sha512-MH3iktneSL8JSttcgJBIb6zmDpaurbtMSQcYwCcsGoyq4fJ2pBIwJaViSa0KrNkMsWAMZEOY9O72rf1umu7VKw==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.2.4", + "warn-once": "^0.1.1" + }, + "peerDependencies": { + "@react-navigation/native": "^7.0.13", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, + "node_modules/@react-navigation/routers": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.1.1.tgz", + "integrity": "sha512-OycWRj95p+/zENl9HU6tvvT6IUuxgVJirgsA0W9rQn3RC+9Hb0UVYA0+8avdt+WpMoWdrvwTxTXneB5mjYzHrw==", + "license": "MIT", + "dependencies": { + "nanoid": "3.3.7" + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -3576,6 +3692,27 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-native": { + "version": "0.70.19", + "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.19.tgz", + "integrity": "sha512-c6WbyCgWTBgKKMESj/8b4w+zWcZSsCforson7UdXtXMecG3MxCinYi6ihhrHVPyUrVzORsvEzK8zg32z4pK6Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-native-vector-icons": { + "version": "6.4.18", + "resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.18.tgz", + "integrity": "sha512-YGlNWb+k5laTBHd7+uZowB9DpIK3SXUneZqAiKQaj1jnJCZM0x71GDim5JCTMi4IFkhc9m8H/Gm28T5BjyivUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*", + "@types/react-native": "^0.70" + } + }, "node_modules/@types/react-test-renderer": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.3.1.tgz", @@ -4477,7 +4614,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "devOptional": true, "funding": [ { "type": "github", @@ -4829,6 +4965,19 @@ "dev": true, "license": "MIT" }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4847,6 +4996,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/colorette": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", @@ -5165,6 +5324,15 @@ "node": ">=0.10.0" } }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -6318,7 +6486,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -6431,6 +6598,15 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -7074,7 +7250,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "devOptional": true, "funding": [ { "type": "github", @@ -9826,6 +10001,24 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -9997,7 +10190,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10553,7 +10745,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -10565,14 +10756,12 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -10595,6 +10784,24 @@ ], "license": "MIT" }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", @@ -10677,6 +10884,18 @@ } } }, + "node_modules/react-freeze": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz", + "integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=17.0.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -10745,6 +10964,126 @@ } } }, + "node_modules/react-native-fast-image": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-8.6.3.tgz", + "integrity": "sha512-Sdw4ESidXCXOmQ9EcYguNY2swyoWmx53kym2zRsvi+VeFCHEdkO+WG1DK+6W81juot40bbfLNhkc63QnWtesNg==", + "license": "(MIT AND Apache-2.0)", + "peerDependencies": { + "react": "^17 || ^18", + "react-native": ">=0.60.0" + } + }, + "node_modules/react-native-html-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/react-native-html-parser/-/react-native-html-parser-0.1.0.tgz", + "integrity": "sha512-G5d0pk7TA9YgjVJhLRf2zMOOf1HXd8ItM9JYsTS2pXoEfSRfMfRU5iTj03LGwvHU2NT9kc18NDJeLgZY6+Bbvg==", + "engines": { + "node": ">=0.1" + } + }, + "node_modules/react-native-safe-area-context": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.0.0.tgz", + "integrity": "sha512-4K4TvEbRsTDYuSSJZfMNKuJNn1+qgrSkOBwRoreiHcuqy1egrHpkhPhoN1Zg1+b3BxcVXlKXtMIf4eVaG/DPJw==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-screens": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.3.0.tgz", + "integrity": "sha512-G0u8BPgu2vcRZoQTlRpBXKa0ElQSDvDBlRe6ncWwCeBmd5Uqa2I3tQ6Vn6trIE6+yneW/nD4p5wihEHlAWZPEw==", + "license": "MIT", + "dependencies": { + "react-freeze": "^1.0.0", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-url-polyfill": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz", + "integrity": "sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA==", + "license": "MIT", + "dependencies": { + "whatwg-url-without-unicode": "8.0.0-3" + }, + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/react-native-vector-icons": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.2.0.tgz", + "integrity": "sha512-n5HGcxUuVaTf9QJPs/W22xQpC2Z9u0nb0KgLPnVltP8vdUvOp6+R26gF55kilP/fV4eL4vsAHUqUjewppJMBOQ==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2", + "yargs": "^16.1.1" + }, + "bin": { + "fa-upgrade.sh": "bin/fa-upgrade.sh", + "fa5-upgrade": "bin/fa5-upgrade.sh", + "fa6-upgrade": "bin/fa6-upgrade.sh", + "generate-icon": "bin/generate-icon.js" + } + }, + "node_modules/react-native-vector-icons/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/react-native-vector-icons/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/react-native/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -11454,6 +11793,21 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -11535,6 +11889,15 @@ "source-map": "^0.6.0" } }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -11598,6 +11961,15 @@ "node": ">= 0.6" } }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -12337,6 +12709,24 @@ "punycode": "^2.1.0" } }, + "node_modules/use-latest-callback": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.3.tgz", + "integrity": "sha512-7vI3fBuyRcP91pazVboc4qu+6ZqM8izPWX9k7cRnT8hbD5svslcknsh3S9BUhaK11OmgTV4oWZZVSeQAiV53SQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -12392,6 +12782,12 @@ "makeerror": "1.0.12" } }, + "node_modules/warn-once": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz", + "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==", + "license": "MIT" + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -12424,6 +12820,29 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/whatwg-url-without-unicode": { + "version": "8.0.0-3", + "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", + "integrity": "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==", + "license": "MIT", + "dependencies": { + "buffer": "^5.4.3", + "punycode": "^2.1.1", + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/whatwg-url-without-unicode/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=8" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -12676,6 +13095,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.2.tgz", + "integrity": "sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/dialpods/package.json b/dialpods/package.json index 520d416..b24da91 100644 --- a/dialpods/package.json +++ b/dialpods/package.json @@ -10,8 +10,18 @@ "test": "jest" }, "dependencies": { + "@react-navigation/bottom-tabs": "^7.1.3", + "@react-navigation/native": "^7.0.13", + "@react-navigation/native-stack": "^7.1.14", "react": "18.3.1", - "react-native": "0.76.4" + "react-native": "0.76.4", + "react-native-fast-image": "^8.6.3", + "react-native-html-parser": "^0.1.0", + "react-native-safe-area-context": "^5.0.0", + "react-native-screens": "^4.3.0", + "react-native-url-polyfill": "^2.0.0", + "react-native-vector-icons": "^10.2.0", + "zustand": "^5.0.2" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -25,6 +35,7 @@ "@react-native/metro-config": "0.76.4", "@react-native/typescript-config": "0.76.4", "@types/react": "^18.2.6", + "@types/react-native-vector-icons": "^6.4.18", "@types/react-test-renderer": "^18.0.0", "babel-jest": "^29.6.3", "eslint": "^8.19.0", @@ -36,4 +47,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} diff --git a/dialpods/pages/Feed.tsx b/dialpods/pages/Feed.tsx new file mode 100644 index 0000000..e69de29 diff --git a/dialpods/pages/Home.tsx b/dialpods/pages/Home.tsx new file mode 100644 index 0000000..e69de29 diff --git a/dialpods/pages/Library.tsx b/dialpods/pages/Library.tsx new file mode 100644 index 0000000..e69de29 diff --git a/dialpods/pages/Player.tsx b/dialpods/pages/Player.tsx new file mode 100644 index 0000000..e69de29 diff --git a/dialpods/pages/Search.tsx b/dialpods/pages/Search.tsx new file mode 100644 index 0000000..401d0f8 --- /dev/null +++ b/dialpods/pages/Search.tsx @@ -0,0 +1,211 @@ +import React, {useEffect, useState} from 'react'; +import { + View, + Text, + TextInput, + Image, + TouchableOpacity, + StyleSheet, + ActivityIndicator, + ScrollView, +} from 'react-native'; +// import { corsProxy, saveFeed } from '../logic/api'; +import { + parseHTMLRes, + parseRSSFeed, + parseXMLRes, + parseYoutubeChannel, +} from '../logic/utils'; +import useUIStore from '../logic/store'; +import type {SearchResult} from '../logic/types/types'; +import {Button} from 'react-native'; + +function SearchScreen() { + const [loading, setLoading] = useState(false); + const [input, setInput] = useState('https://www.youtube.com/@bookclubradio'); + const [results, setResults] = useState([]); + + console.log('search page loaded'); + function handleButton() { + // handleURL(url); + console.log('button pressed', input); + const url = new URL(input); + console.log('got url', url); + handleYouTube(input); + } + // useEffect(() => { + // console.log('hi!!', input); + // if (!input) setResults([]); + // if (input.length > 2) + // try { + // } catch (_) { + // handleSearch(input); + // } + // }, [input]); + + async function handleURL(url: URL) { + console.log('handling url'); + // if (url.hostname.endsWith('youtube.com')) handleYouTube(url); + // else handleRSS(url); + } + + async function handleYouTube(url: string) { + console.log('handling youtube', url); + // if (!url.search) { + // const res = await corsProxy(url); + console.log('fetching', url); + const res = await fetch(url); + console.log('youtube response', res); + const txt = await res.text(); + console.log('youtube text', txt); + const doc = await parseHTMLRes(txt); + console.log('doc', doc); + console.log('head', doc.head); + const mparsed = parseYoutubeChannel(doc.head); + console.log(mparsed, 'mparsed'); + // if ('error' in mparsed) handleError(url.toString(), mparsed.error); + // else setResults(r => [...r, mparsed.ok]); + // } + } + + async function handleRSS(url: URL) { + // const res = await corsProxy(url); + // const res = await fetch(url); + // if (!('ok' in res)) return handleError(url.toString(), 'error fetching'); + // const s = res.ok; + // if (!('HTML' in s)) return handleError('', ''); + // const doc = await parseXMLRes(s.HTML); + // try { + // const mparsed = parseRSSFeed(url.toString(), doc); + // if ('error' in mparsed) handleError(url.toString(), mparsed.error); + // else setResults(r => [...r, mparsed.ok]); + // } catch (e) { + // console.log('wtf happened', e); + // } + } + + async function handleSearch(input: string) { + setLoading(true); + console.log('searching', input); + } + + function handleError(source: string, error: string) { + const alert = {message: `${error}: ${source}`, timeout: 2000}; + // setAlert(alert); + } + + function close() { + // resync(); + setResults([]); + setInput(''); + } + + return ( + + + Search + + + + {results.map((result, index) => ( + + ))} + + + + + {loading && } + +