Compare commits
30 Commits
@ -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: 42 KiB |
|
After Width: | Height: | Size: 645 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 113 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,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()
|
||||||
|
}
|
||||||
@ -1,79 +0,0 @@
|
|||||||
---
|
|
||||||
id: stampare-via-ssh
|
|
||||||
title: Stampare via SSH
|
|
||||||
description: Come stampare da remoto tramite SSH
|
|
||||||
author: Antonio De Lucreziis
|
|
||||||
tags: [linux, ssh, stampanti]
|
|
||||||
---
|
|
||||||
|
|
||||||
Per stampare in dipartimento non bisogna per forza usare i computer dei laboratori, possiamo che stampare direttamente da remoto tramite SSH. Vediamo come fare!
|
|
||||||
|
|
||||||
Se non l'avete mai fatto prima per prima cosa bisogna poter accedere da remoto ad una macchina del dipartimento chiamata "login", il suo indirizzo è `login.dm.unipi.it`. Per fare l'accesso possiamo usare il seguente comando con l'account di Ateneo (non quello Poisson!)
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
ssh USERNAME_ATENEO@login.dm.unipi.it
|
|
||||||
```
|
|
||||||
|
|
||||||
Una volta connessi possiamo stampare utilizzando il comando `lpr` seguito dal nome del file che vogliamo stampare. Prima però serve trasferire il file che vogliamo stampare sulla macchina "login". Per fare ciò possiamo usare il comando `scp`, quindi per prima cosa usciamo dalla macchina "login" (premere `Ctrl+D` oppure scrivendo `exit`), andiamo nella cartella dove si trova il file che vogliamo stampare e poi eseguiamo il comando:
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
scp NOME_FILE.pdf USERNAME_ATENEO@login.dm.unipi.it:~/Documents
|
|
||||||
```
|
|
||||||
|
|
||||||
Dove `NOME_FILE.pdf` è il nome del file che vogliamo stampare e `Documents` è la cartella dove vogliamo trasferirlo. Una volta trasferito il file possiamo rifare ssh su "login" e stampare il file con il comando:
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
lpr Documents/NOME_FILE.pdf
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternativamente possiamo stampare direttamente il file senza trasferirlo con il comando:
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
cat NOME_FILE.pdf | ssh USERNAME_ATENEO@login.dm.unipi.it lpr OPZIONI... -
|
|
||||||
```
|
|
||||||
|
|
||||||
dove `[OPZIONI...]` sono le opzioni che possiamo passare a `lpr` (vedi sotto). L'ultimo trattino "`-`" è molto importante e indica che il file da stampare è quello in standard input. Più precisamente, `cat NOME_FILE.pdf` invia il contenuto del file `NOME_FILE.pdf` allo standard output e `|` lo ridireziona a input di `ssh`, che a sua volta lo passa a `lpr` via rete.
|
|
||||||
|
|
||||||
## Opzioni di `lpr`
|
|
||||||
|
|
||||||
Il comando `lpr` accetta alcune opzioni che possono essere utili:
|
|
||||||
|
|
||||||
- `-P` seguito dal nome della stampante: permette di specificare la stampante su cui stampare, le stampanti disponibili in dipartimento sono
|
|
||||||
|
|
||||||
- `cdc4` che è la stampante di default e si trova in Aula 4
|
|
||||||
|
|
||||||
- `cdclf` che si trova al piano terra nel corridoio dopo l'Aula 4
|
|
||||||
|
|
||||||
- `cdc3` che si trova in Aula 3 (è un po' vecchia ma di solito funziona)
|
|
||||||
|
|
||||||
- `-#` seguito dal numero di copie: permette di specificare il numero di copie da stampare. In realtà questa opzione non funziona per vari motivi arcani e se uno passa `-#N` per stampare $N$ copie, la stampante stampa $N^2$ copie. (Questo ha scaturito una serie di ragionamenti sul modo ottimo di decomporre $N$ come somma di quadrati [con tanto di sito di comodo](https://shortest-sum-of-squares.netlify.app/)...)
|
|
||||||
|
|
||||||
- `-o sides=two-sided-long-edge`: permette di stampare fronte-retro (che dovrebbe essere già il default)
|
|
||||||
|
|
||||||
- `-o sides=two-sided-short-edge`: permette di stampare fronte-retro con "la rilegatura" delle pagine sul lato corto
|
|
||||||
|
|
||||||
- `-o sides=one-sided`: permette di stampare solo fronte, comodo per stampare i meme di laurea
|
|
||||||
|
|
||||||
- `-o fit-to-page`: permette di ridimensionare il documento per farlo entrare in un foglio (è buona prassi passare sempre questa opzione)
|
|
||||||
|
|
||||||
- `-o media=a4`: permette di specificare il formato del foglio, di default è A4 quindi non dovrebbere servire
|
|
||||||
|
|
||||||
## Altre comodità
|
|
||||||
|
|
||||||
Stampare da remoto porta anche altre comodità, ad esempio possiamo interrompere un file che abbiamo mandato in stampa per sbaglio con il comando (sempre tutti comandi da eseguire su "login")
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
cancel -a
|
|
||||||
```
|
|
||||||
|
|
||||||
Oppure possiamo vedere lo stato della coda di stampa con il comando
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
lpq -a
|
|
||||||
```
|
|
||||||
|
|
||||||
o per vedere lo stato delle nostre stampe
|
|
||||||
|
|
||||||
```bash shell
|
|
||||||
lpstat -l
|
|
||||||
```
|
|
||||||
@ -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>
|
||||||