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