115 lines
3.3 kB
1
import { useEffect, useState } from 'preact/hooks'
2
import { formatDistanceToNow } from 'date-fns/formatDistanceToNow'
3
import { get } from '@dumbjs/pick/get'
4
5
/**
6
* @type {ReturnType<typeof microsearch>}
7
*/
8
let searcher
9
10
async function getData() {
11
const response = await fetch('/api/data').then(d => d.json())
12
13
searcher = microsearch(response, ['title', 'link'])
14
15
return response
16
}
17
18
export default () => {
19
const [sites, setSites] = useState([])
20
const [searchTerm, setSearchTerm] = useState('')
21
22
useEffect(() => {
23
getData().then(d => setSites(d))
24
}, [])
25
26
const recents = sites
27
.toSorted(
28
(x, y) => new Date(y.addedOn).getTime() - new Date(x.addedOn).getTime()
29
)
30
.slice(0, 4)
31
32
const totalCount = sites.length
33
34
const filteredSites = (searchTerm ? searcher(searchTerm) : sites)
35
.toSorted((x, y) => x.title.localeCompare(y.title))
36
37
return (
38
<div class="p-10 mx-auto max-w-4xl">
39
<div class="flex justify-end w-full">
40
<h1 class="font-sans text-sm text-zinc-600">minweb.site</h1>
41
</div>
42
<div class="my-24">
43
<h2 class="font-semibold">Recent</h2>
44
<ul class="flex flex-col gap-4 mt-8 w-full">
45
{recents.map(d => {
46
return (
47
<li class="w-full text-zinc-500">
48
<a
49
href={d.link}
50
class="relative w-full transition duration-300 link"
51
>
52
{d.title}
53
<span class="font-sans italic absolute top-0 text-[8px] text-emerald-400 -right-99 min-w-44">
54
{formatDistanceToNow(new Date(d.addedOn), {
55
addSuffix: true,
56
})}
57
</span>
58
</a>
59
</li>
60
)
61
})}
62
</ul>
63
</div>
64
65
<div class="my-24">
66
<div class="flex flex-wrap gap-2 justify-between">
67
<h2 class="font-semibold">
68
All <span class="text-zinc-500">( {totalCount} )</span>
69
</h2>
70
<div class="flex min-w-56">
71
<input
72
name="search"
73
class="transition-colors duration-300 input focus:ring-0 focus:border-emerald-400"
74
placeholder="search"
75
ref={node => {
76
if (!node) return
77
node.addEventListener('keyup', e => {
78
setSearchTerm(e.target.value)
79
})
80
}}
81
/>
82
</div>
83
</div>
84
<ul class="flex flex-col gap-4 mt-8">
85
{filteredSites.map(d => {
86
return (
87
<li class="w-full text-zinc-500">
88
<a
89
href={d.link}
90
class="relative w-full transition duration-300 link"
91
>
92
{d.title}
93
</a>
94
</li>
95
)
96
})}
97
</ul>
98
</div>
99
</div>
100
)
101
}
102
103
function microsearch(collection, paths) {
104
const index = collection.map(d => paths.map(p => get(d, p)))
105
return term => {
106
return index
107
.map((d, index) => {
108
return [d, index]
109
})
110
.filter(val =>
111
val[0].find(t => t.toLowerCase().includes(term.toLowerCase()))
112
)
113
.map(matches => collection[matches[1]])
114
}
115
}
116