117 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
.slice()
28
.sort(
29
(x, y) => new Date(y.addedOn).getTime() - new Date(x.addedOn).getTime()
30
)
31
.slice(0, 4)
32
33
const totalCount = sites.length
34
35
const filteredSites = (searchTerm ? searcher(searchTerm) : sites)
36
.slice()
37
.sort((x, y) => x.title.localeCompare(y.title))
38
39
return (
40
<div class="p-10 mx-auto max-w-4xl">
41
<div class="flex justify-end w-full">
42
<h1 class="font-sans text-sm text-zinc-600">minweb.site</h1>
43
</div>
44
<div class="my-24">
45
<h2 class="font-semibold">Recent</h2>
46
<ul class="flex flex-col gap-4 mt-8 w-full">
47
{recents.map(d => {
48
return (
49
<li class="w-full text-zinc-500">
50
<a
51
href={d.link}
52
class="relative w-full transition duration-300 link"
53
>
54
{d.title}
55
<span class="font-sans italic absolute top-0 text-[8px] text-emerald-400 -right-99 min-w-44">
56
{formatDistanceToNow(new Date(d.addedOn), {
57
addSuffix: true,
58
})}
59
</span>
60
</a>
61
</li>
62
)
63
})}
64
</ul>
65
</div>
66
67
<div class="my-24">
68
<div class="flex flex-wrap gap-2 justify-between">
69
<h2 class="font-semibold">
70
All <span class="text-zinc-500">( {totalCount} )</span>
71
</h2>
72
<div class="flex min-w-56">
73
<input
74
name="search"
75
class="transition-colors duration-300 input focus:ring-0 focus:border-emerald-400"
76
placeholder="search"
77
ref={node => {
78
if (!node) return
79
node.addEventListener('keyup', e => {
80
setSearchTerm(e.target.value)
81
})
82
}}
83
/>
84
</div>
85
</div>
86
<ul class="flex flex-col gap-4 mt-8">
87
{filteredSites.map(d => {
88
return (
89
<li class="w-full text-zinc-500">
90
<a
91
href={d.link}
92
class="relative w-full transition duration-300 link"
93
>
94
{d.title}
95
</a>
96
</li>
97
)
98
})}
99
</ul>
100
</div>
101
</div>
102
)
103
}
104
105
function microsearch(collection, paths) {
106
const index = collection.map(d => paths.map(p => get(d, p)))
107
return term => {
108
return index
109
.map((d, index) => {
110
return [d, index]
111
})
112
.filter(val =>
113
val[0].find(t => t.toLowerCase().includes(term.toLowerCase()))
114
)
115
.map(matches => collection[matches[1]])
116
}
117
}
118