Skip to content

Commit 7668590

Browse files
committedMar 14, 2023
.
1 parent c082abf commit 7668590

15 files changed

+156
-102
lines changed
 

‎app/[...missing].tsx

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { Heading, YStack } from 'tamagui'
44

55
export default function NotFoundScreen() {
66
const pathname = usePathname()
7-
console.log('pathname', pathname)
87

98
return (
109
<YStack flex={1}>

‎app/feeds.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default function Feeds() {
3131
<AddFeedButton />
3232
</XStack>
3333
<FlatList
34-
data={feeds}
34+
data={feeds.filter((t) => !t.deleted)}
3535
style={{
3636
flex: 1,
3737
}}

‎app/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default function FlowPage() {
2121

2222
useEffect(() => {
2323
fetchFeedFlow(feeds)
24-
}, [feeds.length])
24+
}, [feeds.filter((t) => !t.deleted).length])
2525

2626
useEffect(() => {
2727
tagFeedEntries(entries)

‎app/shared/entryByTag.tsx

+3-11
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,20 @@
11
import EntryList from 'components/EntryList'
2-
import dayjs from 'dayjs'
32
import { useRouter, useSearchParams } from 'expo-router'
3+
import useEntryFlow from 'hooks/useEntryFlow'
44
import { NavArrowLeft } from 'iconoir-react-native'
55
import _ from 'lodash'
66
import { useEffect, useState } from 'react'
77
import { Pressable } from 'react-native'
88
import { useSafeAreaInsets } from 'react-native-safe-area-context'
9-
import { useAppSelector } from 'store/hooks'
109
import { XStack, YStack, Text, Input } from 'tamagui'
11-
import { FeedEntry } from 'types'
1210

1311
export default function EntryByTag() {
1412
const [tag, setTag] = useState('')
1513
const { tag: _tag } = useSearchParams()
1614
const insets = useSafeAreaInsets()
1715
const router = useRouter()
18-
const compactEntries = useAppSelector((state) =>
19-
state.feed.flow.map((t) => t.entries || [])
20-
)
21-
const entries = _.flatten(compactEntries)
22-
.filter((t) => t.tags?.includes(tag))
23-
.sort((a: FeedEntry, b: FeedEntry) =>
24-
dayjs(b.published).diff(dayjs(a.published))
25-
)
16+
const { entries: _entries } = useEntryFlow()
17+
const entries = _entries.filter((t) => t.tags?.includes(tag))
2618

2719
useEffect(() => {
2820
if (typeof _tag === 'string') {

‎app/shared/feed.tsx

+52-13
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,42 @@
11
import Header from 'components/Header'
2-
import { Stack, useSearchParams } from 'expo-router'
2+
import { useSearchParams } from 'expo-router'
33
import { extract } from 'lib/parser'
44
import { useEffect, useState } from 'react'
55
import { Pressable } from 'react-native'
66
import { YStack, Text, XStack, Spinner } from 'tamagui'
7-
import { FeedData } from 'types'
8-
import { useAppSelector } from 'store/hooks'
7+
import { Feed, FeedData, FeedListType } from 'types'
98
import Favicon from 'components/Favicon'
109
import _ from 'lodash'
1110
import FeedInfo from 'components/FeedInfo'
1211
import { EmojiLookUp } from 'iconoir-react-native'
1312
import AddFeedButton from 'components/AddFeedButton'
1413
import EntryList from 'components/EntryList'
14+
import useFeeds from 'hooks/useFeeds'
15+
import { createEntries, resubFeed, subFeed, unsubFeed } from 'lib/db'
16+
import useEntryFlow from 'hooks/useEntryFlow'
1517

1618
export default function FeedProfile() {
1719
const [data, setData] = useState<FeedData>()
1820
const [error, setError] = useState()
1921
const [loading, setLoading] = useState(false)
20-
const { url, title, description } = useSearchParams()
21-
const flows = useAppSelector((state) => state.feed.flow)
22-
const source = flows.find((f) => f.url === url)
22+
const { url, title, from } = useSearchParams()
23+
const { feeds } = useFeeds()
24+
const feed = feeds.find((f) => f.url === url)
25+
const { entries } = useEntryFlow()
2326

2427
useEffect(() => {
2528
setData(undefined)
2629
setError(undefined)
27-
if (source) {
28-
setData(source)
30+
if (feed) {
31+
setData(feed)
2932
} else if (url) {
3033
setLoading(true)
3134
extract(url as string)
3235
.then((res) => {
3336
if (res) {
3437
setData({ ...res, url })
38+
// auto sub
39+
handleSubscribe({ ...res, url })
3540
}
3641
setLoading(false)
3742
})
@@ -40,9 +45,43 @@ export default function FeedProfile() {
4045
setLoading(false)
4146
})
4247
}
43-
}, [url, source])
48+
}, [url, feed])
4449

45-
const favicon = source?.favicon || data?.favicon
50+
const handleSubscribe = async (_data?: FeedData) => {
51+
try {
52+
const fd = _data || data
53+
54+
if (!fd) {
55+
return
56+
}
57+
const _feed: Feed = {
58+
url: url as string,
59+
title: fd.title || '',
60+
favicon: fd.favicon || '',
61+
description: fd.description || '',
62+
language: fd.language || '',
63+
}
64+
65+
if (feed) {
66+
if (feed.deleted) {
67+
resubFeed(_feed)
68+
} else {
69+
unsubFeed(feed)
70+
}
71+
} else {
72+
subFeed(_feed)
73+
if (Array.isArray(fd?.entries)) {
74+
createEntries(fd.entries)
75+
}
76+
}
77+
} catch (error) {}
78+
}
79+
80+
const favicon = feed?.favicon || data?.favicon
81+
const isSubed = feed && !feed?.deleted
82+
const feedEntries = isSubed
83+
? entries.filter((e) => e.sourceUrl === url)
84+
: data?.entries || []
4685

4786
return (
4887
<YStack flex={1}>
@@ -63,7 +102,7 @@ export default function FeedProfile() {
63102
</Text>
64103
</XStack>
65104
}
66-
right={<FeedInfo source={data} />}
105+
right={<FeedInfo source={data} handleSubscribe={handleSubscribe} />}
67106
/>
68107
<YStack flex={1}>
69108
{error && (
@@ -98,8 +137,8 @@ export default function FeedProfile() {
98137
</YStack>
99138
) : (
100139
<EntryList
101-
entries={data?.entries || []}
102-
type="tags"
140+
entries={feedEntries}
141+
type={(from as FeedListType) || 'tags'}
103142
withHeader={false}
104143
/>
105144
)}

‎app/shared/reader.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export default function Reader() {
9292
const title = typeof t === 'string' ? t : t.title
9393
const isLast = idx + 1 === entry?.tags?.length
9494
return (
95-
<Link key={idx} href={`shared/tags?tag=${title}`}>
95+
<Link key={idx} href={`shared/entryByTag?tag=${title}`}>
9696
<XStack mr={2} mb={4}>
9797
<Text
9898
color="$color11"

‎app/tags.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { YStack } from 'tamagui'
2+
export default function TagsPage() {
3+
return <YStack flex={1}></YStack>
4+
}

‎components/DrawerPanel.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ const routes = [
2323
title: 'Feeds',
2424
Icon: RssFeedTag,
2525
},
26-
{
27-
href: '/tags',
28-
title: 'Tags',
29-
Icon: Label,
30-
},
26+
// {
27+
// href: '/tags',
28+
// title: 'Tags',
29+
// Icon: Label,
30+
// },
3131
// {
3232
// href: '/explore',
3333
// title: 'Explore',

‎components/FeedInfo.tsx

+15-26
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import { useNavigation } from 'expo-router'
22
import useFeeds from 'hooks/useFeeds'
33
import { InfoEmpty } from 'iconoir-react-native'
4-
import { createFeed, deleteFeed } from 'lib/db'
54
import { useState } from 'react'
65
import { Pressable } from 'react-native'
7-
import { useAppDispatch } from 'store/hooks'
86
import { Text, Sheet, YStack, H5, Anchor, Button } from 'tamagui'
9-
import { Feed, FeedData, Source } from 'types'
7+
import { FeedData, Source } from 'types'
108
import Favicon from './Favicon'
119

12-
export default function FeedInfo({ source }: { source?: Source | FeedData }) {
10+
export default function FeedInfo({
11+
source,
12+
handleSubscribe,
13+
}: {
14+
source?: Source | FeedData
15+
handleSubscribe: () => Promise<void>
16+
}) {
1317
const [position, setPosition] = useState(0)
1418
const [open, setOpen] = useState(false)
1519

@@ -19,27 +23,12 @@ export default function FeedInfo({ source }: { source?: Source | FeedData }) {
1923
return null
2024
}
2125

22-
const _source = feeds.find((f) => f.url === source.url)
23-
const isSubscribed = !!_source
24-
const handleSubscribe = () => {
25-
try {
26-
const feed: Feed = {
27-
url: source.url!,
28-
title: source.title || '',
29-
favicon: source.favicon || '',
30-
description: source.description || '',
31-
language: source.language || '',
32-
}
33-
34-
if (_source) {
35-
deleteFeed(feed)
36-
} else {
37-
createFeed(feed)
38-
}
39-
setOpen(false)
40-
// @ts-ignore
41-
navigation.jumpTo('index')
42-
} catch (error) {}
26+
const old = feeds.find((f) => f.url === source.url)
27+
const isSubscribed = !!old && !old.deleted
28+
const _handleSubscribe = async () => {
29+
await handleSubscribe()
30+
// @ts-ignore
31+
navigation.jumpTo('index')
4332
}
4433
return (
4534
<>
@@ -82,7 +71,7 @@ export default function FeedInfo({ source }: { source?: Source | FeedData }) {
8271
bc={isSubscribed ? '$color5' : '#f0353c'}
8372
size="$4"
8473
color={isSubscribed ? '$color11' : '$color1'}
85-
onPress={handleSubscribe}
74+
onPress={_handleSubscribe}
8675
>
8776
{isSubscribed ? 'Unsubscribe' : 'Subscribe'}
8877
</Button>

‎components/SourceItem.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useRouter } from 'expo-router'
22
import useEntryFlow from 'hooks/useEntryFlow'
33
import { Pressable } from 'react-native'
4-
import { useAppSelector } from 'store/hooks'
54
import { Text, XStack } from 'tamagui'
65
import { Source } from 'types'
76
import Favicon from './Favicon'
@@ -15,7 +14,8 @@ export default function SourceItem({
1514
}) {
1615
const router = useRouter()
1716
const { entries } = useEntryFlow()
18-
const unreadCount = entries.filter((t) => !t.read).length || 0
17+
const unreadCount =
18+
entries.filter((t) => !t.read && t.sourceUrl === item.url).length || 0
1919

2020
return (
2121
<Pressable

‎hooks/useBookmarks.ts

-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ export default function useBookmarks() {
3030
const listener = PubSub.subscribe(
3131
PubEvent.BOOKMARKS_UPDATE,
3232
(_, rows: FeedEntry[]) => {
33-
console.log('BOOKMARKS_UPDATE', rows)
34-
3533
entriesStore.set(rows)
3634
}
3735
)

‎hooks/useEntryFlow.ts

-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ export default function useEntryFlow() {
3636
return entriesStore.subscribe(setEntries)
3737
}, [])
3838

39-
console.log('entries', entries.length)
40-
4139
useEffect(() => {
4240
const listener = PubSub.subscribe(
4341
PubEvent.ENTRYFLOW_UPDATE,

‎lib/db.ts

+34-7
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export async function initSQLite() {
4040
}, onError)
4141
}
4242

43-
export async function createFeed(feed: Feed) {
43+
export async function subFeed(feed: Feed) {
4444
const db = await openDatabase('./db/aleph.db')
4545
db.transaction(
4646
(tx) => {
@@ -63,16 +63,35 @@ export async function createFeed(feed: Feed) {
6363
)
6464
}
6565

66-
export async function deleteFeed(feed: Feed) {
66+
export async function resubFeed(feed: Feed) {
6767
const db = await openDatabase('./db/aleph.db')
6868
db.transaction(
6969
(tx) => {
70-
tx.executeSql('UPDATE feeds deleted = 1 WHERE id = ?', [feed.url])
70+
tx.executeSql(
71+
'UPDATE feeds SET deleted = 0 WHERE url = ?',
72+
[feed.url],
73+
() => {
74+
onUpdated(tx)
75+
}
76+
)
77+
},
78+
onError,
79+
function onSuccess() {}
80+
)
81+
}
82+
83+
export async function unsubFeed(feed: Feed) {
84+
const db = await openDatabase('./db/aleph.db')
85+
db.transaction(
86+
(tx) => {
87+
tx.executeSql('UPDATE feeds SET deleted = 1 WHERE url = ?', [feed.url])
7188
tx.executeSql(
7289
'DELETE FROM entries WHERE sourceUrl = ? AND bookmarked = 0',
73-
[feed.url]
90+
[feed.url],
91+
() => {
92+
onUpdated(tx)
93+
}
7494
)
75-
onUpdated(tx)
7695
},
7796
onError,
7897
function onSuccess() {}
@@ -83,7 +102,7 @@ export async function updateFeed(feed: Feed) {
83102
const db = await openDatabase('./db/aleph.db')
84103
db.transaction(
85104
(tx) => {
86-
tx.executeSql('UPDATE feeds title = ? WHERE id = ?', [
105+
tx.executeSql('UPDATE feeds SET title = ? WHERE url = ?', [
87106
feed.title,
88107
feed.url,
89108
])
@@ -186,7 +205,15 @@ function formatEntry(entry: {
186205

187206
function onUpdated(tx: SQLite.SQLTransaction) {
188207
tx.executeSql('SELECT * FROM feeds', [], (_, { rows }) => {
189-
PubSub.publish(PubEvent.FEEDS_UPDATE, rows._array)
208+
PubSub.publish(
209+
PubEvent.FEEDS_UPDATE,
210+
rows._array.map((t) => {
211+
return {
212+
...t,
213+
deleted: t.deleted === 1,
214+
}
215+
})
216+
)
190217
})
191218

192219
tx.executeSql(

‎lib/task.ts

+29-29
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import _ from 'lodash'
33
import { store } from 'store'
44
import { Feed, FeedEntry } from 'types'
55
import { HOST } from './constants'
6-
import { createEntries } from './db'
6+
import { createEntries, updateEntries } from './db'
77
import { extract } from './parser'
88
import { post } from './request'
99

@@ -21,23 +21,24 @@ export async function fetchFeedFlow(feeds: Feed[]) {
2121
feeds.map(async (feed) => {
2222
try {
2323
const result = await extract(feed.url)
24+
const entries = (result.entries || [])
25+
.map((entry: FeedEntry) => ({
26+
...entry,
27+
sourceUrl: feed.url,
28+
id: entry.link || entry.id,
29+
tags: [],
30+
}))
31+
.filter((entry: FeedEntry) => {
32+
return (
33+
dayjs().diff(dayjs(entry.published), 'day') <=
34+
DAYS_LIMIT[publishLimit]
35+
)
36+
})
2437

2538
return {
2639
...result,
2740
url: feed.url,
28-
entries: (result.entries || [])
29-
.map((entry: FeedEntry) => ({
30-
...entry,
31-
sourceUrl: feed.url,
32-
id: entry.id || entry.link,
33-
tags: [],
34-
}))
35-
.filter((entry: FeedEntry) => {
36-
return (
37-
dayjs().diff(dayjs(entry.published), 'day') >
38-
DAYS_LIMIT[publishLimit]
39-
)
40-
}),
41+
entries,
4142
}
4243
} catch (error) {
4344
return null
@@ -54,26 +55,25 @@ export async function fetchFeedFlow(feeds: Feed[]) {
5455
export async function tagFeedEntries(entries: FeedEntry[]) {
5556
try {
5657
const untaggedEntries = entries.filter((entry) => {
57-
return !entry.tags || entry.tags.length === 0
58+
return (
59+
entry.id.startsWith('http') && (!entry.tags || entry.tags.length === 0)
60+
)
5861
})
5962

63+
if (untaggedEntries.length === 0) {
64+
return
65+
}
6066
const result = await post(`${HOST}/keywords`, {
61-
entries: untaggedEntries.map((t) => t.id),
67+
entries: untaggedEntries.slice(0, 10).map((t) => t.id),
6268
})
6369

64-
// await Promise.all(
65-
// untagged.map(async (feed) => {
66-
// try {
70+
const taggedEntries = untaggedEntries.map((entry, idx) => {
71+
return {
72+
...entry,
73+
tags: result[idx] || [],
74+
}
75+
})
6776

68-
// store.dispatch({
69-
// type: 'feed/tagFeedEntries',
70-
// payload: {
71-
// sourceUrl: feed.sourceUrl,
72-
// entries: result,
73-
// },
74-
// })
75-
// } catch (error) {}
76-
// })
77-
// )
77+
updateEntries(taggedEntries)
7878
} catch (error) {}
7979
}

‎tsconfig.json

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,13 @@
33
"compilerOptions": {
44
"strict": true,
55
"baseUrl": "."
6-
}
6+
},
7+
"exclude": [
8+
"node_modules/",
9+
"babel.config.js",
10+
"metro.config.js",
11+
"jest.config.js",
12+
".expo/",
13+
"dist/"
14+
]
715
}

0 commit comments

Comments
 (0)
Please sign in to comment.