Compare commits
50 Commits
feat-admin
...
main
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"printWidth": 110,
|
|
||||||
"singleQuote": true,
|
|
||||||
"quoteProps": "consistent",
|
|
||||||
"tabWidth": 4,
|
|
||||||
"useTabs": false,
|
|
||||||
"semi": false,
|
|
||||||
"arrowParens": "avoid"
|
|
||||||
}
|
|
@ -0,0 +1,27 @@
|
|||||||
|
/** @type {import("prettier").Config} */
|
||||||
|
export default {
|
||||||
|
printWidth: 120,
|
||||||
|
singleQuote: true,
|
||||||
|
quoteProps: 'consistent',
|
||||||
|
tabWidth: 4,
|
||||||
|
useTabs: false,
|
||||||
|
semi: false,
|
||||||
|
arrowParens: 'avoid',
|
||||||
|
|
||||||
|
plugins: ['prettier-plugin-astro'],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: '*.astro',
|
||||||
|
options: {
|
||||||
|
parser: 'astro',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: '*.{yml,yaml,json}',
|
||||||
|
excludeFiles: 'package-lock.json',
|
||||||
|
options: {
|
||||||
|
tabWidth: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
@ -1,3 +1,11 @@
|
|||||||
{
|
{
|
||||||
"npm.packageManager": "bun"
|
"npm.packageManager": "bun",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"[astro]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[yaml]": {
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 2.8 MiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 645 KiB |
After Width: | Height: | Size: 1.8 MiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 262 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 342 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 608 KiB |
After Width: | Height: | Size: 295 KiB |
@ -0,0 +1,119 @@
|
|||||||
|
import { useEffect, useState } from 'preact/hooks'
|
||||||
|
import { Funnel } from '@phosphor-icons/react'
|
||||||
|
import { marked } from 'marked'
|
||||||
|
|
||||||
|
import extendedLatex from '@/client/lib/marked-latex'
|
||||||
|
|
||||||
|
marked.use(
|
||||||
|
extendedLatex({
|
||||||
|
lazy: false,
|
||||||
|
render: (formula: string, display: boolean) => {
|
||||||
|
return display ? '$$' + formula + '$$' : '$' + formula + '$'
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
import type { Database } from '@/data/domande-esami.yaml'
|
||||||
|
|
||||||
|
const useRemoteValue = <T,>(url: string): T | null => {
|
||||||
|
const [value, setValue] = useState<T | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch(url)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(value => setValue(value))
|
||||||
|
.catch(error => console.error(error))
|
||||||
|
}, [url])
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
course: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DomandeEsamiCourse = ({ course }: Props) => {
|
||||||
|
const database = useRemoteValue<Database>(`/domande-esami/api/${course}.json`)
|
||||||
|
if (!database) {
|
||||||
|
return <>Loading...</>
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('requestIdleCallback' in window) {
|
||||||
|
// @ts-ignore
|
||||||
|
requestIdleCallback(() => window.renderMath())
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
setTimeout(() => window.renderMath(), 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
const courseTags = [
|
||||||
|
...new Set(
|
||||||
|
database.questions.filter(question => question.course === course).flatMap(question => question.tags),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
const [selectedTag, setSelectedTag] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const filteredQuestions = database.questions
|
||||||
|
.filter(question => question.course === course)
|
||||||
|
.filter(question => (selectedTag ? question.tags.includes(selectedTag) : true))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="grid-center text-center">
|
||||||
|
<h3>
|
||||||
|
<a href="/domande-esami">Domande Orali</a>
|
||||||
|
</h3>
|
||||||
|
<h1>{database.names[course]}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{courseTags.length > 1 && (
|
||||||
|
<div class="card filter">
|
||||||
|
<div class="grid-h">
|
||||||
|
<Funnel />
|
||||||
|
<strong>Filtra Tag</strong>
|
||||||
|
</div>
|
||||||
|
<div class="flex-row-wrap">
|
||||||
|
{!selectedTag
|
||||||
|
? courseTags.map(tag => (
|
||||||
|
<div class="chip clickable" onClick={() => setSelectedTag(tag)}>
|
||||||
|
{tag}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: courseTags.map(tag => (
|
||||||
|
<div
|
||||||
|
class={tag === selectedTag ? 'chip clickable' : 'chip clickable disabled'}
|
||||||
|
onClick={() => setSelectedTag(tag === selectedTag ? null : tag)}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div class="wide-card-list" id="questions">
|
||||||
|
{filteredQuestions.length === 0 ? (
|
||||||
|
<div class="grid-center">
|
||||||
|
<em>No questions found</em>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
filteredQuestions.map(question => (
|
||||||
|
<div class="card">
|
||||||
|
<div
|
||||||
|
class="text"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: marked(question.content, { async: false }),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div class="metadata">
|
||||||
|
{question.tags.map(tag => (
|
||||||
|
<div class="chip small">{tag}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
const icons = Object.fromEntries(
|
||||||
|
Object.entries(
|
||||||
|
import.meta.glob<{ default: ImageMetadata }>(`node_modules/@phosphor-icons/core/assets/light/*.svg`, {
|
||||||
|
eager: true,
|
||||||
|
}),
|
||||||
|
).map(([path, module]) => [path.split('/').pop()!.split('.')[0].replace('-light', ''), module]),
|
||||||
|
)
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PhosphorIcon = ({ name }: Props) => {
|
||||||
|
const icon = icons[name]
|
||||||
|
|
||||||
|
if (!icon) {
|
||||||
|
throw new Error(`Icon "${name}" not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <img class="phosphor-icon" src={icon.default.src} alt={name} />
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
const $debugConsole = document.createElement('div')
|
||||||
|
|
||||||
|
$debugConsole.style.position = 'fixed'
|
||||||
|
$debugConsole.style.bottom = '0'
|
||||||
|
$debugConsole.style.left = '0'
|
||||||
|
$debugConsole.style.width = '100%'
|
||||||
|
$debugConsole.style.height = '25vh'
|
||||||
|
$debugConsole.style.backgroundColor = 'black'
|
||||||
|
$debugConsole.style.color = 'white'
|
||||||
|
$debugConsole.style.overflow = 'auto'
|
||||||
|
$debugConsole.style.padding = '10px'
|
||||||
|
$debugConsole.style.boxSizing = 'border-box'
|
||||||
|
$debugConsole.style.fontFamily = 'monospace'
|
||||||
|
$debugConsole.style.zIndex = '9999'
|
||||||
|
$debugConsole.style.fontSize = '15px'
|
||||||
|
$debugConsole.style.opacity = '0.8'
|
||||||
|
|
||||||
|
document.body.appendChild($debugConsole)
|
||||||
|
|
||||||
|
function logDebugConsole(...args) {
|
||||||
|
$debugConsole.innerHTML += args.join(' ') + '<br>'
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error = logDebugConsole
|
||||||
|
console.warn = logDebugConsole
|
||||||
|
console.log = logDebugConsole
|
||||||
|
console.debug = logDebugConsole
|
@ -0,0 +1,83 @@
|
|||||||
|
// took from: https://github.com/sxyazi/marked-extended-latex
|
||||||
|
// this has a peer dependency bug
|
||||||
|
|
||||||
|
const CLASS_NAME = 'latex-b172fea480b'
|
||||||
|
|
||||||
|
const extBlock = options => ({
|
||||||
|
name: 'latex-block',
|
||||||
|
level: 'block',
|
||||||
|
start(src) {
|
||||||
|
return src.match(/\$\$[^\$]/)?.index ?? -1
|
||||||
|
},
|
||||||
|
tokenizer(src, tokens) {
|
||||||
|
const match = /^\$\$([^\$]+)\$\$/.exec(src)
|
||||||
|
return match ? { type: 'latex-block', raw: match[0], formula: match[1] } : undefined
|
||||||
|
},
|
||||||
|
renderer(token) {
|
||||||
|
if (!options.lazy) return options.render(token.formula, true)
|
||||||
|
return `<span class="${CLASS_NAME}" block>${token.formula}</span>`
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const extInline = options => ({
|
||||||
|
name: 'latex',
|
||||||
|
level: 'inline',
|
||||||
|
start(src) {
|
||||||
|
return src.match(/\$[^\$]/)?.index ?? -1
|
||||||
|
},
|
||||||
|
tokenizer(src, tokens) {
|
||||||
|
const match = /^\$([^\$]+)\$/.exec(src)
|
||||||
|
return match ? { type: 'latex', raw: match[0], formula: match[1] } : undefined
|
||||||
|
},
|
||||||
|
renderer(token) {
|
||||||
|
if (!options.lazy) return options.render(token.formula, false)
|
||||||
|
return `<span class="${CLASS_NAME}">${token.formula}</span>`
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let observer
|
||||||
|
/* istanbul ignore next */
|
||||||
|
export default (options = {}) => {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (options.lazy && options.env !== 'test') {
|
||||||
|
observer = new IntersectionObserver(
|
||||||
|
(entries, self) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isIntersecting) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const span = entry.target
|
||||||
|
self.unobserve(span)
|
||||||
|
|
||||||
|
Promise.resolve(options.render(span.innerText, span.hasAttribute('block'))).then(html => {
|
||||||
|
span.innerHTML = html
|
||||||
|
})
|
||||||
|
span.classList.add('latex-rendered')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 1.0 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
extensions: [extBlock(options), extInline(options)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
export const observe = () => {
|
||||||
|
if (!observer) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
observer.disconnect()
|
||||||
|
document.querySelectorAll(`span.${CLASS_NAME}:not(.latex-rendered)`).forEach(span => {
|
||||||
|
observer.observe(span)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
export const disconnect = () => {
|
||||||
|
observer?.disconnect()
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
import PhosphorIcon from './PhosphorIcon.astro'
|
||||||
|
|
||||||
|
const ICONS_MAP: Record<string, string> = {
|
||||||
|
github: 'github-logo',
|
||||||
|
linkedin: 'linkedin-logo',
|
||||||
|
website: 'globe',
|
||||||
|
mail: 'mailbox',
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
fullName: string
|
||||||
|
description: string
|
||||||
|
|
||||||
|
image: ImageMetadata
|
||||||
|
|
||||||
|
entranceDate: number
|
||||||
|
exitDate?: number
|
||||||
|
|
||||||
|
founder?: boolean
|
||||||
|
|
||||||
|
social?: {
|
||||||
|
github?: string
|
||||||
|
linkedin?: string
|
||||||
|
website?: string
|
||||||
|
mail?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { fullName, description, image, entranceDate, exitDate, founder, social } = Astro.props
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="bubble">
|
||||||
|
<img src={image.src} alt={fullName.toLowerCase()} />
|
||||||
|
<div class="title">{fullName}</div>
|
||||||
|
<div class="date">{entranceDate}—{exitDate ?? 'Presente'}</div>
|
||||||
|
{founder && <div class="founder">Fondatore</div>}
|
||||||
|
<div class="description">{description}</div>
|
||||||
|
{
|
||||||
|
social && (
|
||||||
|
<div class="social">
|
||||||
|
{Object.entries(social).map(([key, value]) => (
|
||||||
|
<a href={value} target="_blank" rel="noopener noreferrer">
|
||||||
|
<PhosphorIcon name={ICONS_MAP[key] ?? 'question-mark'} />
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
import { Image } from 'astro:assets'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name } = Astro.props
|
||||||
|
|
||||||
|
const icons = Object.fromEntries(
|
||||||
|
Object.entries(
|
||||||
|
import.meta.glob<{ default: ImageMetadata }>(`node_modules/@phosphor-icons/core/assets/light/*.svg`),
|
||||||
|
).map(([path, module]) => [path.split('/').pop()!.split('.')[0].replace('-light', ''), module]),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!icons[name]) {
|
||||||
|
throw new Error(`Icon "${name}" not found`)
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<Image class="phosphor-icon" src={icons[name]()} alt={name} />
|
@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
id: eliminare-sito-poisson
|
||||||
|
title: Come tolgo la mia pagina Poisson da Google?
|
||||||
|
description: Sei divenuto troppo famoso 🤩 e vuoi eliminare la tua pagina Poisson dai risultati di ricerca di Google? Ecco come farlo!
|
||||||
|
author: Antonio De Lucreziis
|
||||||
|
tags: [poisson, sito]
|
||||||
|
---
|
||||||
|
|
||||||
|
Hai un nuovo sito web e vuoi che venga indicizzato da Google prima della tua pagina Poisson? Oppure sei diventato troppo famoso, e vuoi eliminare la tua pagina Poisson dai risultati di ricerca di Google? In entrambi i casi, vediamo come fare.
|
||||||
|
|
||||||
|
Ricorda che la tua pagina Poisson è indicizzata da Google in quanto pubblicamente accessibile. Per "toglierla" dai risultati di ricerca dovremo chiedere a Google di non indicizzarla più, e quindi innanzitutto dire a Google che siamo i proprietari di quella pagina (altrimenti non ci permetterà di rimuoverla).
|
||||||
|
|
||||||
|
> **Attenzione**: in questo articolo, sostituisci sempre `USERNAME` con il tuo username Poisson (non `n.cognome`, quello è il tuo username di Ateneo).
|
||||||
|
|
||||||
|
## Cancellazione pagina Poisson
|
||||||
|
|
||||||
|
Invece di eliminare la tua pagina Poisson, puoi anche solo ridirezionarla al tuo nuovo sito web. Se fossi proprio sicuro di volerla eliminare, vediamo ora come fare.
|
||||||
|
|
||||||
|
A questo punto, se vogliamo anche eliminare tutta la pagina Poisson, possiamo farlo con il comando
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ssh USERNAME@poisson.phc.dm.unipi.it
|
||||||
|
$ cd public_html
|
||||||
|
$ rm -rf *
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Attenzione!** La cartella `public_html` nella propria home in realtà è un **link simbolico** alla cartella `public_html`, che fisicamente sta altrove. Per sostituire o cancellare il contenuto della propria pagina Poisson, dovremo eliminare tutti i file contenuti nella cartella `public_html`, _ma non la cartella stessa_.
|
||||||
|
|
||||||
|
## Google Search Console
|
||||||
|
|
||||||
|
### 1. Verifica della proprietà
|
||||||
|
|
||||||
|
Prima di tutto visita la pagina <https://search.google.com/search-console>, fai l'accesso con un account Google di tuo piacimento e clicca su "Aggiungi proprietà". Poisson utilizza la convenzione che le pagine personali sono raggiungibili all'indirizzo `https://poisson.phc.dm.unipi.it/~USERNAME`, quindi seleziona l'opzione "Prefisso URL" / "URL prefix" per poi inserire l'URL della tua pagina Poisson (es. `https://poisson.phc.dm.unipi.it/~USERNAME`).
|
||||||
|
|
||||||
|
Dopo una breve attesa, Google ci chiederà di verificare la nostra proprietà con alcuni metodi. Il metodo più semplice è quello di inserire un file di verifica nella nostra cartella pubblica. Scarica il file di verifica e copialo nella tua cartella pubblica: se ad esempio il file si chiama `google1234567890.html`, puoi caricarlo nella tua cartella pubblica con il seguente comando ([clicca QUI se non ricordi le credenziali](/guide/recupero-password)):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vai nella cartella in cui hai scaricato il file di verifica
|
||||||
|
$ ls
|
||||||
|
... google1234567890.html ...
|
||||||
|
|
||||||
|
# Copia il file nella public_html su Poisson
|
||||||
|
$ scp google1234567890.html USERNAME@poisson.phc.dm.unipi.it:public_html/
|
||||||
|
```
|
||||||
|
|
||||||
|
Dopo qualche minuto, torniamo su Google Search Console e clicchiamo su "Verifica". Se tutto è andato a buon fine, Google ci darà accesso ai dati di indicizzazione della nostra pagina Poisson.
|
||||||
|
|
||||||
|
### 2. Richiesta di Rimozione
|
||||||
|
|
||||||
|
Nella barra laterale di sinistra, nella sezione "Indicizzazione", clicchiamo su "Rimozioni", dopodichè in "Rimozioni Temporanee" clicchiamo su "Nuova Richiesta" e selezioniamo l'opzione "Rimuovi tutti gli URL con questo prefisso". Come prefisso, andiamo ad inserire `https://poisson.phc.dm.unipi.it/~USERNAME` per rimuovere la nostra pagina Poisson e tutte le sue sottopagine. Infine, clicchiamo su "Invia richiesta di rimozione" per confermare.
|
||||||
|
|
||||||
|
### 3. Attesa...
|
||||||
|
|
||||||
|
Considera che Google potrebbe metterci fino a qualche giorno per rimuovere le pagine indicizzate. Ben fatto! Ora la tua pagina Poisson non sarà più indicizzata da Google.
|
||||||
|
|
||||||
|
## Redirect
|
||||||
|
|
||||||
|
Se invece vuoi che la tua pagina Poisson rimandi al tuo nuovo sito web, puoi impostare un redirect. Per fare ciò, ti basterà creare un file `.htaccess` nella cartella `public_html/` della tua home con il seguente contenuto:
|
||||||
|
|
||||||
|
```apache
|
||||||
|
RedirectMatch 307 /~USERNAME/.* https://www.mio-nuovo-sito.com/
|
||||||
|
```
|
||||||
|
|
||||||
|
Qui, il codice `307` indica un redirect temporaneo; se sei sicuro di volere un redirect permanente puoi usare direttamente il codice `301` (la differenza tecnica tra i due è che il primo non salva la cache del redirect nel browser, mentre il secondo sì).
|
||||||
|
|
||||||
|
## Conclusioni
|
||||||
|
|
||||||
|
Per rimuovere la tua pagina Poisson dai risultati di ricerca di altri motori di ricerca come Bing, DuckDuckGo, ecc., bisogna seguire procedure simili. Se fosse proprio necessario scrivi pure a noi macchinisti, e ricorda sempre che la tua pagina Poisson è pubblica e accessibile a tutti, quindi non metterci cose che non vuoi che siano pubbliche.
|
@ -0,0 +1,94 @@
|
|||||||
|
# the schema of this file in "@/files.d.ts"
|
||||||
|
|
||||||
|
- fullName: Antonio De Lucreziis
|
||||||
|
entranceDate: 2019
|
||||||
|
description: |
|
||||||
|
Appassionato di geometria computazionale, parser, teoria dei linguaggi di programmazione, Smalltalk e Lisp.
|
||||||
|
social:
|
||||||
|
github: https://github.com/aziis98
|
||||||
|
website: https://poisson.phc.dm.unipi.it/~delucreziis/
|
||||||
|
|
||||||
|
- fullName: Luca Lombardo
|
||||||
|
entranceDate: 2024
|
||||||
|
description: Appassionato di algoritmi e strutture dati, Rust e di quando matematica e informatica si incontrano.
|
||||||
|
social:
|
||||||
|
github: https://github.com/lukefleed
|
||||||
|
website: https://lukefleex.xyz
|
||||||
|
linkedin: https://www.linkedin.com/in/l-lombardo/
|
||||||
|
|
||||||
|
- fullName: Francesco Minnocci
|
||||||
|
entranceDate: 2022
|
||||||
|
description: Chitarrista classico ossessionato con la geometria aritmetica, linux e il rock progressivo.
|
||||||
|
social:
|
||||||
|
github: https://github.com/BachoSeven
|
||||||
|
website: https://bachoseven.com
|
||||||
|
|
||||||
|
- fullName: Francesco Baldino
|
||||||
|
entranceDate: 2022
|
||||||
|
description: Bla bla Star Wars
|
||||||
|
social:
|
||||||
|
github: https://github.com/Fran314
|
||||||
|
website: https://poisson.phc.dm.unipi.it/~baldino
|
||||||
|
|
||||||
|
- fullName: Illya Serdyuk
|
||||||
|
entranceDate: 2020
|
||||||
|
social:
|
||||||
|
github: https://github.com/Kratacoa
|
||||||
|
|
||||||
|
# Vecchi Macchinisti
|
||||||
|
|
||||||
|
- fullName: Francesco Manicastri
|
||||||
|
entranceDate: 2020
|
||||||
|
exitDate: 2024
|
||||||
|
social:
|
||||||
|
linkedin: https://www.linkedin.com/in/gustavo-sass%C3%ACnculo-phd-92916a202/
|
||||||
|
|
||||||
|
- fullName: Cristiano Cricci
|
||||||
|
entranceDate: 2010
|
||||||
|
exitDate: 2019
|
||||||
|
social:
|
||||||
|
website: https://poisson.phc.dm.unipi.it/~cricci/
|
||||||
|
|
||||||
|
- fullName: Tommaso Biannucci
|
||||||
|
entranceDate: 2019
|
||||||
|
exitDate: 2022
|
||||||
|
social:
|
||||||
|
github: https://gitlab.com/churli
|
||||||
|
website: https://churli.gitlab.io/
|
||||||
|
|
||||||
|
- fullName: Letizia D'Achille
|
||||||
|
entranceDate: 2018
|
||||||
|
exitDate: 2022
|
||||||
|
description: Appassionata di crittografia, teoria dei codici e matematica computazionale.
|
||||||
|
social:
|
||||||
|
github: https://github.com/letizia-dachille
|
||||||
|
website: https://letizia-dachille.github.io/
|
||||||
|
linkedin: https://www.linkedin.com/in/letizia-dachille/
|
||||||
|
|
||||||
|
- fullName: Emiliano Rago
|
||||||
|
entranceDate: 2000
|
||||||
|
exitDate: 2006
|
||||||
|
description: Traviato da Linux in età troppo giovane ha difficoltà ad usare il mouse ma ama gli shortcuts con una decina di tasti.
|
||||||
|
social:
|
||||||
|
linkedin: https://www.linkedin.com/in/emiliano-rago-1a8018109/
|
||||||
|
|
||||||
|
- fullName: Francesco Caporali
|
||||||
|
entranceDate: 2018
|
||||||
|
exitDate: 2022
|
||||||
|
social:
|
||||||
|
github: https://github.com/caporali
|
||||||
|
website: https://caporali.github.io/
|
||||||
|
linkedin: https://www.linkedin.com/in/francescocaporali
|
||||||
|
|
||||||
|
- fullName: Antonio Spanu
|
||||||
|
entranceDate: 2005
|
||||||
|
exitDate: 2009
|
||||||
|
social:
|
||||||
|
linkedin: https://www.linkedin.com/in/antonio-spanu-609b3516a/
|
||||||
|
|
||||||
|
- fullName: Riccardo Murri
|
||||||
|
entranceDate: 1995
|
||||||
|
exitDate: 2000
|
||||||
|
founder: true
|
||||||
|
social:
|
||||||
|
github: https://github.com/riccardomurri
|
@ -0,0 +1,44 @@
|
|||||||
|
declare module '*.yaml' {
|
||||||
|
const value: any // Add type definitions here if desired
|
||||||
|
export default value
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@/data/macchinisti.yaml' {
|
||||||
|
type Macchinista = {
|
||||||
|
fullName: string
|
||||||
|
description: string
|
||||||
|
|
||||||
|
entranceDate: number
|
||||||
|
exitDate?: number
|
||||||
|
|
||||||
|
social: Record<string, string>
|
||||||
|
|
||||||
|
founder?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const value: Macchinista[]
|
||||||
|
export default value
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@/data/domande-esami.yaml' {
|
||||||
|
export type Question = {
|
||||||
|
course: string
|
||||||
|
content: string
|
||||||
|
tags: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Group = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
items: Array<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Database = {
|
||||||
|
names: Record<string, string>
|
||||||
|
groups: Group[]
|
||||||
|
questions: Question[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const value: Database
|
||||||
|
export default value
|
||||||
|
}
|
@ -1,123 +0,0 @@
|
|||||||
---
|
|
||||||
import Header from '@/components/Header.astro'
|
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro'
|
|
||||||
---
|
|
||||||
|
|
||||||
<BaseLayout title="Admin | PHC" pageTags={'admin'}>
|
|
||||||
<Header />
|
|
||||||
<div class="layout-admin">
|
|
||||||
<div class="sidebar">
|
|
||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="/admin">
|
|
||||||
<span class="material-symbols-outlined">dashboard</span>
|
|
||||||
Dashboard
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/admin/utenti">
|
|
||||||
<span class="material-symbols-outlined">group</span>
|
|
||||||
Utenti
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/admin/appunti">
|
|
||||||
<span class="material-symbols-outlined">book_2</span>
|
|
||||||
Appunti
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/admin/database">
|
|
||||||
<span class="material-symbols-outlined">database</span>
|
|
||||||
Database
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<div class="admin-dashboard-grid max-width-content">
|
|
||||||
<div class="card">
|
|
||||||
<div class="title">
|
|
||||||
<a href="/admin/utenti">Utenti</a>
|
|
||||||
</div>
|
|
||||||
<div class="text">Statistiche degli utenti di Poisson e PHC</div>
|
|
||||||
<div class="boxes">
|
|
||||||
<div class="box">
|
|
||||||
<div class="title">Utenti Poisson</div>
|
|
||||||
<div class="material-symbols-outlined extra-large">group</div>
|
|
||||||
<div class="description">1356</div>
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<div class="title">Utenti PHC</div>
|
|
||||||
<div class="material-symbols-outlined extra-large">group</div>
|
|
||||||
<div class="description">345</div>
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<div class="title">Utenti con account collegato</div>
|
|
||||||
<div class="material-symbols-outlined extra-large">group</div>
|
|
||||||
<div class="description">56</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="title">
|
|
||||||
<a href="/admin/appunti">Appunti</a>
|
|
||||||
</div>
|
|
||||||
<div class="text">Statistiche degli Appunti</div>
|
|
||||||
<div class="boxes">
|
|
||||||
<div class="box">
|
|
||||||
<div class="title">Appunti Caricati</div>
|
|
||||||
<div class="material-symbols-outlined extra-large">book_2</div>
|
|
||||||
<div class="description">567</div>
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<div class="title">Appunti Pubblicati</div>
|
|
||||||
<div class="material-symbols-outlined extra-large">book_2</div>
|
|
||||||
<div class="description">345</div>
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<div class="title">In attesa di revisione</div>
|
|
||||||
<div class="material-symbols-outlined extra-large">book_2</div>
|
|
||||||
<div class="description">234</div>
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<div class="title">Spazio Usato (di Minio)</div>
|
|
||||||
<div class="material-symbols-outlined extra-large">storage</div>
|
|
||||||
<div class="description">34.5 GB</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="title">
|
|
||||||
<a href="/admin/database">Database</a>
|
|
||||||
</div>
|
|
||||||
<div class="text">Statistiche del Database</div>
|
|
||||||
<div class="boxes">
|
|
||||||
<div class="box">
|
|
||||||
<div class="title">Record nel database</div>
|
|
||||||
<div class="material-symbols-outlined extra-large">database</div>
|
|
||||||
<div class="description">3456</div>
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<div class="title">Record modificati</div>
|
|
||||||
<div class="material-symbols-outlined extra-large">database</div>
|
|
||||||
<div class="description">234</div>
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<div class="title">Record eliminati</div>
|
|
||||||
<div class="material-symbols-outlined extra-large">database</div>
|
|
||||||
<div class="description">56</div>
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<div class="title">Spazio utilizzato</div>
|
|
||||||
<div class="material-symbols-outlined extra-large">storage</div>
|
|
||||||
<div class="description">39.5 MB</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
@ -1,88 +0,0 @@
|
|||||||
---
|
|
||||||
import Header from '@/components/Header.astro'
|
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro'
|
|
||||||
---
|
|
||||||
|
|
||||||
<BaseLayout title="Utenti | Admin | PHC" pageTags={'admin'}>
|
|
||||||
<Header />
|
|
||||||
<div class="layout-admin">
|
|
||||||
<div class="sidebar">
|
|
||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="/admin">
|
|
||||||
<span class="material-symbols-outlined">dashboard</span>
|
|
||||||
Dashboard
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/admin/utenti">
|
|
||||||
<span class="material-symbols-outlined">group</span>
|
|
||||||
Utenti
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/admin/appunti">
|
|
||||||
<span class="material-symbols-outlined">book_2</span>
|
|
||||||
Appunti
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/admin/database">
|
|
||||||
<span class="material-symbols-outlined">database</span>
|
|
||||||
Database
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<div class="admin-table max-width-content">
|
|
||||||
<div class="table-header">
|
|
||||||
<div class="table-title">
|
|
||||||
<span class="material-symbols-outlined">group</span>
|
|
||||||
Utenti
|
|
||||||
</div>
|
|
||||||
<div class="table-actions">
|
|
||||||
<a href="/admin/utenti/aggiungi" class="button">Aggiungi</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="table-filter stack-h">
|
|
||||||
<input type="text" class="fill-w shrink" placeholder="Cerca utente" />
|
|
||||||
<button class="icon">
|
|
||||||
<div class="material-symbols-outlined">search</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="table-body">
|
|
||||||
<div class="table-row header">
|
|
||||||
<div class="table-cell">Nome</div>
|
|
||||||
<div class="table-cell">Cognome</div>
|
|
||||||
<div class="table-cell">Email</div>
|
|
||||||
<div class="table-cell">Ruolo</div>
|
|
||||||
<div class="table-cell">Azioni</div>
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
Array.from({ length: 50 }).map(() => (
|
|
||||||
<Fragment>
|
|
||||||
<div class="table-row">
|
|
||||||
<div class="table-cell">Mario</div>
|
|
||||||
<div class="table-cell">Rossi</div>
|
|
||||||
<div class="table-cell">mario@rossi.com</div>
|
|
||||||
<div class="table-cell">studente</div>
|
|
||||||
<div class="table-cell">
|
|
||||||
<a href="/admin/utenti/modifica" class="button icon">
|
|
||||||
<div class="material-symbols-outlined">edit</div>
|
|
||||||
</a>
|
|
||||||
<a href="/admin/utenti/elimina" class="button icon">
|
|
||||||
<div class="material-symbols-outlined">delete</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
import type { GetStaticPaths } from 'astro'
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
import Footer from '@/components/Footer.astro'
|
||||||
|
import Header from '@/components/Header.astro'
|
||||||
|
|
||||||
|
import database from '@/data/domande-esami.yaml'
|
||||||
|
import { DomandeEsamiCourse } from '@/client/DomandeEsamiCourse'
|
||||||
|
|
||||||
|
export const getStaticPaths = (() => {
|
||||||
|
return Object.keys(database.names).map(course => ({
|
||||||
|
params: { course },
|
||||||
|
}))
|
||||||
|
}) satisfies GetStaticPaths
|
||||||
|
|
||||||
|
const { course } = Astro.params
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title="Domande Orali | PHC" pageTags={'domande-esami'}>
|
||||||
|
<Header />
|
||||||
|
<main>
|
||||||
|
<DomandeEsamiCourse client:only="preact" course={course} />
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</BaseLayout>
|
@ -0,0 +1,11 @@
|
|||||||
|
import type { APIRoute } from 'astro'
|
||||||
|
|
||||||
|
import database from '@/data/domande-esami.yaml'
|
||||||
|
|
||||||
|
export const GET: APIRoute = ({}) => {
|
||||||
|
return new Response(JSON.stringify(database), {
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import type { APIRoute, GetStaticPaths } from 'astro'
|
||||||
|
|
||||||
|
import database from '@/data/domande-esami.yaml'
|
||||||
|
|
||||||
|
export const getStaticPaths = (() => {
|
||||||
|
return Object.keys(database.names).map(course => ({
|
||||||
|
params: { course },
|
||||||
|
}))
|
||||||
|
}) satisfies GetStaticPaths
|
||||||
|
|
||||||
|
export const GET: APIRoute = ({ params: { course } }) => {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
groups: [],
|
||||||
|
names: Object.fromEntries(Object.entries(database.names).filter(([key]) => key === course)),
|
||||||
|
questions: database.questions.filter(question => question.course === course),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
import { PhosphorIcon } from '@/client/Icon'
|
||||||
|
import Footer from '@/components/Footer.astro'
|
||||||
|
import Header from '@/components/Header.astro'
|
||||||
|
import BaseLayout from '@/layouts/BaseLayout.astro'
|
||||||
|
|
||||||
|
import database from '@/data/domande-esami.yaml'
|
||||||
|
|
||||||
|
const courseQuestionCounts = Object.fromEntries(
|
||||||
|
database.questions.reduce((acc, question) => {
|
||||||
|
acc.set(question.course, (acc.get(question.course) || 0) + 1)
|
||||||
|
return acc
|
||||||
|
}, new Map()),
|
||||||
|
)
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title="Domande Orali | PHC" pageTags={'domande-esami'}>
|
||||||
|
<Header />
|
||||||
|
<main>
|
||||||
|
<h1>Domande Orali</h1>
|
||||||
|
{
|
||||||
|
database.groups.map(group => (
|
||||||
|
<details open>
|
||||||
|
<summary>
|
||||||
|
<h2 id={group.id}>
|
||||||
|
<div class="details-closed">
|
||||||
|
<PhosphorIcon name="caret-down" />
|
||||||
|
</div>
|
||||||
|
<div class="details-openned">
|
||||||
|
<PhosphorIcon name="caret-up" />
|
||||||
|
</div>
|
||||||
|
{group.name}
|
||||||
|
</h2>
|
||||||
|
</summary>
|
||||||
|
<div class="wide-card-list">
|
||||||
|
{group.items
|
||||||
|
.filter(course => courseQuestionCounts[course] > 0)
|
||||||
|
.map(course => (
|
||||||
|
<a href={`/domande-esami/${course}`}>
|
||||||
|
<div class="card">
|
||||||
|
<h2>{database.names[course]}</h2>
|
||||||
|
<div class="text">
|
||||||
|
<p>{courseQuestionCounts[course] || 0} domande</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3>Come Contribuire</h3>
|
||||||
|
<div class="card large">
|
||||||
|
<div class="text">
|
||||||
|
<p>
|
||||||
|
Se hai raccolto delle domande da un orale, puoi inviarcele per email all'indirizzo
|
||||||
|
<a href="mailto:macchinisti@lists.dm.unipi.it"> macchinisti@lists.dm.unipi.it</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</BaseLayout>
|
@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from '../layouts/BaseLayout.astro'
|
||||||
|
import Header from '../components/Header.astro'
|
||||||
|
import Footer from '../components/Footer.astro'
|
||||||
|
import Bubble from '../components/Bubble.astro'
|
||||||
|
|
||||||
|
import macchinisti from '@/data/macchinisti.yaml'
|
||||||
|
macchinisti.sort((a, b) => b.entranceDate - a.entranceDate)
|
||||||
|
|
||||||
|
// Import all images from assets folder
|
||||||
|
const images = Object.fromEntries(
|
||||||
|
Object.entries(
|
||||||
|
import.meta.glob<{ default: ImageMetadata }>('@/assets/macchinisti/*', {
|
||||||
|
eager: true,
|
||||||
|
}),
|
||||||
|
).map(([path, module]) => [path.split('/').pop()!.split('.')[0], module]),
|
||||||
|
)
|
||||||
|
|
||||||
|
const currentMacchinisti = macchinisti.filter(macchinista => !macchinista.exitDate)
|
||||||
|
const pastMacchinisti = macchinisti.filter(macchinista => macchinista.exitDate)
|
||||||
|
|
||||||
|
const getMacchinistaPicture = (fullName: string) => {
|
||||||
|
const macchinistaId = fullName.toLowerCase().replace(/\s+/g, '-')
|
||||||
|
const { default: image } = images[macchinistaId] ?? images['fallback']
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title="Macchinisti | PHC" pageTags={'macchinisti'}>
|
||||||
|
<Header />
|
||||||
|
<main>
|
||||||
|
<div class="card large" style={{ '--card-base': '#e1766b' }}>
|
||||||
|
<div class="title">Ecco i Macchinisti!</div>
|
||||||
|
<div class="text">
|
||||||
|
<p>
|
||||||
|
<em>> Chi sono i macchinisti?</em>
|
||||||
|
<br />
|
||||||
|
Questo è l'appellativo dato agli studenti che si occupano di gestire l'infrastuttura e i servizi del
|
||||||
|
PHC (vedi la homepage per informazioni su come diventare un macchinista). Qua sotto trovi i macchinisti
|
||||||
|
attualmente attivi in PHC.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bubble-array">
|
||||||
|
{
|
||||||
|
currentMacchinisti.map(macchinista => (
|
||||||
|
<Bubble
|
||||||
|
image={getMacchinistaPicture(macchinista.fullName)}
|
||||||
|
fullName={macchinista.fullName}
|
||||||
|
entranceDate={macchinista.entranceDate}
|
||||||
|
description={macchinista.description}
|
||||||
|
social={macchinista.social}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card large" style={{ '--card-base': '#6BD6E1' }}>
|
||||||
|
<div class="title"><s>Deus</s> Ex Macchinisti</div>
|
||||||
|
<div class="text">
|
||||||
|
<p>
|
||||||
|
Qui raccogliamo qualche informazione sui macchinisti del passato, che hanno contribuito a rendere il
|
||||||
|
PHC quello che è oggi.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bubble-array">
|
||||||
|
{
|
||||||
|
pastMacchinisti.map(macchinista => (
|
||||||
|
<Bubble
|
||||||
|
fullName={macchinista.fullName}
|
||||||
|
description={macchinista.description}
|
||||||
|
image={getMacchinistaPicture(macchinista.fullName)}
|
||||||
|
entranceDate={macchinista.entranceDate}
|
||||||
|
exitDate={macchinista.exitDate}
|
||||||
|
founder={macchinista.founder}
|
||||||
|
social={macchinista.social}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</BaseLayout>
|