forked from phc/website
feat: poisson user page with fuzzy search
This commit is contained in:
+3
-3
@@ -19,9 +19,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
integrations: [preact(), mdx()],
|
integrations: [preact(), mdx()],
|
||||||
adapter: node({
|
// adapter: node({
|
||||||
mode: 'standalone',
|
// mode: 'standalone',
|
||||||
}),
|
// }),
|
||||||
output: 'hybrid',
|
output: 'hybrid',
|
||||||
outDir: './out/astro',
|
outDir: './out/astro',
|
||||||
build: {
|
build: {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"astro": "^4.15.11",
|
"astro": "^4.15.11",
|
||||||
"better-sqlite3": "^9.4.3",
|
"better-sqlite3": "^9.4.3",
|
||||||
"drizzle-orm": "^0.29.4",
|
"drizzle-orm": "^0.29.4",
|
||||||
|
"fuse.js": "^7.0.0",
|
||||||
"katex": "^0.16.9",
|
"katex": "^0.16.9",
|
||||||
"preact": "^10.19.6",
|
"preact": "^10.19.6",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { useComputed, useSignal, type ReadonlySignal } from '@preact/signals'
|
||||||
|
import type { JSX } from 'preact/jsx-runtime'
|
||||||
|
|
||||||
|
export const ShowMore = <T extends any>({
|
||||||
|
items,
|
||||||
|
pageSize,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
items: ReadonlySignal<T[]>
|
||||||
|
pageSize: number
|
||||||
|
children: (item: T) => JSX.Element
|
||||||
|
}) => {
|
||||||
|
const $shownItems = useSignal(pageSize)
|
||||||
|
|
||||||
|
const $paginatedItems = useComputed(() => {
|
||||||
|
return items.value.slice(0, $shownItems.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log($paginatedItems.value)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{$paginatedItems.value.map(children)}
|
||||||
|
<div class="show-more">
|
||||||
|
{$shownItems.value < items.value.length && (
|
||||||
|
<button onClick={() => ($shownItems.value += pageSize)}>Mostra altri</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
+46
-17
@@ -1,5 +1,8 @@
|
|||||||
import { useSignal } from '@preact/signals'
|
import { useComputed, useSignal } from '@preact/signals'
|
||||||
|
import Fuse from 'fuse.js'
|
||||||
import { useEffect } from 'preact/hooks'
|
import { useEffect } from 'preact/hooks'
|
||||||
|
import { trottleDebounce } from './lib/util'
|
||||||
|
import { ShowMore } from './Paginate'
|
||||||
|
|
||||||
type User = {
|
type User = {
|
||||||
uid: string
|
uid: string
|
||||||
@@ -20,37 +23,63 @@ function applyPatches(users: User[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const UtentiPage = () => {
|
export const UtentiPage = () => {
|
||||||
const utentiSignal = useSignal<User[]>([])
|
const $utentiData = useSignal<User[]>([])
|
||||||
|
const $fuse = useSignal<Fuse<User> | null>(null)
|
||||||
|
|
||||||
|
const $searchText = useSignal('')
|
||||||
|
const $searchResults = useComputed(() =>
|
||||||
|
$searchText.value.trim().length > 0
|
||||||
|
? ($fuse.value?.search($searchText.value).map(result => result.item) ?? [])
|
||||||
|
: $utentiData.value,
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('https://poisson.phc.dm.unipi.it/users.json')
|
fetch('https://poisson.phc.dm.unipi.it/users.json')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
applyPatches(data)
|
applyPatches(data)
|
||||||
utentiSignal.value = data
|
|
||||||
|
$utentiData.value = data
|
||||||
|
$fuse.value = new Fuse(data, {
|
||||||
|
keys: ['gecos'],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<input type="text" placeholder="Cerca un utente Poisson..." />
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Cerca un utente Poisson..."
|
||||||
|
onInput={e => ($searchText.value = e.currentTarget.value)}
|
||||||
|
value={$searchText.value}
|
||||||
|
/>
|
||||||
<span class="material-symbols-outlined">search</span>
|
<span class="material-symbols-outlined">search</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-results">
|
<div class="search-results">
|
||||||
{utentiSignal.value.map(user => (
|
{$searchResults.value ? (
|
||||||
<div class="search-result">
|
<ShowMore items={$searchResults} pageSize={10}>
|
||||||
<div class="icon">
|
{poissonUser => (
|
||||||
<span class="material-symbols-outlined">person</span>
|
console.log(poissonUser),
|
||||||
</div>
|
(
|
||||||
<div class="text">{user.gecos}</div>
|
<div class="search-result">
|
||||||
<div class="right">
|
<div class="icon">
|
||||||
<a href={`https://poisson.phc.dm.unipi.it/~${user.uid}`}>
|
<span class="material-symbols-outlined">person</span>
|
||||||
<span class="material-symbols-outlined">globe</span>
|
</div>
|
||||||
</a>
|
<div class="text">{poissonUser.gecos}</div>
|
||||||
</div>
|
<div class="right">
|
||||||
</div>
|
<a href={`https://poisson.phc.dm.unipi.it/~${poissonUser.uid}`}>
|
||||||
))}
|
<span class="material-symbols-outlined">globe</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ShowMore>
|
||||||
|
) : (
|
||||||
|
<>Nessun risultato</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
export const trottleDebounce = <T extends any[], R>(
|
||||||
|
fn: (...args: T) => R,
|
||||||
|
delay: number,
|
||||||
|
options: { leading?: boolean; trailing?: boolean } = {},
|
||||||
|
): ((...args: T) => R | undefined) => {
|
||||||
|
let lastCall = 0
|
||||||
|
let lastResult: R | undefined
|
||||||
|
let lastArgs: T | undefined
|
||||||
|
let timeout: NodeJS.Timeout | undefined
|
||||||
|
|
||||||
|
const leading = options.leading ?? true
|
||||||
|
const trailing = options.trailing ?? true
|
||||||
|
|
||||||
|
return (...args: T): R | undefined => {
|
||||||
|
lastArgs = args
|
||||||
|
if (leading && Date.now() - lastCall >= delay) {
|
||||||
|
lastCall = Date.now()
|
||||||
|
lastResult = fn(...args)
|
||||||
|
} else {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
if (trailing && lastArgs) {
|
||||||
|
lastCall = Date.now()
|
||||||
|
lastResult = fn(...lastArgs)
|
||||||
|
}
|
||||||
|
}, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(lastResult)
|
||||||
|
return lastResult
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
# Questo documento è utilizzato nella homepage del sito nella card principale
|
||||||
title: Cos'è il PHC?
|
title: Cos'è il PHC?
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -594,4 +594,8 @@
|
|||||||
max-width: 25rem;
|
max-width: 25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.show-more {
|
||||||
|
place-self: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,6 +239,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.utenti {
|
.utenti {
|
||||||
|
background: #ffffe4;
|
||||||
|
|
||||||
main {
|
main {
|
||||||
justify-self: center;
|
justify-self: center;
|
||||||
|
|
||||||
@@ -250,6 +252,14 @@
|
|||||||
padding: 5rem 0;
|
padding: 5rem 0;
|
||||||
|
|
||||||
gap: 5rem;
|
gap: 5rem;
|
||||||
|
|
||||||
|
.search-result {
|
||||||
|
background: #ffeabc;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: #ffd270;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user